Get Patterns Heatmap
GET /patterns/heatmap?granularity=month
Month-binned issue-arrival grid for the population-pipeline card: one row
per pattern (plus an unassigned row for issues with no pattern_id),
one cell per calendar month spanning the workspace’s data.
Boundary convention: each month bucket is
[YYYY-MM-01T00:00:00Z, first-of-next-month) UTC — inclusive start,
exclusive end. GET /issues?date_from=<month-start>&date_to=<next-month- start> uses the exact same convention: _issue_where’s date_to
comparison (app/store/postgres.py, app/store/memory.py) was changed from
inclusive (<=) to exclusive (<) as part of this endpoint so a
heatmap cell and the equivalent list_issues total always agree — see
tests/test_patterns_heatmap_postgres_parity.py, which asserts this for
every cell including the exact-midnight boundary instant.
The months axis is derived entirely from data (min..max
source_created_at present in the workspace) — never from wall-clock
now() (Design Rule 1). Patterns with zero member issues in range still
get a zero-filled row (“rows exist for every client”). 404 not_fitted
when the workspace has no succeeded engine run yet — an unfitted
workspace gets an honest 404, never a fabricated empty-but-200 grid.