Files
stack/tests/pfs/test_codetables.py
kert a44c9982fc fix(pfs): empty CPT parse must not wipe an edition (F4)
write_cpt_edition is delete-then-insert per edition_year; writing an
empty parse would silently erase whatever real data is already on file
for that year. (parse_epub's own guard — raising when _chapter_groups
finds no chapter content at all — landed in the prior commit, since it
shares the same content_root refactor in pfs/cpt_epub.py.)

write_cpt_edition now refuses (raises ValueError) an edition with zero
codes unless force=True. cpt_load.ingest wraps each edition's parse +
write in try/except ValueError, surfaces the failure as
out[year] = {"error": <message>} and continues with the other
requested editions instead of aborting the whole run.
2026-09-09 21:11:39 -04:00

540 lines
17 KiB
Python

"""pfs.codetables — DDL + writers for the code element/event/family tables."""
from __future__ import annotations
import duckdb
import pytest
from pfs.codetables import (
ElementRow,
EventRow,
FamilyRow,
ReviewRow,
cpt_years,
ensure_tables,
is_missing_table_error,
read_all_elements,
read_all_events,
read_cpt_codes,
read_cpt_instructions,
read_cpt_sections,
read_elements,
read_events,
read_families,
write_cpt_edition,
write_elements,
write_events,
write_families,
)
from pfs.cpt_model import (
CptAlternate,
CptCode,
CptCrosswalk,
CptEdition,
CptInstruction,
CptListEntry,
CptReference,
CptSection,
)
@pytest.fixture
def con():
c = duckdb.connect(":memory:")
ensure_tables(c)
yield c
c.close()
def _el(code="99490", value="consent", year=2015):
return ElementRow(
code, year, "activity", value, "", "Consent;", "DE2VH9PD", 1245, 67716, "fr"
)
class TestIsMissingTableError:
def test_true_for_a_real_duckdb_catalog_error(self, con):
with pytest.raises(Exception) as exc_info:
con.execute("SELECT * FROM pfs.nope")
assert is_missing_table_error(exc_info.value)
def test_false_when_only_catalog_present(self):
assert not is_missing_table_error(Exception("Catalog Error: something else"))
def test_false_when_only_does_not_exist_present(self):
# A message that happens to say "does not exist" for an unrelated
# reason (not a Catalog error) must not be treated as "missing
# table, just nothing derived yet" — that would swallow a real bug.
assert not is_missing_table_error(Exception("file does not exist"))
def test_true_when_both_substrings_present(self):
assert is_missing_table_error(
Exception('Catalog Error: Table "pfs.code_family" does not exist')
)
class TestDDL:
def test_idempotent(self, con):
ensure_tables(con)
names = {
r[0]
for r in con.execute(
"SELECT table_name FROM information_schema.tables WHERE table_schema='pfs'"
).fetchall()
}
assert {
"code_element",
"code_element_review",
"code_event",
"code_family",
"cpt_section",
"cpt_code",
"cpt_instruction",
"cpt_reference",
"cpt_crosswalk",
"cpt_list",
"cpt_code_alt",
} <= names
class TestElements:
def test_write_replaces_per_code(self, con):
assert (
write_elements(
con,
"99490",
[_el(), _el(value="24-7-access")],
[ReviewRow("99490", "odd line", "activity", "", "DE2VH9PD", 1246)],
)
== 2
)
assert write_elements(con, "99490", [_el()], []) == 1
rows = read_elements(con, "99490")
assert [r.value for r in rows] == ["consent"]
assert (
con.execute(
"SELECT count(*) FROM pfs.code_element_review WHERE code='99490'"
).fetchone()[0]
== 0
)
def test_other_codes_untouched(self, con):
write_elements(con, "99490", [_el()], [])
write_elements(con, "G0556", [_el(code="G0556")], [])
write_elements(con, "99490", [], [])
assert read_elements(con, "99490") == []
assert len(read_elements(con, "G0556")) == 1
class TestReadAll:
def test_read_all_elements_matches_per_code_reader(self, con):
write_elements(
con, "99490", [_el(value="24-7-access"), _el(value="consent")], []
)
write_elements(con, "G0556", [_el(code="G0556")], [])
out = read_all_elements(con)
assert set(out) == {"99490", "G0556"}
assert out["99490"] == read_elements(con, "99490")
assert out["G0556"] == read_elements(con, "G0556")
def test_read_all_elements_empty(self, con):
assert read_all_elements(con) == {}
def test_read_all_events_matches_per_code_reader(self, con):
rows = [
EventRow(
"99439",
2021,
"replaces",
"G2058",
"99439",
"YBM4IZUS",
1578,
84639,
"fr",
True,
"",
),
EventRow("99439", 2021, "appeared", "", "", "", 0, 0, "rvu", True, ""),
]
write_events(con, "99439", rows)
write_events(
con,
"G2058",
[EventRow("G2058", 2020, "appeared", "", "", "", 0, 0, "rvu", True, "")],
)
out = read_all_events(con)
assert set(out) == {"99439", "G2058"}
assert out["99439"] == read_events(con, "99439")
assert out["G2058"] == read_events(con, "G2058")
def test_read_all_events_empty(self, con):
assert read_all_events(con) == {}
class TestEvents:
def test_roundtrip_sorted_by_year(self, con):
rows = [
EventRow(
"99439",
2021,
"replaces",
"G2058",
"99439",
"YBM4IZUS",
1578,
84639,
"fr",
True,
"",
),
EventRow("99439", 2021, "appeared", "", "", "", 0, 0, "rvu", True, ""),
]
assert write_events(con, "99439", rows) == 2
got = read_events(con, "99439")
assert [e.kind for e in got] == ["appeared", "replaces"]
assert got[1].anchored is True
class TestFamilies:
def test_full_replace(self, con):
write_families(
con,
[
FamilyRow(
"CCM", "Chronic Care Management", "99490", "base", 2015, None, "", 0
)
],
)
write_families(
con,
[
FamilyRow(
"CCM",
"Chronic Care Management",
"99439",
"add-on",
2021,
None,
"YBM4IZUS",
1578,
)
],
)
fams = read_families(con)
assert len(fams) == 1 and fams[0].code == "99439" and fams[0].role == "add-on"
def test_note_round_trips(self, con):
# Ruling C1: `note` is the last field, defaulted, so a caller that
# omits it (as `test_full_replace` above does) still works — and
# a caller that sets it gets it back unchanged.
path_key = (
"Evaluation and Management > Care Management Services > "
"Chronic Care Management Services"
)
write_families(
con,
[
FamilyRow(
"CCM",
"Chronic Care Management",
"99490",
"base",
2015,
None,
"",
0,
path_key,
)
],
)
fams = read_families(con)
assert len(fams) == 1
assert fams[0].note == path_key
def test_note_defaults_to_empty_string(self, con):
write_families(
con,
[
FamilyRow(
"CCM", "Chronic Care Management", "99490", "base", 2015, None, "", 0
)
],
)
assert read_families(con)[0].note == ""
def test_ensure_tables_upgrades_a_pre_existing_table_without_note(self):
# A replica created before `note` existed has an 8-column
# pfs.code_family — ensure_tables must add the column in place,
# not require a drop/recreate (Ruling C1).
con = duckdb.connect(":memory:")
try:
con.execute("CREATE SCHEMA IF NOT EXISTS pfs;")
con.execute(
"CREATE TABLE pfs.code_family ("
"key VARCHAR, name VARCHAR, code VARCHAR, role VARCHAR, "
"since INTEGER, until INTEGER, item_key VARCHAR, p_id INTEGER)"
)
con.execute(
"INSERT INTO pfs.code_family VALUES "
"('CCM', 'Chronic Care Management', '99490', 'base', 2015, NULL, '', 0)"
)
ensure_tables(con)
cols = {
r[0]
for r in con.execute(
"SELECT column_name FROM information_schema.columns "
"WHERE table_schema='pfs' AND table_name='code_family'"
).fetchall()
}
assert "note" in cols
# The pre-existing row survives the upgrade with note = NULL,
# and a fresh write still round-trips.
row = read_families(con)[0]
assert row.code == "99490" and row.note is None
write_families(
con,
[
FamilyRow(
"CCM",
"Chronic Care Management",
"99491",
"base",
2015,
None,
"",
0,
"some path key",
)
],
)
assert read_families(con)[0].note == "some path key"
finally:
con.close()
def _tiny_edition(year=2024, codes=None, alternates=()):
sections = (
CptSection(
sec_id="sec_1",
level=2,
title="Chronic Care Management Services",
path=(
"Evaluation and Management",
"Care Management Services",
"Chronic Care Management Services",
),
code_lo="99490",
code_hi="99491",
guideline="Sample guideline text.",
),
)
if codes is None:
codes = (
CptCode(
code="99490",
sec_id="sec_1",
descriptor="Chronic care management services first 20 minutes.",
stem="Chronic care management services",
elements=("first element;",),
tail="first 20 minutes.",
addon=False,
resequenced=False,
new=False,
revised=False,
telemedicine=False,
parent="",
mod51_exempt=False,
audio_only=False,
fda_pending=False,
pla=False,
category="I",
),
CptCode(
code="99439",
sec_id="sec_1",
descriptor="each additional 20 minutes.",
stem="Chronic care management services",
elements=("first element;",),
tail="each additional 20 minutes.",
addon=True,
resequenced=False,
new=False,
revised=False,
telemedicine=False,
parent="99490",
mod51_exempt=False,
audio_only=False,
fda_pending=False,
pla=False,
category="I",
),
)
instructions = (
CptInstruction(
code="99439",
kind="use-with",
text="(Use 99439 in conjunction with 99490)",
targets=("99490",),
),
)
references = (
CptReference(
code="99490",
kind="cpt-changes",
years=(2015, 2022),
text="CPT Changes: An Insider's View 2015, 2022",
),
)
crosswalks = (
CptCrosswalk(
current_code="99490",
former_code="99091T",
year_deleted="2022",
citations="CPT Changes 2022",
),
)
lists = (CptListEntry(appendix="D", code="99439"),)
return CptEdition(
year=year,
sections=sections,
codes=codes,
instructions=instructions,
references=references,
crosswalks=crosswalks,
lists=lists,
alternates=alternates,
)
class TestCptEditionEmptyGuardF4:
def test_zero_codes_refused_without_force(self, con):
edition = _tiny_edition(codes=())
with pytest.raises(ValueError, match="zero codes"):
write_cpt_edition(con, edition, "GQGTPGYV")
# And it must never have wiped whatever was already on file —
# write a real edition first, then the refused zero-code write
# for the same year must leave it untouched.
write_cpt_edition(con, _tiny_edition(), "GQGTPGYV")
with pytest.raises(ValueError, match="zero codes"):
write_cpt_edition(con, edition, "GQGTPGYV")
assert len(read_cpt_codes(con, 2024)) == 2
def test_zero_codes_written_with_force(self, con):
edition = _tiny_edition(codes=())
counts = write_cpt_edition(con, edition, "GQGTPGYV", force=True)
assert counts["codes"] == 0
class TestCptEdition:
def test_write_returns_counts_per_table(self, con):
counts = write_cpt_edition(con, _tiny_edition(), "GQGTPGYV")
assert counts == {
"sections": 1,
"codes": 2,
"instructions": 1,
"references": 1,
"crosswalks": 1,
"lists": 1,
"alternates": 0,
}
def test_alternates_round_trip(self, con):
alt = CptAlternate(
code="99490", sec_id="sec_guideline", reason="guidelines-reprint"
)
counts = write_cpt_edition(con, _tiny_edition(alternates=(alt,)), "GQGTPGYV")
assert counts["alternates"] == 1
row = con.execute(
"SELECT edition_year, item_key, code, sec_id, reason FROM pfs.cpt_code_alt"
).fetchone()
assert row == (2024, "GQGTPGYV", "99490", "sec_guideline", "guidelines-reprint")
def test_round_trip_reads(self, con):
write_cpt_edition(con, _tiny_edition(), "GQGTPGYV")
secs = read_cpt_sections(con, 2024)
assert len(secs) == 1
assert secs[0].item_key == "GQGTPGYV"
assert secs[0].path == [
"Evaluation and Management",
"Care Management Services",
"Chronic Care Management Services",
]
assert (
secs[0].path_key
== "Evaluation and Management > Care Management Services > Chronic Care Management Services"
)
codes = read_cpt_codes(con, 2024)
assert {c.code for c in codes} == {"99490", "99439"}
addon = next(c for c in codes if c.code == "99439")
assert addon.addon is True
assert addon.parent == "99490"
assert addon.elements == ["first element;"]
instr = read_cpt_instructions(con, 2024)
assert len(instr) == 1
assert instr[0].targets == ["99490"]
assert cpt_years(con) == [2024]
def test_delete_then_insert_per_edition_year(self, con):
write_cpt_edition(con, _tiny_edition(year=2021), "AJM4KF4G")
write_cpt_edition(con, _tiny_edition(year=2024), "GQGTPGYV")
assert cpt_years(con) == [2021, 2024]
write_cpt_edition(
con, _tiny_edition(year=2024), "GQGTPGYV"
) # idempotent re-run
assert cpt_years(con) == [2021, 2024]
assert len(read_cpt_codes(con, 2021)) == 2 # untouched by the 2024 rewrite
assert len(read_cpt_codes(con, 2024)) == 2
def test_duplicate_code_raises_and_writes_nothing(self, con):
# C8: the loader's own last-ditch guard against a resequenced
# placeholder row slipping past the parser's filter.
dup_codes = (
CptCode(
code="99490",
sec_id="sec_1",
descriptor="a",
stem="a",
elements=(),
tail="",
addon=False,
resequenced=False,
new=False,
revised=False,
telemedicine=False,
parent="",
mod51_exempt=False,
audio_only=False,
fda_pending=False,
pla=False,
category="I",
),
CptCode(
code="99490",
sec_id="sec_1",
descriptor="b",
stem="b",
elements=(),
tail="",
addon=False,
resequenced=False,
new=False,
revised=False,
telemedicine=False,
parent="",
mod51_exempt=False,
audio_only=False,
fda_pending=False,
pla=False,
category="I",
),
)
with pytest.raises(ValueError, match="99490"):
write_cpt_edition(con, _tiny_edition(codes=dup_codes), "GQGTPGYV")
assert read_cpt_codes(con, 2024) == []