"""Regression: strong_negative_miss == cell[1][5] and is a strict subset of any_negative. ChatGPT review 0a6e24af — v1 incorrectly set strong==any via (pago-pexp)>=2 on {1,3,5}. Strong negative = Better(1)→Worse(5) ONLY. No M1 scoring / L1 changes. Research-only. """ from __future__ import annotations import json from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] V2_JSON = ROOT / "data/m1/v2/m1long_v2_selection_ipw_sensitivity.json" V2_NAMED = ROOT / "data/m1/v2/m1long_v2_selection_ipw_sensitivity-v2.json" V1_BUGGY = ROOT / "data/m1/v2/m1long_v2_selection_ipw_sensitivity-v1-buggy.json" EXPECTED_STRONG = { "unweighted": 105 / 1424, # 0.07373595505617977 # weighted values verified from published 3x3 mass / total "ipw_stabilized": 0.08648604054331696, "ipw_trunc_1_99": 0.08640382332643122, "ipw_trunc_5_95": 0.08579335721272811, } EXPECTED_ANY_UNWEIGHTED = 382 / 1424 # 0.26825842696629215 def _load_pack() -> dict: for p in (V2_NAMED, V2_JSON): if p.exists(): return json.loads(p.read_text()) pytest.fail(f"missing corrected pack at {V2_JSON} or {V2_NAMED}") def _cell15(block: dict) -> float: detail = block["detail"] shares = detail["shares"] return float(shares["1"]["5"]) @pytest.mark.parametrize("spec", list(EXPECTED_STRONG)) def test_strong_equals_cell_1_5(spec: str): pack = _load_pack() block = pack["three_by_three_pexp_pago"][spec] strong = block["share_strong_negative_miss"] any_neg = block["share_any_negative_miss"] cell = _cell15(block) assert strong == pytest.approx(cell, abs=1e-12) assert strong == pytest.approx(EXPECTED_STRONG[spec], abs=1e-9) assert strong < any_neg # subset: strong mass cannot exceed any-negative assert strong <= any_neg + 1e-15 def test_any_negative_unweighted_unchanged(): pack = _load_pack() uw = pack["three_by_three_pexp_pago"]["unweighted"] assert uw["share_any_negative_miss"] == pytest.approx(EXPECTED_ANY_UNWEIGHTED, abs=1e-12) # 1→3 + 1→5 + 3→5 = 92+105+185 = 382 c = uw["detail"]["counts"] assert c["1"]["3"] + c["1"]["5"] + c["3"]["5"] == 382 assert c["1"]["5"] == 105 def test_v1_buggy_preserved_and_was_wrong(): assert V1_BUGGY.exists(), "v1 buggy artifact must be preserved" buggy = json.loads(V1_BUGGY.read_text()) for spec, block in buggy["three_by_three_pexp_pago"].items(): # historical bug identity assert block["share_strong_negative_miss"] == pytest.approx( block["share_any_negative_miss"], abs=1e-15 ), spec def test_summarize_helper_unit(): """Unit-level guard independent of full microdata rebuild.""" shares = { "1": {"1": 0.24, "3": 0.06, "5": 0.07}, "3": {"1": 0.20, "3": 0.17, "5": 0.13}, "5": {"1": 0.02, "3": 0.04, "5": 0.07}, } any_neg = shares["1"]["3"] + shares["1"]["5"] + shares["3"]["5"] strong = shares["1"]["5"] # emulate fixed rule strong_calc = 0.0 any_calc = 0.0 for r, cols in shares.items(): for c, s in cols.items(): ri, ci = int(r), int(c) if ci > ri: any_calc += s if ri == 1 and ci == 5: strong_calc += s assert strong_calc == pytest.approx(strong) assert any_calc == pytest.approx(any_neg) assert strong_calc < any_calc # buggy rule would equal any buggy = sum(s for r, cols in shares.items() for c, s in cols.items() if int(c) - int(r) >= 2) assert buggy == pytest.approx(any_neg)