test(opps): enforce CF correctness in CI via a real Addendum B fixture
All checks were successful
CI / lint (push) Successful in 35s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / mc (push) Has been skipped
Deploy / report (push) Successful in 16s
CI / test (push) Successful in 13m3s

The self-consistency check previously skipped in headless CI (no aco
DuckDB), so CI only guarded that the CFs were *unchanged*, not that they
were *correct*. Add a committed slice of 30 real Addendum B rows
(fixtures/addendum_b_sample.csv — real published payment_rate, weights
~0.05 to ~700, CY2021-CY2026) and reconcile OppsPricer against it in an
in-memory DuckDB. This runs in CI with no external data, and because the
rows are real, a wrong conversion factor makes weight × CF diverge from
the published rate and fails the build.

Verified fail-closed: reverting the CY2026 CF to the old wrong value
fails the golden test, this fixture test, and the full-DB check together.
This commit is contained in:
kert
2026-07-08 12:17:00 -04:00
parent b21b1ae453
commit c1c51d6da2
2 changed files with 84 additions and 6 deletions

View File

@@ -0,0 +1,31 @@
hcpcs,status_indicator,relative_weight,payment_rate,year
C2645,U,0.0566,4.69,2021
37195,T,3.7532,310.75,2021
51040,J1,21.6552,1792.99,2021
58561,J1,53.2572,4409.54,2021
69930,J1,415.8068,34427.56,2021
C2645,U,0.0557,4.69,2022
11982,Q1,3.2928,277.18,2022
15946,T,20.7807,1749.26,2022
0430T,J1,39.7464,3345.73,2022
38240,J1,487.3894,41026.98,2022
C2645,U,0.0548,4.69,2023
95707,S,3.2723,280.06,2023
43242,J1,20.3492,1741.59,2023
19330,Q2,40.1682,3437.8,2023
38240,J1,493.4673,42233.4,2023
C2645,U,0.0537,4.69,2024
57156,T,3.5016,305.98,2024
15220,T,19.9049,1739.33,2024
15958,T,39.1593,3421.82,2024
38240,J1,596.4656,52120.36,2024
C2645,U,0.0526,4.69,2025
91132,S,3.4922,311.4,2025
15100,T,20.5142,1829.23,2025
0717T,T,41.0565,3660.97,2025
38240,J1,663.2611,59142.33,2025
C2645,U,0.0513,4.69,2026
28630,T,2.7568,252.01,2026
31238,J1,19.8922,1818.45,2026
19371,J1,43.7591,4000.24,2026
38240,J1,702.9866,64263.52,2026
1 hcpcs status_indicator relative_weight payment_rate year
2 C2645 U 0.0566 4.69 2021
3 37195 T 3.7532 310.75 2021
4 51040 J1 21.6552 1792.99 2021
5 58561 J1 53.2572 4409.54 2021
6 69930 J1 415.8068 34427.56 2021
7 C2645 U 0.0557 4.69 2022
8 11982 Q1 3.2928 277.18 2022
9 15946 T 20.7807 1749.26 2022
10 0430T J1 39.7464 3345.73 2022
11 38240 J1 487.3894 41026.98 2022
12 C2645 U 0.0548 4.69 2023
13 95707 S 3.2723 280.06 2023
14 43242 J1 20.3492 1741.59 2023
15 19330 Q2 40.1682 3437.8 2023
16 38240 J1 493.4673 42233.4 2023
17 C2645 U 0.0537 4.69 2024
18 57156 T 3.5016 305.98 2024
19 15220 T 19.9049 1739.33 2024
20 15958 T 39.1593 3421.82 2024
21 38240 J1 596.4656 52120.36 2024
22 C2645 U 0.0526 4.69 2025
23 91132 S 3.4922 311.4 2025
24 15100 T 20.5142 1829.23 2025
25 0717T T 41.0565 3660.97 2025
26 38240 J1 663.2611 59142.33 2025
27 C2645 U 0.0513 4.69 2026
28 28630 T 2.7568 252.01 2026
29 31238 J1 19.8922 1818.45 2026
30 19371 J1 43.7591 4000.24 2026
31 38240 J1 702.9866 64263.52 2026

