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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ research/**/.cache/
research/**/results.tsv
research/**/run.log
research/**/audit_plots/

# generated figures (research/figures/ generators; deliverable copies travel with the deliverable)
reports/plots/
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@ All notable changes to `views-frames` are documented here. The format is based o
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
[Semantic Versioning](https://semver.org/) as governed in `GOVERNANCE.md`.

## [1.9.0] — 2026-07-24

**The tower-tip MAP reads the top floor (ADR-019 Amendment 3): `tip_mass` 0.5 → 0.25.**
Behavior change to `tower_point`/`summarize_tower` outputs, shipped MINOR per the C-44/C-45
precedent (estimator amendment with ADR evidence); `CONFORMANCE_FLOOR` stays `1.0.0`.

### Changed
- **`tip_mass` default 0.5 → 0.25 (the top-quartile floor).** The tower-tip MAP is now the
median of the **top floor of the published tower**, matching the design intent the name
always promised. Evidence (`research/map_hdi/tip_mass_study.py`, 1000-replicate battery +
duplicate-capture frontier + the real-cell C-44 gate): the 0.5 shorth carried a structural
rightward bias that does not shrink with sample count; 0.25 roughly halves it, beats the
shorth on RMSE at pooled S, reads zero-inflated cells exactly, and passes the real-cell
zero-stack gate with margin (masses ≤ 0.15 resurrect the C-44 signal loss; 0.20 has zero
margin). Consumer note: published MAPs shift toward the mode on skewed cells — the intended
C-32 direction.

### Added
- **MAP-containment law** in `assert_summarizer_contract`: every floor holding more than half
the tip floor's draws (asymptotically mass > `tip_mass`/2 = 12.5%) provably contains the
tip — wider floors by nesting, narrower qualifying floors by the sub-window trim argument.
All published bands (50/90/95/99) qualify; the unguaranteed region *shrinks* versus the old
default (was: everything below 0.5).
- `research/map_hdi/tip_mass_study.py` — the committed evidence trail for the amendment.
- `research/figures/` — permanent, seeded generators for the PRN06 tower figures (overlay +
detail, upright-tower rendering), with `reports/plots/` as the gitignored output home.

## [1.8.1] — 2026-07-02

**Falsification-audit hardening (four-axis audit 2026-07-02; register C-67/C-68/C-69/C-70).**
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
> containers (`FeatureFrame`, `PredictionFrame`, and their anticipated siblings)
> that every other repo depends on and that depends on nothing internal.
>
> **Status:** **v1.8.0 — frozen API, published to PyPI** (frozen since v1.0.0, ADR-018; the
> **Status:** **v1.9.0 — frozen API, published to PyPI** (frozen since v1.0.0, ADR-018; the
> v1.1 surface is
> purely additive — the coherent posterior summary, ADR-019; v1.2.0 rebuilt the tower
> `outside-in`, C-44; v1.3.0 makes the tower summary distribution-agnostic — no magnitude
Expand All @@ -15,7 +15,10 @@
> sibling package **`views_frames_reconcile`** — forecast reconciliation, ADR-023; v1.8.0
> adds the native point-country broadcast + the self-describing reconciliation mode,
> the three showcase notebooks, and the frames-family hardening pass — Reconcile.md CIC,
> ADR-025 immutability-by-convention, the adversarial red-test batch). This
> ADR-025 immutability-by-convention, the adversarial red-test batch; v1.9.0 moves the
> tower-tip MAP to the **top floor** of the published tower — `tip_mass` 0.25 with the
> **MAP-containment law** in the conformance suite, ADR-019 Amendment 3 — and adds the
> `research/figures/` tower-figure toolkit). This
> README is the design
> bible; the contract it specifies is realised in `src/views_frames/` (index, frames,
> io, conformance suite) plus the `src/views_frames_summarize/` sibling package
Expand Down
26 changes: 24 additions & 2 deletions docs/ADRs/019_coherent_posterior_summary_hdi_tower.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@
> `tip_mass`-floor density, and the magnitude rule is now an **optional, off-by-default**
> `config['zero_cutoff']` (read live) for count consumers that want it. The summary family is
> now **distribution-agnostic**. See the *Decided properties* below.
>
> **Amendment 3 (2026-07-24 — `tip_mass` 0.5 → 0.25, the top-quartile floor).** The design
> intent was always a **tower-tip** MAP — the point read off an *upper* floor of the tower —
> but the shipped default read the 0.5 (shorth) floor, whose median carries a **structural
> bias away from the mode** on skewed shapes that does not shrink with sample count
> (measured: +0.12 to +2.0 across the shape battery; the narrow-floor bias is roughly half,
> everywhere). A simulation study (`research/map_hdi/tip_mass_study.py`: 1000-replicate
> accuracy/stability sweep, a duplicate-capture frontier, and the real-cell C-44 gate)
> settled the choice: floors of mass ≤ 0.15 **resurrect the C-44 zero-stack signal loss on
> the real faoapi cells** (a floor's median is safe only while a duplicate stack is under
> half its draws), 0.20 passes with zero margin, and **0.25 passes with margin** while
> beating the shorth on RMSE at pooled sample counts and reading zero-inflated cells
> exactly. Decided: `tip_mass = 0.25`. With it comes the **MAP-containment law**, now
> asserted in `conformance.py`: every floor holding more than half the tip floor's draws
> (a floor of mass `m` spans `floor(m·S)+1` draws; asymptotically `m > tip_mass/2 = 12.5%`)
> provably contains the tip — wider floors by nesting, narrower qualifying floors because a
> contiguous sub-window longer than half its parent cannot trim away the parent's median.
> Floors below that threshold carry no containment guarantee and are below platform sample
> resolution (the published bands 50/90/95/99 all qualify under both old and new defaults;
> the change *shrinks* the unguaranteed region from mass < 0.25 to mass ≤ ~0.125).
> Consumer note: published MAPs shift toward the mode on skewed cells — the intended
> direction (the C-32 lineage). The full shrinking-limit MAP remains #89.

---

Expand Down Expand Up @@ -80,7 +102,7 @@ new surface (MINOR under ADR-018); the frozen estimators (`map_estimate`, `hdi`,
**independent of the other requested masses** (the reproducibility guarantee that resolves
C-33).
- `tower_point(frame) -> (N, …, 1)` frame. The "tower tip": the median of the configurable
**`tip_mass`** floor's samples (default 0.5 — the "shorth"); zero-inflation is read off
**`tip_mass`** floor's samples (default 0.25 — the top-quartile floor, Amendment 3); zero-inflation is read off
that floor's density (an optional `zero_cutoff` magnitude rule is off by default — C-45).
Unbinned and median-based, so it carries **none** of `map_estimate`'s
histogram tie-break bias (mitigates C-32), and — reading a *mass-aware* floor rather than
Expand Down Expand Up @@ -185,7 +207,7 @@ decide); cross-repo adoption (consumers adopt when ready).
- The bimodality flag is a heuristic with deliberately limited recall on *ambiguous*
(overlapping) mixtures, unequal-weight splits, and a tall-narrow mode beside a spread one
— documented as a conservative trade, not a formal test, and **registered as C-34**.
- `tower_point` reads a fixed `tip_mass` floor (default 50% — the shorth), so it is **not**
- `tower_point` reads a fixed `tip_mass` floor (default 25% — the top-quartile floor, Amendment 3), so it is **not**
a consistency guarantee to the true mode; a fully-principled convergent mode remains #89.
- `map_estimate` remains in the frozen surface (biased), now with a documented better
alternative — a residual a naïve consumer can still step on.
Expand Down
18 changes: 16 additions & 2 deletions docs/CICs/Summarize.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
> additively in v1.6.0** (`src/views_frames_summarize/expected_shortfall.py`,
> `tests/test_summarize_expected_shortfall.py`; register C-55/C-56 Resolved). Best-case ships **no
> code** (a low quantile + `exceedance(0)`). The §3/§4/§6/§8–§11 entries marked *(ADR-022)* are live.
>
> **Amendment (2026-07-24, ADR-019 Amendment 3).** `tip_mass` default **0.5 → 0.25** (the
> top-quartile floor — the tower-tip MAP now reads the top floor of the published tower), with
> the **MAP-containment law** added to `assert_summarizer_contract`: every floor holding more
> than half the tip floor's draws provably contains the tip. Simulation evidence in
> `research/map_hdi/tip_mass_study.py`; behavior change to `tower_point`/`summarize_tower`
> outputs (MAPs shift toward the mode on skewed cells — the intended C-32 direction).

---

Expand Down Expand Up @@ -95,11 +102,18 @@ re-derive (ADR-017).
HDI with no nesting constraint, use the frozen `hdi`; use the tower when you need a
*coherent, reproducible* family of bands.
- `tower_point(frame)` → a `(N, …, 1)` frame: the **tower tip**, the median of the
configurable **`tip_mass`** floor (default 0.5 — the shorth). Zero-inflation is read off
configurable **`tip_mass`** floor (default 0.25 — the top-quartile floor, the top floor of
the published tower; ADR-019 Amendment 3, evidence `research/map_hdi/tip_mass_study.py`).
Zero-inflation is read off
that floor's density (a zero-majority row reads 0; the optional `zero_cutoff` magnitude
rule is off by default — C-45). Unbinned and median-based, so it carries **none** of
`map_estimate`'s histogram tie-break bias (mitigates C-32) **and** is robust to minority
duplicates (C-44).
duplicates (C-44 — a floor's median is safe while a duplicate stack is under half its
draws; at 0.25 that tolerates the real-cell zero-stacks with margin). **MAP-containment
law** (asserted in the conformance suite): every floor holding more than half the tip
floor's draws — asymptotically mass > `tip_mass`/2 = 12.5% — provably contains the tip;
all published bands (50/90/95/99) qualify. Narrower floors carry no containment
guarantee and are below platform sample resolution.
It is **not** a consistency-guaranteed mode; pair it with `bimodality`. **Caveat (the
semantic shift — read before adopting over a histogram MAP):** on right-skewed /
zero-inflated / multi-cluster posteriors `tower_point` returns the **densest** mode, which
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "views-frames"
version = "1.8.1"
version = "1.9.0"
description = "The VIEWS platform data-contract layer: immutable array+identifier frames (numpy only, root of the dependency DAG)."
authors = [
{ name = "Simon Polichinel von der Maase", email = "simmaa@prio.org" },
Expand Down
111 changes: 111 additions & 0 deletions research/figures/NOTE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Research note — the tower figures (PRN06 provenance, method, and where plotting lives)

This note records what the two published tower figures show, exactly how they were made,
how they were nearly lost, and the placement decision for figure tooling in this repo.

## The figures

FAO Project 02 **Pre-Release Note 06** (2026-06-24) embeds two figures:

- **`tower_overlay`** — "The HDI tower over the empirical forecast distribution
(synthetic, n = 2000)": a 2×2 zoo (right-skewed, near-symmetric, zero-inflated,
bimodal), each panel a grey histogram with the full 25-floor nested HDI tower in blue
and the tower-tip MAP as a red dashed line. Tips: 1.28 / 5.89 / 0.00 / 2.56 [bimodal
flag].
- **`tower_detail`** — one right-skewed posterior with the full tower and the 50/90/95%
floors highlighted. Tip: 1.51.

