Skip to content

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 ImpactResult and exposed via GET /v1/results/{id}/freq-curve
  • Insurance params (deductible, cover) accepted on impact requests; insured_loss persisted
  • UI surfaces scenario selector, cost-benefit panel, frequency-curve panel
  • Engine released as climate-lama-engine 0.2.0 to 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 BaseGeoTIFFIngestor strategy extracted under src/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.0 exposes CostBenefitResult on 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-benefit return 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_unit on IngestRequest is dead code: the request field is accepted and propagated to the worker, but every BaseGeoTIFFIngestor subclass 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-Hazard milestone 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