Skip to content

feat(alerts): count alerts per day over a period - #724

Open
Majyles wants to merge 3 commits into
pyronear:mainfrom
Majyles:feat/alerts-count-per-day
Open

Majyles wants to merge 3 commits into
pyronear:mainfrom
Majyles:feat/alerts-count-per-day

Conversation

@Majyles

@Majyles Majyles commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

What
Adds GET /alerts/all/fromdate/count-per-day so that the History calendar (pyronear/new-pyro-platform#308) gets its daily alert counts in one request instead of one per day.

How (as agreed in #719)

  • from_date / to_date (inclusive), at most 62 days: a month calendar shows at most 6 weeks, and this bounds the per-day risk lookups. Inverted or longer periods → 422.
  • tz: IANA timezone used to split the days, UTC by default to match /alerts/all/fromdate. Unknown timezone → 422. (tz on /all/fromdate is left out of scope, as discussed.)
  • Same alerts as /alerts/all/fromdate/count: organization scoping (admins see all), and the risk filter of each day, or the risk_score override. The per-day risk scores are fetched concurrently, and days sharing the same scores are counted by a single grouped query (a single query when the risk API is not configured).
  • Response: [{"date": "2026-10-23", "count": 2}, …], only days with alerts.

Tests

  • Per-day counts, organization scoping, admin view, consistency with /all/fromdate/count, and a tz case where 23:30 UTC falls on the next day in Paris.
  • Validation: inverted period, 63 days vs 62, unknown timezone, missing parameter, unauthenticated.
  • Risk filter applied day by day (mocked risk API, one lookup per day with the organization), and the risk_score override skipping the risk API.

Full suite, ruff and ty pass locally. Also tried in pyro-envdev against the demo alerts.

Note
The response field is date, consistent with from_date / to_date; in the schema it is typed dt.date since a field named date would otherwise shadow the type for Pydantic.

Closes #719

Generated with Claude Code

Add GET /alerts/all/fromdate/count-per-day so that a month calendar gets
its daily alert counts in one request instead of one per day.

- from_date / to_date (inclusive), at most 62 days: a month calendar shows
  at most 6 weeks, and this bounds the per-day risk lookups.
- tz: IANA timezone used to split the days, UTC by default to match
  /alerts/all/fromdate.
- Same alerts as /alerts/all/fromdate/count: organization scoping, and the
  risk filter of each day (fetched concurrently; days sharing the same
  scores are counted by a single query), or the risk_score override.
- Returns [{"date": ..., "count": ...}] for the days with alerts only.

Closes pyronear#719

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.96%. Comparing base (68bde16) to head (2cf20d7).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #724      +/-   ##
==========================================
+ Coverage   94.87%   94.96%   +0.08%     
==========================================
  Files          60       60              
  Lines        3278     3334      +56     
==========================================
+ Hits         3110     3166      +56     
  Misses        168      168              
Flag Coverage Δ
backend 95.11% <100.00%> (+0.08%) ⬆️
client 91.30% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MateoLostanlen MateoLostanlen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the approach matches what we agreed in #719. A few things before merging:

Blocking

  1. The 62-day cap is checked after the day list is built. days is built for the whole range first, so from_date=0001-01-01&to_date=9999-12-31 creates ~3.65M dates before returning 422. Could you check (to_date - from_date).days + 1 > MAX_COUNT_PER_DAY_PERIOD before building the list?

  2. No index-friendly filter on started_at. The queries only filter on date(timezone(tz, timezone('UTC', started_at))) IN (...), so each one scans all of the organization's alerts (all alerts for admins). FWI classes change most days, so grouping by identical scores rarely helps: a month can mean ~30 sequential queries, each a full scan. Adding Alert.started_at >= start AND < end (the period bounds converted from tz to UTC in Python) would fix it.

  3. Up to 62 concurrent risk API calls. Each call opens a new httpx.AsyncClient, and a failure or timeout returns {}, so that day is counted with no risk filter and no error. The calendar can then disagree with /all/fromdate/count for that day. A semaphore and one shared client (as in #708) would make this safer.

Non-blocking

  • tz is checked with Python's zoneinfo, but the conversion runs in Postgres, which uses its own timezone database. A name Python accepts but Postgres doesn't would give a 500 instead of a 422.
  • Risk scores are also fetched for days after today. For the current month's calendar that can be ~30 wasted calls; we could clip the period to today in tz.
  • The sequence filter (AlertSequence join + max_conf_filter_clause) is copied from _build_alerts_from_date_stmt. Since the goal is the same alerts as /all/fromdate/count, a small shared helper would stop the two from drifting apart.
  • Telemetry is sent before validation, so rejected requests are counted too.
  • raise HTTPException(...) in the except should be from None (ruff B904, "B" is enabled).
  • Nit: _user_auth in the tests does what the existing tests do inline with pytest.get_token(...). Could we use one style or the other in this file?

Majyles and others added 2 commits October 26, 2026 22:13
…er-day

# Conflicts:
#	src/tests/endpoints/test_alerts.py
- Check the period length on the dates, before listing the days.
- Filter started_at on UTC ranges: day boundaries are computed in Python
  with zoneinfo and days are bucketed with a CASE, so Postgres no longer
  converts timezones (index-friendly, consistent tz validation).
- Clip the period to today in tz: no risk lookup for future days.
- RiskService: shared httpx client closed in the app lifespan, and a
  semaphore bounding concurrent calls.
- Share the sequence filter of /all/fromdate in _filter_alerts_by_sequences.
- Send telemetry after validation, chain the tz exception with from None,
  inline get_token in tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Return the number of alerts per day over a period

2 participants