Data flow¶
Ingest¶
- Producers publish to raw Kafka topics (
firms.hotspots.raw,openaq.measurements.raw,weather.wind.raw, optionalweather.grid.raw). - Malformed payloads may be copied to source DLQs and/or summarized in
analytics.parse_errors.
Normalize¶
- Spark jobs consume raw topics and upsert
normalized.*rows (fires, AQ, wind, optional grid cells). - Spatial enrichment sets
county_geoid/tract_geoidwhen points fall insidegeo.*.
Derive¶
- Plume jobs write
analytics.smoke_plume_exposures(wind_v1,wind_grid_v2corridor heuristics). - Optional dispersion jobs write
analytics.smoke_dispersion_exposures(gaussian_v0proxy — not regulatory dispersion). - Risk jobs write
analytics.smoke_risk_scores(models v1–v5 per configuration). - Optional dispersion vs AQ comparisons land in
analytics.dispersion_aq_comparisonswith evidence labels.
Observe / evaluate¶
analytics.risk_observationsandmake evaluate-riskpopulateanalytics.risk_model_evaluations— engineering metrics only.
Present / alert¶
- Grafana reads
analytics.v_*views. analytics.fn_alert_candidatessurfaces candidates;analytics.alert_eventsstores materialized incidents when enabled.
Calibration exports¶
make export-calibration writes timestamped CSV bundles (optional Parquet) under artifacts/calibration/<UTC-stamp>/ with redacted metadata — offline review snapshots, not regulatory submissions.
flowchart TB
subgraph sources["Sources"]
F[FIRMS]
A[OpenAQ]
W[NWS / wind / grid]
C[Census boundaries]
end
subgraph buf["Kafka / Redpanda"]
K[Raw + DLQ topics]
end
subgraph pipe["Spark normalization"]
S[Batch jobs]
end
subgraph pg["PostGIS analytics"]
P[(Normalized + risk + calibration)]
end
subgraph observe["Inspection"]
R[Risk / maps / SQL views]
G[Grafana]
AL[Alerts + runbooks]
E[Calibration CSV / Parquet exports]
end
F --> K
A --> K
W --> K
K --> S
C --> P
S --> P
P --> R
R --> G
R --> AL
P --> E