Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions tests/test_product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""The declared FAO product (unfao/product.py) — ADR-013 §4.2a/§6/§4.1a/§11.4 pins."""

from views_postprocessing.unfao import product


def test_targets_are_the_pinned_wire_vocabulary():
# §7a: wire names, never internal model names; order stable for manifests.
assert product.TARGETS == ("lr_ged_sb", "lr_ged_ns", "lr_ged_os")


def test_s_min_is_the_walking_skeleton_floor():
assert product.S_MIN == 2


def test_consumer_document_name_is_the_faoapi_pin():
# §4.1a: any other name is invisible to faoapi's unconditional name filter.
assert product.CONSUMER_DOCUMENT_NAME == "un_fao"


def test_upload_interlock_defaults_off():
# §11.4: the default configuration must be unable to touch the live bucket.
assert product.UPLOAD_ENABLED is False
30 changes: 30 additions & 0 deletions tests/test_wire_naming.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Hop-B name templates (wire/naming.py) — golden-string tests against the §10
fixture's actual file names (the templates' executable pin)."""

from pathlib import Path

from views_postprocessing.unfao.wire import naming

_FIX = Path(__file__).resolve().parent / "fixtures" / "wire_contract"


def test_shard_name_reproduces_the_fixture():
name = naming.shard_name("fixture_run_0", "lr_ged_sb", 543)
assert name == "fixture_run_0__lr_ged_sb__m000543.arrow.parquet"
assert (_FIX / name).exists()


def test_run_manifest_name_reproduces_the_fixture():
name = naming.run_manifest_name("fixture_run_0")
assert name == "fixture_run_0__manifest.json"
assert (_FIX / name).exists()


def test_sidecar_name_reproduces_the_fixture():
name = naming.sidecar_name("fixture_run_0")
assert name == "fixture_run_0__sidecar.parquet"
assert (_FIX / name).exists()


def test_month_padding_is_six_digits():
assert naming.shard_name("r", "t", 7).endswith("__m000007.arrow.parquet")
36 changes: 36 additions & 0 deletions views_postprocessing/unfao/product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""The declared FAO delivery product (ADR-013 §4.2a's "views-postprocessing
configuration" — settled home per the 2026-07-19 CCP decision recorded in the ADR).

Everything here is a **declaration**, never an inference: the delivery refuses to
ship until reality matches these constants, and changing the product is a human
decision plus an edit here — reviewed, git-historied, fail-loud. One reason to
change: the FAO partner relationship.

The constants and their contract homes:

- ``TARGETS`` — the expected target set (§4.2a): a run is *complete* only when every
target listed here has a manifested Hop-A leg. Adding a target follows Amendment
A1 (§7a): maintainer names it, producer's mapping gains an entry, this tuple
gains an entry. Wire vocabulary only — never internal model names.
- ``S_MIN`` — the §6 no-collapse floor passed to ``delivery.draws``. 2 is the
walking-skeleton value; the production pinning mechanism is an OPEN maintainer
item (ADR-013 Post-adoption record, 2026-07-19).
- ``CONSUMER_DOCUMENT_NAME`` — the §4.1a store-document ``name`` pin. faoapi's
query layer filters on it unconditionally; a document under any other name is
invisible to the consumer. Config-owned by views-faoapi; changing it is a
contract amendment.
- ``UPLOAD_ENABLED`` — the §11.4 upload interlock: ``False`` means the sink writes
artifacts locally and never calls the store. Overriding requires an explicit
launch-config declaration (wired in the sink story), and the first live
enablement is gated on faoapi's C-161 closure notice.
"""

from __future__ import annotations

TARGETS: tuple[str, ...] = ("lr_ged_sb", "lr_ged_ns", "lr_ged_os")

S_MIN: int = 2

CONSUMER_DOCUMENT_NAME: str = "un_fao"

UPLOAD_ENABLED: bool = False
8 changes: 8 additions & 0 deletions views_postprocessing/unfao/wire/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""ADR-013 Hop-B wire mechanics (epic #105).

One closure: this package changes when the wire contract
(``docs/ADRs/013_sampled_forecast_wire_contract.md``) changes, and for no other
reason. The FAO *product* declaration lives outside it (``unfao/product.py`` —
different reason to change); representation-free *rules* live below it
(``delivery/``); the manager above composes it.
"""
31 changes: 31 additions & 0 deletions views_postprocessing/unfao/wire/naming.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Hop-B file-name templates (ADR-013 §4.1b).

A file name is a **locator only, never identity** (§3.3's lesson): consumers select
by store-document metadata and verify by manifest content hashes; these names exist
so humans and buckets can address the artifacts. Do not parse meaning back out of
them.

Not to be confused with the store-document ``name`` *field* (§4.1a,
``product.CONSUMER_DOCUMENT_NAME``): that routes faoapi's queries; these name files.
The two never interchange.

Templates are pinned by the §10 golden fixture and golden-string tested — a drift
here is a contract change, not a refactor.
"""

from __future__ import annotations


def shard_name(run_id: str, target: str, time_id: int) -> str:
"""`{run_id}__{target}__m{time_id:06d}.arrow.parquet` — one per (target, month)."""
return f"{run_id}__{target}__m{time_id:06d}.arrow.parquet"


def run_manifest_name(run_id: str) -> str:
"""`{run_id}__manifest.json` — ONE per run (§4.2, the commit marker)."""
return f"{run_id}__manifest.json"


def sidecar_name(run_id: str) -> str:
"""`{run_id}__sidecar.parquet` — one per run (§5)."""
return f"{run_id}__sidecar.parquet"
Loading