fix(opps): CY2020 CF→80.784 (NFRM) + verify all 13 years vs Addendum B
With CY2014-2020 Addendum B now ingested, every year's conversion factor is cross-checked against CMS's own published rates (weight × CF vs payment_rate) — all 13 reconcile 100% exact, independently confirming the Federal-Register CFs for 2014-2020. One correction fell out: CY2020 = 80.784 (not 80.793). The archived addenda are the NFRM (as-published) version, whose national rates were computed with the as-published 80.784; CMS later cited a corrected 80.793, but our source data uses 80.784, so that is the value that reproduces it to the cent. Update the rule, the golden pin, and the note. Extend the CI fixture (tests/opps/fixtures/addendum_b_sample.csv) from CY2021-2026 to all 13 years — 5 real rows per year spanning APC weights ~0.05 to ~700 — so the in-CI correctness proof now covers every year.
This commit is contained in:
@@ -77,7 +77,9 @@ class RuleYear(BaseModel):
|
|||||||
# reporting (not the reduced/OQR-penalty CF). Verified against each
|
# reporting (not the reduced/OQR-penalty CF). Verified against each
|
||||||
# year's Final Rule preamble and cross-checked against the empirical
|
# year's Final Rule preamble and cross-checked against the empirical
|
||||||
# payment_rate / relative_weight ratio in opps.addendum_b (CY2021+).
|
# payment_rate / relative_weight ratio in opps.addendum_b (CY2021+).
|
||||||
# CY2020 uses the corrected $80.793 (the raw final rule stated $80.784).
|
# CY2020 = 80.784, the as-published NFRM value that reproduces the CY2020
|
||||||
|
# Addendum B national rates we ingest (verified to the cent). CMS later cited
|
||||||
|
# a corrected $80.793, but the archived NFRM addenda — our source — use 80.784.
|
||||||
|
|
||||||
RULES: dict[int, RuleYear] = {
|
RULES: dict[int, RuleYear] = {
|
||||||
2014: RuleYear(
|
2014: RuleYear(
|
||||||
@@ -127,7 +129,7 @@ RULES: dict[int, RuleYear] = {
|
|||||||
),
|
),
|
||||||
2020: RuleYear(
|
2020: RuleYear(
|
||||||
year=2020,
|
year=2020,
|
||||||
conversion_factor=80.793,
|
conversion_factor=80.784,
|
||||||
outlier_threshold=4625.0,
|
outlier_threshold=4625.0,
|
||||||
cms_rule_id="CMS-1717-FC",
|
cms_rule_id="CMS-1717-FC",
|
||||||
federal_register_citation="84 FR 61142",
|
federal_register_citation="84 FR 61142",
|
||||||
|
|||||||
@@ -1,4 +1,39 @@
|
|||||||
hcpcs,status_indicator,relative_weight,payment_rate,year
|
hcpcs,status_indicator,relative_weight,payment_rate,year
|
||||||
|
57170,T,0.1421,10.33,2014
|
||||||
|
P9038,R,3.0182,219.34,2014
|
||||||
|
28635,T,17.2302,1252.15,2014
|
||||||
|
25151,T,35.4456,2575.9,2014
|
||||||
|
33249,Q3,442.3292,32144.95,2014
|
||||||
|
A9527,U,0.1787,13.25,2015
|
||||||
|
74160,Q3,3.2481,240.83,2015
|
||||||
|
23000,T,18.0849,1340.89,2015
|
||||||
|
75833,Q2,34.5196,2559.42,2015
|
||||||
|
33270,J1,415.4941,30806.39,2015
|
||||||
|
A9527,U,0.0968,7.14,2016
|
||||||
|
62252,S,3.2713,241.18,2016
|
||||||
|
24071,T,19.1832,1414.28,2016
|
||||||
|
27696,T,32.4936,2395.59,2016
|
||||||
|
33249,J1,413.561,30489.78,2016
|
||||||
|
C2645,U,0.0625,4.69,2017
|
||||||
|
74262,Q3,3.532,264.9,2017
|
||||||
|
17108,T,19.0286,1427.16,2017
|
||||||
|
54620,J1,33.8861,2541.49,2017
|
||||||
|
69930,J1,424.3083,31823.55,2017
|
||||||
|
P9043,R,0.1957,15.39,2018
|
||||||
|
67810,T,3.4036,267.65,2018
|
||||||
|
14000,T,19.944,1568.32,2018
|
||||||
|
93530,J1,35.7776,2813.41,2018
|
||||||
|
C9741,J1,414.903,32626.31,2018
|
||||||
|
C2645,U,0.059,4.69,2019
|
||||||
|
67938,T,3.5667,283.52,2019
|
||||||
|
15760,T,19.4862,1548.96,2019
|
||||||
|
52250,J1,36.8205,2926.86,2019
|
||||||
|
38240,J1,476.6985,37892.76,2019
|
||||||
|
C2645,U,0.0581,4.69,2020
|
||||||
|
59070,T,3.3508,270.69,2020
|
||||||
|
C5273,T,20.0852,1622.56,2020
|
||||||
|
38525,J1,37.4977,3029.21,2020
|
||||||
|
38240,J1,463.3555,37431.71,2020
|
||||||
C2645,U,0.0566,4.69,2021
|
C2645,U,0.0566,4.69,2021
|
||||||
37195,T,3.7532,310.75,2021
|
37195,T,3.7532,310.75,2021
|
||||||
51040,J1,21.6552,1792.99,2021
|
51040,J1,21.6552,1792.99,2021
|
||||||
|
|||||||
|
@@ -37,15 +37,16 @@ _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).
|
||||||
#
|
#
|
||||||
# Sources:
|
# Sources (every year cross-checked BOTH ways):
|
||||||
# CY2014-CY2020 — each year's Federal Register OPPS/ASC Final Rule
|
# Federal Register — each year's OPPS/ASC Final Rule preamble ("we are
|
||||||
# preamble ("we are using a conversion factor of $X").
|
# using a conversion factor of $X"), primary for CY2014-2020.
|
||||||
# CY2021-CY2026 — empirical payment_rate / relative_weight in
|
# Empirical — payment_rate / relative_weight in opps.addendum_b (all 13
|
||||||
# opps.addendum_b (5,000+ APC-priced rows/year), which
|
# years now ingested, 5,000+ APC-priced rows/year), which
|
||||||
# reproduces the published national rate to the cent.
|
# reproduces the published national rate to the cent.
|
||||||
# CY2020 = 80.793 is the corrected value; the final rule as published
|
# CY2020 = 80.784, the as-published NFRM value that reproduces the CY2020
|
||||||
# stated 80.784 and CMS later corrected it (the CY2021 rule
|
# Addendum B we ingest (the archived NFRM addenda are our source).
|
||||||
# builds off 80.793).
|
# CMS later cited a corrected 80.793; we track the value that
|
||||||
|
# matches our data.
|
||||||
# CY2016 is genuinely below CY2015 — a one-time -2.0pp packaged-lab
|
# CY2016 is genuinely below CY2015 — a one-time -2.0pp packaged-lab
|
||||||
# ("two-times") adjustment more than offset the market basket.
|
# ("two-times") adjustment more than offset the market basket.
|
||||||
_EXPECTED_CF: dict[int, float] = {
|
_EXPECTED_CF: dict[int, float] = {
|
||||||
@@ -55,7 +56,7 @@ _EXPECTED_CF: dict[int, float] = {
|
|||||||
2017: 75.001,
|
2017: 75.001,
|
||||||
2018: 78.636,
|
2018: 78.636,
|
||||||
2019: 79.490,
|
2019: 79.490,
|
||||||
2020: 80.793,
|
2020: 80.784,
|
||||||
2021: 82.797,
|
2021: 82.797,
|
||||||
2022: 84.177,
|
2022: 84.177,
|
||||||
2023: 85.585,
|
2023: 85.585,
|
||||||
@@ -98,8 +99,8 @@ class TestAddendumBFixtureConsistency:
|
|||||||
Addendum B rows.
|
Addendum B rows.
|
||||||
|
|
||||||
The fixture holds real published ``payment_rate`` values (never
|
The fixture holds real published ``payment_rate`` values (never
|
||||||
computed), spanning APC weights from ~0.05 to ~700 across CY2021-
|
computed), spanning APC weights from ~0.05 to ~700 across every year
|
||||||
CY2026. If any of those years' conversion factors is wrong, the
|
CY2014-CY2026. If any of those years' conversion factors is wrong, the
|
||||||
calculated ``weight × CF`` diverges from the published rate and this
|
calculated ``weight × CF`` diverges from the published rate and this
|
||||||
fails — no external dataset required, so the correctness check runs
|
fails — no external dataset required, so the correctness check runs
|
||||||
in headless CI.
|
in headless CI.
|
||||||
@@ -119,7 +120,7 @@ class TestAddendumBFixtureConsistency:
|
|||||||
)
|
)
|
||||||
pricer = OppsPricer()
|
pricer = OppsPricer()
|
||||||
years = pricer.years_available(con)
|
years = pricer.years_available(con)
|
||||||
assert years == [2021, 2022, 2023, 2024, 2025, 2026]
|
assert years == list(range(2014, 2027))
|
||||||
for year in years:
|
for year in years:
|
||||||
r = reconcile(pricer, con, year)
|
r = reconcile(pricer, con, year)
|
||||||
assert r.matched_rows > 0, f"CY{year}: no fixture rows"
|
assert r.matched_rows > 0, f"CY{year}: no fixture rows"
|
||||||
|
|||||||
Reference in New Issue
Block a user