Phase 1 — Multi-Hazard + Scenario + Cost-Benefit¶
Status: Closed 2026-04-17 (target was 2026-07-15 — delivered ~3 months early). See plan.md for the active phase.
Goal¶
Expand the vertical slice beyond river flood to multiple hazard types (TC, WF, WS), wire cost-benefit analysis, expose frequency curves, add scenario metadata, and honour optional insurance parameters (deductible, cover).
Definition of done¶
- TC / WF / WS ingest pipelines live, each with a seeded impact function
- Measures CRUD + cost-benefit compute path return benefit/cost ratio and NPV
- Frequency curve persisted on
ImpactResultand exposed viaGET /v1/results/{id}/freq-curve - Insurance params (
deductible,cover) accepted on impact requests;insured_losspersisted - UI surfaces scenario selector, cost-benefit panel, frequency-curve panel
- Engine released as
climate-lama-engine 0.2.0to PyPI
All conditions met at close.
Scope note on scenarios¶
In Phase 1, scenario (e.g. ssp2-4.5) shipped as a metadata label on
hazard datasets — a filter and provenance tag. No CMIP6 intensity scaling was
applied. Climate-adjusted hazard grids remain a Phase 4+ candidate.
Outcome summary¶
- Four hazard types live end-to-end: river flood (existing), tropical cyclone,
wildfire, storm Europe. Shared
BaseGeoTIFFIngestorstrategy extracted undersrc/climate_lama/core/ingest/. - Per-hazard default impact functions seeded via Alembic data migrations. Sources documented in ../DECISIONS.md ADR-023 (Emanuel 2011 + Eberenz 2021 for TC, Lüthi 2021 for WF, Klawa–Ulbrich 2003 for WS).
climate-lama-engine 0.2.0exposesCostBenefitResulton the public surface; edge-case hardening shipped (zero-exposure, single-event, degenerate impf). Backbone bumped its pin.- Cost-benefit Celery task +
POST /v1/compute/cost-benefit+GET /v1/results/{id}/cost-benefitreturn NPV, B/C ratio, avoided risk per measure. - Frequency curve computed once at impact time, persisted alongside
eai_exp/aai_agg/at_event, and served via a dedicated endpoint for UI plotting. - Domain-exception → HTTPException mapping centralised (#87) so every auth/RBAC error in Phase 2 flows through the same envelope.
GitHub issues¶
Engine (climate-lama-engine 0.2.0 released to PyPI)¶
| Issue | Title |
|---|---|
| engine#6 | Expose CostBenefitResult from package public surface |
| engine#7 | Edge-case hardening (zero-exposure, single-event, degenerate impf) |
| engine#8 | Release 0.2.0 to PyPI |
Backbone (climate-lama)¶
| Issue | Title |
|---|---|
| #61 | PLAN.md — close Phase 0 block, refresh Phase 1 scope |
| #42 | Refactor: common BaseGeoTIFFIngestor strategy |
| #47 | TC ingestor (wind speed, m/s) |
| #43 | WF ingestor (fire weather index) |
| #48 | WS ingestor (gust speed, m/s) |
| #44 | Scenario label plumbing — ingest + catalog + filter |
| #49 | Research: impact function sources for TC/WF/WS (ADR-023) |
| #50 | Seed TC impact function(s) |
| #51 | Seed WF impact function(s) |
| #52 | Seed WS impact function(s) |
| #53 | Per-hazard impf auto-resolution (default when ID omitted) |
| #54 | Measures table, ORM model, Alembic migration |
| #55 | Measures CRUD API POST/GET/DELETE /v1/measures |
| #56 | CB Celery task + EngineAdapter wiring |
| #57 | CB endpoints POST /v1/compute/cost-benefit + GET /v1/results/{id}/cost-benefit |
| #58 | Persist freq-curve on ImpactResult (migration + task) |
| #59 | GET /v1/results/{id}/freq-curve endpoint |
| #60 | Optional deductible / cover on impact request; persist insured_loss |
| #31 | UI wizard: swap step order (hazard before exposure) |
UI (in ui/)¶
| Issue | Title |
|---|---|
| #62 | Scenario selector (SSP dropdown + time horizon) |
| #63 | Activate TC/WF/WS hazard type options |
| #64 | Frequency-curve panel (loss vs RP line chart) |
| #65 | Cost-benefit panel (measure selector, NPV, B/C ratio) |
Follow-up polish merged during Phase 1¶
| Issue | Title |
|---|---|
| #83 | Tighten ExposureStep.hazardDataset prop (non-nullable) |
| #85 | Loading skeleton for hazard grid while API response pending |
| #86 | Expose is_default in GET /v1/impact-functions response |
| #87 | Centralise domain-exception → HTTPException mapping |
| #95 | Register WildfireGeoTIFFIngestor in hazard ingest map (unmilestoned on GitHub; closed as Phase 1 post-merge fix) |
| #109 | Synthetic hazard generator for UI smoke-testing (retro-filed 2026-04-18; tooling + exposure fixture broadening) |
| #110 | Add storm_europe to hazard selector placeholders (retro-filed 2026-04-18; UI wiring gap post-#63) |
Carry-overs and loose ends¶
| Issue | Title | Disposition |
|---|---|---|
| #16 | Scope centroid assignment to hazard dataset bbox | Deferred to Phase 3 — low risk while we stay single-region |
| #18 | Minimalistic React wizard for end-to-end demo | Superseded — wizard shipped in Phase 0; close as done |
| #37 | Revisit session-scoped event loop | Informational — leave open as a reference if concurrent-write tests are added |
| #68 | Status update chore | Superseded by Phase 1 close — close as stale |
| #84 | UI Vitest + Testing Library setup | Deferred to Phase 2 Wave 2 UI split; open, unmilestoned |
| #88 | Avoid unused DB sessions in compute endpoint | Moved to Phase 2 — folded into Wave 1 prerequisite cleanup |
| #96 | Align hazard intensity units between ingestors and seeded impact curves | Correctness bug carried to Phase 2. WF silently returns EAD=0 (FWI fed to Kelvin-calibrated Lüthi curve); WS saturates at MDD=1 everywhere (raw m/s fed to v/v98-normalised Klawa–Ulbrich). See issue body for hazard-by-hazard breakdown. |
Improvement points noted for Phase 2 backlog¶
intensity_unitonIngestRequestis dead code: the request field is accepted and propagated to the worker, but everyBaseGeoTIFFIngestorsubclass hardcodes the unit as a class attribute. Tracked as #94 and folded into Phase 2 Wave 1 Stream A.- Issues closed during Phase 1 without a
Phase 1 -- Multi-Hazardmilestone label on GitHub: #63 (activate TC/WF/WS UI hazard cards) and #95 (WF ingestor registration fix). Both relabeled to the Phase 1 milestone during the 2026-04-18 audit.
Retrospective audit (2026-04-18)¶
Post-phase cross-reference surfaced five regression-signal / safety-net items that would have helped close Phase 1 with more confidence, all carried forward to Phase 2 rather than reopening Phase 1:
| Issue | Title |
|---|---|
| #104 | test: cross-hazard end-to-end smoke test (RF/TC/WF/WS) |
| #105 | feat(ingest): enforce intensity_unit / impact-curve compatibility invariant |
| #106 | test: EAD plausibility assertion per hazard on reference datasets |
| #107 | test: frequency-curve monotonicity invariant |
| #108 | test: scenario metadata round-trip (ingest → catalog → compute → result) |
105 is the preventive invariant paired with the active fix in #96 (carried¶
over above); both will land in the same Phase 2 release cycle.
A second-pass commit-level audit also surfaced two post-close polish items
that had landed directly on main without a filed issue; both were filed
retrospectively, attached to the closed Phase 1 milestone, and closed:
| Issue | Title |
|---|---|
| #109 | chore(tooling): synthetic hazard generator for UI smoke-testing |
| #110 | fix(ui): add storm_europe to hazard selector placeholders |