View File

@@ -2,8 +2,8 @@
The OPPS conversion factor per year is correctness-critical reference The OPPS conversion factor per year is correctness-critical reference
data: OppsPricer computes national APC rates as ``weight × CF`` and data: OppsPricer computes national APC rates as ``weight × CF`` and
reconciles them against CMS's published Addendum B. Two guards keep it reconciles them against CMS's published Addendum B. Three guards keep
bulletproof: it bulletproof:
1. ``TestGoldenConversionFactors`` pins every year's CF to the value 1. ``TestGoldenConversionFactors`` pins every year's CF to the value
taken from that year's Federal Register OPPS Final Rule (CY2014- taken from that year's Federal Register OPPS Final Rule (CY2014-
@@ -12,19 +12,28 @@ bulletproof:
CY2026). Runs everywhere (no data needed), so any accidental edit CY2026). Runs everywhere (no data needed), so any accidental edit
fails CI. Changing a value requires updating the citation below too. fails CI. Changing a value requires updating the citation below too.
2. ``TestAddendumBSelfConsistency`` (skipped when the aco DuckDB is 2. ``TestAddendumBFixtureConsistency`` reconciles OppsPricer against a
absent, e.g. headless CI) proves the CFs actually reproduce CMS's committed slice of *real* Addendum B rows (fixtures/addendum_b_
published Addendum B rate to the cent — the independent check that sample.csv — real published payment_rate, not computed). It runs in
the golden values above are *correct*, not merely *unchanged*. CI with no external data, and because the rows are real a wrong CF
fails here — the independent correctness proof, enforced in CI.
3. ``TestAddendumBSelfConsistency`` (skipped when the aco DuckDB is
absent, e.g. headless CI) does the same against the full loaded
Addendum B — broader coverage where the data is present.
""" """
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from conf import path from conf import path
from opps.rules import RULES from opps.rules import RULES
_FIXTURE = Path(__file__).parent / "fixtures" / "addendum_b_sample.csv"
# Full OPPS conversion factor — the rate for hospitals that MEET quality # Full OPPS conversion factor — the rate for hospitals that MEET quality
# reporting (not the reduced/OQR-penalty CF). # reporting (not the reduced/OQR-penalty CF).
# #
@@ -84,6 +93,44 @@ class TestGoldenConversionFactors:
assert cur_cf > prev_cf assert cur_cf > prev_cf
class TestAddendumBFixtureConsistency:
"""CI-runnable correctness proof against a committed slice of real
Addendum B rows.
The fixture holds real published ``payment_rate`` values (never
computed), spanning APC weights from ~0.05 to ~700 across CY2021-
CY2026. If any of those years' conversion factors is wrong, the
calculated ``weight × CF`` diverges from the published rate and this
fails — no external dataset required, so the correctness check runs
in headless CI.
"""
def test_fixture_reconciles_to_the_cent(self) -> None:
import duckdb
from rec.engine import reconcile
from rec.pricers.opps import OppsPricer
con = duckdb.connect(":memory:")
con.execute("CREATE SCHEMA opps")
con.execute(
"CREATE TABLE opps.addendum_b AS SELECT * FROM read_csv_auto(?)",
[str(_FIXTURE)],
)
pricer = OppsPricer()
years = pricer.years_available(con)
assert years == [2021, 2022, 2023, 2024, 2025, 2026]
for year in years:
r = reconcile(pricer, con, year)
assert r.matched_rows > 0, f"CY{year}: no fixture rows"
assert r.exact_matches == r.matched_rows, (
f"CY{year}: {r.matched_rows - r.exact_matches} of "
f"{r.matched_rows} real Addendum B rows do not match "
f"weight × CF to the cent — the CY{year} conversion "
f"factor is wrong."
)
@pytest.mark.skipif(not _HAS_ACO_DB, reason="data/aco.duckdb not present") @pytest.mark.skipif(not _HAS_ACO_DB, reason="data/aco.duckdb not present")
class TestAddendumBSelfConsistency: class TestAddendumBSelfConsistency:
"""Prove weight × CF reproduces CMS's published Addendum B rate. """Prove weight × CF reproduces CMS's published Addendum B rate.