Epic: #51 · foundational — every other story depends on this · story implementation
Why
The input-integrity guards must be representation-agnostic invariants (so they survive the pandas→views-frames migration and are reusable by the coming UN agencies), with pandas isolated to a single seam, and called by — not inherited into — the manager. This story stands up that structure once, so S1–S5 each drop in cleanly. It also clears the package hygiene that makes unfao/ "scream what it does."
Mirror the clean convention already used by views_postprocessing/reconciliation/ (one concept per file).
Work
- Create
views_postprocessing/delivery/ (partner-agnostic, representation-free): an __init__.py and the home for the coming invariant modules (coverage.py, observed_range.py, identity.py, provenance.py) + a constants module (region→count, the 82 gids). No import pandas anywhere under delivery/.
- Create
views_postprocessing/unfao/extraction.py: the FAO-local pandas→primitives seam (e.g. cells_of(df) -> set[int], months_of(df) -> np.ndarray, file_metadata(...) -> dict). The only representation-aware module the guards are fed from.
- Wire the manager to call, not inherit: add a single delivery-validation step the manager invokes (initially a no-op pass-through) —
extract → call guards → raise — so S1–S5 register their checks there. Do not add guard logic as methods of UNFAOPostProcessorManager.
- Package hygiene: delete the orphaned
unfao/frames.py (unused conformance artifact) and the dead unfao/mapping/ husk (C-39 leftover); fix the "frame" name overload in enrichment.py (a pd.DataFrame is not a "prediction frame").
Acceptance criteria
Validation
ruff check . clean; PYTHONPATH=. pytest -q green (the design-contract stubs become real passing assertions).
Files
views_postprocessing/delivery/ (new), views_postprocessing/unfao/extraction.py (new), views_postprocessing/unfao/managers/unfao.py (the call hook), views_postprocessing/unfao/enrichment.py (naming), delete views_postprocessing/unfao/frames.py + unfao/mapping/, tests/test_input_integrity_design_contract.py.
Epic: #51 · foundational — every other story depends on this ·
storyimplementationWhy
The input-integrity guards must be representation-agnostic invariants (so they survive the pandas→views-frames migration and are reusable by the coming UN agencies), with pandas isolated to a single seam, and called by — not inherited into — the manager. This story stands up that structure once, so S1–S5 each drop in cleanly. It also clears the package hygiene that makes
unfao/"scream what it does."Mirror the clean convention already used by
views_postprocessing/reconciliation/(one concept per file).Work
views_postprocessing/delivery/(partner-agnostic, representation-free): an__init__.pyand the home for the coming invariant modules (coverage.py,observed_range.py,identity.py,provenance.py) + a constants module (region→count, the 82 gids). Noimport pandasanywhere underdelivery/.views_postprocessing/unfao/extraction.py: the FAO-local pandas→primitives seam (e.g.cells_of(df) -> set[int],months_of(df) -> np.ndarray,file_metadata(...) -> dict). The only representation-aware module the guards are fed from.extract → call guards → raise— so S1–S5 register their checks there. Do not add guard logic as methods ofUNFAOPostProcessorManager.unfao/frames.py(unused conformance artifact) and the deadunfao/mapping/husk (C-39 leftover); fix the "frame" name overload inenrichment.py(apd.DataFrameis not a "prediction frame").Acceptance criteria
views_postprocessing/delivery/exists and imports cleanly; nothing under it imports pandas.views_postprocessing/unfao/extraction.pyexists and is the sole pandas-aware extraction module.unfao/frames.pyandunfao/mapping/are gone;enrichment.pyno longer calls a DataFrame a "prediction frame".tests/test_input_integrity_design_contract.py, the scaffold/hygiene stubs (test_delivery_contract_package_exists,test_delivery_invariants_are_pandas_free,test_extraction_seam_is_isolated_in_one_module,test_enrichment_does_not_call_a_dataframe_a_prediction_frame,test_no_lingering_mapping_directory) flip from xfail to passing and theirxfailmarkers are removed.Validation
ruff check .clean;PYTHONPATH=. pytest -qgreen (the design-contract stubs become real passing assertions).Files
views_postprocessing/delivery/(new),views_postprocessing/unfao/extraction.py(new),views_postprocessing/unfao/managers/unfao.py(the call hook),views_postprocessing/unfao/enrichment.py(naming), deleteviews_postprocessing/unfao/frames.py+unfao/mapping/,tests/test_input_integrity_design_contract.py.