feat(pfs): ProposedRule registry — CY2026 NPRM parameters w/ pincite (refs #591)
This commit is contained in:
@@ -66,6 +66,71 @@ class CfSegment(BaseModel):
|
|||||||
(``"CAA 2024 §3201"``, etc.) or derivation note."""
|
(``"CAA 2024 §3201"``, etc.) or derivation note."""
|
||||||
|
|
||||||
|
|
||||||
|
class ProposedRule(BaseModel):
|
||||||
|
"""Parameters as *proposed* in the year's NPRM (pre-final).
|
||||||
|
|
||||||
|
CMS publishes a Notice of Proposed Rulemaking (NPRM) months before
|
||||||
|
the Final Rule; the two can and do differ (comment period,
|
||||||
|
statutory adjustments, and CMS's own re-estimates all move the
|
||||||
|
numbers between proposed and final). This model captures the
|
||||||
|
NPRM's own stated values so they can be compared against the
|
||||||
|
eventual ``RuleYear`` final values — it is never itself used for
|
||||||
|
payment math.
|
||||||
|
|
||||||
|
CY2026 values are sourced from the CY 2026 PFS NPRM (CMS-1832-P,
|
||||||
|
90 FR 32352, July 16, 2025), ingested into Zotero as
|
||||||
|
:pincite:`2KVJ2HKX`."""
|
||||||
|
|
||||||
|
cms_rule_id: str
|
||||||
|
"""CMS internal rule identifier, e.g. ``"CMS-1832-P"`` (the ``-P``
|
||||||
|
suffix denotes "proposed"; the Final Rule reuses the number with
|
||||||
|
``-F``)."""
|
||||||
|
|
||||||
|
fr_document_number: str
|
||||||
|
"""Federal Register document number, e.g. ``"2025-13271"`` — the
|
||||||
|
identifier stamped on the captured full-text file."""
|
||||||
|
|
||||||
|
federal_register_citation: str
|
||||||
|
"""Federal Register citation of the NPRM, e.g. ``"90 FR 32352"``
|
||||||
|
— from the txt header."""
|
||||||
|
|
||||||
|
published: date
|
||||||
|
"""NPRM publication date in the Federal Register."""
|
||||||
|
|
||||||
|
comment_close: date
|
||||||
|
"""End of the public comment period (from the NPRM's DATES
|
||||||
|
section)."""
|
||||||
|
|
||||||
|
conversion_factor: float
|
||||||
|
"""Proposed CY2026 nonqualifying-APM (non-QP) standard conversion
|
||||||
|
factor. See :pincite:`2KVJ2HKX`."""
|
||||||
|
|
||||||
|
cf_qp: float | None = None
|
||||||
|
"""Proposed CY2026 qualifying-APM (QP) standard conversion
|
||||||
|
factor. See :pincite:`2KVJ2HKX`."""
|
||||||
|
|
||||||
|
anesthesia_cf: float | None = None
|
||||||
|
"""Proposed CY2026 nonqualifying-APM (non-QP) anesthesia
|
||||||
|
conversion factor. See :pincite:`2KVJ2HKX`."""
|
||||||
|
|
||||||
|
anesthesia_cf_qp: float | None = None
|
||||||
|
"""Proposed CY2026 qualifying-APM (QP) anesthesia conversion
|
||||||
|
factor. See :pincite:`2KVJ2HKX`."""
|
||||||
|
|
||||||
|
budget_neutrality_adjustor: float = 1.0
|
||||||
|
"""Budget-neutrality adjustment the NPRM applied when deriving
|
||||||
|
the proposed CFs from the CY2025 conversion factor. See
|
||||||
|
:pincite:`2KVJ2HKX`."""
|
||||||
|
|
||||||
|
telehealth_originating_site_fee: float | None = None
|
||||||
|
"""Proposed telehealth originating site facility fee ($) for
|
||||||
|
HCPCS code Q3014. See :pincite:`2KVJ2HKX`."""
|
||||||
|
|
||||||
|
notes: str = ""
|
||||||
|
"""Free-text transcription notes — caveats, values not stated in
|
||||||
|
the NPRM, or discrepancies with the eventual Final Rule."""
|
||||||
|
|
||||||
|
|
||||||
class RuleYear(BaseModel):
|
class RuleYear(BaseModel):
|
||||||
"""Annual PFS rule parameters for a single calendar year."""
|
"""Annual PFS rule parameters for a single calendar year."""
|
||||||
|
|
||||||
@@ -161,6 +226,14 @@ class RuleYear(BaseModel):
|
|||||||
federal_register_citation: str = ""
|
federal_register_citation: str = ""
|
||||||
"""Federal Register citation (e.g. '89 FR 67668')."""
|
"""Federal Register citation (e.g. '89 FR 67668')."""
|
||||||
|
|
||||||
|
proposed: ProposedRule | None = None
|
||||||
|
"""NPRM (proposed-rule) parameters for this year, when captured.
|
||||||
|
|
||||||
|
The fields above this one are the **Final Rule** values and remain
|
||||||
|
authoritative for all payment math — ``proposed`` is informational
|
||||||
|
only, for comparing what CMS proposed against what it finalized.
|
||||||
|
Never read ``proposed.*`` in place of a final field."""
|
||||||
|
|
||||||
# ── Derived lookups ───────────────────────────────────────────
|
# ── Derived lookups ───────────────────────────────────────────
|
||||||
|
|
||||||
def cf_for_date(self, d: date) -> float:
|
def cf_for_date(self, d: date) -> float:
|
||||||
@@ -346,6 +419,29 @@ RULES: dict[int, RuleYear] = {
|
|||||||
clinical_labor_rate_update=True,
|
clinical_labor_rate_update=True,
|
||||||
telehealth_originating_site_fee=30.38,
|
telehealth_originating_site_fee=30.38,
|
||||||
federal_register_citation="90 FR 101174",
|
federal_register_citation="90 FR 101174",
|
||||||
|
# NPRM values (pre-final) per :pincite:`2KVJ2HKX` — CMS-1832-P,
|
||||||
|
# 90 FR 32352, transcribed from data/fr_downloads/2025-13271.txt.
|
||||||
|
proposed=ProposedRule(
|
||||||
|
cms_rule_id="CMS-1832-P",
|
||||||
|
fr_document_number="2025-13271",
|
||||||
|
federal_register_citation="90 FR 32352",
|
||||||
|
published=date(2025, 7, 16),
|
||||||
|
comment_close=date(2025, 9, 12),
|
||||||
|
conversion_factor=33.4209, # nonqualifying APM standard CF
|
||||||
|
cf_qp=33.5875, # qualifying APM standard CF
|
||||||
|
anesthesia_cf=20.5728, # nonqualifying APM anesthesia CF
|
||||||
|
anesthesia_cf_qp=20.6754, # qualifying APM anesthesia CF
|
||||||
|
budget_neutrality_adjustor=1.0055, # "0.55 percent positive"
|
||||||
|
telehealth_originating_site_fee=31.85, # HCPCS Q3014
|
||||||
|
notes=(
|
||||||
|
"Transcribed from data/fr_downloads/2025-13271.txt "
|
||||||
|
"(NPRM full text) per :pincite:`2KVJ2HKX`. CFs and "
|
||||||
|
"anesthesia CFs at file line ~39205-39220 (90 FR "
|
||||||
|
"32800); telehealth fee at line ~4710 (90 FR 32396); "
|
||||||
|
"comment-close DATES at line ~106 (90 FR 32352). "
|
||||||
|
"Final Rule values differ (see fields above)."
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pfs.rules import RULES, RuleYear, cy2024, cy2025, cy2026
|
from pfs.rules import RULES, ProposedRule, RuleYear, cy2024, cy2025, cy2026
|
||||||
|
|
||||||
# ── RuleYear model ───────────────────────────────────────────────
|
# ── RuleYear model ───────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -232,3 +232,67 @@ class TestCfForDate:
|
|||||||
)
|
)
|
||||||
with pytest.raises(ValueError, match="outside every CF segment"):
|
with pytest.raises(ValueError, match="outside every CF segment"):
|
||||||
ry.cf_for_date(date(2024, 1, 15))
|
ry.cf_for_date(date(2024, 1, 15))
|
||||||
|
|
||||||
|
|
||||||
|
# ── ProposedRule — CY2026 NPRM parameters ────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
class TestProposedRule:
|
||||||
|
"""ProposedRule model and RULES[2026].proposed values."""
|
||||||
|
|
||||||
|
def test_cy2026_has_proposed_rule(self):
|
||||||
|
p = RULES[2026].proposed
|
||||||
|
assert p is not None
|
||||||
|
assert p.cms_rule_id == "CMS-1832-P"
|
||||||
|
assert p.fr_document_number == "2025-13271"
|
||||||
|
assert p.published.year == 2025
|
||||||
|
assert p.comment_close > p.published
|
||||||
|
# exact transcribed values — from data/fr_downloads/2025-13271.txt
|
||||||
|
assert p.conversion_factor == 33.4209
|
||||||
|
assert p.cf_qp == 33.5875
|
||||||
|
assert p.conversion_factor != RULES[2026].conversion_factor # proposed ≠ final
|
||||||
|
|
||||||
|
def test_cy2026_proposed_anesthesia_cfs(self):
|
||||||
|
p = RULES[2026].proposed
|
||||||
|
assert p.anesthesia_cf == 20.5728
|
||||||
|
assert p.anesthesia_cf_qp == 20.6754
|
||||||
|
|
||||||
|
def test_cy2026_proposed_budget_neutrality_adjustor(self):
|
||||||
|
assert RULES[2026].proposed.budget_neutrality_adjustor == 1.0055
|
||||||
|
|
||||||
|
def test_cy2026_proposed_telehealth_fee(self):
|
||||||
|
assert RULES[2026].proposed.telehealth_originating_site_fee == 31.85
|
||||||
|
|
||||||
|
def test_cy2026_proposed_federal_register_citation(self):
|
||||||
|
assert RULES[2026].proposed.federal_register_citation == "90 FR 32352"
|
||||||
|
|
||||||
|
def test_cy2026_proposed_published_date(self):
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
assert RULES[2026].proposed.published == date(2025, 7, 16)
|
||||||
|
|
||||||
|
def test_cy2026_proposed_comment_close_date(self):
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
assert RULES[2026].proposed.comment_close == date(2025, 9, 12)
|
||||||
|
|
||||||
|
def test_pre_2026_years_have_no_proposed_block(self):
|
||||||
|
assert RULES[2025].proposed is None
|
||||||
|
|
||||||
|
def test_proposed_rule_default_notes(self):
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
p = ProposedRule(
|
||||||
|
cms_rule_id="CMS-0000-P",
|
||||||
|
fr_document_number="2099-00000",
|
||||||
|
federal_register_citation="99 FR 1",
|
||||||
|
published=date(2099, 1, 1),
|
||||||
|
comment_close=date(2099, 3, 1),
|
||||||
|
conversion_factor=1.0,
|
||||||
|
)
|
||||||
|
assert p.notes == ""
|
||||||
|
assert p.cf_qp is None
|
||||||
|
assert p.anesthesia_cf is None
|
||||||
|
assert p.anesthesia_cf_qp is None
|
||||||
|
assert p.budget_neutrality_adjustor == 1.0
|
||||||
|
assert p.telehealth_originating_site_fee is None
|
||||||
|
|||||||
Reference in New Issue
Block a user