fix(pfs): since = earliest evidence; derived families get no synonyms (refs F3 F5)
`since` used to take cpt_since unconditionally, so a code first ingested
in the 2019 CPT edition read `since=2019` even when the RVU/FR record
shows it created years earlier (99490, CY2015). Now `since =
min(cpt_since, appeared-event year)` when an appeared event exists —
cpt_since is only the earliest *ingested* edition, not proof the code
didn't exist before that.
Derived (non-hand) families used to get `(key.lower(), name.lower())`
as synonyms, turning ~1,334 one/two-word CPT heading names ("Repair",
"Excision") into chat triggers and costing detect_codes 0.50s/call at
8.5k families. Derived families now get an empty synonym tuple; hand
families are unaffected. Matching a derived family by name is #699's
job (a precompiled alternation).
This commit is contained in:
@@ -377,7 +377,16 @@ def _group_rows(
|
||||
else:
|
||||
role = "base"
|
||||
if c in cpt_since:
|
||||
since = cpt_since[c]
|
||||
appeared = next((ev.year for ev in evs if ev.kind == "appeared"), None)
|
||||
# C13: cpt_since is only the earliest edition *on hand* — it
|
||||
# is not evidence the code didn't exist earlier. When an
|
||||
# RVU/FR "appeared" event is older, that's the real origin
|
||||
# (e.g. 99490 created CY2015, first ingested CPT edition
|
||||
# 2019); when no such event exists, cpt_since is the only
|
||||
# evidence there is.
|
||||
since = (
|
||||
min(cpt_since[c], appeared) if appeared is not None else cpt_since[c]
|
||||
)
|
||||
until = (
|
||||
None
|
||||
if newest_cpt_year is not None and cpt_last_year[c] >= newest_cpt_year
|
||||
@@ -752,11 +761,14 @@ def load_families(con: Any) -> dict[str, Family]:
|
||||
for r in rows:
|
||||
fam = out.get(r.key)
|
||||
codes = (*(fam.codes if fam else ()), r.code)
|
||||
syn = (
|
||||
HAND_FAMILIES[r.key].synonyms
|
||||
if r.key in HAND_FAMILIES
|
||||
else (r.key.lower(), r.name.lower())
|
||||
)
|
||||
# Ruling F3: a derived (non-hand) family gets no synonyms at all
|
||||
# — one/two-word CPT heading names ("Repair", "Excision") would
|
||||
# otherwise become chat triggers across ~1,334 derived families,
|
||||
# and scanning every family's synonyms against every chunk costs
|
||||
# 0.50s/call at ~8.5k families. Matching a derived family by name
|
||||
# is #699's job (a precompiled alternation), not this one. Hand
|
||||
# families keep their curated synonyms.
|
||||
syn = HAND_FAMILIES[r.key].synonyms if r.key in HAND_FAMILIES else ()
|
||||
out[r.key] = Family(r.key, r.name, tuple(sorted(set(codes))), syn)
|
||||
return out
|
||||
|
||||
|
||||
@@ -1025,6 +1025,63 @@ class TestCptPresenceC12:
|
||||
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
|
||||
@@ -1099,6 +1156,59 @@ class TestLoadAndRefresh:
|
||||
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_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 —
|
||||
|
||||
Reference in New Issue
Block a user