M6 design amendment 3e: mortality-rates shape bridge + parameter-dir binding (§2.8.10, closes factory-referee F1/F2) - #196
Conversation
…binding (§2.8.10.5, closes factory-referee F1/F2) Docs-only amendment (§2.8.10 + §10 JSON revision 11) closing the two run-gating findings the factory referee filed against the merged spec/engine pair (PR #191 comment 4969829131); the factory itself (67e7fad) is faithful with zero build choices, so both are findings, not build defects. F1 (blocking): the pinned seven-band mortality_external_rates shape is rejected by AgeSexMortalityModel.__post_init__ (bands must start at age 0, contiguous through 120) at the run's first phase, fit_mortality_model (m6_runner.py:348), before any pre-flight/score/write. Pinned resolution = option (b): an input-side inert (0,24) projection-coverage pad on BOTH fit inputs -- external central_rate is outcome-inert (aligned_rate = psid_rate cancels it), exposure death=0 gives a <25 hazard of exactly 0 (no modeled mortality below the age-25 PSID exposure floor); the engine is untouched. Verified by execution on the merged refit.py: unpadded raises; padded yields a valid 8-band 0->120 model with the fourteen 25+ cells byte-identical and the <25 hazard 0. Option (a) rejected: it needs an engine no-exposure fallback and imports an NCHS level, violating the aligned_rate=psid_rate anchor contract. F2 (should-fix): the version gate reads importlib.metadata but load_ssa_parameters reads YAML from POPULACE_DYNAMICS_PE_US_DIR (ss/params.py:187-227), so a mismatched dir passes the gate. Pinned resolution = a factory-side assert binding the resolved parameter dir to distribution('policyengine-us').locate_file('policyengine_us') / parameters/gov/ssa, verified against the real 1.752.2 install. N1: soften the _era_mapping docstring's phantom "verified in build()" (era_map is provenance-only; no assert added). Pins the one-line factory/adapter patch pointers for the patch lane. Edits no gates.yaml cell, moves no threshold, writes no test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Referee — amendment 3e (d798a57), verified against code + executionVerdict: VERIFIED (ready to merge). Both factory-referee findings are resolved by executable, pinned designs that I reproduced myself against the merged engine (real F1 — the inert
|
| check | result |
|---|---|
| NCHS 0-24 pad central_rate | male 0.00076582, female 0.00045150 — matches the doc's "≈0.000766 / ≈0.000451" |
| unpadded fit (7-band) | raises ValueError: mortality bands must start at age zero — the run's first phase (m6_runner.py:348) |
| padded fit | valid 8-band model ((0,24),(25,34),…,(85,120)) |
| 14 twenty-five-plus cells | byte-identical (==) to the unpadded arithmetic |
| pad value/weight invariance | 25+ cells unchanged across external 0.00077 ↔ 0.5 and weight 1.0 ↔ 17.3 (and NCHS/3.14) |
<25 hazard |
exactly 0.0 (both sexes; psid_rate=0 → −expm1(0)=0), invariant to every pad value/weight |
| option (a) rejection | external 0-24 row without exposure → ValueError: undefined mortality fit cell 0-24|female (the weighted_exposure ≤ 0 guard, refit.py:1070-1082) |
| T*-dating is load-bearing | the 2014-dated pad survives ≤T* truncation (2 rows kept); a 2015-dated pad drops wholesale |
The inertness is not just arithmetic — it clears AgeSexMortalityModel.__post_init__'s band/coverage/cell/[0,1] checks (0 ∈ [0,1]) without perturbing a single fitted value, because the pad's "0-24" age_band never matches a 25+ row's cell selection (refit.py:1071-1074). The option-(a) level-import objection is real in code: aligned_rate = central_rate·(psid_rate/central_rate) = psid_rate (refit.py:1083-1088) cancels the external rate on every cell, so consuming a real NCHS <25 level would require an engine no-exposure fallback and would break the aligned_rate = psid_rate anchor contract — option (b) touches zero engine surface. Confirmed MORTALITY_BANDS == mf.BANDS (registered_m6_inputs.py:238) is untouched (pad appended after the seven-band build). The (0,24) band feeds only apply_mortality as a zero hazard: _projected_mortality_cells maps to the seven bands and drops <25 (m6_runner.py:708-722), and both the scored and the report-only mortality_drift/shock surfaces route through it (m6_runner.py:855-874; certifies_nothing_about_mortality_drift: True, :1244) — no scored or disclosed cell sees <25.
F2 — assert_pe_us_param_dir (reproduced, both directions)
Ran the pinned assert verbatim in the 1.752.2 venv:
- claim confirmed:
distribution("policyengine-us").locate_file("policyengine_us")== Path(policyengine_us.__file__).parent. - passes when
POPULACE_DYNAMICS_PE_US_DIR→ the metadata-versioned site-packages install (resolved == versioned;load_ssa_parameters()then loads it,pe_us_revision="unknown"as the doc's provenance note predicts). - fires when the env var → the local 1.690.7 checkout while metadata still reads 1.752.2 (
resolved= checkout…/ssa≠versioned= site-packages…/ssa), the exact F2 gap. Reproduced the root cause: without the assert,load_ssa_parameters()silently loads the checkout YAML (pe_us_revision="2542e38bc0"— a real hash, not "unknown"). The mechanism is sound; note it also fires when the env var is unset (default → the checkout), so it forces the operator to point at the 1.752.2 install — a strengthening, not just a check.
Pointers, N1, scope, CI
- Patch pointers accurate (zero-discovery):
build_inputsstep (1) isassert_pe_us_version()(F2 assert lands right after); step (4) isnchs_2010_external_rates()+mortality_exposure_adapter(...)(F1 pad appended on their outputs beforeload_m6_inputs). Every cited line resolves at this head:steps.py:57-68/:98-109,refit.py:1061-1068/:1083-1088/:1089,m6_runner.py:348/:708-722/:855-874/:1244,build_mortality_floors.py:223-224,ss/params.py:52-54/:187-193/:226,extract_ssa_claim_ages_2014.py:327-328. - N1 sound — agree. Soften the docstring, no assert:
era_mapis provenance-only (read by nothing — re-confirmed),build()has no such assert, and an assert would guard a non-load-bearing property that could spuriously fail on a valid future edition with a legitimately sex-specific era boundary. The proposed replacement clause is accurate. - Docs-only at the git level:
docs/design/m6_projection_engine.mdonly, +178/−4. Them6-design-parametersJSON block parses;revision == 11;amendment_3e_sectionpresent;nextpointer threads 3e → patch → referee → merge → third registration. - 39 doc-bound tests pass (
test_gate_m6_floors23 +test_gate_m6_derivations16); no test hardcodes the design revision, so 10→11 is safe. CI green (pytest 3.11 + 3.13, lint, build —gh pr checksexit 0), MERGEABLE / CLEAN at d798a57.
Observation (non-blocking): the zero <25 hazard is a slight downward bias vs the true NCHS ~7.7e-4/yr, but it is ungated, disclosed, deterministic, and — per the §2.8.4 F1 adjudication — reaches no gated tolerance; the honest alternative (option a) is correctly rejected. The design is internally consistent and the resolution minimal.
Verified in a throwaway worktree at d798a57 + a fresh pe-us 1.752.2 venv (proof scripts run against the merged engine, no repo edits). No merge/ready action taken; worktree/venv removed.
…(3e patch: F1/F2/N1 + bridge test) (#197) * M6 factory: (0,24) projection-coverage pad + pe-us param-dir binding (amendment 3e patch, F1/F2/N1) The zero-discovery patch pinned by ratified amendment 3e (§2.8.10.5, PR #196), verified against the merged engine by the 3e referee (#196 comment 4970729902): - F1: `_pad_below_25_projection_coverage(external_rates, exposure, *, boundary_year)` appends the inert (0,24) band per sex to BOTH mortality fit inputs after the seven-band construction — external pad = the NCHS 2010 (l_0−l_25)/(T_0−T_25) central rate (outcome-inert; aligned_rate = psid_rate cancels it), exposure pad dated event_year = required_interview_year = T* with death = 0 so the fitted <25 hazard is exactly 0. Called in build_inputs step (4). MORTALITY_BANDS and the floors artifacts untouched. - F2: `assert_pe_us_param_dir()` binds the directory load_ssa_parameters will read (env var / default resolution) to the metadata-versioned policyengine-us install's own on-disk location; called in step (1) right after assert_pe_us_version(). Closes the silent-wrong-load seam the referee demonstrated with a 1.690.7 checkout behind a 1.752.2 install. - N1: the extractor's era-mapping docstring no longer claims a build() verification that does not exist (era records are provenance-only). - The missing fit_mortality_model bridge test the factory referee flagged: factory-shaped rates now route through prepare → fit in committed tests — unpadded seven-band inputs raise "start at age zero"; padded inputs fit an 8-band 0→120 model whose <25 hazard is exactly 0 and whose fourteen 25+ cells equal the PSID arithmetic and are invariant to pad value and weight. Plus param-dir gate tests via an injected fake distribution (no real policyengine-us needed; the module stays in the always-run unit tier, with the env var referenced through the loader's own constant so the tier classifier does not demote 21 existing always-run factory tests). - tier_counts.json: unit 410 → 420 (full-collection recount). No gates.yaml change, no floor, no scored surface touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Mark registration 2 stale in the runner guard 4967241464 is a publicly graded pre-scoring stop (grading 4967433717) and must not authorize a PSID-touching run; only a fresh third registration can. Disclosed in the PR body as an addition beyond the pinned 3e patch pointers (guard hygiene from the public record, no design choice). tier_counts: artifact 1010 -> 1011 (the added parametrize case; full-collection recount). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Apply black formatting (CI formatter) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
M6 design amendment 3e — mortality-rates shape bridge + parameter-dir binding
Docs-only amendment to §2.8.10 (new §2.8.10.5) + §10 JSON revision 11, closing the two run-gating findings the adversarial factory referee filed against the merged spec/engine pair in #191 comment 4969829131. The factory itself (
registered_m6_inputs.py, merged67e7fad) was graded MERGE-READY (build only) — it faithfully implements the pinned §2.8.10 shape with zero build-lane choices, so both are findings in the spec/engine, not build defects. This amendment is the reviewed pin the referee names as the third-gate_m6-registration unblock. It edits nogates.yamlcell, moves no threshold, builds no floor, and writes no test.F1 (blocking) — the pinned seven-band shape is rejected by the projection model
§2.8.10.3 pins
mortality_external_ratesover the sevenMORTALITY_BANDS(25-34 … 85+), butfit_mortality_modelderives its model bands from those rows and constructsAgeSexMortalityModel, whose__post_init__requires bands starting at age 0, contiguous through 120 (engine/steps.py:57-68— so materialized births get a defined hazard, not thenp.zerosdefault). The run hits this at its first phase,fit_mortality_model(m6_runner.py:348), before pre-flights/score/write, raisingmortality bands must start at age zero. A bare "add a 0-24 external row" then trips the per-cell fit gate (no <25 PSID exposure). The whole mortality data pipeline — floors, holdout truth, scored cells, external anchor — is 25+ only; the 0-start requirement is solely a projection-model invariant.Pinned resolution — option (b): an input-side inert
(0,24)projection-coverage pad on both fit inputs, engine untouched.central_rate = (l_0 − l_25)/(T_0 − T_25)(NCHS-2010, ≈ 0.000766 M / 0.000451 F) — outcome-inert becausealigned_rate = central_rate × (psid_rate/central_rate) = psid_ratecancels it.event_year = required_interview_year = 2014(survives ≤T* truncation),death = 0→psid_rate = 0→ <25 hazard = 0 exactly (no modeled mortality below the age-25 PSID exposure floor).Verified by execution on the merged
refit.py: unpadded raises; padded yields a valid 8-band 0→120 model; the fourteen 25+ cells are byte-identical and invariant to the pad's value/weight; theMORTALITY_BANDS == mf.BANDSguard is untouched (pad appended after the seven-band build), so committed floors are undisturbed. Option (a) rejected: it needs an engine no-exposure fallback and imports an NCHS level as the hazard, violating thealigned_rate = psid_rateanchor contract (PSID levels, NCHS inert).F2 (should-fix) — the version gate does not bind the parameter directory
assert_pe_us_versionreadsimportlib.metadata, butload_ssa_parametersreads YAML fromPOPULACE_DYNAMICS_PE_US_DIR(ss/params.py:187-227) — a mismatched dir passes the gate (referee-demonstrated: 1.752.2 pip + env→1.690.7 checkout). Pinned resolution: a factory-sideassert_pe_us_param_dir()binding(_resolve_pe_us(None)/_SSA)todistribution("policyengine-us").locate_file("policyengine_us")/parameters/gov/ssa— verified against the real 1.752.2 site-packages install.N1 (nit)
Soften
_era_mapping's docstring's phantom "verified inbuild()" (era_mapis provenance-only; no assert added).Patch pointers (zero-discovery for the patch lane)
scripts/registered_m6_inputs.py: add_pad_below_25_projection_coverage(...), call inbuild_inputsstep (4); addassert_pe_us_param_dir(), call in step (1).scripts/extract_ssa_claim_ages_2014.py: soften the_era_mappingdocstring.fit_mortality_modelbridge test the referee flagged.Stays DRAFT pending the amendment-3e referee round.
🤖 Generated with Claude Code