Part of epic #105. Depends on S2. Parallel-eligible with S4/S5.
Problem
Hop B ships one views_frames.io.arrow file per (target, month) (§4.1) with the §2 header embedded; no writer exists.
Work
views_postprocessing/unfao/wire/shard.py: write_shard(values, time, unit, *, header, path) -> (name, sha256) calling views_frames.io.arrow.save(path, values=..., time=..., unit=..., level='pgm', metadata=header); name from wire.naming.shard_name; sha256 of the complete file bytes (§4.2 manifest needs it).
- Small per-month slice helper in
unfao/frame_extraction.py (frame → per-month (values, time, unit) primitives) so the writer stays frame-API-free.
Acceptance criteria
- Fed the fixture Track-A frame + S2 header: produced bytes byte-equal
fixture_run_0__lr_ged_sb__m000543.arrow.parquet; returned sha256 matches SHA256SUMS.
- Byte test asserts pyarrow 23.0.1 (fixture README pin) and fails loud with a 'pinned toolchain' message on mismatch — never skip-silent.
Tests: tests/test_wire_shard.py. Oracle: fixture arrow shard.
🤖 Generated with Claude Code
Part of epic #105. Depends on S2. Parallel-eligible with S4/S5.
Problem
Hop B ships one
views_frames.io.arrowfile per (target, month) (§4.1) with the §2 header embedded; no writer exists.Work
views_postprocessing/unfao/wire/shard.py:write_shard(values, time, unit, *, header, path) -> (name, sha256)callingviews_frames.io.arrow.save(path, values=..., time=..., unit=..., level='pgm', metadata=header); name fromwire.naming.shard_name; sha256 of the complete file bytes (§4.2 manifest needs it).unfao/frame_extraction.py(frame → per-month (values, time, unit) primitives) so the writer stays frame-API-free.Acceptance criteria
fixture_run_0__lr_ged_sb__m000543.arrow.parquet; returned sha256 matches SHA256SUMS.Tests:
tests/test_wire_shard.py. Oracle: fixture arrow shard.🤖 Generated with Claude Code