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
67 changes: 67 additions & 0 deletions tests/test_wire_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""§2 header builder (wire/header.py) — byte parity with both fixture envelopes."""

import json
import zipfile
from pathlib import Path

import pytest
from views_frames.io import arrow as vf_arrow

from views_postprocessing.unfao.wire import header

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

_FIXTURE_KWARGS = dict(
sample_count=4,
dtype="float32",
target="lr_ged_sb",
time_id=543,
run_id="fixture_run_0",
generated_at="2026-07-15T00:00:00Z",
provenance={
"ensemble": "fixture_ensemble",
"pipeline_core_version": "0.0.0-fixture",
"reconciled": False,
},
sharding_index=0,
sharding_count=1,
)


def test_byte_parity_with_the_track_a_header():
with zipfile.ZipFile(_FIX / "fixture_run_0__lr_ged_sb__m000543.tap.zip") as zf:
canonical = zf.read("metadata.json")
built = json.dumps(header.build_header(**_FIXTURE_KWARGS), indent=2).encode()
assert built == canonical # ONE header, byte-for-byte (§2 key-order rule)


def test_equals_the_arrow_shard_embedded_header():
state = vf_arrow.load(_FIX / "fixture_run_0__lr_ged_sb__m000543.arrow.parquet")
assert header.build_header(**_FIXTURE_KWARGS) == state["metadata"]


def test_extra_provenance_key_raises():
kwargs = dict(_FIXTURE_KWARGS)
kwargs["provenance"] = {**kwargs["provenance"], "git_sha": "abc"}
with pytest.raises(header.HeaderError, match="git_sha"):
header.build_header(**kwargs)


def test_missing_provenance_key_raises():
kwargs = dict(_FIXTURE_KWARGS)
kwargs["provenance"] = {"ensemble": "e"}
with pytest.raises(header.HeaderError, match="missing"):
header.build_header(**kwargs)


def test_provenance_key_order_is_pinned_regardless_of_input_order():
kwargs = dict(_FIXTURE_KWARGS)
kwargs["provenance"] = dict(reversed(list(kwargs["provenance"].items())))
built = json.dumps(header.build_header(**kwargs), indent=2).encode()
with zipfile.ZipFile(_FIX / "fixture_run_0__lr_ged_sb__m000543.tap.zip") as zf:
assert built == zf.read("metadata.json")


def test_contract_version_matches_the_canonical_manifest():
manifest = json.loads((_FIX / "fixture_run_0__lr_ged_sb__manifest.json").read_text())
assert manifest["contract_version"] == header.CONTRACT_VERSION
75 changes: 75 additions & 0 deletions views_postprocessing/unfao/wire/header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""The §2 contract header builder (ADR-013).

One header, both envelopes: the dict built here becomes ``metadata.json`` inside a
Hop-A archive and the ``views_frames`` embedded metadata of a Hop-B arrow shard —
byte-for-byte, which is why **key order is pinned** (§2's key-order rule: writers
emit exactly the order below so independently written headers can byte-match the
§10 fixture; readers must never depend on it).

Closed sub-objects (§2.1 rule-1 clarification, 2026-07-19): ``id_semantics``,
``provenance``, and ``sharding`` accept no extra keys — adding one is a contract
amendment, not a free addition. This builder enforces that on the only sub-object
the caller supplies (``provenance``); the other two are constructed here.

Mirrors the §10 fixture generator's header (``scripts/build_wire_fixture.py``) —
deliberately not imported from it: the fixture's bytes stay canonical and this
module must independently reproduce them (that is the test).
"""

from __future__ import annotations

CONTRACT_VERSION = "1.5"

_PROVENANCE_KEYS = ("ensemble", "pipeline_core_version", "reconciled")

_ID_SEMANTICS = {"time": "views_month_id", "unit": "priogrid_id"}


class HeaderError(ValueError):
"""The declared header inputs violate the §2 schema."""


def build_header(
*,
sample_count: int,
dtype: str,
target: str,
time_id: int,
run_id: str,
generated_at: str,
provenance: dict,
sharding_index: int,
sharding_count: int,
) -> dict:
"""The §2 header, keys in the pinned contract order.

``provenance`` must carry exactly the three §2.2 keys; everything else is
declared by the caller (typically passed through from the Hop-A header —
the sink mints nothing, §10.2).
"""
if set(provenance) != set(_PROVENANCE_KEYS):
extra = sorted(set(provenance) - set(_PROVENANCE_KEYS))
missing = sorted(set(_PROVENANCE_KEYS) - set(provenance))
raise HeaderError(
f"provenance is a closed sub-object (§2.2): exactly {list(_PROVENANCE_KEYS)}; "
f"extra={extra}, missing={missing}."
)
return {
"contract_version": CONTRACT_VERSION,
"frame_type": "prediction",
"representation": "samples",
"sample_count": sample_count,
"dtype": dtype,
"spatial_level": "pgm",
"target": target,
"time_id": time_id,
"run_id": run_id,
"generated_at": generated_at,
"id_semantics": dict(_ID_SEMANTICS),
"provenance": {key: provenance[key] for key in _PROVENANCE_KEYS},
"sharding": {
"scheme": "per_month",
"index": sharding_index,
"count": sharding_count,
},
}
Loading