Phase 13 — Event Replay (Live Event Mode, rungs R0 + R1)¶
Opened 2026-08-27 by owner instruction, from the charter exploration/07-live-risk.md (session 6b amendments in its §12). Serves Goal A (a fourth computable form of hazard — an observed event — through the one unified pipeline) and Goal C (Moment 2 gains steps 8–9). Runs beside Phase 11, backbone-first per Phase 11's investment rule; the UI wave is data-driven, so it can land beside the pack waves rather than after them.
What it is. Run the platform's impact chain against a hazard that happened instead of one that is probable: a real past Greek earthquake (USGS ShakeMap) and a real past Greek fire (NASA FIRMS hotspots), each replayed through the real ingest API as an event snapshot and computed on a new deterministic-event lane that reports event loss, never an annual figure. The answer is assets-at-risk on the portfolio page, ranked, with a screening-grade event loss, the snapshot's observed-at and its source version.
What it is not. No live feeds, no cadence, no second worker, no spread model, no sensors — those are rungs R2–R4 in the charter's §4 ladder and get their own phase. Replay and live share one screen by design, so nothing built here is throwaway.
Design record (decisions made 2026-08-27, verified by two reassess passes)¶
| Decision | Choice | Why (evidence) |
|---|---|---|
| Time dimension | on the dataset: hazard_datasets.valid_at + hazard_datasets.live_event_id → live_events |
one dataset per snapshot ⇒ one surface per snapshot with no writer change (core/surface_writer.py:684-698 reuses by exact dataset_id); /v1/risk/lookup already pins dataset_id (api/v1/risk.py:99-105); the RFC's parked "time on the surface" shape is amended, not contradicted |
| Event loss storage | new RLS'd table event_impact_results (event_loss NOT NULL, impact_per_event, loss_exp_path, provenance, grade note) |
Result.ead/.aai are NOT NULL and annual and consumed by smoke_e2e, the results API and portfolio_service:111-116; ADR-065 rejected a sentinel frequency; ADR-068/069 make frequency-less rows indicator-only — the lane must never write ead/aai |
| Compute entry | POST /v1/live-events/{id}/compute with the landed exposure selector (exposure_dataset_id | portfolio_id, #1008) |
/compute/impact keeps refusing frequency-less datasets; the event lane needs validate_impact_refs's ref/tenancy/unit checks without assert_dataset_eai_eligible (core/compute_service.py:176 is unconditional today) |
| Grade | a lane property: EVENT_LANE_GRADE_NOTE + event_result_grade() beside _SCREENING_GRADE_PERILS, never an edit to it |
HazardType.EARTHQUAKE is already in _SCREENING_GRADE_PERILS with a note bound to ESHM20 return-period grids (core/eai_eligibility.py:156-165); editing it would re-label the annual EAI path |
| Ingest | IngestRequest gains a single-event mode (file_path + event: {label, valid_at}, no return_periods) writing ADR-068's unlabelled event, plus an optional intensity_unit restricted to units the seeded curves declare for that peril |
mode 1 requires return periods (api/v1/hazards.py:47-116); the slice axis is NetCDF-only; source_format accepts only geotiff/netcdf (worker/ingest/adapters.py:310-313) and INGESTOR_REGISTRY is peril-keyed — a second wildfire ingestor has no slot, so FIRMS is rasterised to GeoTIFF and declared K |
| Snapshot visibility | hidden by default from GET /v1/datasets, lookup precedence and matrix resolution; GET /v1/hazards/{id}/layers and the tile route resolve normally; single-event mode still runs convert_to_cog |
a 1 km ShakeMap or FIRMS raster outscores ESHM20/JRC in core/dataset_precedence.py and would hijack every citizen pin; the UI overlay needs cog_path + legend (api/v1/hazards.py:885-893) |
| Dark landing | GET /v1/live-events returns 200 {"data": []} when live_mode_enabled=False; write paths refuse with the existing 422 taxonomy; the UI Events section mounts only on a non-empty list |
the UI has no feature-flag layer and /v1/info carries only version/environment/sha — a data-driven conditional degrades correctly on any stack in any deploy order |
| UI home | /portfolios/:id/events/:eventId, entered from an Events section on /portfolios/:id; no header entry |
Phase 11 guardrail 4's target is invisible surfaces; /portfolios/:id itself is a nav-less nested route reached by click (ui src/App.tsx:157); the 11-link header has no overflow mechanism |
| Smoke | the event leg runs on a vendored, Greece-clipped ShakeMap GeoTIFF in tests/fixtures/reference_datasets/, staged by sdk-smoke.yml like river_flood.tif |
the walk never egresses (smoke_e2e.py:93-112), a release gate cannot depend on a third-party host, and ShakeMaps are revised — a live fetch is non-deterministic. USGS terms verified 2026-08-26: USGS-produced data is U.S. public domain, credit "U.S. Geological Survey" |
| Replay events | earthquake 2020-10-30 Samos–İzmir M7.0; wildfire Evia, 3–10 Aug 2021 | scripts/historical_catalog_manifest.json names no candidate for either (no earthquake block; wildfire status: "gap") — both get new manifest rows; perils.wildfire.status stays "gap" (an observed footprint is not #412's return-period footprint) |
| #625 | superseded by W3 and closed when W3's issues are filed, its two unique checks carried into the rasteriser issue; #412 gets a scope comment (never an edit) | #625's body is W3 almost verbatim; #412 is return-period sourcing and scopes the K mismatch out |
| Poller | deferred to the R2 phase (recommendation recorded in the charter §12: in-worker beat task, adapters under ingest/sources/, service-function calls only) |
out of scope here |
Guardrails — every Phase 13 issue¶
Inherited from phase-11-showcase.md (1–5), plus:
- No sentinel frequency, ever. An event dataset's rows carry
rp,exceedance_freqandmarginal_freqNULL; the lane passes unit weights to the engine and discardsaai_agg,eai_expand the frequency curve at the adapter. Noead/aaion any event row or payload. _SCREENING_GRADE_PERILSis byte-identical before and after the phase.- Snapshots are invisible to the probabilistic readers (catalog list,
lookup precedence, matrix resolution) unless addressed by
live_event_id. - No push, no subscription, no email; the word "alert" does not appear in any user-facing copy. Every live surface renders the line "Not for life-safety or emergency response — consult official civil-protection sources." and the snapshot's observed-at, age at render and source version.
- Real data through the real API only (ADR-077): replay content is a real
past event fetched by a pack from the primary provider, with a
docs/data/volume-budget.mdrow (licence verbatim) before download and ascripts/historical_catalog_manifest.jsonrow; nothing copied fromrisk-assessment-tool(exploration/05-rat-convergence.md). - Migration PRs are gated (
migrations-gate.ymlonmigrations/**andmodels/**) — the one path where NO-PR-CI's "merge on mergeable" does not apply; typed Alembic ops only; new org tables use migration 0082'sNULLIF(...)RLS predicate.
Scope — waves and items¶
Dependency line: W0 → W1 → {W2, W3, W4} → exit. W2/W3/W4 are mutually
independent once W1 lands. Sizes are drafts; /phase-split grounds and
re-sizes each item against the code.
W0 — Foundations (backbone)¶
| Item | Delta (grounding) | Acceptance |
|---|---|---|
W0.1 live_mode_enabled flag + SSRF allowlist entries |
config.py has the default-off pattern (score_bands_enabled:198, geocoder_enabled:555); allowlist at config.py:208-214 lacks earthquake.usgs.gov and firms.modaps.eosdis.nasa.gov (pack hygiene — packs download operator-side; the platform ingests local file_paths) |
flag default False; both hosts pass core/ssrf.py's validator; unit tests |
W0.2 migration 0087: live_events + hazard_datasets.live_event_id/valid_at |
no live_event/valid_at anywhere in src/ (grep 2026-08-26); latest migration 0086 |
live_events(id, org_id, portfolio_id nullable FK, haz_type, name, status, started_at, ended_at, source, provenance) — portfolio_id is the book the event is watched against and what GET /v1/live-events?portfolio_id= filters on (added at split time, 2026-08-27) with FORCE RLS via the 0082 predicate; FK ON DELETE CASCADE; index (live_event_id, valid_at); alembic upgrade head from scratch under the gate; RLS coverage test lists the table |
| W0.3 single-event ingest mode | IngestRequest.check_input_mode (api/v1/hazards.py:107-116) requires file_path+return_periods; core/ingest/base.py:1708-1720 always computes marginal freqs; derive_dataset_axis (aggregate_and_commit.py:1150-1172) yields SCENARIO_YEAR only for NetCDF slice layouts |
file_path + event: {label, valid_at} accepted (exactly one of the three modes); worker emits one ADR-068 unlabelled event with NULL rp/freqs; optional intensity_unit accepted only if in seeded_units_for(haz_type, ...), else 422; convert_to_cog still runs; dataset lands with live_event_id + valid_at when the request names an event |
| W0.4 snapshot visibility rule | HazardRepository.list() filters status only (db/repositories/hazard_repository.py:273-275); lookup_service.select_dataset and compute_batch_service.resolve_matrix_cell_datasets consume dataset_precedence with no live notion |
default queries add live_event_id IS NULL; ?live_event_id= lists snapshots; /v1/hazards/{id}/layers returns a raster LayerSpec for a snapshot; a lookup pin next to a snapshot still answers from the probabilistic dataset (test) |
W1 — Deterministic-event lane (backbone)¶
| Item | Delta (grounding) | Acceptance |
|---|---|---|
| W1.1 event-lane grade + validation | core/eai_eligibility.py:136-165 (EaiGrade, _SCREENING_GRADE_PERILS); validate_impact_refs calls assert_dataset_eai_eligible unconditionally (core/compute_service.py:176) |
EVENT_LANE_GRADE_NOTE + event_result_grade(); validate_event_impact_refs (or require_eai=False) keeps ref/tenancy/unit/exposure-type checks, accepts SCENARIO_YEAR; test: same earthquake dataset yields different grade notes on the two lanes; _SCREENING_GRADE_PERILS unchanged |
W1.2 migration 0088 event_impact_results + compute_event_impact task |
worker/tasks.py:494-531 _event_frequency_vector refuses non-RP; engine_adapter.py:177-181 already fills impact_per_event from at_event |
table (org_id, job_id UNIQUE, live_event_id, hazard_dataset_id, exposure_dataset_id, impact_function_id, valid_at, event_loss NOT NULL, impact_per_event, loss_exp_path, grade note, provenance); task reuses compute_impact's load steps, passes np.ones(n_events), asserts axis is not RETURN_PERIOD, stores at_event and per-point losses, discards aai_agg/eai_exp/freq curve; routed to compute; excluded from prewarm_result_cache; ADR-078 in docs/DECISIONS.md extending ADR-065/068 |
W1.3 /v1/live-events API + error codes |
no such router; core/errors.py is the stable-code registry (sdk-regenerate.yml fires on it) |
POST /v1/live-events (create), GET /v1/live-events (200 empty when flag off), GET /{id}, GET /{id}/snapshots?as_of=, POST /{id}/compute (202 + job id; selector exposure_dataset_id|portfolio_id; refused with a stable code when flag off), GET /v1/live-events/{id}/results/{job_id} returning event_loss, impact_per_event, per-point losses joined to exposure rows, valid_at, grade note; codes E_LIVE_MODE_DISABLED, E_LIVE_EVENT_NOT_FOUND; SDK errors.py mirrored; docs/CLIMATE_LAMA.md API section |
W2 — Earthquake replay (pack + smoke)¶
| Item | Delta (grounding) | Acceptance |
|---|---|---|
| W2.1 ShakeMap replay pack + vendored fixture | scripts/packs/ convention (dataset_identity.py, manifest rows); no earthquake block in scripts/historical_catalog_manifest.json; tests/scripts/test_historical_catalog_manifest.py asserts the schema |
scripts/packs/shakemap_event.py: fetch the archived USGS product for 2020-10-30 Samos–İzmir M7.0 (event id resolved at run time via the FDSN API — the exact query is [UNVERIFIED] and is this item's first step), parse grid.xml PGA, convert %g → g, write GeoTIFF + source_manifest.json; volume-budget row (USGS public domain, credit "U.S. Geological Survey") before download; manifest row; a Greece-clipped, small (<1 MB) tests/fixtures/reference_datasets/earthquake_event_pga.tif + README; an EarthquakeIngestor ingest in single-event mode succeeds locally |
| W2.2 smoke event leg | scripts/smoke_e2e.py:108-138 singular constants, one ingest body at :828-836; sdk-smoke.yml:80-90,159-163 stages river_flood.tif |
HazardLeg dataclass with the river-flood leg unchanged + an event leg (return_periods=None, SDK_SMOKE_EVENT_TIF); creates a LiveEvent, ingests the fixture, computes on the lane against the walk's exposure, asserts event_loss > 0, non-empty per-point vector, valid_at, grade note == EVENT_LANE_GRADE_NOTE, and "ead"/"aai" absent; sdk-smoke.yml stages the fixture; the existing leg's assertions unchanged |
W3 — Wildfire replay (pack) — parallel with W2¶
| Item | Delta (grounding) | Acceptance |
|---|---|---|
| W3.1 FIRMS brightness-temperature pack (supersedes #625) | design of record is core/ingest/wildfire.py:33-38's docstring ("ingest wildfire as a real intensity — FIRMS brightness temperature, threshold 295 K"); seeded Lüthi curves in K at 1/4/10 km (impact_function_seeder.py:586-621); no FIRMS code in src/ or scripts/ |
scripts/packs/firms_bt_event.py: FIRMS archive CSV (VIIRS 375 m, Evia bbox, 2021-08-03..10, MAP_KEY from env) → per-day 1 km EPSG:4326 GeoTIFF of max bright_ti4 (nodata elsewhere); volume-budget row (NASA "full and open", LANCE FIRMS acknowledgement string; no named licence — recorded as such) before download; manifest row with perils.wildfire.status left "gap"; ingest each day in single-event mode with intensity_unit="K", indicator=false under one LiveEvent (one dataset per timestep, ADR-068); the seeded 1 km Lüthi curve auto-resolves and the lane computes; ops check (manual, post-merge): ingested via the unified pipeline, catalogued under the event, tiles render, event loss runs |
W4 — UI (climate-lama-ui) — parallel with W2/W3¶
A backbone coordinator issue carries the contract; children live in the UI repo per the cross-repo convention in plan.md.
| Item | Delta (grounding, verified 2026-08-26) | Acceptance |
|---|---|---|
| W4.1 Events section + route + rail | PortfolioDetailPage.tsx has Stat and a module-private WorstMembersTable (:107-165) fed by the screening rollup; no live types in src/types/index.ts; hand-written src/api/client.ts |
Events section on /portfolios/:id mounts only when GET /v1/live-events?portfolio_id= is non-empty (byte-identical page on []); route /portfolios/:id/events/:eventId; rail = event header (name, hazard chip, Replay/Near-real-time chip, source + version, observed-at, age at render), EventLossHeadline (currency from the backbone unit via formatNumber, EaiGradeBadge, 3-tier RAG_COLORS only), inline-accordion "why this number" with ProvenanceLine, RankedAssetsTable lifted from WorstMembersTable (PortfolioDetailPage keeps using it — pure refactor guarded by its test), life-safety line; live i18n namespace; no "alert" string (test) |
W4.2 LiveEventMap + layer panel generalisation + SnapshotScrubber |
LayerKey closed union (types/index.ts:684); LayerPanel literal 3 rows (:84-88); LookupMap/ResultMap remove+re-add the raster source per change; YearRangeSlider is a dual-thumb scenario control |
MapLibre map with the snapshot COG via hazardRasterTileUrl + /layers legend, exposure points coloured by per-point event loss (existing riskColor ramp), layer rows from a LayerRowDef[] prop; scrubber over the event's snapshots (play/pause, step, ticks, observed/now marker, aria-valuetext), switching layers by visibility/opacity with a ±2 warm window — never remove/add sources; layout = aside at inline-start + Splitter + map + scrubber row (RiskLookupPage.tsx:452-538 anatomy, logical properties, h-[45vh] map below sm, resize test) |
W4.3 useIntervalFetch |
usePoller is one-shot; useNotifications has the right loop hard-wired |
generic hook extracted, useNotifications refactored onto it (NotificationBell test is the guard); the event view refreshes on it only while an event is open |
Issues (split 2026-08-27 — /phase-split, sized by /analyze)¶
Milestone "Phase 13 -- Event Replay" in both repos, label milestone:phase-13.
| Wave | Backbone | UI (climate-lama-ui) |
|---|---|---|
| W0 | #1018 flag + allowlist · #1019 migration 0087 · #1020 single-event ingest mode · #1021 intensity_unit override · #1022 snapshot visibility |
— |
| W1 | #1023 event-lane grade + validator (ADR-078) · #1024 migration 0088 event_impact_results · #1025 compute_event_impact · #1026 /v1/live-events CRUD + codes · #1027 compute + results read |
— |
| W2 | #1028 ShakeMap pack + fixture · #1029 smoke event leg | — |
| W3 | #1030 FIRMS pack (supersedes #625, closed) | — |
| W4 | #1031 coordinator (not a build target) | ui#240 foundation · ui#241 useIntervalFetch · ui#242 LiveEventMap + LayerPanel · ui#243 SnapshotScrubber · ui#244 RankedAssetsTable + EventLossHeadline · ui#245 Events section + page |
Dependency line as filed: #1018/#1019/#1023 roots → #1020 → {#1021, #1022, #1024} → #1025 → #1026 → #1027 → {#1029 (also #1028), #1030}; #1028 hangs off #1020. UI: ui#240 → {ui#242, ui#243, ui#244}; ui#241 independent; ui#245 last. Land with /phase-build phase-13 in each repo (backbone first; ui#240 captures its fixtures from #1027).
Build status (2026-09-03)¶
| Wave | Issues → PRs | Landed |
|---|---|---|
| W0 | #1018→#1123, #1019→#1124, #1020→#1125, #1021→#1126, #1022→#1127 | 2026-08-31 |
| W1 | #1023→#1130, #1024→#1129, #1025→#1131, #1026→#1133, #1027→#1134 | 2026-08-31/09-01 |
| W2 | #1028→#1143 (ShakeMap replay pack), #1029→#1144 (event leg in the smoke walk) | 2026-09-02 |
| W3 | #1030→#1145 (FIRMS brightness-temperature pack), →#1153 (key-leak fix: _silence_transport_url_logging, real extract replaces the synthetic fixture) |
2026-09-02 |
| W4 (backbone) | #1031 coordinator — closed on the children's evidence, not a /build target |
2026-09-02 |
| W4 (UI) | ui#240→#288, ui#241→#287+#289, ui#242→#290, ui#243→#291, ui#244→#292, ui#245→#293, ui#294 trunk-gate fix→#295, docs+version→#297 | 2026-08-31/09-01/02 |
All fourteen backbone issues (#1018–#1031) and all six UI issues are closed
as of 2026-09-03. #1028
closed 2026-09-02 (21:52 UTC) on sdk-smoke.yml run 33597653760 satisfying
its stack-proof, and coordinator
#1031 closed
the same day once both children (its UI child and the smoke evidence) were
in. #1030 closed
2026-09-03 once the FIRMS pack ran for real against the live archive and the
Evia replay landed on prod (see "Prod replays" below); the ops tracker
#1146 closed with
it. Released as backbone v0.21.0
(fe05e17) + UI v0.12.0 (c35b1dc), deployed to prod 2026-09-02 06:41
UTC beside climate-lama-climada-sidecar:local (healthy). Live mode has
been on prod since 2026-09-02 (.env.prod hand edit adding
LIVE_MODE_ENABLED=true, reaching the running api/worker containers) —
the Events section stays dark until a live event exists for a portfolio, per
the dark-landing design record.
Mid-review: mid-review-phase-13.md — verdict
Ready for next phase. Phase 13 CLOSED 2026-09-03 on the owner's walk of
Moment 2 steps 8–9 on both replay books (exit criterion 2, the last open one);
all three exit criteria met and the showcase markers are live.
Prod replays (2026-09-03)¶
Both replays ran on prod (v0.21.0, live mode on) as an API-key session for
the org admin, every write through the public API, against real LitPop Greece
books — no invented assets.
Precondition fixed first: prod's region vocabulary held only
GRC | Attica | 1, CYP | Cyprus | 0 and CYP | Larnaca | 1, so an ingest
declaring region: "Greece" was refused (E_VALIDATION {"region": "Greece"})
although every live Greek dataset already carried that region. The tracked GADM
4.1 admin-0 fixture (gadm41_GRC_0.json.gz, sha256 b6febf95…, 145,741 B) was
loaded with scripts/load_admin_boundaries.py --geojson inside the api
container → GRC | Greece | 0 inserted 07:15:36 UTC. Command recorded in
RUNBOOK.md, "Country data run on prod".
| Samos–İzmir M7.0 (W2) | North Evia wildfire (W3) | |
|---|---|---|
| Event date | 2020-10-30 | 3–10 Aug 2021 |
| Book | 9de7a920-3aed-4aa4-b06e-08b46c989068 — 100 LitPop Greece rows in 25.8–27.2 E, 37.3–38.6 N |
dbe2a065-1f7b-47e5-8ee1-1f9e789a8087 — 216 LitPop Greece rows in 22.8–24.1 E, 38.5–39.1 N |
| Live event | 2e3fd696-b326-442c-b72c-c21363730e5e, earthquake, open, source "USGS ShakeMap, atlas product us7000c7y0 v1" |
da01cb7a-fa36-4510-8fcb-5357134f2508, wildfire, open, source "NASA FIRMS VIIRS S-NPP 375 m, VIIRS_SNPP_SP (Collection 2)" |
| Snapshots | one — 47564d9e-e13d-404b-b6b2-bd4ca58c13e6, valid_at 2020-10-30T11:51:27Z, unit g (ingest job 3bcbd98b…, 6 s) |
six, one per acquisition day, all unit K, indicator=false, ~6 s each: 35d9724f… 08-05T00:31Z · 795a32a1… 08-06T00:12Z · ce94fa69… 08-07T01:33Z · 77ab86d9… 08-08T01:15Z · 84de6c15… 08-09T00:56Z · 8db5cecb… 08-10T00:37Z |
| Curve | — | impact_function_id omitted on every compute; the seeded Luthi 2021 WF buildings 1km default (b671d3e2…, unit K) auto-resolved |
| Compute | job 76e9ec1d-8d89-45d7-9a08-4473ff6bb2fb, completed in 111 s |
six jobs, all completed |
| Event loss (USD) | 585,548,574, grade screening; top members 134,898,561 / 78,285,453 / 74,662,151 |
08-05 926,127 · 08-06 21,451,558 · 08-07 15,095,527 · 08-08 13,491,984 · 08-09 5,470,912 · 08-10 15,714,748 — grade screening; the 08-06 peak matches the fire's worst day |
ead/aai |
absent from the payload | absent from every payload |
| Layers | raster, /v1/tiles/cog/47564d9e…/{z}/{x}/{y}.png, legend unit g, domain 0.0024–0.129 g, band max 0.526 g (titiler statistics) |
raster, tile route, legend unit K |
The FIRMS pack itself ran against the live archive: 2 requests (2021-08-03
day_range 5, 2021-08-08 day_range 3), both HTTP 200, 314,404 B raw, 4,092
rows; brightness column pinned as bright_ti4 (Collection 2, version
= 2); six per-day GeoTIFFs, 113 × 67 EPSG:4326 float32, 11,606 B total. The
vendored fixture is now a real extract (rows[::16] of 2021-08-06),
fixture_is_synthetic = false. Volume-budget row MEASURED (raw 0.000314 GB,
processed 0.0000116 GB). The pack's first live run printed the MAP_KEY
through httpx's INFO request log (it is a URL path segment) — fixed in PR
1153 with a prove-it-fails test; the key reached only a build agent's local¶
transcript, never a commit, an issue or CI.
Guardrail results (exit criterion 3, verified on trunk 7cc61db)¶
| Guardrail | Result |
|---|---|
6 — no ead/aai on any event row or payload |
HOLDS — absent from all seven prod result payloads |
7 — _SCREENING_GRADE_PERILS byte-identical |
HOLDS — unchanged vs v0.20.0; the diff adds only EVENT_LANE_GRADE_NOTE beside it (core/eai_eligibility.py:156 and :192), never an edit to the mapping |
| 8 — snapshots invisible to the probabilistic readers | HOLDS on prod — GET /v1/hazards?haz_type=earthquake lists only the two ESHM20 datasets, ?haz_type=wildfire only the two FWI datasets, with seven event snapshots resident |
| 9 — no "alert" in user-facing copy; life-safety line everywhere | HOLDS — the UI's live namespace and live components contain no non-ARIA "alert"; the life-safety line is pinned by src/test/LiveEventPage.test.tsx |
| 10 — real data through the real API only | HOLDS — both replays fetched from the primary provider, volume-budget and manifest rows present, fixture a real extract |
— perils.wildfire.status still "gap" |
HOLDS — an observed footprint is not #412's return-period footprint |
Exit criteria:
| # | State | Evidence |
|---|---|---|
| 1 — Smoke | MET | sdk-smoke.yml run 33597653760 green on trunk fe05e17 (2026-09-02 06:09 UTC), the release gate for v0.21.0: OK [EVT_GRC] event_loss=49450.52513357252 USD across 3 asset(s), valid_at=2020-10-30T11:51:27Z, grade=screening and the line E2E_READY_EVT_GRC live_event_id=a19306b9-0767-4a19-8655-4672870409d7 snapshot_id=d139b6e2-173a-4685-9283-0fe9f9f55c33 exposure_id=72d459e3-ef98-444c-bc6c-4face2f40a77 job_id=cd5acd99-6ced-4ec3-8a58-2580b74c0bda event_loss=49450.5251 assets=3 valid_at=2020-10-30T11:51:27Z elapsed=10.5s |
| 2 — Walk | MET | Both replay books exist on prod with computed losses and rendered tile routes (see "Prod replays" above); the ops tracker #1146 closed 2026-09-03 on that evidence, and the owner walked Moment 2 steps 8–9 in the browser the same day — /portfolios/9de7a920… opened the Samos card to its header, life-safety line, the 585,548,574 USD screening-grade loss, the ranked table and the ShakeMap raster; /portfolios/dbe2a065… played the six-tick Evia scrubber with observed-at and loss changing per day and peaking on 6 August; a book without an event showed no Events heading; a page search for "alert" found nothing. The showcase markers are live |
| 3 — Guardrails | MET | Every guardrail re-verified 2026-09-03 on trunk 7cc61db and against prod — see the guardrail table above. _SCREENING_GRADE_PERILS byte-identical vs v0.20.0, no non-ARIA "alert" in the UI's live namespace or components, the life-safety line pinned by src/test/LiveEventPage.test.tsx, snapshots hidden from the prod catalog, perils.wildfire.status still "gap" |
Conflict hotspots (files touched by >1 item)¶
src/climate_lama/api/v1/hazards.py (W0.3, W0.4), src/climate_lama/models/hazard.py
(W0.2, W0.3), src/climate_lama/config.py (W0.1), src/climate_lama/core/errors.py
+ sdk/python/climate_lama_client/errors.py (W1.3), src/climate_lama/worker/tasks.py
(W1.2), scripts/historical_catalog_manifest.json + its test (W2.1, W3.1),
docs/data/volume-budget.md (W2.1, W3.1), scripts/smoke_e2e.py +
.github/workflows/sdk-smoke.yml (W2.2); UI: src/types/index.ts, src/api/client.ts,
src/App.tsx, src/components/LayerPanel.tsx, src/i18n/index.ts (W4.1–W4.3).
Exit criteria¶
- Smoke:
sdk-smoke.ymlgreen on trunk runningscripts/smoke_e2e.pywith the event leg — the vendored ShakeMap fixture ingested in single-event mode, computed on the lane,event_loss > 0, per-point vector non-empty,valid_atpresent, grade note ==EVENT_LANE_GRADE_NOTE, noead/aai. Deliberately narrower than the charter's §11 wording (reproducing a ranked list from a stored snapshot series): a series is rung R2; the single- snapshot assertion is the correctly scoped form for R0+R1. - Walk: the owner walks showcase-script Moment 2 steps 8–9 on a compose
stack (
UI_TAG=sha-<short>) for both replays — Samos 2020 and Evia 2021 — and flips the markers. - Guardrails hold:
_SCREENING_GRADE_PERILSunchanged;grep -rn "alert"over the UI'slivenamespace and live components returns 0; every live surface renders the life-safety line (UI test);perils.wildfire.statusstill"gap".
Out of scope (deliberate)¶
Feeds and cadence (FIRMS/EFFIS/USGS polling, live queue, second worker,
Redis lock, snapshot retention/pin rule), ELMFIRE/WindNinja/weather, sensors and
drones, uncertainty ranges, notifications/triggers, a fourth showcase moment,
positioning/customers — all in the charter's ladder or Parked ledger.
Design canvas¶
Owner's artifact "Live Event Mode" (5 artboards: earthquake replay = leading candidate, wildfire near-real-time, two direction sketches, phone layout; all figures sample values): https://claude.ai/code/artifact/a5e73a24-ca16-49f6-bf83-770ec7504573