Files
stack/tests/pfs/test_families.py
kert aa108797a2 fix(pfs): refresh_from never clears FAMILIES mid-swap; fix its stale docstring (refs #691 #692)
Minor from the final fix wave: refresh_from used to FAMILIES.clear()
then .update(merged) — a concurrent detect_codes/family_of read
(the chat runs each turn in a threadpool worker) could observe an
empty registry in the window between those two calls. Now
FAMILIES.update(merged) first, then delete whatever key isn't in the
new merge — every in-between state is old UNION new, never empty.

Also fixes the FAMILIES module docstring, which still said "the chat
sees hand families only" — stale since Task 1 (P49) wired
llm.evidence.warm/_connect to call refresh_from on every replica
(re)open, at API startup and again before every detect_codes call.

New tests: a stale key not in the new merge is dropped by the end of
refresh_from; refresh_from never calls FAMILIES.clear() (verified by
swapping in a dict subclass whose clear() raises).
2026-09-10 13:32:05 -04:00

1556 lines
59 KiB
Python

"""pfs.families — code-family registry + deterministic code detection."""
from __future__ import annotations
import logging
import re
import time
import duckdb
import pytest
from pfs.codetables import (
CptCodeRow,
CptInstructionRow,
CptSectionRow,
ElementRow,
EventRow,
FamilyRow,
ensure_tables,
write_families,
)
from pfs.families import (
FAMILIES,
HAND_FAMILIES,
MAX_FAMILY_EXPAND,
Detection,
Family,
_cpt_edges,
_trie_alternation,
cpt_groups,
derive_families,
detect_codes,
family_of,
find_codes,
load_families,
rebuild_index,
refresh_from,
stem_tokens,
)
# restore_families fixture: tests/conftest.py (shared with tests/llm — I5).
class TestFindCodes:
def test_hcpcs_and_cpt(self):
assert find_codes("Codes G0556 and 99490 apply; see g0557.") == (
"99490",
"G0556",
"G0557",
)
def test_lower_case_codes_are_normalised(self):
assert find_codes("what is g0556?") == ("G0556",)
def test_no_false_hits_on_years_and_fr_pages(self):
# A Federal Register citation is stripped before scanning, so its page
# number (43842 — a well-formed CPT code) never reads as a code.
assert find_codes("91 FR 43842, CY2026") == ()
def test_fr_citation_alone_yields_nothing(self):
assert find_codes("What does 91 FR 43842 say?") == ()
def test_dedupes_and_sorts(self):
assert find_codes("G0558 G0556 G0558") == ("G0556", "G0558")
def test_empty(self):
assert find_codes("") == ()
class TestRegistry:
def test_apcm_family(self):
f = FAMILIES["APCM"]
assert f.codes == ("G0556", "G0557", "G0558")
assert "advanced primary care management" in f.synonyms
assert family_of("G0557") is f
assert family_of("00000") is None
def test_all_families_present(self):
assert set(FAMILIES) == {"ACP", "CCM", "PCM", "TCM", "APCM"}
class TestDetectCodes:
def test_family_name_expands_to_codes(self):
d = detect_codes("What is APCM and how is it valued?")
assert d == Detection(
codes=("G0556", "G0557", "G0558"), families=("APCM",), explicit=()
)
def test_synonym_case_insensitive_word_boundary(self):
d = detect_codes("Tell me about Advanced Primary Care Management.")
assert d.families == ("APCM",)
assert detect_codes("the apcmx code").families == ()
def test_single_member_code_expands_family(self):
d = detect_codes("How much does G0557 pay?")
assert d.explicit == ("G0557",)
assert d.families == ("APCM",)
assert d.codes == ("G0556", "G0557", "G0558")
def test_unregistered_code_stays_alone(self):
d = detect_codes("value of 99213")
assert d == Detection(codes=("99213",), families=(), explicit=("99213",))
def test_multiple_families(self):
d = detect_codes("compare CCM and TCM")
assert d.families == ("CCM", "TCM")
assert d.codes == tuple(sorted(FAMILIES["CCM"].codes + FAMILIES["TCM"].codes))
def test_nothing(self):
assert detect_codes("what did commenters say about telehealth?") == Detection(
(), (), ()
)
class TestTrieAlternation:
"""Fix round 2, item 1: ``_trie_alternation``'s optional-continuation
bug. When a node has exactly one continuation, ``body`` was an
unwrapped (possibly multi-character) string, and a trailing ``?``
applied to it bound to only its *last character* — so any phrase
that is a proper prefix of a longer registered phrase (e.g.
"cardiac catheterization" inside "cardiac catheterization for
congenital heart defects") silently stopped matching at all. The
task-1 report's "verified identical" claim was based on running one
sample question through the live registry, which happened not to
exercise a prefix-of-another-phrase pair — it did not prove
equivalence in general, and was wrong."""
#: A two-way prefix collision ("cardiac catheterization" is a strict
#: prefix of the "... for congenital heart defects" phrase), a
#: three-way share (both "... services" and "... program" continue
#: the same "chronic care management" prefix, so that node has an
#: end marker *and* two children), plus the five phrases the report
#: named as broken live.
PHRASES = (
"cardiac catheterization",
"cardiac catheterization for congenital heart defects",
"chronic care management",
"chronic care management services",
"chronic care management program",
"tricuspid valve",
"tricuspid valve repair",
"adaptive behavior assessments",
"repair and/or reconstruction",
"subcutaneous cardiac rhythm monitor",
)
SENTENCES = (
"Discuss cardiac catheterization for congenital heart defects in infants.",
"A cardiac catheterization was performed without complication.",
"Chronic care management services are billed monthly.",
"Our chronic care management program includes personalized outreach.",
"We provide chronic care management for our patients, full stop.",
"Tricuspid valve repair is a common cardiac procedure.",
"The tricuspid valve was evaluated by echo.",
"Adaptive behavior assessments are conducted for autism evaluations.",
"This code covers repair and/or reconstruction of the tendon.",
"A subcutaneous cardiac rhythm monitor was implanted last week.",
"None of these words appear in this sentence at all.",
) + PHRASES # each phrase alone too
@staticmethod
def _flat_re(phrases) -> re.Pattern[str]:
ordered = sorted(phrases, key=len, reverse=True)
alt = "|".join(re.escape(p) for p in ordered)
return re.compile(rf"\b(?:{alt})\b", re.IGNORECASE)
@staticmethod
def _trie_re(phrases) -> re.Pattern[str]:
alt = _trie_alternation(sorted(phrases))
return re.compile(rf"\b(?:{alt})\b", re.IGNORECASE)
def test_trie_matches_flat_alternation(self):
flat_re = self._flat_re(self.PHRASES)
trie_re = self._trie_re(self.PHRASES)
for s in self.SENTENCES:
lowered = s.lower()
flat = [m.group(0) for m in flat_re.finditer(lowered)]
trie = [m.group(0) for m in trie_re.finditer(lowered)]
assert trie == flat, (s, flat, trie)
# And directly: every prefix-collision phrase, embedded in its
# longer sibling, must still match on its own.
assert self._trie_re(self.PHRASES).search("cardiac catheterization today")
assert self._trie_re(self.PHRASES).search("chronic care management today")
class TestDetectDerivedFamilies:
"""#699: the chat must see derived (``pfs.code_family``) families,
not only the five hand families — by member code always, by name
only when the name reads as a real phrase."""
def test_detect_derived_family_by_member_code(self, restore_families):
restore_families.FAMILIES["SUTURE-REMOVAL"] = Family(
"SUTURE-REMOVAL", "Removal of Sutures", ("15850", "15851"), ()
)
rebuild_index()
d = detect_codes("removal of sutures 15850")
assert "SUTURE-REMOVAL" in d.families
assert d.codes == ("15850", "15851")
def test_derived_family_name_phrase_requires_two_words(self, restore_families):
# Both are CPT-named (cpt=True) — the single-word heading title
# must still never fire regardless of the B1 CPT-name gate.
restore_families.FAMILIES["GENE"] = Family(
"GENE", "GENE", ("81400",), (), cpt=True
)
restore_families.FAMILIES["WOUND-CARE"] = Family(
"WOUND-CARE", "Complex Wound Debridement Services", ("11042",), (), cpt=True
)
rebuild_index()
# The single-word heading name never fires, even though the text
# contains it verbatim.
assert "GENE" not in detect_codes("the GENE panel result").families
# A qualifying (>= 12 chars, >= 2 words) CPT-named derived name does.
d = detect_codes("How are Complex Wound Debridement Services valued?")
assert "WOUND-CARE" in d.families
assert "11042" in d.codes
def test_derived_family_name_requires_cpt_flag(self, restore_families):
# Ruling B1: a stem-derived family's "name" is a raw RVU short
# description, not a real phrase — even a long, multi-word one
# must never become a name trigger unless the family is
# CPT-named (fam.cpt). Member-code detection is unaffected.
restore_families.FAMILIES["STEM-GROUP"] = Family(
"STEM-GROUP", "Complex Remote Monitoring Setup Review", ("99999",), ()
)
rebuild_index()
d = detect_codes("How is Complex Remote Monitoring Setup Review valued?")
assert "STEM-GROUP" not in d.families
assert detect_codes("value of 99999").families == ("STEM-GROUP",)
def test_family_of_is_indexed(self, restore_families):
synthetic = {
f"F{i:04d}": Family(
f"F{i:04d}", f"Synthetic Family {i}", (f"{10000 + i}",), ()
)
for i in range(5000)
}
restore_families.FAMILIES.clear()
restore_families.FAMILIES.update(synthetic)
rebuild_index()
start = time.perf_counter()
for i in range(10000):
code = f"{10000 + (i % 5000)}"
fam = family_of(code)
assert fam is not None and fam.key == f"F{i % 5000:04d}"
elapsed = time.perf_counter() - start
assert elapsed < 0.2, elapsed
def test_wide_family_does_not_expand_codes(self, restore_families):
# Ruling B2: a family with more than MAX_FAMILY_EXPAND (20) codes
# is still named (in `families`/`wide`) but its member codes are
# not folded into `codes` — only explicit codes survive.
wide_codes = tuple(f"{20000 + i}" for i in range(378))
assert len(wide_codes) > MAX_FAMILY_EXPAND
restore_families.FAMILIES["WIDE-FAMILY"] = Family(
"WIDE-FAMILY",
"Comprehensive Ambulatory Service Bundle",
wide_codes,
(),
cpt=True,
)
rebuild_index()
d = detect_codes("How is the Comprehensive Ambulatory Service Bundle valued?")
assert d.explicit == ()
assert d.codes == d.explicit # nothing expanded
assert "WIDE-FAMILY" in d.families
assert "WIDE-FAMILY" in d.wide
def test_narrow_family_still_expands_under_the_cap(self, restore_families):
codes = tuple(f"{30000 + i}" for i in range(MAX_FAMILY_EXPAND))
restore_families.FAMILIES["NARROW-FAMILY"] = Family(
"NARROW-FAMILY",
"Narrow Bundled Service Package",
codes,
(),
cpt=True,
)
rebuild_index()
d = detect_codes("How is the Narrow Bundled Service Package valued?")
assert d.codes == codes
assert "NARROW-FAMILY" in d.families
assert d.wide == ()
def test_hand_families_regression(self):
# Unchanged from TestDetectCodes — the phrase/index refactor must
# not alter a single hand-family detection.
assert detect_codes("What is APCM and how is it valued?") == Detection(
codes=("G0556", "G0557", "G0558"), families=("APCM",), explicit=()
)
d = detect_codes("Tell me about Advanced Primary Care Management.")
assert d.families == ("APCM",)
assert detect_codes("the apcmx code").families == ()
d = detect_codes("How much does G0557 pay?")
assert d.explicit == ("G0557",)
assert d.families == ("APCM",)
assert d.codes == ("G0556", "G0557", "G0558")
assert detect_codes("value of 99213") == Detection(
codes=("99213",), families=(), explicit=("99213",)
)
d = detect_codes("compare CCM and TCM")
assert d.families == ("CCM", "TCM")
assert d.codes == tuple(sorted(FAMILIES["CCM"].codes + FAMILIES["TCM"].codes))
assert detect_codes("what did commenters say about telehealth?") == Detection(
(), (), ()
)
class TestDetectCodesLive:
@pytest.mark.live
def test_detect_codes_under_5ms_on_live_registry(self, restore_families):
from conf import ROOT
replica = ROOT / "data" / "replica" / "aco.ro.duckdb"
if not replica.exists():
pytest.skip(f"no live replica at {replica}")
con = duckdb.connect(str(replica), read_only=True)
try:
n = refresh_from(con)
finally:
con.close()
assert n > 5000, f"expected the full derived registry, got {n} families"
question = (
"Given the history of chronic care management services and "
"99490, how does CY2026 valuation compare to CY2025 for "
"transitional care management, and what changed for advance "
"care planning codes 99497 and 99498 under the proposed rule? "
"Please also note principal care management billing rules."
)[:300]
start = time.perf_counter()
detect_codes(question)
elapsed_ms = (time.perf_counter() - start) * 1000
assert elapsed_ms < 5.0, f"{elapsed_ms:.3f} ms on {n} families"
def _el(code, type_, value, detail=""):
return ElementRow(code, 2025, type_, value, detail, "", "", 0, 0, "fr")
def _ev(code, year, kind, frm="", to=""):
return EventRow(code, year, kind, frm, to, "", 0, 0, "fr", True, "")
class TestStemTokens:
def test_strips_time_and_actor_noise(self):
assert stem_tokens("Chrnc care mgmt staff 1st 20") == frozenset(
{"chrnc", "care", "mgmt"}
)
assert stem_tokens("Chrnc care mgmt phys ea addl") == frozenset(
{"chrnc", "care", "mgmt"}
)
assert stem_tokens("Adv prim care mgmt lvl 1") == frozenset(
{"adv", "prim", "care", "mgmt"}
)
def test_none_description_yields_empty_set(self):
# A real pfs.rvu row can carry a NULL description (corpus parsing
# artifact, e.g. a garbage hcpcs value) — stem_tokens must not
# crash on it (#687 live-run regression: AttributeError on
# `None.lower()` in derive_families).
assert stem_tokens(None) == frozenset()
class TestDerive:
def test_ccm_reproduced_with_predecessor_and_addon_roles(self):
# #687 ruling 16: `not-with` is an exclusion, not a membership
# signal, and was dropped from _LINK_RELATIONS. The `not-with`
# element below is now inert as a merge edge — 99487 joins CCM
# through the stem-Jaccard + shared "comprehensive-care-plan"
# activity rule instead (its stem overlaps 99490's >= 0.5).
elements = {
"99490": [
_el("99490", "actor", "clinical-staff-directed"),
_el("99490", "activity", "comprehensive-care-plan"),
],
"99439": [
_el("99439", "relation", "addon-of", "99490"),
_el("99439", "relation", "not-with", "99487"),
],
"99487": [_el("99487", "activity", "comprehensive-care-plan")],
"99489": [_el("99489", "relation", "addon-of", "99487")],
"99491": [
_el("99491", "actor", "physician-or-qhp-personally"),
_el("99491", "activity", "comprehensive-care-plan"),
],
"99437": [_el("99437", "relation", "addon-of", "99491")],
"G2058": [_el("G2058", "relation", "addon-of", "99490")],
"99497": [_el("99497", "activity", "advance-directive-discussion")],
"99498": [_el("99498", "relation", "addon-of", "99497")],
}
events = {
"G2058": [
_ev("G2058", 2020, "appeared"),
_ev("G2058", 2021, "disappeared"),
_ev("G2058", 2021, "replaced_by", to="99439"),
],
"99439": [
_ev("99439", 2021, "appeared"),
_ev("99439", 2021, "replaces", frm="G2058"),
],
}
descriptions = {
"99490": "Chrnc care mgmt staff 1st 20",
"99439": "Chrnc care mgmt staf ea addl",
"99487": "Cplx chrnc care 1st 60 min",
"99489": "Cplx chrnc care ea addl 30",
"99491": "Chrnc care mgmt phys 1st 30",
"99437": "Chrnc care mgmt phys ea addl",
"G2058": "Ccm add 20min",
"99497": "Advncd care plan 30 min",
"99498": "Advncd care plan addl 30 min",
}
rows = derive_families(elements, events, descriptions)
ccm = {r.code: r for r in rows if r.key == "CCM"}
assert set(ccm) == {
"99490",
"99439",
"99487",
"99489",
"99491",
"99437",
"G2058",
}
assert ccm["99439"].role == "add-on" and ccm["G2058"].role == "predecessor"
assert ccm["G2058"].since == 2020 and ccm["G2058"].until == 2021
assert ccm["99490"].role == "base"
acp = {r.code for r in rows if r.key == "ACP"}
assert acp == {"99497", "99498"}
assert all(
r.name == HAND_FAMILIES[r.key].name for r in rows if r.key in HAND_FAMILIES
)
def test_multi_code_replaced_by_does_not_merge(self):
# #687 ruling 16: a `replaced_by` event naming more than one
# to_codes is ambiguous (a blanket "these codes are replaced by
# ..." sentence spanning several families in the real corpus) and
# must not become a merge edge.
elements = {"11111": [], "22222": []}
events = {"11111": [_ev("11111", 2021, "replaced_by", to="22222 33333")]}
descriptions = {"11111": "Alpha widget", "22222": "Zeta gadget"}
rows = derive_families(elements, events, descriptions)
keys = {r.code: r.key for r in rows}
assert keys["11111"] != keys["22222"]
def test_not_with_relation_does_not_merge(self):
# #687 ruling 16: `not-with` is an explicit exclusion (cannot be
# billed together), the opposite of family membership.
elements = {
"99490": [_el("99490", "relation", "not-with", "G0556")],
"G0556": [],
}
descriptions = {
"99490": "Chrnc care mgmt staff 1st 20",
"G0556": "Adv prim care mgmt lvl 1",
}
rows = derive_families(elements, {}, descriptions)
keys = {r.code: r.key for r in rows}
assert keys["99490"] != keys["G0556"]
def test_bridging_addon_chain_splits_by_hand_family_and_warns(self, caplog):
# #687 ruling 16 guard: a component that reaches into two or more
# hand families is split by a BFS seeded from each hand family's
# own codes, run in HAND_FAMILIES order (ACP, CCM, PCM, TCM,
# APCM). Here "77777" is addon-of both a CCM seed (99490) and an
# ACP seed (99497), joining all three into one component; ACP
# comes first in HAND_FAMILIES, so its BFS reaches — and claims —
# the bridging code before CCM's BFS runs.
elements = {
"99490": [],
"99497": [],
"77777": [
_el("77777", "relation", "addon-of", "99490"),
_el("77777", "relation", "addon-of", "99497"),
],
}
with caplog.at_level(logging.WARNING, logger="pfs.families"):
rows = derive_families(elements, {}, {})
by_code = {r.code: r for r in rows}
assert by_code["99490"].key == "CCM"
assert by_code["99497"].key == "ACP"
assert by_code["77777"].key == "ACP"
assert any("spans hand families" in rec.message for rec in caplog.records), (
caplog.text
)
def test_unknown_family_gets_stem_key(self):
elements = {
"99453": [_el("99453", "activity", "device-data-review")],
"99454": [_el("99454", "activity", "device-data-review")],
}
rows = derive_families(
elements,
{},
{
"99453": "Rem mntr physiol param setup",
"99454": "Rem mntr physiol param dev supl",
},
)
keys = {r.key for r in rows}
assert len(keys) == 1 and next(iter(keys)).startswith("REM-MNTR")
def test_no_activity_elements_never_merges_on_stem_alone(self):
# Both codes have identical stem tokens (jaccard == 1.0, well past
# the 0.5 threshold) but neither has an activity element — an empty
# activity set must not compare equal-and-qualifying, or every
# activity-less code with a common description would merge with
# every other one. Word order differs so a stem-based merge would
# be visible as a shared key even though the codes never touch.
elements = {"11111": [], "22222": []}
descriptions = {"11111": "Foo bar widget", "22222": "Bar widget foo"}
rows = derive_families(elements, {}, descriptions)
keys = {r.code: r.key for r in rows}
assert keys["11111"] != keys["22222"]
def test_disjoint_stem_never_merges(self):
# 10000/10001 share enough stem tokens (and a matching activity) to
# merge; 20000's stem shares no token with either, so it must never
# even be compared, let alone merged.
elements = {
"10000": [_el("10000", "activity", "act-a")],
"10001": [_el("10001", "activity", "act-a")],
"20000": [_el("20000", "activity", "act-a")],
}
descriptions = {
"10000": "Alpha beta gamma",
"10001": "Alpha beta delta",
"20000": "Zeta eta theta",
}
rows = derive_families(elements, {}, descriptions)
keys = {r.code: r.key for r in rows}
assert keys["10000"] == keys["10001"]
assert keys["20000"] != keys["10000"]
def test_non_code_member_is_dropped(self):
# I3: a corpus parsing artifact like '\x1a' must never become a
# family of its own — reject non-code-shaped members before
# deriving anything.
elements = {
"99453": [_el("99453", "activity", "device-data-review")],
"\x1a": [_el("\x1a", "activity", "device-data-review")],
}
descriptions = {
"99453": "Rem mntr physiol param setup",
"\x1a": "corpus artifact",
}
rows = derive_families(elements, {}, descriptions)
assert {r.code for r in rows} == {"99453"}
def test_duplicate_derived_keys_get_representative_suffix(self):
# I3: 627 stem keys in the live table are shared by >= 2 unrelated
# components (e.g. "GENE" x13); a collision must not silently
# merge them under `load_families`' one-row-per-key model.
elements = {
"10001": [_el("10001", "activity", "act-a")],
"10002": [_el("10002", "activity", "act-a")],
"20001": [_el("20001", "activity", "act-b")],
"20002": [_el("20002", "activity", "act-b")],
}
descriptions = {
"10001": "Rare widget",
"10002": "Rare widget",
"20001": "Rare widget",
"20002": "Rare widget",
}
rows = derive_families(elements, {}, descriptions)
keys = {r.code: r.key for r in rows}
assert keys["10001"] == keys["10002"]
assert keys["20001"] == keys["20002"]
assert keys["10001"] != keys["20001"]
both = {keys["10001"], keys["20001"]}
assert "RARE-WIDGET" in both
other = (both - {"RARE-WIDGET"}).pop()
assert other.startswith("RARE-WIDGET-")
def test_none_description_does_not_raise_and_stands_alone(self):
# #687 live-run regression: a code whose `descriptions` mapping
# holds an explicit None (a real NULL pfs.rvu description, not a
# missing key — dict.get(c, "") only substitutes its default when
# c is absent) must not crash derive_families, and with nothing
# else linking it to another code it lands in its own family.
elements = {"99999": [_el("99999", "activity", "act-a")]}
descriptions = {"99999": None}
rows = derive_families(elements, {}, descriptions)
assert {r.code for r in rows} == {"99999"}
assert rows[0].key == "99999"
assert rows[0].name == "99999"
def test_many_distinct_stems_is_fast(self):
# ~2,000 codes whose descriptions share no stem token with any
# other code's. The token-bucketed merge must not degrade to the
# O(n^2) all-pairs scan this guards against; kept generous (< 2s)
# so CI timing noise doesn't make it flaky.
def _word(i: int) -> str:
letters = []
n = i + 1
while n:
n, r = divmod(n - 1, 26)
letters.append(chr(97 + r))
return "".join(reversed(letters))
n = 2000
elements = {}
descriptions = {}
for i in range(n):
code = f"{10000 + i}"
elements[code] = [_el(code, "activity", "act")]
descriptions[code] = f"stem{_word(i)} term{_word(i + n)}"
start = time.perf_counter()
rows = derive_families(elements, {}, descriptions)
elapsed = time.perf_counter() - start
assert len(rows) == n
assert elapsed < 2.0
def _cpt_section(sec_id, path, guideline=""):
path = tuple(path)
return CptSectionRow(
edition_year=2024,
item_key="ITEM0001",
sec_id=sec_id,
level=len(path),
title=path[-1],
path=list(path),
path_key=" > ".join(path),
code_lo="",
code_hi="",
guideline=guideline,
)
def _cpt_code(code, sec_id, *, year=2024, parent="", addon=False, descriptor=""):
return CptCodeRow(
edition_year=year,
item_key="ITEM0001",
code=code,
sec_id=sec_id,
category="I",
descriptor=descriptor,
stem="",
elements=[],
tail="",
parent=parent,
addon=addon,
resequenced=False,
new=False,
revised=False,
telemedicine=False,
mod51_exempt=False,
audio_only=False,
fda_pending=False,
pla=False,
)
class TestCptGroups:
def test_lowest_heading_wins_not_the_parent(self):
# Task 4 ruling: "Care Management Services" (h1) groups >= 2 codes
# too (6, rolled up), but cpt_groups must stop at the h2 leaf
# heading each code actually sits under, not walk up past it.
sections = [
_cpt_section(
"sec_ccm",
(
"Evaluation and Management",
"Care Management Services",
"Chronic Care Management Services",
),
),
_cpt_section(
"sec_ccx",
(
"Evaluation and Management",
"Care Management Services",
"Complex Chronic Care Management Services",
),
),
]
codes = [
_cpt_code("99490", "sec_ccm"),
_cpt_code("99439", "sec_ccm"),
_cpt_code("99491", "sec_ccm"),
_cpt_code("99437", "sec_ccm"),
_cpt_code("99487", "sec_ccx"),
_cpt_code("99489", "sec_ccx"),
]
groups = cpt_groups(codes, sections)
assert groups["99490"][:3] == (
"CHRONIC-CARE-MANAGEMENT-SERVICES",
"Chronic Care Management Services",
"Evaluation and Management > Care Management Services > "
"Chronic Care Management Services",
)
assert set(groups["99490"][3]) == {"99490", "99439", "99491", "99437"}
assert groups["99487"][:3] == (
"COMPLEX-CHRONIC-CARE-MANAGEMENT-SERVICES",
"Complex Chronic Care Management Services",
"Evaluation and Management > Care Management Services > "
"Complex Chronic Care Management Services",
)
assert set(groups["99487"][3]) == {"99487", "99489"}
# Never the h1 "Care Management Services" rollup:
assert all("Care Management Services)" not in g[1] for g in groups.values())
assert all(g[1] != "Care Management Services" for g in groups.values())
def test_singleton_leaf_walks_up_to_the_grouping_parent(self):
sections = [
_cpt_section("sec_parent", ("Chapter X", "Section A")),
_cpt_section("sec_leaf1", ("Chapter X", "Section A", "Leaf One")),
_cpt_section("sec_leaf2", ("Chapter X", "Section A", "Leaf Two")),
]
codes = [
_cpt_code("10001", "sec_leaf1"),
_cpt_code("10002", "sec_leaf2"),
]
groups = cpt_groups(codes, sections)
assert groups["10001"][:3] == (
"SECTION-A",
"Section A",
"Chapter X > Section A",
)
assert groups["10002"][:3] == groups["10001"][:3]
assert set(groups["10001"][3]) == {"10001", "10002"}
def test_same_title_under_different_parents_disambiguated(self):
sections = [
_cpt_section(
"sec_office",
("Chapter A", "Office Visits", "New or Established Patient"),
),
_cpt_section(
"sec_home",
("Chapter B", "Home Visits", "New or Established Patient"),
),
]
codes = [
_cpt_code("20001", "sec_office"),
_cpt_code("20002", "sec_office"),
_cpt_code("20003", "sec_home"),
_cpt_code("20004", "sec_home"),
]
groups = cpt_groups(codes, sections)
# "Chapter A ..." sorts first, so it keeps the bare slug; the
# colliding "Chapter B ..." heading is disambiguated by its
# parent title (deterministic — sorted path order, first occupant
# wins the bare key).
assert groups["20001"][0] == "NEW-OR-ESTABLISHED-PATIENT"
assert groups["20003"][0] == "NEW-OR-ESTABLISHED-PATIENT@HOME-VISITS"
assert groups["20001"][0] != groups["20003"][0]
class TestDeriveCpt:
def test_ccm_hand_family_wins_merged_component_note_is_own_heading(self):
# The CCM and Complex CCM CPT headings are two separate multi-code
# groups; slice-1's stem+activity edge still joins 99487 into the
# same component as before (unchanged fixture from
# test_ccm_reproduced_with_predecessor_and_addon_roles), so the
# merged component intersects the CCM hand family and the hand
# key/name win for every code in it — but each code's `note`
# stays its own CPT heading's path, not the family's.
sections = [
_cpt_section(
"sec_ccm",
(
"Evaluation and Management",
"Care Management Services",
"Chronic Care Management Services",
),
),
_cpt_section(
"sec_ccx",
(
"Evaluation and Management",
"Care Management Services",
"Complex Chronic Care Management Services",
),
),
]
cpt_codes = [
_cpt_code("99490", "sec_ccm"),
_cpt_code("99439", "sec_ccm", parent="99490", addon=True),
_cpt_code("99491", "sec_ccm"),
_cpt_code("99437", "sec_ccm", parent="99491", addon=True),
_cpt_code("99487", "sec_ccx"),
_cpt_code("99489", "sec_ccx", parent="99487", addon=True),
]
elements = {
"99490": [_el("99490", "activity", "comprehensive-care-plan")],
"99487": [_el("99487", "activity", "comprehensive-care-plan")],
"99491": [_el("99491", "activity", "comprehensive-care-plan")],
}
descriptions = {
"99490": "Chrnc care mgmt staff 1st 20",
"99439": "Chrnc care mgmt staf ea addl",
"99487": "Cplx chrnc care 1st 60 min",
"99489": "Cplx chrnc care ea addl 30",
"99491": "Chrnc care mgmt phys 1st 30",
"99437": "Chrnc care mgmt phys ea addl",
}
rows = derive_families(
elements,
{},
descriptions,
cpt_codes=cpt_codes,
cpt_sections=sections,
)
by_code = {r.code: r for r in rows}
assert {c for c in by_code} == {
"99490",
"99439",
"99487",
"99489",
"99491",
"99437",
}
assert all(r.key == "CCM" for r in by_code.values())
assert all(r.name == HAND_FAMILIES["CCM"].name for r in by_code.values())
ccm_path_key = (
"Evaluation and Management > Care Management Services > "
"Chronic Care Management Services"
)
ccx_path_key = (
"Evaluation and Management > Care Management Services > "
"Complex Chronic Care Management Services"
)
for c in ("99490", "99439", "99491", "99437"):
assert by_code[c].note == ccm_path_key
for c in ("99487", "99489"):
assert by_code[c].note == ccx_path_key
# cpt_code.addon and parent both feed the add-on role and its edge.
assert by_code["99439"].role == "add-on"
assert by_code["99489"].role == "add-on"
# Book-derived since: earliest edition_year in cpt_codes.
assert by_code["99490"].since == 2024
def test_heading_with_no_elements_still_becomes_a_family(self):
sections = [
_cpt_section(
"sec_rpm",
(
"Medicine",
"Remote Physiologic Monitoring Treatment Management Services",
),
)
]
cpt_codes = [
_cpt_code("99457", "sec_rpm", year=2024),
_cpt_code("99458", "sec_rpm", year=2024, parent="99457", addon=True),
]
rows = derive_families({}, {}, {}, cpt_codes=cpt_codes, cpt_sections=sections)
by_code = {r.code: r for r in rows}
assert set(by_code) == {"99457", "99458"}
assert all(
r.key == "REMOTE-PHYSIOLOGIC-MONITORING-TREATMENT-MANAGEMENT-SERVICES"
for r in by_code.values()
)
assert all(r.since == 2024 for r in by_code.values())
assert all(r.until is None for r in by_code.values())
assert by_code["99458"].role == "add-on"
def test_singleton_leaf_grouped_at_parent_heading(self):
sections = [
_cpt_section("sec_parent", ("Chapter X", "Section A")),
_cpt_section("sec_leaf1", ("Chapter X", "Section A", "Leaf One")),
_cpt_section("sec_leaf2", ("Chapter X", "Section A", "Leaf Two")),
]
cpt_codes = [
_cpt_code("30001", "sec_leaf1"),
_cpt_code("30002", "sec_leaf2"),
]
rows = derive_families({}, {}, {}, cpt_codes=cpt_codes, cpt_sections=sections)
keys = {r.code: r.key for r in rows}
assert keys["30001"] == keys["30002"] == "SECTION-A"
def test_same_title_headings_get_distinct_keys(self):
sections = [
_cpt_section(
"sec_office",
("Chapter A", "Office Visits", "New or Established Patient"),
),
_cpt_section(
"sec_home",
("Chapter B", "Home Visits", "New or Established Patient"),
),
]
cpt_codes = [
_cpt_code("40001", "sec_office"),
_cpt_code("40002", "sec_office"),
_cpt_code("40003", "sec_home"),
_cpt_code("40004", "sec_home"),
]
rows = derive_families({}, {}, {}, cpt_codes=cpt_codes, cpt_sections=sections)
keys = {r.code: r.key for r in rows}
assert keys["40001"] == keys["40002"]
assert keys["40003"] == keys["40004"]
assert keys["40001"] != keys["40003"]
def test_use_with_instruction_and_parent_join_codes_with_no_heading(self):
# No cpt_sections at all — these codes have no CPT heading of
# their own, so the only thing that can join them is the
# use-with instruction (add-on -> primary) and the parent field
# (semicolon-rule child -> parent).
cpt_codes = [
_cpt_code("50000", "sec_missing", descriptor="Alpha widget"),
_cpt_code("50001", "sec_missing", descriptor="Beta gadget"),
_cpt_code(
"50002", "sec_missing", parent="50000", descriptor="Gamma sprocket"
),
]
instructions = [
CptInstructionRow(
edition_year=2024,
item_key="ITEM0001",
code="50001",
kind="use-with",
text="(Use 50001 in conjunction with 50000)",
targets=["50000"],
)
]
rows = derive_families(
{},
{},
{
"50000": "Alpha widget",
"50001": "Beta gadget",
"50002": "Gamma sprocket",
},
cpt_codes=cpt_codes,
cpt_sections=[],
cpt_instructions=instructions,
)
keys = {r.code: r.key for r in rows}
assert keys["50000"] == keys["50001"] == keys["50002"]
assert {r.note for r in rows if r.code in ("50000", "50001", "50002")} == {""}
def test_not_with_instruction_never_joins(self):
cpt_codes = [
_cpt_code("60000", "sec_missing", descriptor="Zeta thing"),
_cpt_code("60001", "sec_missing", descriptor="Eta gizmo"),
]
instructions = [
CptInstructionRow(
edition_year=2024,
item_key="ITEM0001",
code="60001",
kind="not-with-time",
text="(Do not report 60001 for service time reported with 60000)",
targets=["60000"],
)
]
rows = derive_families(
{},
{},
{"60000": "Zeta thing", "60001": "Eta gizmo"},
cpt_codes=cpt_codes,
cpt_sections=[],
cpt_instructions=instructions,
)
keys = {r.code: r.key for r in rows}
assert keys["60000"] != keys["60001"]
def test_hcpcs_code_keeps_slice1_derivation(self):
# A HCPCS G-code absent from pfs.cpt_code joins a CPT family only
# through a slice-1 edge (here: a replaced_by event), never
# through cpt_groups.
sections = [
_cpt_section(
"sec_ccm",
(
"Evaluation and Management",
"Care Management Services",
"Chronic Care Management Services",
),
)
]
cpt_codes = [
_cpt_code("99490", "sec_ccm"),
_cpt_code("99439", "sec_ccm", parent="99490", addon=True),
]
events = {
"G2058": [
_ev("G2058", 2020, "appeared"),
_ev("G2058", 2021, "disappeared"),
_ev("G2058", 2021, "replaced_by", to="99439"),
],
"99439": [_ev("99439", 2021, "replaces", frm="G2058")],
}
rows = derive_families(
{}, events, {}, cpt_codes=cpt_codes, cpt_sections=sections
)
by_code = {r.code: r for r in rows}
assert by_code["G2058"].key == by_code["99490"].key == "CCM"
assert by_code["G2058"].note == ""
assert by_code["G2058"].role == "predecessor"
assert by_code["G2058"].since == 2020 and by_code["G2058"].until == 2021
class TestCptEdgesHelper:
def test_skips_only_edges_between_distinct_headings(self):
# #687 controller review (Ruling C11), tested against the helper
# in isolation: an edge is skipped only when BOTH endpoints
# already resolve to a heading and those headings differ.
groups_by_code = {
"70001": (
"HEADING-A",
"Heading A",
"Chapter > Heading A",
("70001", "70002"),
),
"70002": (
"HEADING-A",
"Heading A",
"Chapter > Heading A",
("70001", "70002"),
),
"70003": (
"HEADING-B",
"Heading B",
"Chapter > Heading B",
("70003", "70004"),
),
}
unioned = []
def fake_union(a, b):
unioned.append((a, b))
instructions = [
CptInstructionRow(
edition_year=2024,
item_key="I",
code="70001",
kind="use-with",
text="",
targets=["70003"], # both headed, distinct -> skipped
),
CptInstructionRow(
edition_year=2024,
item_key="I",
code="70001",
kind="use-with",
text="",
targets=["70005"], # 70005 unheaded -> unioned
),
]
_cpt_edges(fake_union, groups_by_code, instructions, {}, {})
assert unioned == [("70001", "70005")]
class TestCptEdgeGuardC11:
def _two_headings(self):
sections = [
_cpt_section("sec_a", ("Chapter", "Heading A")),
_cpt_section("sec_b", ("Chapter", "Heading B")),
]
cpt_codes = [
_cpt_code("70001", "sec_a"),
_cpt_code("70002", "sec_a"),
_cpt_code("70003", "sec_b"),
_cpt_code("70004", "sec_b"),
]
return sections, cpt_codes
def test_use_with_between_two_headed_codes_does_not_bridge(self):
sections, cpt_codes = self._two_headings()
instructions = [
CptInstructionRow(
edition_year=2024,
item_key="ITEM0001",
code="70001",
kind="use-with",
text="(Use 70001 in conjunction with 70003)",
targets=["70003"],
)
]
rows = derive_families(
{},
{},
{},
cpt_codes=cpt_codes,
cpt_sections=sections,
cpt_instructions=instructions,
)
keys = {r.code: r.key for r in rows}
assert keys["70001"] == keys["70002"]
assert keys["70003"] == keys["70004"]
assert keys["70001"] != keys["70003"]
def test_use_with_from_unheaded_code_still_joins_the_headed_family(self):
sections, cpt_codes = self._two_headings()
# 70005 sits alone under its own chapter — no sibling anywhere it
# rolls up to, so it never qualifies for a heading of its own.
sections = sections + [_cpt_section("sec_x", ("Solo Chapter", "Heading X"))]
cpt_codes = cpt_codes + [_cpt_code("70005", "sec_x")]
instructions = [
CptInstructionRow(
edition_year=2024,
item_key="ITEM0001",
code="70005",
kind="use-with",
text="(Use 70005 in conjunction with 70001)",
targets=["70001"],
)
]
rows = derive_families(
{},
{},
{},
cpt_codes=cpt_codes,
cpt_sections=sections,
cpt_instructions=instructions,
)
keys = {r.code: r.key for r in rows}
assert keys["70005"] == keys["70001"] == keys["70002"]
def test_parent_between_two_headed_codes_does_not_bridge(self):
sections, cpt_codes = self._two_headings()
# 70003's `parent` names a code in a DIFFERENT heading — the
# guard must hold even though a real semicolon-rule parent is
# always within the same heading in practice.
cpt_codes = [
c if c.code != "70003" else _cpt_code("70003", "sec_b", parent="70001")
for c in cpt_codes
]
rows = derive_families({}, {}, {}, cpt_codes=cpt_codes, cpt_sections=sections)
keys = {r.code: r.key for r in rows}
assert keys["70003"] == keys["70004"]
assert keys["70003"] != keys["70001"]
def test_addon_of_between_two_headed_codes_does_not_bridge(self):
sections, cpt_codes = self._two_headings()
elements = {"70003": [_el("70003", "relation", "addon-of", "70001")]}
rows = derive_families(
elements, {}, {}, cpt_codes=cpt_codes, cpt_sections=sections
)
keys = {r.code: r.key for r in rows}
assert keys["70003"] == keys["70004"]
assert keys["70003"] != keys["70001"]
def test_addon_of_from_unheaded_code_still_joins(self):
sections, cpt_codes = self._two_headings()
elements = {"77000": [_el("77000", "relation", "addon-of", "70001")]}
rows = derive_families(
elements, {}, {}, cpt_codes=cpt_codes, cpt_sections=sections
)
keys = {r.code: r.key for r in rows}
assert keys["77000"] == keys["70001"]
def test_note_value_is_unaffected_by_the_guard(self):
# The guard only skips a union edge; it must not touch a code's
# own note (still its own heading's path_key).
sections, cpt_codes = self._two_headings()
instructions = [
CptInstructionRow(
edition_year=2024,
item_key="ITEM0001",
code="70001",
kind="use-with",
text="(Use 70001 in conjunction with 70003)",
targets=["70003"],
)
]
rows = derive_families(
{},
{},
{},
cpt_codes=cpt_codes,
cpt_sections=sections,
cpt_instructions=instructions,
)
by_code = {r.code: r for r in rows}
assert by_code["70001"].note == "Chapter > Heading A"
assert by_code["70003"].note == "Chapter > Heading B"
class TestCptPresenceC12:
def test_code_absent_from_newest_edition_gets_since_and_until_from_all_editions(
self,
):
sections = [_cpt_section("sec_a", ("Chapter", "Heading A"))]
cpt_codes = [
_cpt_code("90001", "sec_a", year=2024),
_cpt_code("90002", "sec_a", year=2024),
]
cpt_presence = {
"90001": (2019, 2021, 2022, 2024),
"90002": (2019, 2021, 2022, 2024),
"80001": (2019, 2020, 2021, 2022), # gone by 2024, not in cpt_codes
}
rows = derive_families(
{},
{},
{},
cpt_codes=cpt_codes,
cpt_sections=sections,
cpt_presence=cpt_presence,
)
by_code = {r.code: r for r in rows}
assert "80001" in by_code
assert by_code["80001"].since == 2019
assert by_code["80001"].until == 2023
def test_code_new_in_newest_edition_has_no_until(self):
sections = [_cpt_section("sec_a", ("Chapter", "Heading A"))]
cpt_codes = [
_cpt_code("90001", "sec_a", year=2024),
_cpt_code("90003", "sec_a", year=2024),
]
cpt_presence = {
"90001": (2019, 2021, 2022, 2024),
"90003": (2024,),
}
rows = derive_families(
{},
{},
{},
cpt_codes=cpt_codes,
cpt_sections=sections,
cpt_presence=cpt_presence,
)
by_code = {r.code: r for r in rows}
assert by_code["90001"].since == 2019
assert by_code["90003"].since == 2024
assert by_code["90003"].until is None
def test_without_cpt_presence_falls_back_to_cpt_codes_alone(self):
# Pre-C12 behavior preserved when the caller doesn't have a
# multi-edition presence map to give.
sections = [_cpt_section("sec_a", ("Chapter", "Heading A"))]
cpt_codes = [
_cpt_code("90001", "sec_a", year=2024),
_cpt_code("90002", "sec_a", year=2024),
]
rows = derive_families({}, {}, {}, cpt_codes=cpt_codes, cpt_sections=sections)
by_code = {r.code: r for r in rows}
assert by_code["90001"].since == 2024
assert by_code["90001"].until is None
class TestSinceEarliestEvidenceC13:
"""F5 / Ruling C13: ``since`` = min(cpt_since, appeared-event year)
when an ``appeared`` event exists — cpt_since is only the earliest
*ingested* edition, not evidence the code didn't exist earlier."""
def test_since_is_min_of_cpt_presence_and_appeared_event(self):
# 99490 was created CY2015 but the oldest CPT edition on hand is
# 2019 — since must read 2015, not 2019.
sections = [_cpt_section("sec_a", ("Chapter", "Heading A"))]
cpt_codes = [_cpt_code("99490", "sec_a", year=2024)]
cpt_presence = {"99490": (2019, 2021, 2022, 2024)}
events = {"99490": [_ev("99490", 2015, "appeared")]}
rows = derive_families(
{},
events,
{},
cpt_codes=cpt_codes,
cpt_sections=sections,
cpt_presence=cpt_presence,
)
by_code = {r.code: r for r in rows}
assert by_code["99490"].since == 2015
def test_since_falls_back_to_cpt_presence_when_no_appeared_event(self):
sections = [_cpt_section("sec_a", ("Chapter", "Heading A"))]
cpt_codes = [_cpt_code("90001", "sec_a", year=2024)]
cpt_presence = {"90001": (2019, 2021, 2022, 2024)}
rows = derive_families(
{},
{},
{},
cpt_codes=cpt_codes,
cpt_sections=sections,
cpt_presence=cpt_presence,
)
by_code = {r.code: r for r in rows}
assert by_code["90001"].since == 2019
def test_since_keeps_cpt_presence_when_appeared_event_is_later(self):
# An appeared event newer than the CPT evidence must not win —
# min(), not "prefer the event".
sections = [_cpt_section("sec_a", ("Chapter", "Heading A"))]
cpt_codes = [_cpt_code("90001", "sec_a", year=2024)]
cpt_presence = {"90001": (2019, 2021, 2022, 2024)}
events = {"90001": [_ev("90001", 2021, "appeared")]}
rows = derive_families(
{},
events,
{},
cpt_codes=cpt_codes,
cpt_sections=sections,
cpt_presence=cpt_presence,
)
by_code = {r.code: r for r in rows}
assert by_code["90001"].since == 2019
class TestLoadAndRefresh:
def test_load_and_refresh_in_place(self, restore_families):
mod = restore_families
con = duckdb.connect(":memory:")
try:
ensure_tables(con)
assert load_families(con) == {}
before = dict(mod.FAMILIES)
assert refresh_from(con) == 0 and mod.FAMILIES == before
write_families(
con,
[
FamilyRow(
"CCM",
"Chronic Care Management",
"99490",
"base",
None,
None,
"",
0,
),
FamilyRow(
"CCM",
"Chronic Care Management",
"G2058",
"predecessor",
2020,
2021,
"",
0,
),
],
)
n = refresh_from(con)
# Ruling 17: a hand key the table reaches (CCM) is replaced by
# the derived (superset) codes; every other hand key survives
# untouched since `refresh_from` seeds from HAND_FAMILIES
# first instead of clearing the registry outright.
assert n == len(HAND_FAMILIES)
assert set(mod.FAMILIES) >= set(HAND_FAMILIES)
assert set(mod.FAMILIES["CCM"].codes) == {"99490", "G2058"}
assert mod.FAMILIES["CCM"].name == HAND_FAMILIES["CCM"].name
finally:
con.close()
def test_stale_key_not_in_the_new_merge_is_dropped(self, restore_families):
# Final fix wave minor: a key that was in FAMILIES before this
# refresh (a previous derivation run's family, say) but isn't in
# the new merge must still end up gone — update-then-delete must
# reach the same end state a clear()-then-update would.
mod = restore_families
mod.FAMILIES["STALE-KEY"] = Family("STALE-KEY", "Stale", ("Z9999",), ())
con = duckdb.connect(":memory:")
try:
ensure_tables(con)
write_families(
con,
[
FamilyRow(
"CCM",
"Chronic Care Management",
"99490",
"base",
None,
None,
"",
0,
)
],
)
refresh_from(con)
assert "STALE-KEY" not in mod.FAMILIES
finally:
con.close()
def test_refresh_never_clears_the_registry(self, restore_families):
# Final fix wave minor: refresh_from must never leave a window
# where FAMILIES is empty — verified by swapping in a dict
# subclass whose clear() raises (the old clear()-then-update()
# implementation this replaces would trip it immediately).
class _NoClearDict(dict):
def clear(self) -> None:
raise AssertionError("refresh_from must not clear() FAMILIES")
mod = restore_families
original = mod.FAMILIES
mod.FAMILIES = _NoClearDict(original)
con = duckdb.connect(":memory:")
try:
ensure_tables(con)
write_families(
con,
[
FamilyRow(
"CCM",
"Chronic Care Management",
"99490",
"base",
None,
None,
"",
0,
)
],
)
refresh_from(con) # must not raise
assert "CCM" in mod.FAMILIES
finally:
con.close()
# Swap the plain dict back before restore_families' own
# teardown (.clear()/.update()) runs on mod.FAMILIES.
mod.FAMILIES = original
def test_hand_key_absent_from_table_survives(self, restore_families):
# Ruling 17: only CCM appears in the derived table; the other four
# hand families (ACP, PCM, TCM, APCM) must come through unchanged.
mod = restore_families
con = duckdb.connect(":memory:")
try:
ensure_tables(con)
write_families(
con,
[
FamilyRow(
"CCM",
"Chronic Care Management",
"99490",
"base",
None,
None,
"",
0,
)
],
)
refresh_from(con)
for key in ("ACP", "PCM", "TCM", "APCM"):
assert mod.FAMILIES[key] == HAND_FAMILIES[key]
finally:
con.close()
def test_derived_family_gets_empty_synonyms(self, restore_families):
# F3: a derived (non-hand) family must not become a chat trigger
# off its own one/two-word name — matching by name is #699's job.
mod = restore_families
con = duckdb.connect(":memory:")
try:
ensure_tables(con)
write_families(
con,
[
FamilyRow(
"AORTA-REPAIR",
"Repair",
"99999",
"base",
None,
None,
"",
0,
)
],
)
refresh_from(con)
assert mod.FAMILIES["AORTA-REPAIR"].synonyms == ()
assert detect_codes("repair of the aorta").families == ()
finally:
con.close()
def test_hand_family_keeps_its_synonyms_after_refresh(self, restore_families):
mod = restore_families
con = duckdb.connect(":memory:")
try:
ensure_tables(con)
write_families(
con,
[
FamilyRow(
"CCM",
"Chronic Care Management",
"99490",
"base",
None,
None,
"",
0,
)
],
)
refresh_from(con)
assert mod.FAMILIES["CCM"].synonyms == HAND_FAMILIES["CCM"].synonyms
finally:
con.close()
def test_load_families_sets_cpt_flag_from_note(self):
# Ruling B1: a non-empty `note` (the code's own CPT heading path)
# on any member row marks the whole derived family CPT-named;
# a family with no member carrying a note stays cpt=False.
con = duckdb.connect(":memory:")
try:
ensure_tables(con)
write_families(
con,
[
FamilyRow(
"CHRONIC-CARE-MANAGEMENT-SERVICES",
"Chronic Care Management Services",
"99490",
"base",
None,
None,
"",
0,
"Evaluation and Management > Care Management "
"Services > Chronic Care Management Services",
),
FamilyRow(
"STEM-GROUP",
"Complex Remote Monitoring Setup Review",
"99999",
"base",
None,
None,
"",
0,
"",
),
],
)
fams = load_families(con)
assert fams["CHRONIC-CARE-MANAGEMENT-SERVICES"].cpt is True
assert fams["STEM-GROUP"].cpt is False
finally:
con.close()
def test_schema_present_table_absent_returns_empty(self):
# pfs schema created (e.g. by an earlier ensure_tables call for a
# sibling table) but pfs.code_family itself never materialized —
# an old replica shape, not a real error.
con = duckdb.connect(":memory:")
try:
con.execute("CREATE SCHEMA IF NOT EXISTS pfs;")
assert load_families(con) == {}
finally:
con.close()
def test_wrong_shape_table_raises(self):
# A pfs.code_family table exists but not in the expected shape —
# this is a real bug, not an absent table, and must not be
# swallowed.
con = duckdb.connect(":memory:")
try:
con.execute("CREATE SCHEMA IF NOT EXISTS pfs;")
con.execute("CREATE TABLE pfs.code_family (only_col VARCHAR);")
with pytest.raises(Exception):
load_families(con)
finally:
con.close()