Epic: #85 · S6 · [CROSS-REPO-GATED] · blocked · depends on #88, faoapi #45, register C-40
Background
This is the step that actually propagates (N,S) to FAO. Today _save writes object-dtype df.to_parquet (unfao.py:298,309) to Appwrite, and views-faoapi reads it (pd.read_parquet → to_array_columns decodes object-dtype cells → numpy → build_prediction_frame). The forecast parquet is a wire contract vpp cannot change unilaterally.
Work (gated — coordinated two-sided cutover)
- vpp
_save: emit the forecast as a views-frames arrow sample-frame (views_frames.io.arrow.save — flat columnar time/unit/sample/value) instead of object-dtype parquet.
- views-faoapi: ingest via
arrow.load → PredictionFrame instead of pd.read_parquet → to_array_columns → build_prediction_frame.
Acceptance criteria
Parity / validation
Dual-write / golden-file during cutover: emit both legacy object-dtype parquet and the arrow sample-frame; assert faoapi's reconstructed PredictionFrame is array-equal from both. S1's conformance harness is the shared contract both repos test against.
Dependencies / gate
Blocked on: S3 (interior already a PredictionFrame), faoapi #45 (consumer half + ADR-046 amendment), and register C-40. Never change the emitted wire before faoapi can read it.
Files
views_postprocessing/unfao/managers/unfao.py (_save); coordinated changes in views-faoapi (tracked there). Target API: views_frames.io.arrow.
Review amendment (expert-code-review, 2026-06-28) — wire I/O behind a sink adapter
Emit the arrow sample-frame through a small delivery sink adapter the manager _save calls, not inline to_parquet/arrow.save in the method.
Epic: #85 · S6 ·
[CROSS-REPO-GATED]·blocked· depends on #88, faoapi #45, register C-40Background
This is the step that actually propagates
(N,S)to FAO. Today_savewrites object-dtypedf.to_parquet(unfao.py:298,309) to Appwrite, and views-faoapi reads it (pd.read_parquet→to_array_columnsdecodes object-dtype cells → numpy →build_prediction_frame). The forecast parquet is a wire contract vpp cannot change unilaterally.Work (gated — coordinated two-sided cutover)
_save: emit the forecast as a views-frames arrow sample-frame (views_frames.io.arrow.save— flat columnartime/unit/sample/value) instead of object-dtype parquet.arrow.load→PredictionFrameinstead ofpd.read_parquet→to_array_columns→build_prediction_frame.Acceptance criteria
(N,S)uncollapsed as an arrow sample-frame; faoapi reconstructs thePredictionFrame.Parity / validation
Dual-write / golden-file during cutover: emit both legacy object-dtype parquet and the arrow sample-frame; assert faoapi's reconstructed
PredictionFrameis array-equal from both. S1's conformance harness is the shared contract both repos test against.Dependencies / gate
Blocked on: S3 (interior already a
PredictionFrame), faoapi #45 (consumer half + ADR-046 amendment), and register C-40. Never change the emitted wire before faoapi can read it.Files
views_postprocessing/unfao/managers/unfao.py(_save); coordinated changes in views-faoapi (tracked there). Target API:views_frames.io.arrow.Review amendment (expert-code-review, 2026-06-28) — wire I/O behind a sink adapter
Emit the arrow sample-frame through a small delivery sink adapter the manager
_savecalls, not inlineto_parquet/arrow.savein the method._savecalls it.