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."""
|
||||
|
||||
|
||||
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):
|
||||
"""Annual PFS rule parameters for a single calendar year."""
|
||||
|
||||
@@ -161,6 +226,14 @@ class RuleYear(BaseModel):
|
||||
federal_register_citation: str = ""
|
||||
"""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 ───────────────────────────────────────────
|
||||
|
||||
def cf_for_date(self, d: date) -> float:
|
||||
@@ -346,6 +419,29 @@ RULES: dict[int, RuleYear] = {
|
||||
clinical_labor_rate_update=True,
|
||||
telehealth_originating_site_fee=30.38,
|
||||
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
|
||||
|
||||
from pfs.rules import RULES, RuleYear, cy2024, cy2025, cy2026
|
||||
from pfs.rules import RULES, ProposedRule, RuleYear, cy2024, cy2025, cy2026
|
||||
|
||||
# ── RuleYear model ───────────────────────────────────────────────
|
||||
|
||||
@@ -232,3 +232,67 @@ class TestCfForDate:
|
||||
)
|
||||
with pytest.raises(ValueError, match="outside every CF segment"):
|
||||
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