## The method (and a misreading to guard against)

Both figures call the **installed public API directly** — `hdi_tower`, `tower_point`,
`bimodality` — with no bespoke derivation. `tower_point` is (`tower_point.py`):

```python
tower = _dense_tower(srt, ks) # outside-in nested tower
tip = _median_in(srt, tower[:, t, 0], tower[:, t, 1]) # median of the tip_mass floor
```

i.e. **the median of the draws inside the `tip_mass` floor of the outside-in tower**.
Since ADR-019 Amendment 3 (2026-07-24), `tip_mass` = **0.25** — the top-quartile floor,
the top floor of the published tower — replacing the original 0.5 ("shorth") default,
whose median carried a structural rightward bias on skewed shapes (evidence:
`research/map_hdi/tip_mass_study.py`; the change also brought the MAP-containment law —
every floor holding more than half the tip floor's draws provably contains the MAP).
It is *not* derived from any central/equal-tailed interval.

**The v1 rendering actively invited that misreading (fixed in v2/v3, 2026-07-24).**
The as-published figures drew credible level *ascending*, which put the widest (99%)
floors at the **top** and the narrow floors at the bottom — an upside-down tower — and
placed the tip marker at level 0.5, i.e. **mid-structure: visually a "tower-middle
MAP"**, directly contradicting the "tower-*tip*" name. v2 inverted the vertical
arrangement (99% base at the bottom, floors narrowing upward) but drew the sub-50%
internal grid as a spire *above* the 50% floor — un-topping the tower and again making
the tip read as mid-structure. **v3 is the correct rendering:** the published tower
**ends at the `tip_mass` floor — its top floor** (50% at the time; **25% since ADR-019
Amendment 3**) — with the MAP marker sitting on it, so "the tower-tip MAP is the median
of the top HDI" is literally what the picture shows. Floors below `tip_mass` are the canonical grid's internal machinery and are not
drawn. (The *narrowest-internal-floor* median — the shrinking-HDI-limit mode — remains
the **deferred #89 redesign**: the tip-mass study showed masses ≤ 0.15 resurrect the
C-44 zero-stack signal loss on real cells, which is what fixed the choice at 0.25.)
A smaller cue inherent to the method remains: on right-skewed shapes the tip-floor
median sits slightly right of the histogram peak (lognormal(0.5, 0.55): analytic mode
≈ 1.22; tip 1.38 on the seed-7 realization). v2/v3 also fixed twin-axis label
collisions between panels, the unreadable 0.92–0.99 floor slab, the endpoint "curtain"
verticals, and the detail figure's annotation collision. The rendering fixes preserved
the published values; the **Amendment 3 method change updated them** (see *Reproduce*).
The "central 50% is path-dependent" defect in `research/map_hdi/NOTE.md` describes the
**pre-C-44 / old-faoapi** behavior the tower *replaced*; these figures postdate that fix
(generated 19 minutes after the v1.3.0 research-note commit `96be471`) and — verified
2026-07-24 — `git diff 96be471..HEAD` over the five tower-family modules is **empty**,
so the published figures match the current (v1.8.x) method byte-for-byte.

## Provenance (and the lesson)

The original generators were **ephemeral scratchpad scripts** run on 2026-06-24
08:00:15 / 08:00:58 CEST (matching the PDFs' embedded CreationDates to the second).
They were never committed anywhere; when their provenance was questioned on 2026-07-24,
no filesystem search could find them — they survived only inside a session transcript,
from which they were recovered verbatim. `make_tower_figs.py` in this directory is that
recovery made permanent (same seeds — overlay 7, detail 3 — same shapes, same styling;
a rerun reproduces the published tip values exactly).

**Lesson:** any figure that lands in a deliverable gets a committed, seeded generator
the same day. Ephemeral plotting scripts are provenance debt.

## Where plotting lives in this repo (the placement decision)

**Figure tooling lives under `research/`, never under `src/`.** Considered and
rejected: a `src/views_frames_plotting` sibling package. Grounds: the sibling charters
exclude plotting explicitly and repeatedly (ADR-017:59, ADR-023:64 + :188
"failure-mode to watch", Summarize CIC §2/§11, Reconcile CIC §2); the wheel is
numpy-only and `pyproject.toml` already rules that matplotlib is "notebook-only …
never imported by the package"; ADR-018 would freeze plot styling — the least stable
kind of code — onto the most frozen release unit (a CCP/SAP inversion); and C-52's
accretion guard names exactly this camel's nose. The native precedent followed instead:
`research/map_hdi/audit*.py`, `notebooks/_synthetic.py`, `scripts/` — dev/research
tooling outside the wheel, matplotlib via the `[docs]` extra.

**Standard output location:** `reports/plots/` (gitignored — generated artifacts stay
out of git; a deliverable's copy travels with the deliverable, as PRN06's correctly
does in its own `figures/`).

**Escalation path (recorded once, to close the question):** if cross-repo demand for
these plots ever materializes, they graduate to a separate *unfrozen* distribution
(the D-11 pattern) or to views-reporting, the platform's presentation layer — never
into this wheel.

## Reproduce

```bash
uv run --extra docs python research/figures/make_tower_figs.py # both, into reports/plots/
uv run --extra docs python research/figures/make_tower_figs.py --which overlay --figdir /tmp/x
```

Expected (tip_mass = 0.25, ADR-019 Amendment 3): overlay tips `[1.38, 5.94, 0.0, 2.13]`
(4th panel bimodal-flagged), detail tip `1.47`. (The as-published PRN06 v1 figures carried
the pre-amendment 0.5-floor tips `[1.28, 5.89, 0.0, 2.56]` / `1.51`.)

**PRN06 swap status:** the note still embeds the v1 figures (upside-down tower,
pre-amendment 0.5-floor tips); the v1 generators remain recoverable from the session
transcript if ever needed. Once the current figures are approved, copy them into the
note's `figures/` and recompile.
Loading
Loading