fix(opps): correct OPPS conversion factors CY2014–CY2026
Some checks failed
CI / lint (push) Successful in 32s
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
Infra CI / notebooks (push) Successful in 19s
Infra CI / zotero (push) Successful in 15s
Infra CI / docs (push) Successful in 17s
Infra CI / api (push) Successful in 39s
Infra CI / mc (push) Successful in 24s
Deploy / report (push) Successful in 14s
CI / test (push) Failing after 13m56s

The OppsPricer reconciliation surfaced that opps.rules carried wrong
conversion factors for 11 of 13 years — the calculated APC rate
(weight × CF) missed CMS's published Addendum B rate by a systematic
offset every year. Only CY2017 ($75.001) and CY2018 ($78.636) were
correct.

Corrected to the FULL CF for hospitals meeting quality reporting,
verified two independent ways:
  - CY2021–CY2026 from the empirical payment_rate / relative_weight
    ratio in opps.addendum_b (5,000+ rows/year, tight cluster).
  - CY2014–CY2020 from each year's Federal Register OPPS Final Rule
    preamble.

    2014: 74.305 → 72.672      2021: 83.324 → 82.797
    2015: 75.064 → 74.144      2022: 85.093 → 84.177
    2016: 75.001 → 73.725      2023: 86.304 → 85.585
    2019: 80.784 → 79.490      2024: 87.436 → 87.382
    2020: 82.840 → 80.793      2025: 88.547 → 89.169
                               2026: 89.723 → 91.415

CY2020 uses the corrected $80.793 (the final rule as published stated
$80.784; CMS corrected it, and the CY2021 rule builds off $80.793).
CY2016 is genuinely below CY2015 — CMS applied a one-time −2.0pp
packaged-lab adjustment that year.

After the fix, `stack rec opps` reconciles to 100% exact against
Addendum B for every year with loaded data. No consumer other than
OppsPricer read these values (opps.calcs.payment takes payment_rate
directly), and no test asserted them, so blast radius is contained.
This commit is contained in:
kert
2026-07-08 11:22:58 -04:00
parent 59272d715a
commit 3760541b08
2 changed files with 19 additions and 15 deletions

View File

@@ -97,7 +97,7 @@ Payment Formula
+ (1 - Labor_Share)
Labor_Share ≈ 0.60 (set annually in Final Rule)
Conversion_Factor = $89.723 (CY2026)
Conversion_Factor = $91.415 (CY2026)
Skin Substitute Reclassification (CY2026)
-----------------------------------------

View File

@@ -11,7 +11,7 @@ Usage::
from opps.rules import cy2026, RULES
cf = cy2026.conversion_factor # $89.723
cf = cy2026.conversion_factor # $91.415
skin_rate = cy2026.skin_sub_flat_rate # $127.28
"""
@@ -72,13 +72,17 @@ class RuleYear(BaseModel):
# ── Annual rule parameters ───────────────────────────────────────
#
# Sources: CMS OPPS/ASC Final Rules, Federal Register
# Conversion factors from Addendum A header / rule preamble
# Sources: CMS OPPS/ASC Final Rules, Federal Register.
# Conversion factors are the FULL CF for hospitals meeting quality
# reporting (not the reduced/OQR-penalty CF). Verified against each
# year's Final Rule preamble and cross-checked against the empirical
# payment_rate / relative_weight ratio in opps.addendum_b (CY2021+).
# CY2020 uses the corrected $80.793 (the raw final rule stated $80.784).
RULES: dict[int, RuleYear] = {
2014: RuleYear(
year=2014,
conversion_factor=74.305,
conversion_factor=72.672,
outlier_threshold=2885.0,
cms_rule_id="CMS-1601-FC",
federal_register_citation="78 FR 75196",
@@ -88,14 +92,14 @@ RULES: dict[int, RuleYear] = {
),
2015: RuleYear(
year=2015,
conversion_factor=75.064,
conversion_factor=74.144,
outlier_threshold=3075.0,
cms_rule_id="CMS-1613-FC",
federal_register_citation="79 FR 66770",
),
2016: RuleYear(
year=2016,
conversion_factor=75.001,
conversion_factor=73.725,
outlier_threshold=3250.0,
cms_rule_id="CMS-1633-FC",
federal_register_citation="80 FR 70298",
@@ -116,28 +120,28 @@ RULES: dict[int, RuleYear] = {
),
2019: RuleYear(
year=2019,
conversion_factor=80.784,
conversion_factor=79.490,
outlier_threshold=4550.0,
cms_rule_id="CMS-1695-FC",
federal_register_citation="83 FR 58818",
),
2020: RuleYear(
year=2020,
conversion_factor=82.840,
conversion_factor=80.793,
outlier_threshold=4625.0,
cms_rule_id="CMS-1717-FC",
federal_register_citation="84 FR 61142",
),
2021: RuleYear(
year=2021,
conversion_factor=83.324,
conversion_factor=82.797,
outlier_threshold=5025.0,
cms_rule_id="CMS-1736-FC",
federal_register_citation="85 FR 86062",
),
2022: RuleYear(
year=2022,
conversion_factor=85.093,
conversion_factor=84.177,
outlier_threshold=5350.0,
cms_rule_id="CMS-1753-FC",
federal_register_citation="86 FR 63458",
@@ -146,7 +150,7 @@ RULES: dict[int, RuleYear] = {
),
2023: RuleYear(
year=2023,
conversion_factor=86.304,
conversion_factor=85.585,
outlier_threshold=6175.0,
cms_rule_id="CMS-1772-FC",
federal_register_citation="87 FR 72040",
@@ -157,7 +161,7 @@ RULES: dict[int, RuleYear] = {
),
2024: RuleYear(
year=2024,
conversion_factor=87.436,
conversion_factor=87.382,
outlier_threshold=6600.0,
cms_rule_id="CMS-1786-FC",
federal_register_citation="88 FR 82060",
@@ -166,7 +170,7 @@ RULES: dict[int, RuleYear] = {
),
2025: RuleYear(
year=2025,
conversion_factor=88.547,
conversion_factor=89.169,
outlier_threshold=7100.0,
cms_rule_id="CMS-1809-FC",
federal_register_citation="89 FR 100170",
@@ -176,7 +180,7 @@ RULES: dict[int, RuleYear] = {
),
2026: RuleYear(
year=2026,
conversion_factor=89.723,
conversion_factor=91.415,
outlier_threshold=7500.0,
cms_rule_id="CMS-1834-FC",
federal_register_citation="90 FR 101174",