Explorer: calendar page (#137) #170

Merged
claude-bot merged 3 commits from feat/explorer-calendar into main 2026-08-01 04:54:11 +00:00
Contributor

Closes #137. First of the Explorer's three views, over the merged correlation layer (#135).

What this adds

GET /explorer — a server-rendered month calendar of weather-event activity across all locations the user can see. Per the issue's constraints: no new JS dependencies (first calendar in the app, built as a server-rendered grid), plain-GET month navigation with clamping to [retention horizon, current month], and the standard visible_location_filter applied.

  • Day cells show event count + a severity chip (reusing the existing severity-badge classes; lightning-only activity ranks lowest), linking to /explorer/{date} (day view, #138 — next PR).
  • Day assignment is by each location's own timezone — an event at 03:00 UTC belongs to the previous local day in America/Chicago. Verified against real DST offsets in tests.
  • Bounded query: one padded month-window query selecting only needed columns, bucketed in Python — explicitly not the /stats unbounded-history anti-pattern the issue calls out.
  • Distinct day states: activity / empty / beyond retained history (hatched, driven by the unified 13-month horizon from #133, never hardcoded) / future. Precedence deliberately lets a day with surviving rows read as "activity" even just past the horizon — the cell never hatches over data a click would show.
  • Archived locations' history is included (#134) — the Explorer is exactly the reader soft-delete preserves history for.
  • Nav entry visible to all roles including readonly (read-only view, same gate as the dashboard).

Review fix

The initial implementation followed the dashboard page's "service owns its own AsyncSessionLocal" pattern — which turns out to be untestable (no page route in the app has fixture coverage, and all six page tests failed on real infrastructure trying to resolve the production DB host). The route now injects the request-scoped session via Depends(get_db), which the fixtures override — making /explorer the first page route with real test coverage. Worth a follow-up thought for the other page routes.

Verification

Full bulk suite on the dev server: 927 passed (27 new: grid construction, day-state precedence, DST-boundary bucketing, render for all three roles, visibility exclusion, shared-location access, soft-delete inclusion, month clamping and malformed-param fallback). ruff clean. No migrations.

🤖 Generated with Claude Code

Closes #137. First of the Explorer's three views, over the merged correlation layer (#135). ## What this adds `GET /explorer` — a server-rendered month calendar of weather-event activity across all locations the user can see. Per the issue's constraints: **no new JS dependencies** (first calendar in the app, built as a server-rendered grid), plain-GET month navigation with clamping to [retention horizon, current month], and the standard `visible_location_filter` applied. - Day cells show event count + a severity chip (reusing the existing `severity-badge` classes; lightning-only activity ranks lowest), linking to `/explorer/{date}` (day view, #138 — next PR). - **Day assignment is by each location's own timezone** — an event at 03:00 UTC belongs to the previous local day in America/Chicago. Verified against real DST offsets in tests. - **Bounded query**: one padded month-window query selecting only needed columns, bucketed in Python — explicitly not the `/stats` unbounded-history anti-pattern the issue calls out. - Distinct day states: activity / empty / **beyond retained history** (hatched, driven by the unified 13-month horizon from #133, never hardcoded) / future. Precedence deliberately lets a day with surviving rows read as "activity" even just past the horizon — the cell never hatches over data a click would show. - Archived locations' history is **included** (#134) — the Explorer is exactly the reader soft-delete preserves history for. - Nav entry visible to all roles including readonly (read-only view, same gate as the dashboard). ## Review fix The initial implementation followed the dashboard page's "service owns its own `AsyncSessionLocal`" pattern — which turns out to be untestable (no page route in the app has fixture coverage, and all six page tests failed on real infrastructure trying to resolve the production DB host). The route now injects the request-scoped session via `Depends(get_db)`, which the fixtures override — making `/explorer` the first page route with real test coverage. Worth a follow-up thought for the other page routes. ## Verification Full bulk suite on the dev server: **927 passed** (27 new: grid construction, day-state precedence, DST-boundary bucketing, render for all three roles, visibility exclusion, shared-location access, soft-delete inclusion, month clamping and malformed-param fallback). `ruff` clean. No migrations. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Server-rendered GET /explorer month grid, bounded to one padded query per
month load (never the unbounded-history anti-pattern /stats explicitly
avoided repeating), aggregated in Python and bucketed to each event's LOCAL
calendar day via its location's own timezone. Applies visible_location_filter
per-visibility, includes soft-deleted locations' history per #134, and
clamps navigation to [history_horizon_days month, current month] so
month= never 500s. Day cells link to /explorer/{YYYY-MM-DD} for the day view
landing in #138.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
build_explorer_context owned its own AsyncSessionLocal session, following
the dashboard page's pattern — but that pattern is untestable (no page
route has fixture coverage, and the six /explorer page tests failed on
real infrastructure trying to resolve the production DB host). The route
now injects Depends(get_db), which the test fixtures override and which
shares the request's transaction semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the missing Depends/AsyncSession/get_db imports in main.py
All checks were successful
CI / test (pull_request) Successful in 5m40s
6ac0a00bfe
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/WeatherBot!170
No description provided.