Some checks failed
CI / lint (push) Successful in 53s
CI / notebooks-smoke (push) Successful in 2m23s
Deploy / changes (push) Successful in 15s
CI / test (push) Failing after 4m54s
Infra CI / notebooks (push) Successful in 1m32s
Infra CI / zotero (push) Successful in 20s
Infra CI / api (push) Successful in 1m29s
Infra CI / docs (push) Successful in 1m35s
Infra CI / llm (push) Successful in 1m20s
Infra CI / mc (push) Successful in 21s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Infra CI / bcda (push) Successful in 3m17s
Deploy / api (push) Successful in 1m24s
Deploy / llm (push) Successful in 1m40s
Deploy / mc (push) Has been skipped
Deploy / bcda (push) Successful in 2m8s
Deploy / report (push) Successful in 22s
A family code with deleted + cpt_panel events at one FR paragraph gets a cpt_panel row 'The CPT Editorial Panel deleted <code>, effective CY<year>.' (extractor lineage) where a plain sentence reads as the Panel deleting it and the paragraph writes the year; a model Panel row from the same rule that tells a lineage-deleted code as merged, revised or retained is dropped. IOM/MLN rows cited from FR paragraphs count only from final rules; a provision whose removal the rule finalizes becomes a row saying what was removed and when, citing the finalizing paragraph (ref_p_id = the citing one). Only title-42 parts outside the cited chapters are FR typos.
1658 lines
59 KiB
Python
1658 lines
59 KiB
Python
"""pfs.origin — origin and formation rows (dossier task 6).
|
|
|
|
The EPUBs below are built in ``tmp_path`` from synthetic strings: the repo
|
|
holds no AMA text.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import zipfile
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import duckdb
|
|
import pytest
|
|
|
|
from bib.item import Rule
|
|
from bib.store import Store
|
|
from pfs.codetables import (
|
|
ORIGIN_KINDS,
|
|
GuidanceRow,
|
|
OriginRow,
|
|
ensure_tables,
|
|
read_origin,
|
|
write_guidance,
|
|
write_origin,
|
|
)
|
|
from pfs.origin import (
|
|
ORIGIN_PROMPT_VERSION,
|
|
ORIGIN_SCHEMA,
|
|
_anchor,
|
|
_kind_of,
|
|
_longest_span,
|
|
_page_of,
|
|
action_of,
|
|
book_candidates,
|
|
book_deleted,
|
|
book_support,
|
|
extract_origin,
|
|
family_origin,
|
|
fr_origin_candidates,
|
|
guidance_origins,
|
|
origin_inventory,
|
|
origins_from_output,
|
|
resolve_book,
|
|
shares_run,
|
|
)
|
|
from pfs.runlog import RunLog
|
|
|
|
FAM = SimpleNamespace(
|
|
key="HOME-VISITS",
|
|
codes=("99341", "99342", "99350"),
|
|
synonyms=("home or residence services",),
|
|
)
|
|
CODES = FAM.codes
|
|
|
|
_CONTAINER = (
|
|
'<?xml version="1.0"?><container version="1.0" '
|
|
'xmlns="urn:oasis:names:tc:opendocument:xmlns:container"><rootfiles>'
|
|
'<rootfile full-path="OPS/book.opf" media-type="application/oebps-package+xml"/>'
|
|
"</rootfiles></container>"
|
|
)
|
|
_OPF = (
|
|
'<?xml version="1.0"?><package xmlns="http://www.idpf.org/2007/opf" version="3.0">'
|
|
'<manifest><item id="c1" href="c1.xhtml" media-type="application/xhtml+xml"/>'
|
|
'</manifest><spine><itemref idref="c1"/></spine></package>'
|
|
)
|
|
|
|
|
|
def make_epub(path: Path, pages: dict[str, str]) -> Path:
|
|
body = "".join(
|
|
f'<span id="page_{label}"/>'
|
|
+ "".join(f"<p>{para}</p>" for para in text.split("|"))
|
|
for label, text in pages.items()
|
|
)
|
|
with zipfile.ZipFile(path, "w") as zf:
|
|
zf.writestr("mimetype", "application/epub+zip")
|
|
zf.writestr("META-INF/container.xml", _CONTAINER)
|
|
zf.writestr("OPS/book.opf", _OPF)
|
|
zf.writestr("OPS/c1.xhtml", f"<html><body>{body}</body></html>")
|
|
return path
|
|
|
|
|
|
# synthetic stand-ins; the sentences are invented for the tests
|
|
PAGE12 = (
|
|
"\u25b6(99339 has been deleted. For other services see the care codes.)\u25c0"
|
|
"|Rationale|Codes 99341 and 99350 have been revised for 2023 to use total "
|
|
"time. Home visit code 99343 has been deleted."
|
|
)
|
|
BOOK_PAGES = {
|
|
"ix": "Preface with no code at all.",
|
|
"12": PAGE12,
|
|
"13": "Another page about 99342 only in an unrelated table.",
|
|
}
|
|
|
|
FR_STATUTE = (
|
|
"Section 1861(m) of the Act establishes the home health benefit, and CPT "
|
|
"code 99341 describes a home visit for a new patient."
|
|
)
|
|
FR_RECAP = (
|
|
"In the CY 2019 PFS final rule, we established the homebound exception "
|
|
"for CPT code 99342."
|
|
)
|
|
FR_REQUEST = (
|
|
"Commenters requested that section 1834(m) of the Act cover CPT code 99341."
|
|
)
|
|
FR_HYPO = (
|
|
"If we were to repeal section 1862(a)(1) of the Act, CPT code 99350 "
|
|
"would need to be revised."
|
|
)
|
|
FR_PLAIN = "CPT code 99350 is not subject to the demonstration."
|
|
|
|
|
|
def ask_with(items, calls=None):
|
|
def ask(system, user, schema):
|
|
if calls is not None:
|
|
calls.append((system, user, schema))
|
|
return {"origins": items}
|
|
|
|
ask.model = "qwen-test"
|
|
return ask
|
|
|
|
|
|
def item(**kw):
|
|
base = dict(
|
|
kind="statute",
|
|
reference="Section 1861(m) of the Act",
|
|
year=None,
|
|
codes=["99341"],
|
|
summary="The Act sets up the home health benefit.",
|
|
)
|
|
return {**base, **kw}
|
|
|
|
|
|
# ── pure guards ──────────────────────────────────────────────────────
|
|
|
|
|
|
def test_shares_run_is_case_and_punctuation_insensitive():
|
|
text = "The Panel, in 2022, adopted these Home-Visit entries for everyone."
|
|
assert shares_run(
|
|
"it said: the panel in 2022 adopted these home visit entries", text
|
|
)
|
|
assert not shares_run("the panel adopted entries about home visits", text)
|
|
assert not shares_run("too short", "too short") # fewer words than n
|
|
|
|
|
|
def test_book_summary_copy_guard():
|
|
page = PAGE12.replace("|", "\n")
|
|
copied = item(
|
|
kind="cpt_panel",
|
|
reference="CPT Editorial Panel action",
|
|
codes=["99341"],
|
|
summary="Codes 99341 and 99350 have been revised for 2023 to use total time.",
|
|
)
|
|
fresh = item(
|
|
kind="cpt_panel",
|
|
reference="CPT Editorial Panel action",
|
|
codes=["99350"],
|
|
summary="Editors revised the visit codes to be reported by time.",
|
|
)
|
|
out = origins_from_output(
|
|
{"origins": [copied, fresh]}, page, source="book", codes=CODES
|
|
)
|
|
assert [o["summary"] for o in out] == [fresh["summary"]]
|
|
# the reference is guarded as well
|
|
leaky = {
|
|
**fresh,
|
|
"reference": "codes 99341 and 99350 have been revised for 2023 to use",
|
|
}
|
|
assert origins_from_output({"origins": [leaky]}, page, source="book") == []
|
|
|
|
|
|
def test_fr_reference_must_appear():
|
|
ok = item()
|
|
missing = item(reference="section 1861(s)(1) of the Act")
|
|
out = origins_from_output(
|
|
{"origins": [missing, ok]}, FR_STATUTE, source="fr", codes=CODES
|
|
)
|
|
assert [o["reference"] for o in out] == ["Section 1861(m) of the Act"]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"text",
|
|
[FR_RECAP, FR_REQUEST, FR_HYPO],
|
|
ids=["recap", "commenter-request", "hypothetical"],
|
|
)
|
|
def test_fr_reference_must_sit_in_a_plain_sentence(text):
|
|
ref = {
|
|
FR_RECAP: "the homebound exception",
|
|
FR_REQUEST: "section 1834(m) of the Act",
|
|
FR_HYPO: "section 1862(a)(1) of the Act",
|
|
}[text]
|
|
code = {FR_RECAP: "99342", FR_REQUEST: "99341", FR_HYPO: "99350"}[text]
|
|
out = origins_from_output(
|
|
{"origins": [item(kind="program", reference=ref, codes=[code])]},
|
|
text,
|
|
source="fr",
|
|
codes=CODES,
|
|
rule_year=2023,
|
|
)
|
|
assert out == []
|
|
|
|
|
|
def test_fr_recap_of_the_same_year_is_a_plain_statement():
|
|
text = "In the CY 2023 PFS final rule, we established the homebound exception for CPT code 99342."
|
|
out = origins_from_output(
|
|
{
|
|
"origins": [
|
|
item(
|
|
kind="program", reference="the homebound exception", codes=["99342"]
|
|
)
|
|
]
|
|
},
|
|
text,
|
|
source="fr",
|
|
codes=CODES,
|
|
rule_year=2023,
|
|
)
|
|
assert len(out) == 1
|
|
|
|
|
|
def test_codes_outside_the_family_or_not_named_are_dropped():
|
|
out = origins_from_output(
|
|
{"origins": [item(codes=["99341", "99999", "99342", "99341"])]},
|
|
FR_STATUTE,
|
|
source="fr",
|
|
codes=CODES,
|
|
)
|
|
assert out[0]["codes"] == ["99341"] # 99999 foreign, 99342 not in the text
|
|
gone = origins_from_output(
|
|
{"origins": [item(codes=["99999"])]}, FR_STATUTE, source="fr", codes=CODES
|
|
)
|
|
assert gone == []
|
|
|
|
|
|
def test_code_ranges_in_the_text_name_every_code():
|
|
text = "Section 1861(m) of the Act covers CPT codes 99341-99342."
|
|
out = origins_from_output(
|
|
{"origins": [item(codes=["99342"])]}, text, source="fr", codes=CODES
|
|
)
|
|
assert len(out) == 1
|
|
|
|
|
|
def test_year_and_numbers_must_be_in_the_text():
|
|
ok = item(year=2023, summary="Section 1861 and 2023 set it up.")
|
|
text = "Section 1861(m) of the Act, effective 2023, sets it up for CPT code 99341."
|
|
[row] = origins_from_output({"origins": [ok]}, text, source="fr", codes=CODES)
|
|
assert row["year"] == 2023
|
|
# a year the text never states is dropped to null, not guessed
|
|
[row] = origins_from_output(
|
|
{"origins": [item(year=1965)]}, text, source="fr", codes=CODES
|
|
)
|
|
assert row["year"] is None
|
|
# a summary number the text lacks drops the item
|
|
assert (
|
|
origins_from_output(
|
|
{"origins": [item(summary="It began in 1988 under the Act.")]},
|
|
text,
|
|
source="fr",
|
|
codes=CODES,
|
|
)
|
|
== []
|
|
)
|
|
|
|
|
|
def test_origins_from_output_tolerates_missing_codes_filter_and_empty_output():
|
|
out = origins_from_output({"origins": [item()]}, FR_STATUTE, source="fr")
|
|
assert out[0]["codes"] == ["99341"]
|
|
assert origins_from_output({}, FR_STATUTE, source="fr") == []
|
|
assert (
|
|
origins_from_output({"origins": [item(codes=[])]}, FR_STATUTE, source="fr")
|
|
== []
|
|
)
|
|
|
|
|
|
def test_schema_and_version():
|
|
assert ORIGIN_PROMPT_VERSION == "origin-v1"
|
|
kinds = ORIGIN_SCHEMA["properties"]["origins"]["items"]["properties"]["kind"]
|
|
assert tuple(kinds["enum"]) == ORIGIN_KINDS
|
|
assert ORIGIN_KINDS == (
|
|
"statute",
|
|
"regulation",
|
|
"program",
|
|
"demonstration",
|
|
"cpt_panel",
|
|
"cms_gcode",
|
|
"coverage",
|
|
)
|
|
|
|
|
|
def test_extract_origin_asks_with_the_schema_and_filters():
|
|
calls: list = []
|
|
out = extract_origin(
|
|
FR_STATUTE,
|
|
ask_with([item(), item(reference="not in the text")], calls),
|
|
source="fr",
|
|
codes=CODES,
|
|
)
|
|
assert [o["reference"] for o in out] == ["Section 1861(m) of the Act"]
|
|
system, user, schema = calls[0]
|
|
assert schema is ORIGIN_SCHEMA and "99341" in user and FR_STATUTE in user
|
|
assert "never quote" in system.lower() or "paraphras" in system.lower()
|
|
|
|
|
|
def test_extract_origin_failed_reply_is_empty():
|
|
ask = lambda s, u, sc: None # noqa: E731
|
|
assert extract_origin(FR_STATUTE, ask, source="fr") == []
|
|
|
|
|
|
def test_extract_origin_book_system_differs_from_fr():
|
|
calls: list = []
|
|
extract_origin(BOOK_PAGES["12"], ask_with([], calls), source="book", codes=CODES)
|
|
extract_origin(FR_STATUTE, ask_with([], calls), source="fr", codes=CODES)
|
|
assert calls[0][0] != calls[1][0]
|
|
assert "8" in calls[0][0] or "eight" in calls[0][0].lower()
|
|
|
|
|
|
def test_extract_origin_rejects_an_unknown_source():
|
|
with pytest.raises(ValueError):
|
|
extract_origin("x", ask_with([]), source="web")
|
|
|
|
|
|
# ── candidates ───────────────────────────────────────────────────────
|
|
|
|
|
|
def test_book_candidates(tmp_path):
|
|
path = make_epub(tmp_path / "b.epub", {"": "Lead 99341 text.", **BOOK_PAGES})
|
|
got = book_candidates(path, CODES)
|
|
assert [label for label, _ in got] == ["12", "13"] # unlabeled text can't be cited
|
|
assert book_candidates(path, ()) == []
|
|
|
|
|
|
def test_book_candidates_see_code_ranges(tmp_path):
|
|
path = make_epub(tmp_path / "b.epub", {"4": "Codes 99341-99342 were revised."})
|
|
assert [label for label, _ in book_candidates(path, ("99342",))] == ["4"]
|
|
|
|
|
|
TITLE = "Medicare Program; CY 2023 PFS Final Rule"
|
|
|
|
|
|
def _store(tmp_path):
|
|
s = Store(":memory:", storage_dir=tmp_path / "st")
|
|
key = s.create(
|
|
Rule(title=TITLE, document_number="22-1", date_published="2022-11-18")
|
|
)
|
|
other = s.create(
|
|
Rule(
|
|
title="Home Health Rule",
|
|
document_number="22-2",
|
|
date_published="2022-06-01",
|
|
)
|
|
)
|
|
corr = s.create(
|
|
Rule(
|
|
title="CY 2023 PFS Final Rule; Correction",
|
|
document_number="22-3",
|
|
date_published="2023-01-01",
|
|
)
|
|
)
|
|
anchors = [
|
|
(key, 1, 100, 1, "An unrelated opening paragraph."),
|
|
(key, 2, 100, 2, FR_STATUTE),
|
|
(key, 3, 101, 3, "CPT code 99341 appears here with nothing about origins."),
|
|
(
|
|
key,
|
|
4,
|
|
101,
|
|
4,
|
|
"The home or residence services family includes a new benefit.",
|
|
),
|
|
(
|
|
other,
|
|
7,
|
|
55,
|
|
1,
|
|
"Section 1895 of the Act created the demonstration for 99350.",
|
|
),
|
|
(corr, 9, 1, 1, FR_STATUTE),
|
|
]
|
|
s._con().executemany(
|
|
"INSERT INTO fr_anchors (item_key, p_id, page, ordinal, text) VALUES (?,?,?,?,?)",
|
|
anchors,
|
|
)
|
|
for k, doc in ((key, "22-1"), (other, "22-2"), (corr, "22-3")):
|
|
s._con().execute(
|
|
"INSERT INTO fr_anchor_docs (item_key, document_number, html_url, start_page,"
|
|
" end_page, fr_volume) VALUES (?,?,'u',1,2,87)",
|
|
(k, doc),
|
|
)
|
|
s._con().commit()
|
|
return s, key, other
|
|
|
|
|
|
def test_fr_origin_candidates_filter(tmp_path):
|
|
s, key, other = _store(tmp_path)
|
|
got = fr_origin_candidates(s, CODES, FAM.synonyms)
|
|
assert [(g["item_key"], g["p_id"]) for g in got] == [(other, 7), (key, 2), (key, 4)]
|
|
first = got[1]
|
|
assert first["page"] == 100 and first["rule_year"] == 2023
|
|
assert first["rule_kind"] == "final" and first["text"] == FR_STATUTE
|
|
assert fr_origin_candidates(s, (), ()) == []
|
|
|
|
|
|
def test_resolve_book(tmp_path):
|
|
s = Store(":memory:", storage_dir=tmp_path / "st")
|
|
from bib.item import Source # noqa: PLC0415
|
|
|
|
key = s.create(Source(title="Synthetic Changes 2023"))
|
|
assert resolve_book(s, key) is None # no attachment
|
|
epub = make_epub(tmp_path / "book.epub", BOOK_PAGES)
|
|
s.attach_file(key, epub)
|
|
label, path = resolve_book(s, key)
|
|
assert label == "Synthetic Changes 2023" and path.name.endswith(".epub")
|
|
txt = tmp_path / "n.txt"
|
|
txt.write_text("x")
|
|
k2 = s.create(Source(title="No epub"))
|
|
s.attach_file(k2, txt)
|
|
assert resolve_book(s, k2) is None
|
|
|
|
|
|
# ── guidance ─────────────────────────────────────────────────────────
|
|
|
|
|
|
def _con():
|
|
c = duckdb.connect(":memory:")
|
|
ensure_tables(c)
|
|
return c
|
|
|
|
|
|
def _g(code, kind, locator, src="K1", p_id=5, page=100, item_key=""):
|
|
return GuidanceRow("HOME-VISITS", code, kind, locator, item_key, src, p_id, page)
|
|
|
|
|
|
def test_guidance_origins_map_kinds():
|
|
c = _con()
|
|
g = [
|
|
_g("99341", "cfr", "42 CFR 425.400(c)(1)(v)(A)"),
|
|
_g("99341", "cfr", "42 CFR 425.400(c)(1)(vi)", src="K2", p_id=9, page=200),
|
|
_g("99341", "cfr", "42 CFR 425.400(c)"),
|
|
_g("99341", "iom", "100-04 ch.12 §30.6.4", item_key="IOM1", p_id=6),
|
|
_g("99350", "mln", "MLN 907166", src="K3", p_id=0, page=0),
|
|
_g("99350", "cfr", "42 CFR 410.78(a)(3)"), # telehealth listing
|
|
_g("99350", "cfr", "a CFR cite we cannot parse"),
|
|
]
|
|
write_guidance(c, "HOME-VISITS", g)
|
|
rows = guidance_origins(c, "HOME-VISITS")
|
|
by = {(r.code, r.cite_kind, r.reference): r for r in rows}
|
|
# sub-paragraph variants are one row per CFR section; 410.78 is no origin
|
|
assert len(rows) == 4
|
|
cfr = by[("99341", "cfr", "42 CFR 425.400")]
|
|
assert (cfr.kind, cfr.extractor, cfr.ref_p_id) == ("regulation", "guidance", 0)
|
|
assert (cfr.item_key, cfr.p_id, cfr.page_label, cfr.year) == ("K1", 5, "100", None)
|
|
assert cfr.locator == "42 CFR 425.400" and len(cfr.summary) <= 240
|
|
assert by[("99341", "iom", "100-04 ch.12 §30.6.4")].kind == "coverage"
|
|
mln = by[("99350", "mln", "MLN 907166")]
|
|
assert mln.kind == "coverage" and mln.page_label == "" and mln.p_id == 0
|
|
assert ("99350", "cfr", "a CFR cite we cannot parse") in by
|
|
|
|
|
|
def test_guidance_drops_manual_provisions_the_citing_sentence_removes(tmp_path):
|
|
s, key, other = _store(tmp_path)
|
|
s._con().executemany(
|
|
"INSERT INTO fr_anchors (item_key, p_id, page, ordinal, text) VALUES (?,?,?,?,?)",
|
|
[
|
|
(
|
|
key,
|
|
50,
|
|
1,
|
|
50,
|
|
"We are removing the rule in Section 30.6.14.1 of the manual.",
|
|
),
|
|
(
|
|
key,
|
|
51,
|
|
1,
|
|
51,
|
|
"Section 30.6.14.2 is cited here. We remove other things.",
|
|
),
|
|
(key, 52, 1, 52, ""),
|
|
(key, 53, 1, 53, "We are eliminating Chapter 8 of the manual."),
|
|
(
|
|
key,
|
|
54,
|
|
1,
|
|
54,
|
|
"Chapter 26 sets place of service codes. We rescind nothing here.",
|
|
),
|
|
],
|
|
)
|
|
c = _con()
|
|
write_guidance(
|
|
c,
|
|
"HOME-VISITS",
|
|
[
|
|
_g("99341", "iom", "100-04 ch.12 §30.6.14.1", src=key, p_id=50),
|
|
_g("99341", "iom", "100-04 ch.12 §30.6.14.2", src=key, p_id=51),
|
|
_g("99341", "iom", "100-04 ch.26", src=key, p_id=54),
|
|
_g("99350", "iom", "100-04 ch.9", src=key, p_id=52),
|
|
_g("99350", "iom", "100-04 ch.8", src=key, p_id=53),
|
|
_g("99350", "iom", "100-04 ch.7", src=key, p_id=999),
|
|
_g("99350", "cfr", "42 CFR 425.20", src=key, p_id=50),
|
|
_g("99350", "cfr", "42 CFR 25.400(c)(1)(vi)", src=key, p_id=50),
|
|
],
|
|
)
|
|
kept = {(r.code, r.reference) for r in guidance_origins(c, "HOME-VISITS", s)}
|
|
# removal must be in the sentence that cites the section; a title-42
|
|
# CFR part that does not exist is an FR typo
|
|
assert kept == {
|
|
("99341", "100-04 ch.12 §30.6.14.2"),
|
|
("99341", "100-04 ch.26"),
|
|
("99350", "100-04 ch.9"),
|
|
("99350", "100-04 ch.7"),
|
|
("99350", "42 CFR 425.20"),
|
|
}
|
|
|
|
|
|
def test_the_cfr_part_check_is_for_title_42_only():
|
|
c = _con()
|
|
cites = [
|
|
"45 CFR 170.315(b)",
|
|
"45 CFR 164.502",
|
|
"21 CFR 882.5801",
|
|
"42 CFR 8.12",
|
|
"42 CFR 2.11",
|
|
"42 CFR 10.10",
|
|
"42 CFR 1001.952(h)",
|
|
"42 CFR 1008.1",
|
|
"42 CFR 425.20",
|
|
"42 CFR 25.400(c)(1)(vi)",
|
|
"42 CFR 160.103",
|
|
]
|
|
write_guidance(c, "HOME-VISITS", [_g("99341", "cfr", x) for x in cites])
|
|
kept = {r.reference for r in guidance_origins(c, "HOME-VISITS")}
|
|
assert kept == {
|
|
"45 CFR 170.315",
|
|
"45 CFR 164.502",
|
|
"21 CFR 882.5801",
|
|
"42 CFR 8.12",
|
|
"42 CFR 2.11",
|
|
"42 CFR 10.10",
|
|
"42 CFR 1001.952",
|
|
"42 CFR 1008.1",
|
|
"42 CFR 425.20",
|
|
}
|
|
|
|
|
|
# public FR text: CY 2019 PFS proposed rule (83 FR 35835, CWGP3WJG ¶702)
|
|
# and final rule (83 FR 59630, 9V89NVZG ¶1622-¶1624), CY 2023 final
|
|
# (PETM225D ¶3607)
|
|
P702 = (
|
|
"Medicare pays for E/M visits furnished in the home (a private residence) "
|
|
"under CPT codes 99341 through 99350. However, there is a Medicare Claims "
|
|
"Processing Manual provision requiring that the medical record must "
|
|
"document the medical necessity of the home visit made in lieu of an office "
|
|
"or outpatient visit (Pub. 100-04, Medicare Claims Processing Manual, "
|
|
"Chapter 12, Section 30.6.14.1.B). We agree, so we are proposing to remove "
|
|
"the requirement that the medical record must document the medical "
|
|
"necessity of furnishing the visit in the home rather than in the office."
|
|
)
|
|
P1622 = P702.replace("We agree, so we are proposing", "We agreed, so we proposed")
|
|
P1623 = (
|
|
"Comment: Commenters were generally supportive of our proposal to remove the "
|
|
"requirement that the medical record must document the medical necessity of "
|
|
"furnishing the visit in the home rather than in the office."
|
|
)
|
|
P1624 = (
|
|
"Response: We are finalizing this policy change to remove the requirement "
|
|
"that the medical record must document the medical necessity of furnishing "
|
|
"the visit in the home rather than in the office, as proposed, effective "
|
|
"January 1, 2019."
|
|
)
|
|
P3607 = (
|
|
"As described in Medicare Claims Processing Manual, Publication 100-04, "
|
|
"Chapter 26, place of service codes are two-digit codes placed on health care "
|
|
"professional claims for CPT codes 99341 through 99350."
|
|
)
|
|
|
|
|
|
def _cy2019_store(tmp_path):
|
|
s = Store(":memory:", storage_dir=tmp_path / "st")
|
|
prop = s.create(
|
|
Rule(
|
|
title="Medicare Program; CY 2019 PFS Proposed Rule",
|
|
document_number="18-1",
|
|
date_published="2018-07-27",
|
|
)
|
|
)
|
|
final = s.create(
|
|
Rule(
|
|
title="Medicare Program; CY 2019 PFS Final Rule",
|
|
document_number="18-2",
|
|
date_published="2018-11-23",
|
|
)
|
|
)
|
|
s._con().executemany(
|
|
"INSERT INTO fr_anchors (item_key, p_id, page, ordinal, text) VALUES (?,?,?,?,?)",
|
|
[
|
|
(prop, 702, 35835, 1, P702),
|
|
(final, 1622, 59630, 1, P1622),
|
|
(final, 1623, 59630, 2, P1623),
|
|
(final, 1624, 59631, 3, P1624),
|
|
(final, 3607, 59700, 4, P3607),
|
|
(final, 3608, 59700, 5, "We remove the requirement that X be done."),
|
|
(final, 3609, 59700, 6, "We are finalizing the removal of Y."),
|
|
],
|
|
)
|
|
s._con().execute(
|
|
"INSERT INTO fr_anchor_docs (item_key, document_number, html_url, start_page,"
|
|
" end_page, fr_volume) VALUES (?,?,'u',1,2,83)",
|
|
(final, "18-2"),
|
|
)
|
|
s._con().commit()
|
|
return s, prop, final
|
|
|
|
|
|
def test_coverage_rows_cite_final_rules_and_tell_a_finalized_removal(tmp_path):
|
|
s, prop, final = _cy2019_store(tmp_path)
|
|
c = _con()
|
|
sec = "100-04 ch.12 §30.6.14.1"
|
|
write_guidance(
|
|
c,
|
|
"HOME-VISITS",
|
|
[
|
|
_g("99341", "iom", sec, src=prop, p_id=702, page=35835),
|
|
_g("99341", "iom", sec, src=final, p_id=1622, page=59630),
|
|
_g("99342", "iom", sec, src=prop, p_id=702, page=35835),
|
|
_g("99341", "iom", "100-04 ch.26", src=prop, p_id=702, page=35835),
|
|
_g("99341", "iom", "100-04 ch.26", src=final, p_id=3607, page=59700),
|
|
_g("99341", "iom", "100-04 ch.9", src=final, p_id=3608, page=59700),
|
|
_g("99350", "mln", "MLN 907166", src=prop, p_id=702, page=35835),
|
|
_g("99350", "mln", "MLN 1", src="CPT2023", p_id=0, page=0),
|
|
],
|
|
)
|
|
rows = guidance_origins(c, "HOME-VISITS", s)
|
|
by = {(r.code, r.reference): r for r in rows}
|
|
# proposal-cited rows are gone (99342 has only the proposal), a CPT
|
|
# edition's MLN cite stays
|
|
assert set(by) == {
|
|
("99341", sec),
|
|
("99341", "100-04 ch.26"),
|
|
("99341", "100-04 ch.9"),
|
|
("99350", "MLN 1"),
|
|
}
|
|
gone = by[("99341", sec)]
|
|
assert gone.summary == (
|
|
"The CY2019 final rule removed the requirement in 100-04 ch.12 §30.6.14.1 "
|
|
"that the medical record must document the medical necessity of "
|
|
"furnishing the visit in the home rather than in the office, effective "
|
|
"January 1, 2019."
|
|
)
|
|
assert (gone.kind, gone.cite_kind, gone.year, gone.extractor) == (
|
|
"coverage",
|
|
"iom",
|
|
2019,
|
|
"guidance",
|
|
)
|
|
assert (gone.item_key, gone.p_id, gone.ref_p_id, gone.page_label) == (
|
|
final,
|
|
1624,
|
|
1622,
|
|
"59631",
|
|
)
|
|
assert gone.locator == f"{sec}; 83 FR 59631, ¶1624 (reference ¶1622)"
|
|
pos = by[("99341", "100-04 ch.26")]
|
|
assert (pos.item_key, pos.p_id, pos.ref_p_id) == (final, 3607, 0)
|
|
assert pos.summary == "CMS guidance for 99341 points to 100-04 ch.26."
|
|
# "remove the requirement that X" never finalized: no removal row
|
|
assert by[("99341", "100-04 ch.9")].p_id == 3608
|
|
|
|
|
|
def test_removal_row_without_a_volume_year_or_effective_date(tmp_path):
|
|
from pfs.origin import _removal_row # noqa: PLC0415
|
|
|
|
g = _g("99341", "iom", "100-04 ch.12 §1", src="K", p_id=7)
|
|
found = {"p_id": 7, "page": 12, "req": "x " * 150, "effective": ""}
|
|
row = _removal_row(g, "100-04 ch.12 §1", ("final", None, ""), found)
|
|
# too long for 240 characters: the requirement is left out
|
|
assert row.summary == "The final rule removed a requirement in 100-04 ch.12 §1."
|
|
assert row.locator == "100-04 ch.12 §1; FR p. 12, ¶7" and row.year is None
|
|
|
|
|
|
# ── deletions from lineage ───────────────────────────────────────────
|
|
|
|
FAM_DEL = SimpleNamespace(
|
|
key="HOME-VISITS",
|
|
codes=("99324", "99339", "99341", "99343", "99350"),
|
|
synonyms=(),
|
|
)
|
|
# public FR text, CY 2023 PFS final rule (87 FR 69440 ¶390, 87 FR 69608 ¶1672)
|
|
P390 = (
|
|
"That said, in February 2021, the AMA CPT Editorial Panel deleted the family "
|
|
"of domiciliary codes, CPT codes 99324 to 99340, and merged the services "
|
|
"described by those codes into the existing family of home-based E/M visits, "
|
|
"CPT codes 99341 to 99350 (a range of codes that includes CPT codes 99344, "
|
|
"99345, 99349, and 99350). We stated that we were not considering these "
|
|
"home-based E/M visits as potentially misvalued for CY 2023."
|
|
)
|
|
P1672 = (
|
|
"More specifically, in its February 2021 meeting, effective beginning in 2023, "
|
|
"the CPT Editorial Panel deleted the nine CPT codes in the Domiciliary, Rest "
|
|
"Home (for example, Boarding Home), or Custodial Care Services code family "
|
|
"(CPT codes 99324-99328, and 99334-99337), and one CPT code in the Home "
|
|
"Services family (CPT code 99343), to merge these services with the eight "
|
|
"remaining home visit services. The eight remaining home services CPT codes "
|
|
"(99341, 99342, 99344, 99345, and 99347-99350) were revised to describe Home "
|
|
"or Residence Services."
|
|
)
|
|
MERGED = (
|
|
"The AMA CPT Editorial Panel deleted domiciliary codes 99324-99340 and merged "
|
|
"them into the existing home-based E/M visit codes 99341-99350."
|
|
)
|
|
|
|
|
|
def _ev(code, kind, key, p_id, year=2023):
|
|
from pfs.codetables import EventRow # noqa: PLC0415
|
|
|
|
return EventRow(code, year, kind, "", "", key, p_id, 1, "fr", True, "")
|
|
|
|
|
|
def _deletion_store(tmp_path):
|
|
s, key = _chain_store(
|
|
tmp_path,
|
|
[
|
|
(390, 69440, P390),
|
|
(1672, 69608, P1672),
|
|
(1700, 69610, "The CPT Editorial Panel deleted CPT code 99324 then."),
|
|
(
|
|
1701,
|
|
69610,
|
|
"In the CY 2021 PFS final rule, the CPT Editorial Panel deleted CPT "
|
|
"code 99324, effective 2023.",
|
|
),
|
|
],
|
|
)
|
|
corr = s.create(
|
|
Rule(
|
|
title="CY 2023 PFS Final Rule; Correction",
|
|
document_number="22-9",
|
|
date_published="2023-01-01",
|
|
)
|
|
)
|
|
s._con().execute(
|
|
"INSERT INTO fr_anchors (item_key, p_id, page, ordinal, text) VALUES (?,?,?,?,?)",
|
|
(corr, 5, 1, 5, "In 2023 the CPT Editorial Panel deleted CPT code 99324."),
|
|
)
|
|
s._con().commit()
|
|
return s, key, corr
|
|
|
|
|
|
def test_deletions_come_from_lineage_and_misstated_fates_are_dropped(tmp_path):
|
|
from pfs.codetables import write_events # noqa: PLC0415
|
|
|
|
s, key, corr = _deletion_store(tmp_path)
|
|
c = _con()
|
|
both = ("deleted", "cpt_panel")
|
|
write_events(
|
|
c,
|
|
"99324",
|
|
[_ev("99324", k, key, p) for k in both for p in (390, 1672, 1700, 1701)]
|
|
+ [_ev("99324", k, corr, 5) for k in both]
|
|
+ [_ev("99324", "deleted", key, 999), _ev("99324", "cpt_panel", key, 999)]
|
|
+ [_ev("99324", "telehealth_list", key, 1672)],
|
|
)
|
|
write_events(c, "99339", [_ev("99339", k, key, 390) for k in both])
|
|
write_events(c, "99343", [_ev("99343", k, key, 1672) for k in both])
|
|
write_events(c, "99341", [_ev("99341", "cpt_panel", key, 1672)])
|
|
|
|
def ask(system, user, schema):
|
|
passage = user.split("PASSAGE:")[1]
|
|
if "That said" in passage:
|
|
return {
|
|
"origins": [
|
|
item(
|
|
kind="cpt_panel",
|
|
reference="AMA CPT Editorial Panel",
|
|
codes=["99324", "99339", "99341", "99343", "99350"],
|
|
year=2021,
|
|
summary=MERGED,
|
|
)
|
|
]
|
|
}
|
|
if "More specifically" in passage:
|
|
# the model leaves out 99343
|
|
return {
|
|
"origins": [
|
|
item(
|
|
kind="cpt_panel",
|
|
reference="CPT Editorial Panel",
|
|
codes=["99324", "99341", "99350"],
|
|
year=2023,
|
|
summary="The panel deleted and revised codes to merge "
|
|
"the services into a single family.",
|
|
)
|
|
]
|
|
}
|
|
return {"origins": []}
|
|
|
|
ask.model = "qwen-test"
|
|
rows, _ = family_origin(
|
|
c, s, FAM_DEL, ask=ask, log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
got = {(r.code, r.p_id, r.year, r.extractor[:7]) for r in rows}
|
|
assert got == {
|
|
# ¶390: the model row stands for the deleted codes it tells as
|
|
# deleted and for the merge targets; 99343 (deleted in this rule)
|
|
# is no merge target
|
|
("99324", 390, 2021, "model:q"),
|
|
("99339", 390, 2021, "model:q"),
|
|
("99341", 390, 2021, "model:q"),
|
|
("99350", 390, 2021, "model:q"),
|
|
# ¶1672: the deleted codes from lineage (the sentence writes 2023),
|
|
# the survivors from the model
|
|
("99324", 1672, 2023, "lineage"),
|
|
("99343", 1672, 2023, "lineage"),
|
|
("99341", 1672, 2023, "model:q"),
|
|
("99350", 1672, 2023, "model:q"),
|
|
# ¶390 is the only paragraph deleting 99339 in 2023
|
|
("99339", 390, 2023, "lineage"),
|
|
}
|
|
r = next(r for r in rows if (r.code, r.p_id) == ("99343", 1672))
|
|
assert r.summary == "The CPT Editorial Panel deleted 99343, effective CY2023."
|
|
assert (r.kind, r.reference, r.cite_kind, r.item_key, r.ref_p_id) == (
|
|
"cpt_panel",
|
|
"CPT Editorial Panel",
|
|
"fr_para",
|
|
key,
|
|
1672,
|
|
)
|
|
assert (r.page_label, r.locator) == ("69608", "87 FR 69608, ¶1672")
|
|
|
|
|
|
def test_lineage_origins_need_a_supporting_paragraph(tmp_path):
|
|
from pfs.origin import lineage_origins # noqa: PLC0415
|
|
|
|
s, key, corr = _deletion_store(tmp_path)
|
|
both = ("deleted", "cpt_panel")
|
|
events = {
|
|
# ¶1700 does not write the year; ¶1701 recaps another year's rule;
|
|
# the correction notice and a missing paragraph support nothing
|
|
"99324": [_ev("99324", k, key, p) for k in both for p in (1700, 1701, 999)]
|
|
+ [_ev("99324", k, corr, 5) for k in both],
|
|
# deleted and cpt_panel at different paragraphs
|
|
"99343": [_ev("99343", "deleted", key, 390), _ev("99343", "cpt_panel", key, 1)],
|
|
# ¶390 names 99341 only as a merge target
|
|
"99341": [_ev("99341", k, key, 390) for k in both],
|
|
}
|
|
assert lineage_origins(s, FAM_DEL, events) == []
|
|
|
|
|
|
def test_misstates_deletion():
|
|
from pfs.origin import misstates_deletion # noqa: PLC0415
|
|
|
|
assert misstates_deletion(MERGED, "99343") # a merge target
|
|
assert not misstates_deletion(MERGED, "99324")
|
|
assert misstates_deletion("The Panel revised CPT code 99343.", "99343")
|
|
assert misstates_deletion("The Panel revised the home visit codes.", "99343")
|
|
assert not misstates_deletion("The Panel deleted and revised codes.", "99343")
|
|
assert not misstates_deletion("The Panel acted on the codes.", "99343")
|
|
|
|
|
|
# ── family run ───────────────────────────────────────────────────────
|
|
|
|
|
|
def test_origin_rows_carry_page_label_for_book(tmp_path):
|
|
s, key, other = _store(tmp_path)
|
|
epub = make_epub(tmp_path / "b.epub", BOOK_PAGES)
|
|
book_item = ("BOOK1", "Synthetic Changes 2023", epub)
|
|
paraphrase = item(
|
|
kind="cpt_panel",
|
|
reference="CPT Editorial Panel action",
|
|
codes=["99341", "99350"],
|
|
summary="Editors revised the visit codes to be reported by time.",
|
|
year=2023,
|
|
)
|
|
gone = {**paraphrase, "codes": ["99339"], "summary": "Editors deleted the code."}
|
|
ask = ask_with([paraphrase, gone])
|
|
c = _con()
|
|
rows, counts = family_origin(
|
|
c, s, FAM, ask=ask, log=RunLog(tmp_path / "l.jsonl"), books=[book_item]
|
|
)
|
|
book = [r for r in rows if r.cite_kind == "cpt_book"]
|
|
# 99339 is not a family code; page 13 has no prose
|
|
assert {(r.code, r.page_label, r.p_id, r.item_key) for r in book} == {
|
|
("99341", "12", 0, "BOOK1"),
|
|
("99350", "12", 0, "BOOK1"),
|
|
}
|
|
r = book[0]
|
|
assert r.locator == "Synthetic Changes 2023, p. 12" and r.year == 2023
|
|
assert r.extractor == f"model:qwen-test@{ORIGIN_PROMPT_VERSION}"
|
|
assert r.ref_p_id == 0
|
|
assert all(len(r.summary) <= 240 for r in rows)
|
|
assert counts["failed"] == 0
|
|
|
|
|
|
def test_family_origin_fr_rows_cite_the_paragraph(tmp_path):
|
|
s, key, other = _store(tmp_path)
|
|
ask = ask_with(
|
|
[
|
|
item(codes=["99341"]),
|
|
item(reference="not in the paragraph", codes=["99341"]),
|
|
]
|
|
)
|
|
c = _con()
|
|
write_guidance(
|
|
c,
|
|
"HOME-VISITS",
|
|
[GuidanceRow("HOME-VISITS", "99341", "cfr", "42 CFR 425.20", "", key, 2, 100)],
|
|
)
|
|
rows, counts = family_origin(
|
|
c, s, FAM, ask=ask, log=RunLog(tmp_path / "l.jsonl"), books=[]
|
|
)
|
|
fr = [r for r in rows if r.cite_kind == "fr_para"]
|
|
assert [(r.code, r.item_key, r.p_id, r.ref_p_id, r.page_label) for r in fr] == [
|
|
("99341", key, 2, 2, "100")
|
|
]
|
|
assert fr[0].locator == "87 FR 100, ¶2" and fr[0].kind == "statute"
|
|
assert [r.cite_kind for r in rows if r.extractor == "guidance"] == ["cfr"]
|
|
assert counts == {"done": 2, "skipped": 0, "failed": 0, "failed_pending": 0}
|
|
|
|
|
|
def _chain_store(tmp_path, paras, *, volume="87"):
|
|
s = Store(":memory:", storage_dir=tmp_path / "st")
|
|
key = s.create(
|
|
Rule(title=TITLE, document_number="22-1", date_published="2022-11-18")
|
|
)
|
|
s._con().executemany(
|
|
"INSERT INTO fr_anchors (item_key, p_id, page, ordinal, text) VALUES (?,?,?,?,?)",
|
|
[(key, p, pg, p, t) for p, pg, t in paras],
|
|
)
|
|
if volume:
|
|
s._con().execute(
|
|
"INSERT INTO fr_anchor_docs (item_key, document_number, html_url, start_page,"
|
|
" end_page, fr_volume) VALUES (?,?,'u',1,2,?)",
|
|
(key, "22-1", volume),
|
|
)
|
|
s._con().commit()
|
|
return s, key
|
|
|
|
|
|
def test_the_reference_may_come_from_a_context_paragraph(tmp_path):
|
|
s, key = _chain_store(
|
|
tmp_path,
|
|
[
|
|
(
|
|
8,
|
|
100,
|
|
"Section 1833(x)(2)(B) of the Act defines primary care services:",
|
|
),
|
|
(
|
|
9,
|
|
101,
|
|
"( printed page 101) The benefit covers CPT codes 99341 through 99350.",
|
|
),
|
|
],
|
|
)
|
|
it = item(
|
|
reference="Section 1833(x)(2)(B) of the Act",
|
|
codes=["99341", "99342", "99350"],
|
|
summary="A payment incentive for primary care.",
|
|
)
|
|
calls: list = []
|
|
rows, _ = family_origin(
|
|
_con(), s, FAM, ask=ask_with([it], calls), log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
assert {(r.code, r.p_id, r.ref_p_id, r.kind) for r in rows} == {
|
|
("99341", 9, 8, "statute"),
|
|
("99342", 9, 8, "statute"),
|
|
("99350", 9, 8, "statute"),
|
|
}
|
|
r = rows[0]
|
|
assert r.page_label == "100" and r.locator == "87 FR 100, ¶9 (reference ¶8)"
|
|
# the model saw the preceding paragraph as context
|
|
assert "[¶8] Section 1833(x)(2)(B)" in calls[-1][1]
|
|
assert "PASSAGE:" in calls[-1][1]
|
|
|
|
|
|
def test_a_reference_in_a_context_paragraph_needs_the_codes_in_the_passage(tmp_path):
|
|
s, key = _chain_store(
|
|
tmp_path,
|
|
[
|
|
(8, 100, "Section 1833(x)(2)(B) of the Act defines primary care services:"),
|
|
(9, 101, "The benefit covers CPT codes 99200 through 99210 and 99350."),
|
|
],
|
|
)
|
|
it = item(
|
|
reference="Section 1833(x)(2)(B) of the Act",
|
|
codes=["99341"],
|
|
summary="A payment incentive.",
|
|
)
|
|
rows, _ = family_origin(
|
|
_con(), s, FAM, ask=ask_with([it]), log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
assert rows == []
|
|
|
|
|
|
def test_fr_page_is_the_printed_page_before_the_reference_sentence(tmp_path):
|
|
text = (
|
|
"We note the earlier meeting. ( printed page 45883) The CPT Editorial "
|
|
"Panel deleted CPT code 99341 in 2021."
|
|
)
|
|
s, key = _chain_store(tmp_path, [(5, 45882, text)])
|
|
it = item(
|
|
kind="cpt_panel",
|
|
reference="CPT Editorial Panel",
|
|
codes=["99341"],
|
|
year=2021,
|
|
summary="The panel deleted the code.",
|
|
)
|
|
rows, _ = family_origin(
|
|
_con(), s, FAM, ask=ask_with([it]), log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
assert (rows[0].page_label, rows[0].locator) == ("45883", "87 FR 45883, ¶5")
|
|
# no volume on file: no volume in the locator; no marker: the paragraph page
|
|
s2, _ = _chain_store(
|
|
tmp_path / "b",
|
|
[(5, 45882, text.replace("( printed page 45883)", ""))],
|
|
volume="",
|
|
)
|
|
rows, _ = family_origin(
|
|
_con(), s2, FAM, ask=ask_with([it]), log=RunLog(tmp_path / "l2"), books=[]
|
|
)
|
|
assert (rows[0].page_label, rows[0].locator) == ("45882", "FR p. 45882, ¶5")
|
|
|
|
|
|
def test_fr_rows_dedupe_across_paragraphs_prefer_the_final_rule(tmp_path):
|
|
s = Store(":memory:", storage_dir=tmp_path / "st")
|
|
prop = s.create(
|
|
Rule(
|
|
title="Medicare Program; CY 2023 PFS Proposed Rule",
|
|
document_number="22-0",
|
|
date_published="2022-07-01",
|
|
)
|
|
)
|
|
final = s.create(
|
|
Rule(title=TITLE, document_number="22-1", date_published="2022-11-18")
|
|
)
|
|
s._con().executemany(
|
|
"INSERT INTO fr_anchors (item_key, p_id, page, ordinal, text) VALUES (?,?,?,?,?)",
|
|
[
|
|
(
|
|
prop,
|
|
3,
|
|
10,
|
|
1,
|
|
"The AMA CPT Editorial Panel deleted CPT code 99341 in 2021.",
|
|
),
|
|
(
|
|
final,
|
|
7,
|
|
20,
|
|
1,
|
|
"The CPT Editorial Panel deleted CPT code 99341 in 2021.",
|
|
),
|
|
(
|
|
final,
|
|
8,
|
|
20,
|
|
2,
|
|
"The CPT Editorial Panel deleted CPT code 99350 in 2021.",
|
|
),
|
|
],
|
|
)
|
|
s._con().commit()
|
|
|
|
def ask(system, user, schema):
|
|
ref = "AMA CPT Editorial Panel" if "AMA CPT" in user else "CPT Editorial Panel"
|
|
return {
|
|
"origins": [
|
|
item(
|
|
kind="cpt_panel",
|
|
reference=ref,
|
|
codes=["99341", "99350"],
|
|
year=2021,
|
|
summary="The panel deleted it.",
|
|
)
|
|
]
|
|
}
|
|
|
|
ask.model = "qwen-test"
|
|
rows, _ = family_origin(
|
|
_con(), s, FAM, ask=ask, log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
got = {(r.code, r.item_key, r.p_id, r.reference) for r in rows}
|
|
# one row per code; the final rule's paragraph wins; "AMA" is normalised away
|
|
assert got == {
|
|
("99341", final, 7, "CPT Editorial Panel"),
|
|
("99350", final, 8, "CPT Editorial Panel"),
|
|
}
|
|
|
|
|
|
def test_family_origin_resumes_and_replays_without_a_model(tmp_path):
|
|
s, key, other = _store(tmp_path)
|
|
epub = make_epub(tmp_path / "b.epub", BOOK_PAGES)
|
|
books = [("BOOK1", "Synthetic Changes 2023", epub)]
|
|
log = RunLog(tmp_path / "l.jsonl")
|
|
calls: list = []
|
|
ask = ask_with([item()], calls)
|
|
first, c1 = family_origin(_con(), s, FAM, ask=ask, log=log, books=books)
|
|
n = len(calls)
|
|
assert c1["done"] == n and n > 0
|
|
# a second run with the same ledger asks nothing
|
|
again, c2 = family_origin(_con(), s, FAM, ask=ask, log=log, books=books)
|
|
assert len(calls) == n and c2["skipped"] == n and again == first
|
|
# no model: the finished ledger is replayed, model rows are kept
|
|
replay, c3 = family_origin(
|
|
_con(),
|
|
s,
|
|
FAM,
|
|
ask=None,
|
|
log=RunLog(tmp_path / "l.jsonl"),
|
|
books=books,
|
|
replay_model="qwen-test",
|
|
)
|
|
assert replay == first and c3 == {
|
|
"done": 0,
|
|
"skipped": 0,
|
|
"failed": 0,
|
|
"failed_pending": 0,
|
|
}
|
|
# no model and nothing to replay: only the deterministic guidance rows
|
|
none, _ = family_origin(_con(), s, FAM, ask=None, log=None, books=books)
|
|
assert none == []
|
|
|
|
|
|
def test_family_origin_ledger_holds_no_copied_book_text(tmp_path):
|
|
s, key, other = _store(tmp_path)
|
|
epub = make_epub(tmp_path / "b.epub", BOOK_PAGES)
|
|
copied = item(
|
|
kind="cpt_panel",
|
|
reference="CPT Editorial Panel",
|
|
codes=["99341"],
|
|
summary="Codes 99341 and 99350 have been revised for 2023 to use total time.",
|
|
)
|
|
log = RunLog(tmp_path / "l.jsonl")
|
|
rows, _ = family_origin(
|
|
_con(),
|
|
s,
|
|
FAM,
|
|
ask=ask_with([copied]),
|
|
log=log,
|
|
books=[("BOOK1", "Synthetic Changes 2023", epub)],
|
|
)
|
|
assert not [r for r in rows if r.cite_kind == "cpt_book"]
|
|
lines = (tmp_path / "l.jsonl").read_text().splitlines()
|
|
book = [ln for ln in lines if '"unit": "book:BOOK1:12' in ln]
|
|
assert book and all("have been revised for 2023 to use" not in ln for ln in book)
|
|
|
|
|
|
def test_family_origin_limit_failed_and_retry(tmp_path):
|
|
s, key, other = _store(tmp_path)
|
|
log = RunLog(tmp_path / "l.jsonl")
|
|
ask = ask_with([item()])
|
|
_, c = family_origin(_con(), s, FAM, ask=ask, log=log, books=[], limit=1)
|
|
assert c["done"] == 1
|
|
failing = lambda sy, u, sc: None # noqa: E731
|
|
failing.model = "qwen-test"
|
|
_, c = family_origin(_con(), s, FAM, ask=failing, log=log, books=[])
|
|
assert c["failed"] == 1 and c["skipped"] == 1
|
|
_, c = family_origin(_con(), s, FAM, ask=failing, log=log, books=[])
|
|
assert c["failed_pending"] == 1
|
|
_, c = family_origin(_con(), s, FAM, ask=ask, log=log, books=[], retry_failed=True)
|
|
assert c["done"] == 1
|
|
|
|
|
|
def test_origin_inventory(tmp_path):
|
|
s, key, other = _store(tmp_path)
|
|
epub = make_epub(tmp_path / "b.epub", BOOK_PAGES)
|
|
paras, pages = origin_inventory(s, FAM, [("BOOK1", "Synthetic Changes 2023", epub)])
|
|
assert len(paras) == 2 and pages == {"BOOK1": ["12", "13"]}
|
|
|
|
|
|
# ── writers ──────────────────────────────────────────────────────────
|
|
|
|
|
|
def _row(**kw):
|
|
base = dict(
|
|
family="HOME-VISITS",
|
|
code="99341",
|
|
kind="statute",
|
|
reference="Section 1861(m) of the Act",
|
|
year=None,
|
|
summary="A paraphrase.",
|
|
cite_kind="fr_para",
|
|
item_key="K",
|
|
p_id=2,
|
|
page_label="100",
|
|
locator="FR p. 100, ¶2",
|
|
extractor="m",
|
|
)
|
|
return OriginRow(**{**base, **kw})
|
|
|
|
|
|
def test_write_read_origin_round_trip_and_replace():
|
|
c = duckdb.connect(":memory:")
|
|
rows = [_row(), _row(code="99350", year=2023), _row(year=1965, reference="A")]
|
|
assert write_origin(c, "HOME-VISITS", rows) == 3
|
|
write_origin(c, "OTHER", [_row(family="OTHER")])
|
|
got = read_origin(c, "HOME-VISITS")
|
|
assert len(got) == 3 and got[0].year is None and got[1].year == 1965
|
|
assert write_origin(c, "HOME-VISITS", [_row()]) == 1
|
|
assert len(read_origin(c, "HOME-VISITS")) == 1 and len(read_origin(c, "OTHER")) == 1
|
|
assert write_origin(c, "HOME-VISITS", []) == 0
|
|
|
|
|
|
def _fr(it, text, **kw):
|
|
return origins_from_output({"origins": [it]}, text, source="fr", codes=CODES, **kw)
|
|
|
|
|
|
def test_kind_follows_what_the_reference_is():
|
|
# a section of the Act is a statute whatever the model called it
|
|
[row] = _fr(item(kind="regulation"), FR_STATUTE)
|
|
assert row["kind"] == "statute"
|
|
text = "Section 425.20 of 42 CFR defines primary care services for CPT code 99341."
|
|
[row] = _fr(item(kind="statute", reference="of 42 CFR"), text)
|
|
assert row["kind"] == "regulation"
|
|
# a statute/regulation claim no reference supports is dropped
|
|
assert (
|
|
_fr(
|
|
item(kind="regulation", reference="the CY 2026 MPFS"),
|
|
"The CY 2026 MPFS covers CPT code 99341.",
|
|
)
|
|
== []
|
|
)
|
|
# a G-code creation must name a HCPCS code
|
|
g = "CMS created HCPCS code G2211 to pay for visits with CPT code 99341."
|
|
assert _fr(item(kind="cms_gcode", reference="HCPCS code G2211"), g)
|
|
assert (
|
|
_fr(
|
|
item(kind="cms_gcode", reference="a new add-on"),
|
|
"A new add-on exists for CPT code 99341.",
|
|
)
|
|
== []
|
|
)
|
|
|
|
|
|
def test_pointers_into_the_same_rule_and_proposals_are_no_origin():
|
|
ptr = "Section II.F. of this proposed rule covers the program for CPT code 99341."
|
|
assert (
|
|
_fr(item(kind="program", reference="section II.F. of this proposed rule"), ptr)
|
|
== []
|
|
)
|
|
text = "The program for CPT code 99341 is described in the telehealth section."
|
|
assert (
|
|
_fr(
|
|
item(
|
|
kind="program",
|
|
reference="The program",
|
|
summary="Proposes to add the codes.",
|
|
),
|
|
text,
|
|
)
|
|
== []
|
|
)
|
|
assert _fr(
|
|
item(kind="program", reference="The program", summary="It adds the codes."),
|
|
text,
|
|
)
|
|
|
|
|
|
def test_codes_must_sit_near_the_sentence_that_writes_the_reference():
|
|
text = (
|
|
"Section 1861(m) of the Act establishes the home health benefit. "
|
|
"That is all it does. Unrelated sentence here. "
|
|
"Later we discuss CPT code 99341 in a different context."
|
|
)
|
|
# 99341 is three sentences from the reference sentence: no origin for it
|
|
assert _fr(item(codes=["99341"]), text) == []
|
|
near = (
|
|
"Section 1861(m) of the Act establishes the home health benefit. "
|
|
"It covers CPT code 99341."
|
|
)
|
|
assert _fr(item(codes=["99341"]), near)
|
|
|
|
|
|
def test_a_summary_naming_codes_applies_only_to_those_codes():
|
|
text = "Section 1861(m) of the Act covers CPT codes 99341, 99342 and 99350."
|
|
out = _fr(
|
|
item(
|
|
codes=["99341", "99342", "99350"],
|
|
summary="The Act covers codes 99341-99342.",
|
|
),
|
|
text,
|
|
)
|
|
assert out[0]["codes"] == ["99341", "99342"]
|
|
# a summary naming no code keeps every named code
|
|
assert _fr(item(codes=["99341", "99350"]), text)[0]["codes"] == ["99341", "99350"]
|
|
|
|
|
|
def test_book_rows_are_one_per_code_kind_action_on_the_subject_page(tmp_path):
|
|
s, key, other = _store(tmp_path)
|
|
pages = {
|
|
# conforming change elsewhere: names 99341 but only conforms a section
|
|
"5": "Rationale|In accordance with the revision of codes 99341, the notes were revised.",
|
|
# a section note only (deleted), earlier than the subject page
|
|
"6": "\u25b6(99339 has been deleted. See care codes.)\u25c0|Plain text.",
|
|
# the subject page: a Rationale sentence about the code
|
|
"7": "\u25b6(99339 has been deleted. See care codes.)\u25c0|Rationale|Codes 99341 and 99350 have been revised for 2023.|Code 99343 has been deleted.",
|
|
# a later restatement
|
|
"8": "Rationale|Code 99341 has been revised to use time.",
|
|
}
|
|
epub = make_epub(tmp_path / "b.epub", pages)
|
|
it = item(
|
|
kind="cpt_panel",
|
|
reference="CPT Editorial Panel action",
|
|
codes=["99341", "99350", "99343", "99339"],
|
|
summary="Editors revised the visit codes to be reported by time.",
|
|
)
|
|
fam = SimpleNamespace(
|
|
key="HOME-VISITS", codes=("99341", "99350", "99339", "99343"), synonyms=()
|
|
)
|
|
rows, _ = family_origin(
|
|
_con(),
|
|
s,
|
|
fam,
|
|
ask=ask_with([it]),
|
|
log=RunLog(tmp_path / "l"),
|
|
books=[("B", "Synthetic Changes 2023", epub)],
|
|
)
|
|
book = {(r.code, r.page_label) for r in rows if r.cite_kind == "cpt_book"}
|
|
# revised: the subject page 7 (not the conforming 5, not the later 8);
|
|
# the deleted codes are never "revised"
|
|
assert book == {("99341", "7"), ("99350", "7")}
|
|
gone = {**it, "summary": "Editors deleted these codes."}
|
|
rows, _ = family_origin(
|
|
_con(),
|
|
s,
|
|
fam,
|
|
ask=ask_with([gone]),
|
|
log=RunLog(tmp_path / "l2"),
|
|
books=[("B", "Synthetic Changes 2023", epub)],
|
|
)
|
|
book = {(r.code, r.page_label) for r in rows if r.cite_kind == "cpt_book"}
|
|
# a Rationale sentence beats a section note; 99339 has only the note (page 6 first)
|
|
assert book == {("99343", "7"), ("99339", "6")}
|
|
|
|
|
|
def test_book_support_levels_and_guards():
|
|
deleted = {"99339", "99343"}
|
|
page = (
|
|
"\u25b6(99339 has been deleted. For services see other codes.)\u25c0\n"
|
|
"Rationale\n"
|
|
"Code 99343 has been deleted. Codes 99341-99343 have been revised for time. "
|
|
"In accordance with the deletion of codes 99350, the notes were revised. "
|
|
"Code 99350 has been revised."
|
|
)
|
|
assert book_support(page, "99339", deleted) == {"deleted": "note"}
|
|
assert book_support(page, "99343", deleted) == {"deleted": "own"} # never revised
|
|
assert book_support(page, "99341", deleted) == {"revised": "own"}
|
|
# deleted only when the book marks the code deleted
|
|
assert book_support(page, "99350", deleted) == {"revised": "own"}
|
|
assert book_support(page, "99342", deleted) == {"revised": "own"}
|
|
# no heading and no note: nothing; an index page: nothing
|
|
assert book_support("99341 Home visit, 20 minutes.", "99341", deleted) == {}
|
|
index = (
|
|
" ".join(f"{n} (deleted) 1, 2" for n in range(99320, 99326))
|
|
+ " Rationale 99341 revised"
|
|
)
|
|
assert book_support(index, "99341", deleted) == {}
|
|
# a sentence with a verb for another code only
|
|
assert book_support("Rationale\nCode 99342 has been revised.", "99341", ()) == {}
|
|
|
|
|
|
def test_book_deleted_and_action_of():
|
|
pages = [
|
|
("1", "Codes 99324-99328 and 99334, 99336 have been deleted."),
|
|
(
|
|
"2",
|
|
"In accordance with the deletion of codes 99339 and 99340, the note changed.",
|
|
),
|
|
("3", "99350 (deleted) 4, 5 and 99351 (revised) 6"),
|
|
("4", "Codes 99360 through 99362 have been deleted."),
|
|
]
|
|
assert book_deleted(pages) == {
|
|
"99324", "99325", "99326", "99327", "99328", "99334", "99336",
|
|
"99339", "99340", "99350", "99360", "99361", "99362",
|
|
} # fmt: skip
|
|
assert action_of("Deleted the codes.") == "deleted"
|
|
assert action_of("Revised and combined codes.") == "revised"
|
|
assert action_of("The panel added a new code.") == "created"
|
|
assert action_of("Editors did something.") is None
|
|
|
|
|
|
def test_anchor_keeps_trims_or_rejects_the_models_wording():
|
|
text = "In February 2021 the CPT Editorial Panel deleted codes under section 1861(m) of the Act."
|
|
# verbatim
|
|
assert _anchor("CPT Editorial Panel", text) == (
|
|
"CPT Editorial Panel",
|
|
"CPT Editorial Panel",
|
|
)
|
|
# the longest verbatim span of the wording that holds the body is stored
|
|
# ("decision"/"meeting" are not in the text, so they are never stored)
|
|
assert _anchor("CPT Editorial Panel meeting in February 2021", text) == (
|
|
"CPT Editorial Panel",
|
|
"CPT Editorial Panel",
|
|
)
|
|
assert _anchor("the CPT Editorial Panel deleted codes yesterday", text) == (
|
|
"the CPT Editorial Panel deleted codes",
|
|
"CPT Editorial Panel",
|
|
)
|
|
assert _longest_span("CPT Editorial Panel x", "CPT Editorial Panel", "y") == (
|
|
"CPT Editorial Panel"
|
|
)
|
|
# the body inside the wording has other casing: nothing verbatim around it
|
|
assert _longest_span("a CPT editorial panel b", "CPT Editorial Panel", "x") == (
|
|
"CPT Editorial Panel"
|
|
)
|
|
# else trimmed to the longest verbatim body
|
|
assert _anchor("CPT Editorial Panel meeting in March 2021", text) == (
|
|
"CPT Editorial Panel",
|
|
"CPT Editorial Panel",
|
|
)
|
|
assert _anchor("under section 1861(m) of the Act, as amended", text)[1] == (
|
|
"section 1861(m) of the Act"
|
|
)
|
|
cfr = "The rule at 42 CFR 425.400(c) says so, and § 425.20 too."
|
|
assert _anchor("the 42 CFR 425.400(c)(1) rule", cfr) == (
|
|
"42 CFR 425.400(c)",
|
|
"42 CFR 425.400(c)",
|
|
)
|
|
assert (
|
|
_anchor("the rule in § 425.20(a)", cfr) is None
|
|
) # never cut to a bare section
|
|
# "of the Act" cut off; a deeper subsection the text lacks is no match
|
|
assert (
|
|
_anchor("section 1861(m) as amended", "See section 1861(m) there.")[1]
|
|
== "section 1861(m)"
|
|
)
|
|
assert _anchor("section 1861(s)(1) of the Act", text) is None
|
|
assert _anchor("a program we never name", text) is None
|
|
assert _anchor("the Act of 1965 and section 1861(m)", "Nothing about it.") is None
|
|
|
|
|
|
def test_kind_of_settles_statute_and_regulation_by_reference():
|
|
k = lambda kind, ref: _kind_of({"kind": kind, "reference": ref}) # noqa: E731
|
|
assert k("regulation", "42 U.S.C. § 1395x") == "statute" # bare § is not CFR
|
|
assert k("statute", "section 1866E of the Act") == "statute"
|
|
assert k("statute", "42 CFR 425.20") == "regulation"
|
|
assert k("regulation", "§ 425.20") == "regulation"
|
|
assert k("regulation", "the CY 2026 MPFS") is None
|
|
assert k("statute", "§ 1395x") is None
|
|
# a program that cites the Act stays a program
|
|
assert k("program", "the program under section 1866E of the Act") == "program"
|
|
assert k("demonstration", "Independence at Home") == "demonstration"
|
|
assert k("cms_gcode", "a new add-on") is None
|
|
assert k("cms_gcode", "HCPCS code G2211") == "cms_gcode"
|
|
|
|
|
|
def test_page_of_falls_back_when_the_anchor_is_split_by_a_marker():
|
|
text = "The CPT ( printed page 9) Editorial Panel acted."
|
|
assert _page_of(text, "CPT Editorial Panel", 5) == 5
|
|
assert _page_of(text, "The CPT", 5) == 5
|
|
assert (
|
|
_page_of("( printed page 7) The CPT Editorial Panel", "CPT Editorial Panel", 5)
|
|
== 7
|
|
)
|
|
|
|
|
|
def test_ensure_tables_adds_ref_p_id_to_an_old_table():
|
|
c = duckdb.connect(":memory:")
|
|
c.execute("CREATE SCHEMA pfs")
|
|
c.execute(
|
|
"CREATE TABLE pfs.code_origin (family VARCHAR, code VARCHAR, kind VARCHAR, "
|
|
"reference VARCHAR, year INTEGER, summary VARCHAR, cite_kind VARCHAR, "
|
|
"item_key VARCHAR, p_id INTEGER, page_label VARCHAR, locator VARCHAR, "
|
|
"extractor VARCHAR)"
|
|
)
|
|
c.execute(
|
|
"INSERT INTO pfs.code_origin VALUES ('F','99341','statute','r',NULL,'s',"
|
|
"'fr_para','K',1,'2','l','x')"
|
|
)
|
|
ensure_tables(c)
|
|
[row] = read_origin(c, "F")
|
|
assert row.ref_p_id == 0 and row.code == "99341"
|
|
|
|
|
|
def test_a_sentence_proposing_now_states_no_origin_but_finalizing_one_does():
|
|
prop = "We are proposing to create HCPCS code G2211 for CPT code 99341."
|
|
it = item(
|
|
kind="cms_gcode", reference="HCPCS code G2211", summary="The code exists."
|
|
)
|
|
assert _fr(it, prop) == []
|
|
fin = "We proposed, and are finalizing, HCPCS code G2211 for CPT code 99341."
|
|
assert _fr(it, fin)
|
|
process = item(
|
|
kind="cms_gcode",
|
|
reference="HCPCS code G2211",
|
|
summary="Panels are reviewing the codes.",
|
|
)
|
|
assert _fr(process, fin) == []
|
|
|
|
|
|
def test_the_most_comprehensive_final_statement_wins(tmp_path):
|
|
s, key = _chain_store(
|
|
tmp_path,
|
|
[
|
|
(3, 10, "The CPT Editorial Panel deleted CPT code 99341 in 2021."),
|
|
(
|
|
4,
|
|
11,
|
|
"The CPT Editorial Panel deleted CPT codes 99341, 99342 and 99350 in 2021.",
|
|
),
|
|
],
|
|
)
|
|
|
|
def ask(system, user, schema):
|
|
codes = ["99341", "99342", "99350"] if "99342" in user else ["99341"]
|
|
return {
|
|
"origins": [
|
|
item(
|
|
kind="cpt_panel",
|
|
reference="CPT Editorial Panel",
|
|
codes=codes,
|
|
year=2021,
|
|
summary="The panel deleted them.",
|
|
)
|
|
]
|
|
}
|
|
|
|
ask.model = "qwen-test"
|
|
rows, _ = family_origin(
|
|
_con(), s, FAM, ask=ask, log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
assert {(r.code, r.p_id) for r in rows} == {
|
|
("99341", 4),
|
|
("99342", 4),
|
|
("99350", 4),
|
|
}
|
|
|
|
|
|
def test_context_reaches_three_paragraphs_back(tmp_path):
|
|
s, key = _chain_store(
|
|
tmp_path,
|
|
[
|
|
(1, 10, "Section 1833(x)(2)(B) of the Act defines primary care services:"),
|
|
(2, 10, "99201 through 99215 for office visits;"),
|
|
(3, 10, "99304 through 99340 for nursing facility visits; and"),
|
|
(4, 10, "99341 through 99350 for new and established patient home visits."),
|
|
],
|
|
)
|
|
it = item(
|
|
reference="Section 1833(x)(2)(B) of the Act",
|
|
codes=["99341"],
|
|
summary="Defines primary care services.",
|
|
)
|
|
rows, _ = family_origin(
|
|
_con(), s, FAM, ask=ask_with([it]), log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
assert {(r.p_id, r.ref_p_id) for r in rows} == {(4, 1)}
|
|
|
|
|
|
def test_fr_rows_with_different_years_are_both_kept(tmp_path):
|
|
s, key = _chain_store(
|
|
tmp_path,
|
|
[
|
|
(3, 10, "In February 2021 the CPT Editorial Panel deleted CPT code 99341."),
|
|
(4, 11, "Effective 2023 the CPT Editorial Panel deleted CPT code 99341."),
|
|
],
|
|
)
|
|
|
|
def ask(system, user, schema):
|
|
year = 2023 if "Effective 2023" in user.split("PASSAGE:")[1] else 2021
|
|
return {
|
|
"origins": [
|
|
item(
|
|
kind="cpt_panel",
|
|
reference="CPT Editorial Panel",
|
|
codes=["99341"],
|
|
year=year,
|
|
summary="The panel deleted it.",
|
|
)
|
|
]
|
|
}
|
|
|
|
ask.model = "qwen-test"
|
|
rows, _ = family_origin(
|
|
_con(), s, FAM, ask=ask, log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
assert {(r.p_id, r.year) for r in rows} == {(3, 2021), (4, 2023)}
|
|
|
|
|
|
def test_a_model_range_entry_lists_every_code_in_it():
|
|
text = "Section 1861(m) of the Act covers CPT codes 99341 through 99350."
|
|
out = _fr(item(codes=["99341-99342", "99350", "99999"]), text)
|
|
assert out[0]["codes"] == ["99341", "99342", "99350"]
|
|
|
|
|
|
def test_only_a_paragraph_that_introduces_a_list_lends_its_reference(tmp_path):
|
|
s, key = _chain_store(
|
|
tmp_path,
|
|
[
|
|
(8, 100, "Section 1833(x)(2)(B) of the Act establishes the incentive."),
|
|
(9, 101, "The benefit covers CPT codes 99341 through 99350."),
|
|
],
|
|
)
|
|
it = item(
|
|
reference="Section 1833(x)(2)(B) of the Act",
|
|
codes=["99341"],
|
|
summary="A payment incentive.",
|
|
)
|
|
rows, _ = family_origin(
|
|
_con(), s, FAM, ask=ask_with([it]), log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
assert rows == [] # paragraph 8 does not introduce what follows
|
|
|
|
|
|
def test_telehealth_listing_regulation_is_no_origin():
|
|
text = "Under 42 CFR 410.78 we added CPT code 99341 to the telehealth list."
|
|
it = item(
|
|
kind="regulation", reference="42 CFR 410.78", summary="It lists the code."
|
|
)
|
|
assert _fr(it, text) == []
|
|
|
|
|
|
def test_a_reference_in_the_passage_is_backed_by_the_passage_alone(tmp_path):
|
|
# 73DU6WUG-style: the section is cited in the passage; the context
|
|
# paragraph's wording and numbers must not leak into the summary
|
|
s, key = _chain_store(
|
|
tmp_path,
|
|
[
|
|
(1, 10, "Rural clinics may bill 5000 services under the plan."),
|
|
(
|
|
2,
|
|
10,
|
|
"Section 1899(c) of the Act assigns beneficiaries; see CPT code 99341.",
|
|
),
|
|
],
|
|
)
|
|
leak = item(
|
|
reference="section 1899(c) of the Act",
|
|
codes=["99341"],
|
|
summary="It allows 5000 services by rural clinics.",
|
|
)
|
|
own = item(
|
|
reference="section 1899(c) of the Act",
|
|
codes=["99341"],
|
|
summary="It assigns beneficiaries to the program.",
|
|
)
|
|
rows, _ = family_origin(
|
|
_con(), s, FAM, ask=ask_with([leak, own]), log=RunLog(tmp_path / "l"), books=[]
|
|
)
|
|
assert [r.summary for r in rows] == [own["summary"]]
|
|
# lent by a context paragraph, the context may back the summary
|
|
s2, key = _chain_store(
|
|
tmp_path / "b",
|
|
[
|
|
(
|
|
1,
|
|
10,
|
|
"Section 1899(c) of the Act assigns 5000 beneficiaries, as follows:",
|
|
),
|
|
(2, 10, "The benefit counts CPT code 99341."),
|
|
],
|
|
)
|
|
lent = {**own, "summary": "It assigns 5000 beneficiaries using the codes."}
|
|
rows, _ = family_origin(
|
|
_con(), s2, FAM, ask=ask_with([lent]), log=RunLog(tmp_path / "l2"), books=[]
|
|
)
|
|
assert len(rows) == 1 and rows[0].ref_p_id == 1
|