317 lines
12 KiB
Python
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)
|