Skip to content

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 againcategory 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.

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) restores instanceRows (:119, via loadInstanceDataAtom) and activeIndication (:121, via setIndicationAtom).
  • The only pre-restore validation is FullSnapshotSchema.safeParse (useForecasting.ts:102) — shape only.
  • Caller: ScenarioProvider.tsx:587.
  • assumptions (sections/Configuration/atoms.ts:157-192) and marketLines (atoms/selectors/lines.ts:135-303) derive from the saved instanceRows, not live diseaseConfigFamily → Model/Sales/Monte-Carlo render stale data.
  • Configuration.tsx:114 reads geoDiseaseConfigAtom (atoms/selectors/geo-indication.ts:83-107) → null when live data is gone → permanent spinner (Configuration.tsx:154-165).

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.

  • 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 = raw Solid Tumor/Hematology/null classification). Currently consumed nowhere in this repo — the wiring is net-new.

On restore, re-validate context.activeIndication:

  1. Cheap first pass: check it against the in-memory indicationsAtom set (free — the gated list already drives the dropdown).
  2. Authoritative: call GET /api/forecasting/statistics/readiness; if not ready, clear/flag the indication rather than restoring verbatim.

Cleanest insertion point: model-atoms.ts:121 (the setIndicationAtom restore) or ScenarioProvider.tsx:587 (the caller).

Server-side gate changes (done in bioloupe-data-gov). This task is client-only.