Epic: #51 · S3 · depends on #61 (S0) · story implementation
Problem (C-25)
The forecast is selected by category="forecast" alone (unfao.py:98, newest-wins) — no loa/name/run filter. A stray upload silently ships the wrong predictions to FAO.
Build shape (per the epic design contract)
delivery/identity.py (representation-free): assert_forecast_identity(selected: dict, expected: dict) -> None over plain dicts (name/loa/etc.). No pandas.
unfao/extraction.py: file_metadata(downloaded) -> dict.
- Manager: constrain the selection to the configured ensemble (name +
loa="pgm"), or validate the selected file's metadata post-download via the invariant; fail loud on mismatch; log the chosen file identity.
Acceptance criteria
Files
views_postprocessing/delivery/identity.py (new), unfao/extraction.py (extend), unfao/managers/unfao.py (_read_forecast_data), tests/.
Epic: #51 · S3 · depends on #61 (S0) ·
storyimplementationProblem (C-25)
The forecast is selected by
category="forecast"alone (unfao.py:98, newest-wins) — no loa/name/run filter. A stray upload silently ships the wrong predictions to FAO.Build shape (per the epic design contract)
delivery/identity.py(representation-free):assert_forecast_identity(selected: dict, expected: dict) -> Noneover plain dicts (name/loa/etc.). No pandas.unfao/extraction.py:file_metadata(downloaded) -> dict.loa="pgm"), or validate the selected file's metadata post-download via the invariant; fail loud on mismatch; log the chosen file identity.Acceptance criteria
delivery/identity.pypure + pandas-free; unit-tested on dicts.category="forecast"file with a different name is rejected; the correct one selected.Files
views_postprocessing/delivery/identity.py(new),unfao/extraction.py(extend),unfao/managers/unfao.py(_read_forecast_data),tests/.