alerts

Notable Alerts

The most notable recent movement in each series, spread across datasets.

limit is capped at 50 (the front end only ever requests 8) rather than left unbounded — see security-architecture-and-audit.md §4.

stat_key, when given, narrows the strip to alerts from ONE stat only (stat- region-selector-design.md §4) — the front end passes it once a reader has explicitly picked a stat as the map's active colour-by layer, so a crime-only reader sees a crime-only strip. Omitted (the default) reproduces today's behaviour exactly: unfiltered, ranked across every dataset by the same fairness rule below. This is additive — no existing caller changes behaviour until the front end starts passing the parameter.

geography_id, when given, narrows to series that honestly relate to that geography (P3, 2026-09-19, the per-county broadsheet fragment) — resolved via geo_match_batch() (databeat/geography.py), the SAME ancestry lookup every other "does this series count as this area's own" decision on the site already uses, never re-derived here. exact/aggregate/partial verdicts all count (matching how the dataset-cards panel already treats them as inclusion-with-caveat, not exclusion); none is excluded. Additive, same as stat_key — omitted reproduces today's nationwide behaviour exactly.

cluster, when given, narrows to alerts in ONE topic cluster only (P3 C2, 2026-09-19, "scope-aware headings" — broadsheet-first-vision.md §3: clicking a section heading on the country broadsheet widens to that topic across every geography). Bound in the quota CTE below, not eligible — cluster is ITSELF a computed column there (coalesce(st.cluster, %(residual)s)), and Postgres cannot reference a SELECT-list alias from the same SELECT's own WHERE clause; quota is the first CTE where the computed value is a real selected column available to filter on. Additive, same shape as stat_key/ geography_id — a caller that never passes it sees no behaviour change. The existing per-stat quota (MAX_ALERTS_PER_STAT) still applies WITHIN the filtered cluster, same "no one dataset dominates" discipline the unfiltered strip already has; the cross-cluster round-robin ordering is a no-op once only one cluster's rows remain, so nothing needed disabling for it the way the per-stat quota is suspended for an explicit stat_key.

Not simply /api/alerts ordered by date: series are published on different schedules, so a date-ordered list is dominated by whichever dataset published most recently. Property is monthly and crime quarterly, which meant property's June alerts filled any reasonable page size and crime's January alerts were never reached — the crime data was effectively invisible.

DISTINCT ON takes each series' newest alert first, so the ranking happens across series rather than within one publication date.

THEN THE SAME FAILURE RECURRED ALONG A SECOND AXIS. Ranking the survivors by raw percentage let whichever dataset swings hardest crowd out the rest, exactly as date-ordering had let whichever dataset published last crowd out the rest. Once PSRA landed, all 8 slots were crime: divisional crime counts move 40-70 per cent between quarters as a matter of course, so a county median sale price moving 31 per cent — extraordinary for property, on 800k transactions — ranked about 17th and never appeared. Percentages from different units are not comparable, and sorting them into one list silently asserts that they are.

So rank WITHIN each dataset and interleave: every dataset offers its largest movement before any dataset offers its second. The strip stays ordered by size within each round, and no dataset can monopolise it.

AND THEN IT RECURRED A THIRD TIME, ALONG A THIRD AXIS. "Dataset" was defined as group_name, which is per-HOSPITAL for NTPF ("Beaumont Hospital — Outpatient Waiting List") because Dublin has ~14 hospitals and the name has to distinguish the cards. So NTPF's 90-odd group_names each contributed a rank-1 alert and swept the first round: 5 of 8 notable alerts were NTPF, four of them the same waiting band. Not because hospital waits are the biggest story, but because NTPF contributes 360 of 418 series and therefore wins any count-based contest by construction — a newspaper front page set by word count. The interleaving was working exactly as designed; the buckets were the wrong size.

FIXED BY QUOTA, in three nested passes, using the cluster taxonomy (migration 014):

  1. Best alert per group_name, so the two NTPF entries that survive are two different hospitals rather than two bands at the same one.
  2. At most MAX_ALERTS_PER_STAT per stat_key — the level a reader would call one dataset, and the level at which NTPF is one thing rather than ninety.
  3. Round-robin across the five CLUSTERS: every cluster offers its best before any cluster offers its second. The strip therefore leads with one housing, one crime, one health, one social and one economy story, which is the product's own editorial structure rather than an artefact of which publisher emits the most rows.

The quota is suspended when stat_key is passed, because a reader who has explicitly asked for a crime-only strip wants crime alerts, not two of them.

get/api/v1/alerts/notable

Request

  • Base URL: https://statsmapped.com
  • URL: https://statsmapped.com/api/v1/alerts/notable
  • Auth: none declared

Query parameters

limitinteger
stat_keystring nullable
geography_idstring nullable
clusterstring nullable

Response

Successful Response

countrystring
series_keystring required
display_namestring required
group_namestring nullable
rule_typestring required
period_datestring required
prev_valuenumber required
curr_valuenumber required
pct_changenumber required
directionstring required
geography_idstring nullable
stat_keystring nullable
clusterstring nullable
periodicitystring nullable
dataset_keystring nullable
unitstring nullable
series_latest_periodstring nullable
periods_behindinteger nullable
sample_sizeinteger nullable
sample_size_nounstring nullable

Changes

No recorded changes to this operation across all 1 revision of this API.