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) + (1 - Labor_Share)
Labor_Share ≈ 0.60 (set annually in Final Rule) Labor_Share ≈ 0.60 (set annually in Final Rule)
Conversion_Factor = $89.723 (CY2026) Conversion_Factor = $91.415 (CY2026)
Skin Substitute Reclassification (CY2026) Skin Substitute Reclassification (CY2026)
----------------------------------------- -----------------------------------------

View File

@@ -11,7 +11,7 @@ Usage::
from opps.rules import cy2026, RULES 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 skin_rate = cy2026.skin_sub_flat_rate # $127.28
""" """
@@ -72,13 +72,17 @@ class RuleYear(BaseModel):
# ── Annual rule parameters ─────────────────────────────────────── # ── Annual rule parameters ───────────────────────────────────────
# #
# Sources: CMS OPPS/ASC Final Rules, Federal Register # Sources: CMS OPPS/ASC Final Rules, Federal Register.
# Conversion factors from Addendum A header / rule preamble # 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] = { RULES: dict[int, RuleYear] = {
2014: RuleYear( 2014: RuleYear(
year=2014, year=2014,
conversion_factor=74.305, conversion_factor=72.672,
outlier_threshold=2885.0, outlier_threshold=2885.0,
cms_rule_id="CMS-1601-FC", cms_rule_id="CMS-1601-FC",
federal_register_citation="78 FR 75196", federal_register_citation="78 FR 75196",
@@ -88,14 +92,14 @@ RULES: dict[int, RuleYear] = {
), ),
2015: RuleYear( 2015: RuleYear(
year=2015, year=2015,
conversion_factor=75.064, conversion_factor=74.144,
outlier_threshold=3075.0, outlier_threshold=3075.0,
cms_rule_id="CMS-1613-FC", cms_rule_id="CMS-1613-FC",
federal_register_citation="79 FR 66770", federal_register_citation="79 FR 66770",
), ),
2016: RuleYear( 2016: RuleYear(
year=2016, year=2016,
conversion_factor=75.001, conversion_factor=73.725,
outlier_threshold=3250.0, outlier_threshold=3250.0,
cms_rule_id="CMS-1633-FC", cms_rule_id="CMS-1633-FC",
federal_register_citation="80 FR 70298", federal_register_citation="80 FR 70298",
@@ -116,28 +120,28 @@ RULES: dict[int, RuleYear] = {
), ),
2019: RuleYear( 2019: RuleYear(
year=2019, year=2019,
conversion_factor=80.784, conversion_factor=79.490,
outlier_threshold=4550.0, outlier_threshold=4550.0,
cms_rule_id="CMS-1695-FC", cms_rule_id="CMS-1695-FC",
federal_register_citation="83 FR 58818", federal_register_citation="83 FR 58818",
), ),
2020: RuleYear( 2020: RuleYear(
year=2020, year=2020,
conversion_factor=82.840, conversion_factor=80.793,
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",
), ),
2021: RuleYear( 2021: RuleYear(
year=2021, year=2021,
conversion_factor=83.324, conversion_factor=82.797,
outlier_threshold=5025.0, outlier_threshold=5025.0,
cms_rule_id="CMS-1736-FC", cms_rule_id="CMS-1736-FC",
federal_register_citation="85 FR 86062", federal_register_citation="85 FR 86062",
), ),
2022: RuleYear( 2022: RuleYear(
year=2022, year=2022,
conversion_factor=85.093, conversion_factor=84.177,
outlier_threshold=5350.0, outlier_threshold=5350.0,
cms_rule_id="CMS-1753-FC", cms_rule_id="CMS-1753-FC",
federal_register_citation="86 FR 63458", federal_register_citation="86 FR 63458",
@@ -146,7 +150,7 @@ RULES: dict[int, RuleYear] = {
), ),
2023: RuleYear( 2023: RuleYear(
year=2023, year=2023,
conversion_factor=86.304, conversion_factor=85.585,
outlier_threshold=6175.0, outlier_threshold=6175.0,
cms_rule_id="CMS-1772-FC", cms_rule_id="CMS-1772-FC",
federal_register_citation="87 FR 72040", federal_register_citation="87 FR 72040",
@@ -157,7 +161,7 @@ RULES: dict[int, RuleYear] = {
), ),
2024: RuleYear( 2024: RuleYear(
year=2024, year=2024,
conversion_factor=87.436, conversion_factor=87.382,
outlier_threshold=6600.0, outlier_threshold=6600.0,
cms_rule_id="CMS-1786-FC", cms_rule_id="CMS-1786-FC",
federal_register_citation="88 FR 82060", federal_register_citation="88 FR 82060",
@@ -166,7 +170,7 @@ RULES: dict[int, RuleYear] = {
), ),
2025: RuleYear( 2025: RuleYear(
year=2025, year=2025,
conversion_factor=88.547, conversion_factor=89.169,
outlier_threshold=7100.0, outlier_threshold=7100.0,
cms_rule_id="CMS-1809-FC", cms_rule_id="CMS-1809-FC",
federal_register_citation="89 FR 100170", federal_register_citation="89 FR 100170",
@@ -176,7 +180,7 @@ RULES: dict[int, RuleYear] = {
), ),
2026: RuleYear( 2026: RuleYear(
year=2026, year=2026,
conversion_factor=89.723, conversion_factor=91.415,
outlier_threshold=7500.0, outlier_threshold=7500.0,
cms_rule_id="CMS-1834-FC", cms_rule_id="CMS-1834-FC",
federal_register_citation="90 FR 101174", federal_register_citation="90 FR 101174",