Files
stack/tests/pfs/test_stakes.py
kert e330f90cdc fix(pfs): member-code new payment comes from the exposure calendar, not the payment window (R84c)
new_codes_in judges a family member code new in Y only when
pfs.code_exposure has a becomes-payable row for it in Y; link targets
stay new only at their link since (R84a). The round-3 row-count
heuristic and the payability scan over ValuationRows are gone, so a
truncated valuation(years=N) window can neither invent nor hide a new
code. money_by_specialty takes exposures=; read_becomes_payable reads
them for a family's member codes.
2026-11-12 19:31:15 -05:00

572 lines
20 KiB
Python

"""pfs.stakes - CMS specialty impact tables and money by specialty (T10)."""
from __future__ import annotations
import datetime as dt
from pathlib import Path
import duckdb
import pytest
from bib.item import Source
from bib.store import Store
from pfs import stakes
from pfs.codetables import (
ExposureRow,
ImpactRow,
PspsRow,
ensure_tables,
write_exposures,
)
from pfs.stakes import (
billing_share,
discover_508,
discover_fr,
fr_table_year,
money_by_specialty,
parse_impact_508,
parse_impact_fr_text,
read_impacts,
write_impacts,
)
from pfs.valuation import ValuationRow
FIX = Path(__file__).parent / "fixtures"
def test_parse_impact_508():
rows = parse_impact_508(
FIX / "impact_cy2027_508.txt", rule_year=2027, vintage="final", item_key="K"
)
assert len(rows) == 6
a = rows[0]
assert (a.specialty, a.setting, a.allowed_mil) == (
"ALLERGY/IMMUNOLOGY",
"TOTAL",
240.0,
)
assert (a.impact_work, a.impact_pe, a.impact_mp, a.impact_combined) == (
0.0,
-0.02,
0.0,
-0.02,
)
assert (a.rule_year, a.vintage, a.source, a.item_key, a.page) == (
2027,
"final",
"508",
"K",
0,
)
assert [r.setting for r in rows[:3]] == ["TOTAL", "Non-Facility", "Facility"]
anes = rows[3]
assert (anes.specialty, anes.setting, anes.allowed_mil) == (
"ANESTHESIOLOGY",
"TOTAL",
1672.0,
)
def test_parse_impact_fr_text():
rows = parse_impact_fr_text(
(FIX / "impact_cy2017_fr.txt").read_text(),
rule_year=2017,
vintage="final",
item_key="FR",
)
assert [r.specialty for r in rows] == [
"TOTAL",
"ALLERGY/IMMUNOLOGY",
"ANESTHESIOLOGY",
"AUDIOLOGIST",
]
tot, allergy = rows[0], rows[1]
assert tot.allowed_mil == 89866.0
assert (allergy.allowed_mil, allergy.impact_pe, allergy.impact_combined) == (
231.0,
0.01,
0.01,
)
assert (allergy.setting, allergy.source, allergy.page) == ("TOTAL", "fr", 80544)
def test_fr_text_other_table_titles_and_wrapped_names_and_page_changes():
text = "\n".join(
[
"[[Page 10]]",
"Table 72--CY 2014 PFS Estimated Impact on Total Allowed Charges by Specialty by Selected Proposal*",
"-----",
"ALLERGY..... 1 0 0 0 0",
"Table 7--CY 2014 PFS Estimated Impact on Total Allowed Charges by Specialty *",
"-----",
"(A) Specialty (B)",
"MULTISPECIALTY CLINIC/OTHER PHYS 129 -1.5 0 0 1",
"[[Page 11]]",
"",
"OTHER........... 27 0 0 0 -1",
"A footnote line",
"NOT A ROW..... 1 0 0 0 0",
]
)
rows = parse_impact_fr_text(text, rule_year=2014, vintage="proposed", item_key="X")
assert [(r.specialty, r.page) for r in rows] == [
("MULTISPECIALTY CLINIC/OTHER PHYS", 10),
("OTHER", 11),
]
assert rows[0].impact_work == -0.015
assert rows[1].impact_combined == -0.01
def test_fr_text_older_layouts():
text = "\n".join(
[
"[[Page 5]]",
"Table 134--CY 2013 PFS Final Rule With Comment Period Estimated Impact on Total Allowed Charges by Specialty *",
"-----",
"TOTAL........................................... $ 86,588 0 0 0",
"02-ANESTHESIOLOGY **............................ 1,923 0 1 1",
" TOTAL................ $89,020 0% 0% 0% -1%",
"ANESTHESIOLOGY....... $1,970 0% 1% -2% 0%",
]
)
rows = parse_impact_fr_text(text, rule_year=2013, vintage="final", item_key="X")
assert [r.specialty for r in rows] == [
"TOTAL",
"ANESTHESIOLOGY",
"TOTAL",
"ANESTHESIOLOGY",
]
tot, anes, tot16, anes16 = rows
assert tot.allowed_mil == 86588.0
assert (anes.impact_work, anes.impact_pe, anes.impact_mp, anes.impact_combined) == (
0.0,
0.01,
None,
0.01,
)
assert (tot16.allowed_mil, tot16.impact_combined) == (89020.0, -0.01)
assert (anes16.impact_pe, anes16.impact_mp) == (0.01, -0.02)
def test_percent_and_money_helpers():
assert stakes._pct("-2%") == -0.02
assert stakes._pct("3 %") == 0.03
assert stakes._pct("") is None
assert stakes._pct("1") is None # a bare number is not a 508 percent
assert stakes._pct("1", bare_points=True) == 0.01
assert stakes._pct("x%") is None
assert stakes._pct("x", bare_points=True) is None
assert stakes._money("$1,672") == 1672.0
assert stakes._money("n/a") is None
def test_508_skips_short_and_unknown_setting_lines(tmp_path):
p = tmp_path / "x_508.txt"
p.write_text('title\nA\tB\tC\n"* note"\tx\nFOO\tTOTAL\t$5\t0%\t0%\t0%\t1%\t\n')
rows = parse_impact_508(p, rule_year=2027, vintage="final")
assert [
(r.specialty, r.allowed_mil, r.impact_combined, r.item_key) for r in rows
] == [("FOO", 5.0, 0.01, "")]
@pytest.fixture
def con():
c = duckdb.connect(":memory:")
ensure_tables(c)
yield c
c.close()
def test_write_and_read_impacts(con):
rows = parse_impact_508(
FIX / "impact_cy2027_508.txt", rule_year=2027, vintage="final"
)
fr = parse_impact_fr_text(
(FIX / "impact_cy2017_fr.txt").read_text(),
rule_year=2017,
vintage="final",
item_key="FR",
)
assert write_impacts(con, 2027, "final", rows, source="508") == 6
assert write_impacts(con, 2017, "final", fr, source="fr") == 4
assert write_impacts(con, 2027, "final", rows[:3], source="508") == 3 # replaces
got = read_impacts(con)
assert [(r.rule_year, r.source) for r in got].count((2027, "508")) == 3
assert [r.rule_year for r in read_impacts(con, [2017])] == [2017] * 4
assert isinstance(got[0], ImpactRow)
assert write_impacts(con, 2020, "final", [], source="508") == 0
def test_fr_table_year():
t = "x\nTable 52--CY 2017 PFS Estimated Impact on Total Allowed Charges by Specialty *\n"
assert fr_table_year(t) == 2017
assert (
fr_table_year("Table 5 Estimated Impact on Total Allowed Charges by Specialty")
is None
)
assert fr_table_year("nothing here") is None
def test_impacts_scan_finds_508(tmp_path):
name = "CY 2027 PFS Final Rule Estimated Impact on Total Allowed Charges by Specialty_508.txt"
src = tmp_path / "src"
src.mkdir()
f = src / name
f.write_text((FIX / "impact_cy2027_508.txt").read_text())
other = src / "other_508.txt"
other.write_text("x")
store = Store(":memory:", storage_dir=tmp_path / "storage")
try:
key = store.upsert(Source(title="Impacts", url="https://x/y"))
store.attach_file(key, f)
store.attach_file(key, other)
# an older year that lives only under the pfs_final directory tree
root = tmp_path / "pfs_final"
d = root / "2026" / "files" / "d"
d.mkdir(parents=True)
(d / name.replace("2027", "2026")).write_text("x")
(d / "unrelated_508.txt").write_text("x")
(d / "CY 2026 Impact on Total Allowed Charges by Specialty_508.txt").write_text(
"x"
)
# the 2027 tree copy must not duplicate the bib one
d27 = root / "2027" / "files" / "d"
d27.mkdir(parents=True)
(d27 / name).write_text("x")
found = discover_508(store, root)
finally:
store.close()
got = {(s.rule_year, s.vintage): s for s in found}
assert set(got) == {(2027, "final"), (2026, "final")}
assert got[(2027, "final")].item_key == key and got[(2027, "final")].kind == "508"
assert got[(2026, "final")].item_key == ""
def test_discover_fr(tmp_path, monkeypatch):
import pfs.rounds as rounds
t = tmp_path / "a.txt"
t.write_text((FIX / "impact_cy2017_fr.txt").read_text())
n = tmp_path / "n.txt"
n.write_text("no table")
items = [
{"item_key": "A", "kind": "final"},
{"item_key": "B", "kind": "rule"},
{"item_key": "C", "kind": "proposed"}, # no text on disk
{"item_key": "D", "kind": "final"}, # text without the table
]
paths = {"A": t, "B": t, "C": None, "D": n}
monkeypatch.setattr(rounds, "rule_items", lambda store, anchors: items)
monkeypatch.setattr(rounds, "fr_text_path", lambda store, k: paths[k])
found = discover_fr(object())
assert [(s.rule_year, s.vintage, s.item_key, s.kind) for s in found] == [
(2017, "final", "A", "fr")
]
# ── money by specialty ───────────────────────────────────────────────
def val(code, year, pay, *, proposed=False, status="A"):
return ValuationRow(
code=code,
description="",
vintage=f"CY{year} {'proposed' if proposed else 'final'}",
year=year,
proposed=proposed,
status=status,
work=None,
pe_nf=None,
pe_f=None,
mp=None,
total_nf=None,
total_f=None,
cf=1.0,
pay_nf=pay,
pay_f=None,
label="",
citation="",
url="",
)
def ps(year, code, spec, n):
return PspsRow(year, code, "00", spec, n, None, None, "d", "K")
def exp(code, year, kind="becomes-payable"):
"""An exposure-calendar row (pfs.code_exposure, #693)."""
return ExposureRow(
code=code,
family="",
effective_date=dt.date(year, 1, 1),
year=year,
kind=kind,
status="A",
prior_status="",
rvu_band=0,
non_fac_total=0.0,
delta_pct=0.0,
item_key="",
p_id=0,
page=0,
anchor_kind="",
correction=False,
note="",
)
def test_money_by_specialty_exact():
payment = [val("99350", 2022, 100.0), val("99350", 2023, 90.0)]
psps = [
ps(2022, "99350", "Family Practice", 10.0),
ps(2022, "99350", "Nurse Practitioner", 30.0),
]
out = money_by_specialty(payment, psps, year=2023)
by = {m.specialty: m for m in out}
fp, np_ = by["FAMILY PRACTICE"], by["NURSE PRACTITIONER"]
assert (fp.delta_per_service, fp.delta_total, fp.share) == (-10.0, -100.0, 0.25)
assert (np_.delta_per_service, np_.delta_total, np_.share) == (-10.0, -300.0, 0.75)
assert fp.delta_pct == pytest.approx(-0.10)
assert fp.new_payment is False
assert fp.proposed_delta_total is None and fp.proposed_delta_pct is None
assert [m.specialty for m in out] == ["NURSE PRACTITIONER", "FAMILY PRACTICE"]
def test_missing_year_gives_none_delta_but_counts_in_share():
payment = [
val("A", 2022, 100.0),
val("A", 2023, 50.0),
val("B", 2023, 10.0), # no prior-year payment
val("C", 2022, 5.0),
val("C", 2023, None, status="B"), # no longer paid
]
psps = [
ps(2021, "A", "Family Practice", 5.0), # superseded by the 2022 row
ps(2022, "A", "Family Practice", 10.0),
ps(2022, "B", "Family Practice", 10.0),
ps(2022, "B", "Cardiology", 20.0),
ps(2022, "C", "Cardiology", 5.0),
ps(2022, "A", "Cardiology", None), # suppressed, skipped
ps(2024, "A", "Family Practice", 999.0), # after year-1, ignored
]
by = {
m.specialty: m
for m in money_by_specialty(
payment, psps, year=2023, exposures=[exp("B", 2023)]
)
}
fp, ca = by["FAMILY PRACTICE"], by["CARDIOLOGY"]
assert fp.services == 20.0 and fp.share == pytest.approx(20 / 45)
assert fp.delta_total == -500.0 # only code A carries a delta
assert fp.delta_per_service == -50.0 # per service of A, not of B
assert fp.delta_pct == pytest.approx(-0.5)
assert fp.new_payment is True # the calendar: B becomes payable in 2023
assert ca.services == 25.0
assert (
ca.delta_total is None and ca.delta_per_service is None and ca.delta_pct is None
)
assert ca.new_payment is True
def test_proposed_vs_final_delta_kept_apart():
payment = [
val("A", 2026, 100.0),
val("A", 2027, 90.0),
val("A", 2027, 80.0, proposed=True),
val("A", 2028, 1.0, proposed=True), # another year's NPRM, ignored
val("B", 2027, 40.0, proposed=True), # proposed but no prior final
]
psps = [
ps(2026, "A", "Family Practice", 10.0),
ps(2026, "B", "Family Practice", 1.0),
]
(m,) = money_by_specialty(payment, psps, year=2027)
assert m.delta_total == -100.0
assert m.proposed_delta_total == -200.0
assert m.proposed_delta_per_service == -20.0
assert m.proposed_delta_pct == pytest.approx(-0.2)
def test_suppressed_lines_make_a_lower_bound_not_a_none():
payment = [val("A", 2022, 100.0), val("A", 2023, 90.0)]
psps = [
PspsRow(2022, "A", "08", "Family Practice", None, None, 5.0, "d", "K", 12.0, 3),
PspsRow(2022, "A", "50", "Nurse Practitioner", 8.0, 1.0, 5.0, "d", "K", 8.0, 0),
PspsRow(2022, "A", "06", "Cardiology", None, None, 5.0, "d", "K", None, 2),
]
by = {m.specialty: m for m in money_by_specialty(payment, psps, year=2023)}
assert set(by) == {"FAMILY PRACTICE", "NURSE PRACTITIONER"}
assert by["FAMILY PRACTICE"].services == 12.0
assert by["FAMILY PRACTICE"].lower_bound is True
assert by["FAMILY PRACTICE"].delta_total == -120.0
assert by["NURSE PRACTITIONER"].lower_bound is False
assert billing_share(psps, year=2023) == {
"FAMILY PRACTICE": 0.6,
"NURSE PRACTITIONER": 0.4,
}
def test_unnamed_specialty_falls_back_to_code_and_empty_inputs():
(m,) = money_by_specialty(
[val("A", 2022, 1.0), val("A", 2023, 2.0)],
[PspsRow(2022, "A", "99", "", 4.0, None, None, "d", "K")],
year=2023,
)
assert m.specialty == "CODE 99" and m.delta_total == 4.0
assert money_by_specialty([], [], year=2023) == []
# zero services: share is 0, never a division error
(z,) = money_by_specialty([], [ps(2022, "A", "Family Practice", 0.0)], year=2023)
assert z.share == 0.0 and z.delta_total is None
def test_billing_share():
psps = [
ps(2022, "A", "Family Practice", 10.0),
ps(2022, "A", "Cardiology", 30.0),
ps(2023, "B", "Family Practice", 60.0),
]
got = billing_share(psps, year=2024)
assert got == {"FAMILY PRACTICE": 0.7, "CARDIOLOGY": 0.3}
assert billing_share(psps, year=2023) == {
"FAMILY PRACTICE": 0.25,
"CARDIOLOGY": 0.75,
}
assert billing_share([], year=2023) == {}
class Link:
def __init__(self, target, since):
self.target, self.since = target, since
class FamLink: # a pfs.code_family-shaped row
def __init__(self, code, since):
self.code, self.since = code, since
def test_new_code_with_no_psps_rows_flips_new_payment_for_every_specialty():
# G0318-shaped: payable in 2023, absent in 2022, never billed in PSPS
payment = [
val("99350", 2022, 100.0),
val("99350", 2023, 90.0),
val("G0318", 2023, 15.0),
]
psps = [
ps(2022, "99350", "Family Practice", 10.0),
ps(2022, "99350", "Cardiology", 5.0),
]
out = money_by_specialty(payment, psps, year=2023, exposures=[exp("G0318", 2023)])
assert [m.new_payment for m in out] == [True, True]
assert all(m.new_codes == ("G0318",) for m in out)
assert all(m.delta_note == "new, amount n/a" for m in out)
assert out[0].delta_total == -100.0 # the existing code's delta is untouched
def test_curated_link_since_year_is_a_new_payment_event():
payment = [val("99350", 2026, 100.0), val("99350", 2027, 100.0)]
psps = [ps(2026, "99350", "Family Practice", 10.0)]
links = [Link("B1", 2027), Link("B2", 2027), Link("G2211", 2026)]
(m,) = money_by_specialty(payment, psps, year=2027, links=links)
assert m.new_payment and m.new_codes == ("B1", "B2")
(m,) = money_by_specialty(payment, psps, year=2027, links=[FamLink("B1", 2027)])
assert m.new_codes == ("B1",)
(m,) = money_by_specialty(payment, psps, year=2027)
assert not m.new_payment and m.new_codes == () and m.delta_note == ""
def test_payment_rows_alone_never_flag_a_member_code():
# R84c: D absent in 2022 and X non-payable in 2022 looked "new" in 2023
# from the payment rows; newness now comes only from the calendar
payment = [
val("B", 2022, 5.0),
val("B", 2023, 5.0),
val("D", 2023, 5.0),
val("X", 2022, 5.0, status="B"),
val("X", 2023, 5.0),
]
psps = [ps(2022, "B", "Family Practice", 1.0)]
(m,) = money_by_specialty(payment, psps, year=2023)
assert m.new_codes == () and not m.new_payment and m.delta_note == ""
def test_member_code_is_new_in_its_becomes_payable_year_only():
payment = [val("B", 2022, 5.0), val("B", 2023, 5.0), val("B", 2024, 5.0)]
psps = [ps(2022, "B", "Family Practice", 1.0)]
exposures = [
exp("X", 2023),
exp("D", 2024), # another year
exp("B", 2023, "revalued"), # other calendar kinds never flag
exp("B", 2023, "status-change"),
exp("E", 2023, "ends"),
exp("T", 2023, "telehealth-listed"),
]
flags = {
y: money_by_specialty(payment, psps, year=y, exposures=exposures)[0]
for y in (2023, 2024, 2025)
}
assert {y: m.new_codes for y, m in flags.items()} == {
2023: ("X",),
2024: ("D",),
2025: (),
}
assert flags[2023].new_payment and flags[2023].delta_note == "new, amount n/a"
assert not flags[2025].new_payment
def test_link_target_flags_only_in_its_since_year_not_when_first_payable():
# G2211-shaped: the calendar has it becoming payable in 2024, link since 2026
payment = [val("99350", y, 100.0) for y in (2023, 2024, 2025, 2026)]
payment += [val("G2211", y, 15.0) for y in (2024, 2025, 2026)]
psps = [ps(2022, "99350", "Family Practice", 10.0)]
links = [Link("G2211", 2026)]
exposures = [exp("G2211", 2024)]
flags = {
y: money_by_specialty(payment, psps, year=y, links=links, exposures=exposures)[
0
].new_codes
for y in (2024, 2025, 2026)
}
assert flags == {2024: (), 2025: (), 2026: ("G2211",)}
# a member code (not a link target) still flags by its calendar row
exposures.append(exp("G0318", 2024))
(m,) = money_by_specialty(
payment, psps, year=2024, links=links, exposures=exposures
)
assert m.new_codes == ("G0318",)
def test_truncated_payment_window_neither_flags_nor_hides_a_code():
# valuation(years=7) shape: predecessors 2016-2022, current codes cut to
# 2021-2027. NEWC is genuinely new in 2021 with the same row count as the
# longest history (the round-3 heuristic hid it); 99350 is not new
payment = [val("99324", y, 50.0) for y in range(2016, 2023)]
payment += [val("99350", y, 100.0) for y in range(2021, 2028)]
payment += [val("NEWC", y, 20.0) for y in range(2021, 2028)]
payment += [val("G0318", y, 15.0) for y in range(2023, 2028)]
psps = [ps(2015, "99350", "Family Practice", 10.0)]
exposures = [exp("NEWC", 2021), exp("G0318", 2023), exp("99324", 2023, "ends")]
new = {
y: money_by_specialty(payment, psps, year=y, exposures=exposures)[0].new_codes
for y in range(2016, 2028)
}
assert {y: c for y, c in new.items() if c} == {2021: ("NEWC",), 2023: ("G0318",)}
def test_read_becomes_payable(con):
write_exposures(
con,
[
exp("G0318", 2023),
exp("G0318", 2023, "telehealth-listed"),
exp("99350", 2027, "telehealth-listed"),
exp("99343", 2023, "ends"),
exp("OTHER", 2020),
],
)
got = stakes.read_becomes_payable(con, ["g0318", "99350", "99343"])
assert [(r.code, r.year, r.kind) for r in got] == [
("G0318", 2023, "becomes-payable")
]
assert stakes.read_becomes_payable(con, []) == []