241 lines
8.4 KiB
Python
241 lines
8.4 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-2026, 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
|
|
- for_payment_year(year) lookup, including KeyError on miss
|
|
- Exact-value asserts for transcribed thresholds (see task-6-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]
|
|
|
|
def test_count(self):
|
|
assert len(QPP) == 4
|
|
|
|
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
|
|
|
|
|
|
class TestApmIncentivePct:
|
|
"""Lump-sum APM Incentive Payment sunsets after payment year 2026."""
|
|
|
|
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_no_incentive(self):
|
|
assert QPP[2026].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
|
|
|
|
|
|
# ── Exact transcribed threshold values ───────────────────────────
|
|
|
|
|
|
class TestSpecificValues:
|
|
"""Verify transcribed QP/Partial QP thresholds and risk standards.
|
|
|
|
See task-6-report.md transcription table for sourcing per value."""
|
|
|
|
@pytest.mark.parametrize("year", [2023, 2024, 2025, 2026])
|
|
def test_qp_thresholds(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, 2025, 2026])
|
|
def test_partial_qp_thresholds(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, 2025, 2026])
|
|
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])
|
|
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_unknown_payment_year_raises_keyerror(self):
|
|
with pytest.raises(KeyError):
|
|
for_payment_year(1999)
|