Repository navigation
Description
What this epic delivers
Reconciliation places every country's fatalities on the grid. After this epic, for
every (month, country, draw) the reconciled grid cells sum to the country forecast. When
the grid is all zeros in a draw, the pattern is borrowed from a donor draw in the same month
and country. When no donor exists, the call fails loud. This ships as the new default in
views-frames 3.0.0.
The problem
Reconciliation scales a grid (pgm) forecast so each country's cells sum to that country's
(cm) forecast. Scaling is multiplication, so it can't do one thing: if every cell in a
country is zero in a draw, no multiplier produces a positive total. Today those draws stay
at zero, and the country's fatalities vanish without a word
(src/views_frames_reconcile/proportional.py:81-98). The contract calls this "an edge, not
a failure" (docs/CICs/Reconcile.md §3/§6), and the published conformance check
(conformance.py:72-91) requires it.
Measured on the first production run (chrome_crate → chrome_box, 2026-10-30,
64,742 cells × 36 months × 1,024 draws × 3 targets; #283):
| target | fatality mass lost | draws affected |
|---|---|---|
lr_sb_best |
4.023% | 164,429 |
lr_ns_best |
7.321% | 188,481 |
lr_os_best |
2.943% | 193,903 |
The scaling itself was exact (0 failures across 22.3M triples). All of the loss is this
case. pipeline-core neither causes nor hides it (views-pipeline-core#579). The test suites
in both repos were green because no fixture ever produced the case (#285).
Why it matters
This forecast is bound for a UN partner. The platform's premise is that the country
forecast is the authority: reconciliation exists to impose the country's numbers on the
grid. Today the code honours that in one direction (a zero country wipes the grid's mass)
and quietly abandons it in the other (a positive country with an empty grid loses its
mass). That's half a rule, and it fails silently, which breaks ADR-003 (fail loud).
The decision (made 2026-10-31; ADR-030 records it)
Analysis on the real archive (2026-10-31, read-only against the delivery):
- Every one of the 546,813 empty-grid draws has donors in the same month and country.
The minimum pool is 10 draws and the median is 130–211. Neither a neighbouring-month
fallback nor a no-donor case ever happened. - Empty-grid draws carry real deaths: country value median 3–5, mean 28–78, never below 1.
- A draw's country value barely predicts its grid pattern (Spearman 0.06–0.09), so
k-nearest matching on the country value would behave like a random pick on this data.
So:
- Borrow from the same month. For an empty-grid draw with a positive country value,
copy the pre-scaling pattern of a donor draw chosen at random from the draws in the same
(month, country)group that have grid mass, then scale as usual. - Random donor, with a seed you can reproduce:
default_rng([seed, time, country]). - Fail loud when there is no donor.
- No neighbouring-month step and no k-nearest, for now. Both are noted, not built.
- It's the new default, with no opt-out → 3.0.0. Under GOVERNANCE.md this is a MAJOR
for two reasons: a call that succeeds today (a grid with no donor) starts raising, and
the published conformance laws change. The coordination is small: only
views-pipeline-core (pinned<3.0.0) and views-models use the package.
"Country is the authority" is a premise, not a finding. ADR-030 states it as a decision,
and a register entry keeps the empirical test open.
Where we end up
The reconciled grid sums to the country forecast in every draw, or the call raises.
The tests would catch it if that ever stopped being true, and mutation testing has shown
it. The contract, the conformance suite and the docs say so. 3.0.0 is on PyPI, and both
consumers have adoption issues.
Scope boundaries
In scope: a new donor_fill.py module and its wiring in module.py; the result
reporting; the fail-loud path; the conformance laws; the CIC/ADR/README/CHANGELOG/register;
realistic test fixtures and mutation testing; a real-data proof; the 3.0.0 release.
Not in scope:
proportional.pyandgrouping.pystay byte-for-byte unchanged (CIC §11, ADR-024
rule). The torch-oracle kernel parity test stays green as proof.- The draw-pairing question (C-62 / ADR-024), which is a separate problem.
- Neighbouring-month borrowing and k-nearest donors. They're noted below and come back only
on evidence. - Any edit to another repository. Downstream work is listed as notes, not done here.
The stories
| # | Story | Labels | Waits for |
|---|---|---|---|
| S1 [#287] | ADR-030: the decision, the 3.0.0 bump, register entries | planning, adr, needs-decision | — |
| S2 [#285] | Realistic test fixtures + baseline mutation audit | testing | S1 |
| S3 [#288] | donor_fill: borrow a donor pattern for empty-grid draws |
implementation | S1, S2 |
| S4 [#284] | Fail loud when an empty-grid draw has no donor | implementation | S3 |
| S5 [#289] | Mutation stress-test of the new path (release gate) | testing | S3, S4 |
| S6 [#290] | Restate the conformance laws and the CIC | documentation, implementation | S3, S4 |
| S7 [#291] | README, docstrings, CHANGELOG, register | documentation | S6 |
| S8 [#292] | Real-data proof on the chrome_box archive |
testing | S3, S4 |
| S9 [#293] | Adoption issues in views-pipeline-core and views-models (maintainer) | release | S6 |
| S10 [#294] | Release 3.0.0 (maintainer tags) | release | S5, S7, S8, S9 |
| S11 [#295] | Close-out | planning | S10 |
S1 → S2 → S3 → S4 ─┬→ S5 ──────────────┐
├→ S6 → S7 ─────────┼→ S10 → S11
├→ S8 ──────────────┤
└→ S6 → S9 ─────────┘
The tracking issue #296 lists every issue in order.
Epic-level acceptance criteria
- For every
(time, country, draw)with a positive country value, the reconciled grid
sums to it withinrtol=1e-4, atol=1e-3. Otherwise the call raisesValueError. - A filled draw's cells are a scaled copy of one donor draw from the same group: same
zeros, same proportions. - Same inputs + same seed → bit-identical output, whatever the call chunking or group
order. -
proportional.pyandgrouping.pyare unchanged; the kernel oracle parity test is
green; the e2e oracle test is identical outside filled draws. - Every mutation recorded in S5 is killed.
- On the real
chrome_boxarchive: 0 unplaced mass, 0 scaling failures, and the
filled-draw count equals the 546,813 measured. - CIC, conformance, ADR-030, README, CHANGELOG and register all agree.
bash docs/validate_docs.shpasses. - Full gate green:
uv run pytest --cov --cov-fail-under=100,ruff check .,
ruff format --check .,mypy src/(and--python 3.11),lint-imports. - 3.0.0 on PyPI, verified from a clean venv. Adoption issues filed before the tag.
Noted, not worked here
- views-pipeline-core:
reconcile_frames.py:19-22points at closed S3 — [research] Principled joint probabilistic reconciliation — design + ADR (C-37) #145; #579 drafted
the fix. Widen the pin to<4on adoption (S9). - views-models:
reconciliation/residual.pyshould report 0 unplaced on 3.0.0. Its
MANIFEST wording may change (S9). - The 2026-10-30
chrome_boxUN delivery: whether to re-run it on 3.0.0 is the
maintainer's call. - k-nearest donors: matching needs each donor's pre-reconciliation grid total. The
archive keeps only scaled output, so testing it needs views-models to retain a raw grid
pool. Only if the maintainer asks them. - Neighbouring-month fallback: only if a real run ever hits the no-donor error.
Refs: #283 (problem + measurement), views-pipeline-core#579 (downstream investigation),
ADR-023, ADR-024, C-62.
Activity
feat(reconcile)!: place every country's mass — donor fill for empty-g…
chore(release): 3.0.0 (#298)
docs(register): close out 3.0.0 — C-99 resolved, C-13's second execut…
Polichinel commentedon Nov 1, 2026
All stories are done: S1–S8 merged (#297), S9 adoption issues (#293), S10 released 3.0.0 (#294), S11 close-out (#299). Still open, by decision: C-100 (the country-authority premise) and the parked follow-ups (k-nearest donors, the neighbouring-month fallback).