Curated insights (1/5): chart-generator package + registry - #762
Curated insights (1/5): chart-generator package + registry#76201painadam wants to merge 2 commits into
Conversation
src/api/services/charts.py becomes src/api/services/charts/ with one module per dataset, a base module for the ChartGenerator seam, and an explicit dataset-id -> generator registry that raises on duplicate registration. AnalyzeService switches from a first-can_handle-wins scan to a registry lookup. DATASETS_WITHOUT_CURATED_INSIGHTS is a coverage ratchet enforced by a unit test: every catalog dataset needs a generator or a deliberate exclusion entry. Tests move to the mirror-path tests/unit/api/services/charts/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An unknown dataset_id previously produced a pending job that failed downstream in the analytics handler; fail loudly at the API edge with a 422 instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
One concern I have here is about the background tasks. The api response cycle kicks off a "Background task" using This is not a huge risk right now because if an insight fails its not that bad. But it won't scale if we have a lot of users and a lot of containers. If a container is restarted, this is completely lost. The cycle as in this PR implementation is I would propsoe to make this api call wait for the analytics-api to finish and do the whole ORM cycle in one go. |
|
Good catch @yellowcap The background task spread the cycle over four separate transactions with the response already gone, so a container restart mid-task stranded the job in Addressed in #779 (stacked on this branch, no overlap with 2/5–5/5), which does exactly what you propose. The request waits for the analytics API and the whole ORM cycle happens in one go: Either the full cycle exists or nothing does — there's no intermediate state left to strand. A crash mid-analysis persists nothing and the client sees the error instead of a forever-pending job. A few decisions worth you looking at on that branch:
On the scaling point: synchronous execution fixes durability, not throughput (many users × slow pulls = many held connections). The durable answer is a queue (e.g. pg-backed jobs claimed by a worker), which I've left out of scope here; the |
What & why
Deterministic "curated" insights (
POST /api/analyze→ no LLM) existed only for Tree Cover Loss and Integrated Alerts; every other dataset returned an empty insight. This is the groundwork to expand them to all 12 catalog datasets (the rest land in the branches stacked on top of this one).src/api/services/charts.py(one file, 2 generators, linearcan_handlescan) becomessrc/api/services/charts/:base.py— theChartGeneratorseam +column_to_rows.registry.py— an explicitdataset_id → generatormap that raises on duplicate registration, plusDATASETS_WITHOUT_CURATED_INSIGHTS, a coverage ratchet.AnalyzeServicenow does a registry lookup instead of a first-can_handle-wins scan. The analyze router rejects unknowndataset_ids with a 422 instead of letting a doomed job fail downstream in the analytics handler.Coverage ratchet: a unit test asserts every catalog dataset is either registered or in the explicit exclusion set — so a future catalog YAML added without a curated generator fails CI loudly. This PR leaves the 10 not-yet-implemented datasets in the exclusion set; later branches shrink it to empty.
Tests
tests/unit/api/services/charts/(mirror-path): per-generator + registry ratchet/duplicate tests.tests/api/test_analyze.py: auth + unknown-dataset 422.pre-commit(ruff + mypy) clean.Contract
No behaviour change to existing TCL/Integrated Alerts output. No
AgentState/ stream / FE-type changes.🤖 Generated with Claude Code