Forecasting Saved Model Readiness Followup
Follow-up: Saved-model reload bypasses the forecasting readiness gate (hole c)
Section titled “Follow-up: Saved-model reload bypasses the forecasting readiness gate (hole c)”Repo to fix: bioloupe-forecasting (the React/TS client). Verified against HEAD db877d0.
Origin: companion to the bioloupe-data-gov readiness-gate hardening (2026-06-05). Hole (a) ungated displayName and (b) unrecognized type were addressed by the 2026-06-05 hardening: (b) remains a server-side gate, while the displayName and category gates added then were dropped again — category on 2026-06-16, displayName on 2026-06-17. category is structurally derived from the line id (stored row redundant); displayName always has a non-blank derived fallback when no curated row is stored, so an ungated displayName can no longer blank a line and hole (a) cannot recur — a stored displayName remains an optional curated label. This hole (c) lives entirely in the client and is NOT fixed by the server work.
The bypass
Section titled “The bypass”Loading a saved ForecastingModel/scenario restores the saved activeIndication + instanceRows verbatim, validating only snapshot shape — never current readiness:
loadStoreSnapshot(src/.../model-atoms.ts:113-126) restoresinstanceRows(:119, vialoadInstanceDataAtom) andactiveIndication(:121, viasetIndicationAtom).- The only pre-restore validation is
FullSnapshotSchema.safeParse(useForecasting.ts:102) — shape only. - Caller:
ScenarioProvider.tsx:587.
Failure mode (verified)
Section titled “Failure mode (verified)”assumptions(sections/Configuration/atoms.ts:157-192) andmarketLines(atoms/selectors/lines.ts:135-303) derive from the savedinstanceRows, not livediseaseConfigFamily→ Model/Sales/Monte-Carlo render stale data.Configuration.tsx:114readsgeoDiseaseConfigAtom(atoms/selectors/geo-indication.ts:83-107) → null when live data is gone → permanent spinner (Configuration.tsx:154-165).
Critical nuance
Section titled “Critical nuance”The client has no per-indication readiness signal — its only isReady is referenceRows.length > 0 (Forecasting.tsx:144). The spinner trigger is “the indication’s reference rows (especially the type row) are absent/incomplete in the gated payload,” NOT a flag the client can read.
Endpoints available from the server
Section titled “Endpoints available from the server”- Gated list:
GET /api/forecasting/statistics(useStatistics.ts:47, query key["statistics"]). - Per-disease readiness:
GET /api/forecasting/statistics/readiness?indication=&geo=→{ ready, score, missing, type, type_unknown }(type= rawSolid Tumor/Hematology/nullclassification). Currently consumed nowhere in this repo — the wiring is net-new.
Fix direction
Section titled “Fix direction”On restore, re-validate context.activeIndication:
- Cheap first pass: check it against the in-memory
indicationsAtomset (free — the gated list already drives the dropdown). - Authoritative: call
GET /api/forecasting/statistics/readiness; if notready, clear/flag the indication rather than restoring verbatim.
Cleanest insertion point: model-atoms.ts:121 (the setIndicationAtom restore) or ScenarioProvider.tsx:587 (the caller).
Out of scope
Section titled “Out of scope”Server-side gate changes (done in bioloupe-data-gov). This task is client-only.