diff --git a/changelog.d/us-strike-benefits-reviewed-exclusion.fixed.md b/changelog.d/us-strike-benefits-reviewed-exclusion.fixed.md new file mode 100644 index 0000000000..317913f4bd --- /dev/null +++ b/changelog.d/us-strike-benefits-reviewed-exclusion.fixed.md @@ -0,0 +1 @@ +`strike_benefits` joins the reviewed degenerate-input exclusions: no respondent in the pinned public ASEC person files (income years 2022-2024) reports other-income source code 12 (strike benefits), so the column is identically zero and a fresh-base release refused at the pre-solve degenerate-input gate. The OI_OFF mapping is unchanged and correct; a vintage with a code-12 reporter makes the entry stale and fails the gate again. diff --git a/packages/microcosm-build/tests/test_us_documented_absent_inputs.py b/packages/microcosm-build/tests/test_us_documented_absent_inputs.py index 1b14545843..63bd70f540 100644 --- a/packages/microcosm-build/tests/test_us_documented_absent_inputs.py +++ b/packages/microcosm-build/tests/test_us_documented_absent_inputs.py @@ -71,3 +71,35 @@ def test_register_is_disjoint_from_the_degenerate_exclusions() -> None: assert not set(builder.US_DOCUMENTED_ABSENT_INPUTS) & set( builder.US_DEGENERATE_INPUT_REVIEWED_EXCLUSIONS ) + + +def test_strike_benefits_exclusion_rests_on_zero_code_12_reporters() -> None: + """The ``strike_benefits`` reviewed exclusion says no pinned ASEC person + file carries OI_OFF code 12 (strike benefits). Check that against the + official archives when ``MICROCOSM_ASEC_ARCHIVE_DIR`` holds them + (asecpub23csv.zip, asecpub24csv.zip, asecpub25csv.zip); the archives are + public but large, so no CI lane carries them.""" + + import csv + import io + import os + import zipfile + from pathlib import Path + + import pytest + + builder = _load_builder_module() + assert "strike_benefits" in builder.US_DEGENERATE_INPUT_REVIEWED_EXCLUSIONS + root = os.environ.get("MICROCOSM_ASEC_ARCHIVE_DIR", "") + archives = [Path(root) / f"asecpub{year}csv.zip" for year in (23, 24, 25)] + if not root or not all(path.exists() for path in archives): + pytest.skip("set MICROCOSM_ASEC_ARCHIVE_DIR to the pinned ASEC archives") + for archive in archives: + member = f"pppub{archive.name[7:9]}.csv" + with zipfile.ZipFile(archive) as bundle, bundle.open(member) as raw: + reader = csv.reader(io.TextIOWrapper(raw, encoding="utf-8")) + header = next(reader) + column = header.index("OI_OFF") + codes = {row[column] for row in reader} + assert "12" not in codes, f"{member} has an OI_OFF == 12 reporter" + assert "20" in codes, f"{member} lost the alimony code; check the layout" diff --git a/tools/build_us_fiscal_refresh_release.py b/tools/build_us_fiscal_refresh_release.py index c6dda36861..30bca162d7 100644 --- a/tools/build_us_fiscal_refresh_release.py +++ b/tools/build_us_fiscal_refresh_release.py @@ -827,6 +827,15 @@ def _automatic_gc_suspended(): "Combined partnership/S-corp income is carried in partnership_income " "in pre-PUF-support bases; the S-corp leaf is constant zero there." ), + "strike_benefits": ( + "No respondent in the pinned public ASEC files reports strike benefits: " + "OI_OFF code 12 ('strike benefits', ASEC 2024 public use data " + "dictionary) has zero person rows in pppub23, pppub24 and pppub25 " + "(income years 2022-2024), and the PUF half carries no strike-benefit " + "field, so the OI_OFF == 12 split in alimony.py is identically zero. " + "The mapping is correct; a vintage with a code-12 reporter makes this " + "entry stale and fails the gate." + ), } #: Person inputs SNAP work-requirement rules read that have NO CPS ASEC