Files
stack/tests/qpp/test_rules.py

317 lines
12 KiB
Python

"""Tests for qpp — QP / Advanced-APM registry.
Covers src/qpp/__init__.py:
- QpThresholds / RiskStandards / QppProposed model construction
- QppYear model construction with required and default fields
- QPP dict: performance years 2023-2027, payment_year = performance_year + 2
- qp_cf_applies: False before performance year 2024, True from 2024 onward
- Every entry has a non-empty citation
- QPP[2026].proposed is CMS-1832-P; QPP[2027].proposed is CMS-1848-P
- for_payment_year(year) lookup, including KeyError on miss
- Exact-value asserts for transcribed thresholds (see task-6-report.md,
task-7-report.md)
"""
from __future__ import annotations
import pytest
from qpp import QPP, QppProposed, QppYear, QpThresholds, RiskStandards, for_payment_year
# ── QpThresholds model ───────────────────────────────────────────
class TestQpThresholds:
def test_construction(self):
t = QpThresholds(payment_amount_pct=75.0, patient_count_pct=50.0)
assert t.payment_amount_pct == 75.0
assert t.patient_count_pct == 50.0
# ── RiskStandards model ──────────────────────────────────────────
class TestRiskStandards:
def test_construction(self):
r = RiskStandards(revenue_nominal_pct=8.0, benchmark_nominal_pct=3.0)
assert r.revenue_nominal_pct == 8.0
assert r.benchmark_nominal_pct == 3.0
def test_default_notes(self):
r = RiskStandards(revenue_nominal_pct=8.0, benchmark_nominal_pct=3.0)
assert r.notes == ""
def test_default_benchmark_nominal_pct(self):
r = RiskStandards(revenue_nominal_pct=8.0)
assert r.benchmark_nominal_pct is None
# ── QppProposed model ────────────────────────────────────────────
class TestQppProposed:
def test_required_fields(self):
p = QppProposed(
cms_rule_id="CMS-0000-P",
federal_register_citation="99 FR 1",
changes="proposed no change to anything",
)
assert p.cms_rule_id == "CMS-0000-P"
assert p.changes == "proposed no change to anything"
def test_default_optional_blocks(self):
p = QppProposed(
cms_rule_id="CMS-0000-P",
federal_register_citation="99 FR 1",
changes="no change",
)
assert p.qp_thresholds is None
assert p.partial_qp_thresholds is None
assert p.risk_standards is None
# ── QppYear model ─────────────────────────────────────────────────
class TestQppYear:
def _minimal_kwargs(self):
return dict(
performance_year=2099,
payment_year=2101,
qp_thresholds=QpThresholds(payment_amount_pct=75.0, patient_count_pct=50.0),
partial_qp_thresholds=QpThresholds(
payment_amount_pct=50.0, patient_count_pct=35.0
),
risk_standards=RiskStandards(
revenue_nominal_pct=8.0, benchmark_nominal_pct=3.0
),
apm_incentive_pct=None,
qp_cf_applies=True,
)
def test_required_fields(self):
y = QppYear(**self._minimal_kwargs())
assert y.performance_year == 2099
assert y.payment_year == 2101
def test_default_cehrt_required(self):
y = QppYear(**self._minimal_kwargs())
assert y.cehrt_required is True
def test_default_citation(self):
y = QppYear(**self._minimal_kwargs())
assert y.citation == ""
def test_default_proposed(self):
y = QppYear(**self._minimal_kwargs())
assert y.proposed is None
# ── QPP dict structure ───────────────────────────────────────────
class TestQppDict:
EXPECTED_YEARS = [2023, 2024, 2025, 2026, 2027]
def test_count(self):
assert len(QPP) == 5
def test_keys(self):
assert sorted(QPP.keys()) == self.EXPECTED_YEARS
@pytest.mark.parametrize("year", EXPECTED_YEARS)
def test_values_are_qppyear(self, year):
assert isinstance(QPP[year], QppYear)
@pytest.mark.parametrize("year", EXPECTED_YEARS)
def test_performance_year_field_matches_key(self, year):
assert QPP[year].performance_year == year
@pytest.mark.parametrize("year", EXPECTED_YEARS)
def test_payment_year_is_performance_year_plus_2(self, year):
assert QPP[year].payment_year == year + 2
@pytest.mark.parametrize("year", EXPECTED_YEARS)
def test_citation_non_empty(self, year):
assert QPP[year].citation != ""
class TestQpCfApplies:
"""payment year 2026 (performance year 2024) is the first split-CF year."""
def test_2023_false(self):
assert QPP[2023].qp_cf_applies is False
def test_2024_true(self):
assert QPP[2024].qp_cf_applies is True
def test_2025_true(self):
assert QPP[2025].qp_cf_applies is True
def test_2026_true(self):
assert QPP[2026].qp_cf_applies is True
def test_2027_true(self):
assert QPP[2027].qp_cf_applies is True
class TestApmIncentivePct:
"""Lump-sum APM Incentive Payment: 5% (2019-2024), 3.5% (2025),
1.88% (2026), then a gap for 2027 — but NOT a clean sunset: the
Consolidated Appropriations Act, 2026 (CAA 2026) revived a 3.1%
payment for payment year 2028 only (42 CFR 414.1450(b)(1)(iv), as
amended by CMS-1848-P, 91 FR 44286), before the gap resumes for
2029."""
def test_2023_incentive(self):
assert QPP[2023].apm_incentive_pct == 3.5
def test_2024_incentive(self):
assert QPP[2024].apm_incentive_pct == 1.88
def test_2025_no_incentive(self):
assert QPP[2025].apm_incentive_pct is None
def test_2026_incentive_revived_by_caa_2026(self):
# Payment year 2028: CAA 2026 revived a 3.1% APM Incentive
# Payment — 42 CFR 414.1450(b)(1)(iv), as amended by
# CMS-1848-P, 91 FR 44286 (data/fr_downloads/
# 2026-14327.txt:42632, "(iv) 2028: 3.1 percent."); narrative
# at 91 FR 44218-44219 (2026-14327.txt:37389-37391, 37436).
# This supersedes the CY2026 Final Rule's apm_incentive_pct
# = None (90 FR 49838/49980).
assert QPP[2026].apm_incentive_pct == 3.1
def test_2027_no_incentive(self):
# Payment year 2029: 42 CFR 414.1450(b)(1) (as amended by
# CMS-1848-P, 91 FR 44286) lists applicable percentages only
# for payment years 2019-2024, 2025, 2026, and 2028 — no entry
# for 2029.
assert QPP[2027].apm_incentive_pct is None
# ── QPP[2026].proposed — CMS-1832-P ──────────────────────────────
class TestProposed:
def test_2026_has_proposed(self):
p = QPP[2026].proposed
assert p is not None
assert p.cms_rule_id == "CMS-1832-P"
assert p.federal_register_citation == "90 FR 32352"
def test_2026_changes_non_empty(self):
assert len(QPP[2026].proposed.changes) > 0
def test_earlier_years_have_no_proposed_block(self):
assert QPP[2023].proposed is None
assert QPP[2024].proposed is None
assert QPP[2025].proposed is None
# ── QPP[2027].proposed — CMS-1848-P ──────────────────────────────
class TestProposed2027:
def test_2027_has_proposed(self):
p = QPP[2027].proposed
assert p is not None
assert p.cms_rule_id == "CMS-1848-P"
assert p.federal_register_citation == "91 FR 43842"
def test_2027_changes_non_empty(self):
assert len(QPP[2027].proposed.changes) > 0
def test_2027_no_numeric_change_blocks(self):
# CMS-1848-P's threshold/incentive amendments conform the
# regulation text to already-enacted statutory levels rather
# than proposing new CMS-set numeric values — see
# QppProposed.qp_thresholds docstring.
p = QPP[2027].proposed
assert p.qp_thresholds is None
assert p.partial_qp_thresholds is None
assert p.risk_standards is None
# ── Exact transcribed threshold values ───────────────────────────
class TestSpecificValues:
"""Verify transcribed QP/Partial QP thresholds and risk standards.
Thresholds are keyed to PAYMENT YEAR by 42 CFR 414.1430(a), not QP
Performance Period — see module docstring "Payment-year keying
correction" (task #620). Payment years 2021-2026 (QPP[2023] pay
2025, QPP[2024] pay 2026) and 2028 (QPP[2026], restored by CAA
2026) sit at 50%/35% QP, 40%/25% Partial QP; payment years 2027
and 2029+ (QPP[2025], QPP[2027]) sit at 75%/50% QP, 50%/35%
Partial QP. See task-6-report.md / task-7-report.md for the
original transcription tables and task-620-report.md for the
correction."""
@pytest.mark.parametrize("year", [2025, 2027])
def test_qp_thresholds_75_50(self, year):
assert QPP[year].qp_thresholds.payment_amount_pct == 75.0
assert QPP[year].qp_thresholds.patient_count_pct == 50.0
@pytest.mark.parametrize("year", [2023, 2024, 2026])
def test_qp_thresholds_50_35(self, year):
# 42 CFR 414.1430(a)(1)(ii)/(a)(3)(ii) "2021 through 2026: 50
# percent" / "35 percent" (91 FR 44285); (a)(1)(iv)/(a)(3)(iv)
# "2028: 50 percent" / "35 percent" (91 FR 44286) — CAA 2026
# for QPP[2026]/payment year 2028.
assert QPP[year].qp_thresholds.payment_amount_pct == 50.0
assert QPP[year].qp_thresholds.patient_count_pct == 35.0
@pytest.mark.parametrize("year", [2025, 2027])
def test_partial_qp_thresholds_50_35(self, year):
assert QPP[year].partial_qp_thresholds.payment_amount_pct == 50.0
assert QPP[year].partial_qp_thresholds.patient_count_pct == 35.0
@pytest.mark.parametrize("year", [2023, 2024, 2026])
def test_partial_qp_thresholds_40_25(self, year):
# 42 CFR 414.1430(a)(2)(ii)/(a)(4)(ii) "2021 through 2026: 40
# percent" / "25 percent" (91 FR 44285-44286); (a)(2)(iv)/
# (a)(4)(iv) "2028: 40 percent" / "25 percent" (91 FR 44286) —
# CAA 2026 for QPP[2026]/payment year 2028.
assert QPP[year].partial_qp_thresholds.payment_amount_pct == 40.0
assert QPP[year].partial_qp_thresholds.patient_count_pct == 25.0
@pytest.mark.parametrize("year", [2023, 2024, 2025, 2026, 2027])
def test_risk_standards(self, year):
# Revenue-based standard (42 CFR 414.1415(c)(3)(i)(A)) is
# transcribed (8%); the benchmark-based standard (42 CFR
# 414.1415(c)(3)(i)(B)) is not stated as a bare number in this
# repository's FR corpus and is left None — see
# RiskStandards.notes / module docstring.
assert QPP[year].risk_standards.revenue_nominal_pct == 8.0
assert QPP[year].risk_standards.benchmark_nominal_pct is None
@pytest.mark.parametrize("year", [2023, 2024, 2025, 2026, 2027])
def test_cehrt_required(self, year):
assert QPP[year].cehrt_required is True
# ── for_payment_year lookup ──────────────────────────────────────
class TestForPaymentYear:
def test_2025_maps_to_performance_2023(self):
assert for_payment_year(2025) is QPP[2023]
def test_2026_maps_to_performance_2024(self):
assert for_payment_year(2026) is QPP[2024]
def test_2027_maps_to_performance_2025(self):
assert for_payment_year(2027) is QPP[2025]
def test_2028_maps_to_performance_2026(self):
assert for_payment_year(2028) is QPP[2026]
def test_2029_maps_to_performance_2027(self):
assert for_payment_year(2029) is QPP[2027]
def test_unknown_payment_year_raises_keyerror(self):
with pytest.raises(KeyError):
for_payment_year(1999)