Files
stack/tests/pfs/test_commenters.py
kert 6c4587d0be
Some checks failed
CI / lint (push) Successful in 47s
CI / notebooks-smoke (push) Successful in 2m0s
Deploy / changes (push) Successful in 18s
CI / test (push) Failing after 3m29s
Infra CI / zotero (push) Successful in 29s
Infra CI / notebooks (push) Successful in 1m43s
Infra CI / api (push) Successful in 2m24s
Infra CI / docs (push) Successful in 3m26s
Infra CI / llm (push) Successful in 1m40s
Infra CI / mc (push) Successful in 20s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Infra CI / bcda (push) Successful in 2m59s
Deploy / docs (push) Successful in 1m18s
Deploy / api (push) Successful in 2m31s
Deploy / bcda (push) Successful in 3m11s
Deploy / mc (push) Has been skipped
Deploy / llm (push) Successful in 2m16s
Deploy / report (push) Successful in 1m12s
feat(pfs): commenters carry the submitter name as posted (R60), org names no longer blanked, identity-v5
2026-11-12 16:14:41 -05:00

860 lines
28 KiB
Python

"""pfs.commenters — commenter organization, type, specialty, campaigns (task 8)."""
from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import MagicMock
import duckdb
import httpx
import pytest
from bib.item import Source
from bib.store import Store
from pfs.codetables import (
CommenterRow,
ensure_tables,
read_commenters,
write_commenters,
)
from pfs.commenters import (
CATEGORY_MAP,
IDENTITY_PROMPT_VERSION,
ORG_TYPES,
MirrorUnavailable,
RegsFields,
family_comment_items,
form_groups,
identify,
item_openings,
letterhead_identity,
regs_fields,
specialty_key,
specialty_vocab,
)
from pfs.runlog import RunLog
HOME = SimpleNamespace(
key="HOME-VISITS", codes=("99341", "99342"), pair_phrases=("home or residence",)
)
class FakeMirror:
def __init__(self, attrs=None, per_id=None, fail=False):
self.attrs = attrs or {}
self.per_id = per_id or {}
self.fail = fail
def get_json(self, docket, cid):
if self.fail:
raise RuntimeError("404")
return {"data": {"attributes": self.per_id.get(cid, self.attrs)}}
class FakeAsk:
model = "qwen-test"
def __init__(self, reply):
self.reply = reply
self.calls = []
def __call__(self, system, user, schema):
self.calls.append((system, user, schema))
return self.reply
def engine_with(rows, openings=None, extra=()):
"""Fake pgvector engine that emulates the two chunk queries. *rows* are
the family-matching ``(item_key, text)`` chunks (late in the comment);
*openings* ``{item_key: [chunk texts]}`` are non-matching opening chunks of
the inline comment; *extra* are further ``(item, section, seq, doc,
matched)`` chunks. The query that binds ``keys`` is the openings query."""
engine = MagicMock()
conn = engine.begin.return_value.__enter__.return_value
chunks = [(k, "Inline comment", 100, t, True) for k, t in rows]
for k, parts in (openings or {}).items():
chunks += [(k, "Inline comment", i, t, False) for i, t in enumerate(parts)]
chunks += list(extra)
def execute(sql, params):
by_sec = {}
for c in sorted(chunks, key=lambda c: c[2]):
by_sec.setdefault((c[0], c[1]), []).append(c)
if "keys" in params:
scope = set(params["keys"])
else:
scope = {c[0] for c in chunks if c[4]}
out = []
for secs in by_sec.values():
for rs, c in enumerate(secs, 1):
matched = c[4] and "keys" not in params
if c[0] in scope and (rs <= 6 or matched):
out.append((c[0], c[1], c[2], c[3], rs, matched))
result = MagicMock()
result.fetchall.return_value = sorted(out, key=lambda r: (r[0], r[2]))
return result
conn.execute.side_effect = execute
return engine, conn
@pytest.fixture
def con():
c = duckdb.connect(":memory:")
ensure_tables(c)
c.execute("CREATE SCHEMA terminology")
c.execute(
"CREATE TABLE terminology.cms_acceptable_provider_specialty_codes "
"(specialty_code VARCHAR, specialty_description VARCHAR, "
"pecos_specialty_description VARCHAR)"
)
c.executemany(
"INSERT INTO terminology.cms_acceptable_provider_specialty_codes "
"VALUES (?,?,?)",
[
("08", "Family Practice", ""),
("11", "Internal Medicine", ""),
("01", "Allergy/Immunology", ""),
("x", None, ""),
],
)
yield c
c.close()
@pytest.fixture
def store():
s = Store(":memory:")
for i, k in enumerate(["K1", "K2", "K3", "K4", "NOURL"]):
url = (
"https://example.org/nothing"
if k == "NOURL"
else f"https://www.regulations.gov/comment/CMS-X-{i}"
)
s.create(Source(key=k, title=f"T{k}", url=url))
return s
def _reply(**kw):
base = {
"organization": "",
"org_type": "unknown",
"specialty": "NONE",
"on_behalf_of_organization": False,
}
return {**base, **kw}
def _run(tmp_path, con, store, mirror, ask, rows, key="K1"):
return identify(
engine_with(rows)[0],
store,
con,
HOME,
docket="CMS-2022-0113",
mirror=mirror,
ask=ask,
log=RunLog(tmp_path / "l.jsonl"),
)
def test_submitter_name_is_the_comments_own_posted_name(tmp_path, con, store):
mirror = FakeMirror(
{
"category": "Physician",
"organization": "",
"firstName": " Jane ",
"lastName": "Doe",
}
)
ask = FakeAsk(_reply(org_type="individual_clinician", specialty="FAMILY PRACTICE"))
rows, _ = _run(tmp_path, con, store, mirror, ask, [("K1", "Sincerely, Jane Doe")])
assert rows[0].submitter_name == "Jane Doe"
assert (rows[0].org_type, rows[0].org_name) == ("individual_clinician", "")
write_commenters(con, rows)
assert read_commenters(con)[0].submitter_name == "Jane Doe"
def test_two_comments_never_swap_names(tmp_path, con, store):
# the fake store gives K1 .../CMS-X-0 and K2 .../CMS-X-1
mirror = FakeMirror(
per_id={
"CMS-X-0": {"category": "Individual", "firstName": "Ann", "lastName": "A"},
"CMS-X-1": {"category": "Individual", "firstName": "Bob", "lastName": "B"},
}
)
ask = FakeAsk(_reply(org_type="individual"))
rows, _ = _run(
tmp_path, con, store, mirror, ask, [("K1", "letter one"), ("K2", "letter two")]
)
assert {r.item_key: r.submitter_name for r in rows} == {
"K1": "Ann A",
"K2": "Bob B",
}
assert {r.item_key: r.comment_id for r in rows} == {
"K1": "CMS-X-0",
"K2": "CMS-X-1",
}
def test_solo_practice_keeps_org_name(tmp_path, con, store):
mirror = FakeMirror(
{"category": "", "organization": "", "firstName": "Jane", "lastName": "Doe"}
)
ask = FakeAsk(
_reply(
organization="Doe Family Medicine PLLC",
org_type="health_system",
on_behalf_of_organization=True,
)
)
rows, _ = _run(
tmp_path,
con,
store,
mirror,
ask,
[("K1", "Letter of Doe Family Medicine PLLC about visits")],
)
assert (rows[0].org_name, rows[0].org_type) == (
"Doe Family Medicine PLLC",
"health_system",
)
assert rows[0].submitter_name == "Jane Doe"
assert rows[0].basis == "letterhead_model"
def test_regs_organization_is_kept_even_with_submitter_name():
m = FakeMirror(
{
"category": "Individual",
"organization": "Doe Clinic",
"firstName": "Jane",
"lastName": "Doe",
}
)
assert regs_fields(m, "D", "c") == RegsFields(
"Individual", "Doe Clinic", "Jane Doe"
)
def test_missing_name_is_blank():
for attrs in ({}, {"firstName": None, "lastName": ""}, {"firstName": " "}):
assert regs_fields(FakeMirror(attrs), "D", "c").submitter_name == ""
assert (
regs_fields(FakeMirror({"lastName": "Doe"}), "D", "c").submitter_name == "Doe"
)
def test_regs_fields_fails_closed_on_unreadable_json():
with pytest.raises(MirrorUnavailable):
regs_fields(FakeMirror(fail=True), "D", "c")
odd = MagicMock()
odd.get_json.return_value = {"data": {"attributes": "x"}}
with pytest.raises(MirrorUnavailable):
regs_fields(odd, "D", "c")
odd.get_json.return_value = None
with pytest.raises(MirrorUnavailable):
regs_fields(odd, "D", "c")
# a one-letter name never matches ("A" in "A Plus Health")
m = FakeMirror({"organization": "A Plus Health", "firstName": "A"})
assert regs_fields(m, "D", "c").organization == "A Plus Health"
def test_org_must_appear_in_text():
ask = FakeAsk(
_reply(
organization="AAFP",
org_type="specialty_society",
on_behalf_of_organization=True,
)
)
out = letterhead_identity(
"Dear CMS, we write about visits.", ask, specialties=("X",)
)
assert out["organization"] == ""
assert out["org_type"] == "specialty_society"
# present in text (case/whitespace-normalized) -> kept
out = letterhead_identity("Dear CMS, the aafp writes", ask, specialties=("X",))
assert out["organization"] == "AAFP"
# present only in the regulations.gov organization -> kept
out = letterhead_identity("x", ask, specialties=("X",), reg_org="AAFP Inc")
assert out["organization"] == "AAFP"
def test_org_blanked_when_not_on_behalf_or_person_type_or_after_1200():
base = dict(organization="AAFP", org_type="specialty_society")
t = "AAFP letter"
for reply in (
_reply(**base, on_behalf_of_organization=False),
_reply(
organization="AAFP", org_type="individual", on_behalf_of_organization=True
),
):
assert (
letterhead_identity(t, FakeAsk(reply), specialties=("X",))["organization"]
== ""
)
late = "z" * 1200 + " AAFP"
ask = FakeAsk(_reply(**base, on_behalf_of_organization=True))
assert letterhead_identity(late, ask, specialties=("X",))["organization"] == ""
assert len(ask.calls[0][1]) == 1200
def test_letterhead_normalizes_bad_enums_and_none():
out = letterhead_identity(
"t",
FakeAsk(_reply(org_type="bogus", specialty="NOPE")),
specialties=("FAMILY PRACTICE",),
)
assert (out["org_type"], out["specialty"]) == ("unknown", "NONE")
assert letterhead_identity("t", FakeAsk(None), specialties=()) is None
schema = FakeAsk(None)
letterhead_identity("t", schema, specialties=("A", "B"))
props = schema.calls[0][2]["properties"]
assert props["specialty"]["enum"] == ["A", "B", "NONE"]
assert tuple(props["org_type"]["enum"]) == ORG_TYPES
def test_category_map_precedence(tmp_path, con, store):
mirror = FakeMirror({"category": "Insurance", "organization": "Acme Health Plan"})
ask = FakeAsk(
_reply(
organization="Vendor Inc", org_type="vendor", on_behalf_of_organization=True
)
)
rows, _ = identify(
engine_with([("K1", "Vendor Inc writes")])[0],
store,
con,
HOME,
docket="D",
mirror=mirror,
ask=ask,
log=RunLog(tmp_path / "l.jsonl"),
)
assert (rows[0].org_type, rows[0].basis) == ("payer", "regs_gov_fields")
assert rows[0].org_name == "Acme Health Plan"
assert rows[0].reg_category == "Insurance"
assert CATEGORY_MAP["Insurance"] == "payer"
def test_basis_both_and_unknown(tmp_path, con, store):
mirror = FakeMirror({"category": "Insurance"})
ask = FakeAsk(_reply(org_type="payer", specialty="MULTISPECIALTY"))
rows, _ = identify(
engine_with([("K1", "a")])[0],
store,
con,
HOME,
docket="D",
mirror=mirror,
ask=ask,
log=RunLog(tmp_path / "a.jsonl"),
)
assert rows[0].basis == "both" and rows[0].specialty == "MULTISPECIALTY"
# no model answer and no mapped category: unknown, regs basis, extractor regs
rows, counts = identify(
engine_with([("K1", "a")])[0],
store,
con,
HOME,
docket="D",
mirror=FakeMirror({"category": "Weird"}),
ask=FakeAsk(None),
log=RunLog(tmp_path / "b.jsonl"),
)
assert (rows[0].org_type, rows[0].basis, rows[0].extractor) == (
"unknown",
"regs_gov_fields",
"regs",
)
assert counts["failed"] == 1 and counts["pending"] == 0
def test_resume_limit_and_pending(tmp_path, con, store):
rows_in = [("K1", "one"), ("K2", "two"), ("K3", "three")]
ask = FakeAsk(_reply(org_type="other"))
log = RunLog(tmp_path / "l.jsonl")
seen = []
rows, counts = identify(
engine_with(rows_in)[0],
store,
con,
HOME,
docket="D",
mirror=FakeMirror(),
ask=ask,
log=log,
limit=2,
progress=lambda u, s: seen.append((u, s)),
)
assert counts["done"] == 2 and counts["pending"] == 1 and len(ask.calls) == 2
assert seen == [("K1", "done"), ("K2", "done")]
assert rows[2].extractor == "regs"
rows, counts = identify(
engine_with(rows_in)[0],
store,
con,
HOME,
docket="D",
mirror=FakeMirror(),
ask=ask,
log=RunLog(tmp_path / "l.jsonl"),
)
assert (counts["skipped"], counts["done"], counts["pending"]) == (2, 1, 0)
assert rows[0].extractor == f"model:qwen-test@{IDENTITY_PROMPT_VERSION}"
def test_comment_id_from_store_url(tmp_path, con, store):
rows, _ = identify(
engine_with([("K2", "a"), ("NOURL", "b"), ("MISSING", "c")])[0],
store,
con,
HOME,
docket="D",
mirror=FakeMirror(),
ask=FakeAsk(None),
log=RunLog(tmp_path / "l.jsonl"),
)
by = {r.item_key: r.comment_id for r in rows}
assert by == {"K2": "CMS-X-1", "NOURL": "", "MISSING": ""}
def test_form_groups_marks_campaign(tmp_path, con, store):
base = (
"I am writing to urge CMS to protect home visits for homebound older adults. "
"These services keep patients out of the hospital and must be paid fairly. "
"Please finalize the proposed rule without cuts to the home visit codes. "
)
texts = {
"K1": base + "Alpha",
"K2": base + "Beta",
"K3": base + "Gamma",
"K4": "short",
}
out = form_groups(texts)
assert set(out) == {"K1", "K2", "K3"}
assert {v for v in out.values()} == {("K1", 3)}
# two copies are not a campaign
assert form_groups({"K1": base, "K2": base}) == {}
rows, _ = identify(
engine_with(sorted(texts.items()))[0],
store,
con,
HOME,
docket="D",
mirror=FakeMirror(),
ask=FakeAsk(None),
log=RunLog(tmp_path / "l.jsonl"),
)
assert [(r.form_group, r.form_size) for r in rows] == [
("K1", 3),
("K1", 3),
("K1", 3),
("", 0),
]
def test_specialty_key_normalizes():
assert specialty_key("Allergy/Immunology") == "ALLERGY IMMUNOLOGY"
assert specialty_key("Hospice & Palliative Care") == "HOSPICE AND PALLIATIVE CARE"
assert specialty_key(None) == ""
def test_specialty_vocab(con):
assert specialty_vocab(con) == (
"ALLERGY IMMUNOLOGY",
"FAMILY PRACTICE",
"INTERNAL MEDICINE",
"MULTISPECIALTY",
)
def test_family_comment_items_joins_chunks_and_caps():
engine, conn = engine_with(
[("B", "b1"), ("A", "a1"), ("C", "x" * 4000), ("D", None)],
extra=[("A", "Inline comment", 101, "a2", True)],
)
out = family_comment_items(
engine, ["99341"], "home-visits", ["Home (or) residence"], docket="D1"
)
assert out == [("A", "a1\na2"), ("B", "b1"), ("C", "x" * 3000), ("D", "")]
sql, params = conn.execute.call_args.args
assert "~*" in str(sql) and params["docket"] == "D1"
assert params["family_key"] == "HOME-VISITS" and params["codes"] == ["99341"]
assert params["phrase_re"] == r"Home\ \(or\)\ residence"
engine, conn = engine_with([])
family_comment_items(engine, ["99341"], "X", [], docket="D1")
sql, params = conn.execute.call_args.args
assert "~*" not in str(sql) and "phrase_re" not in params
def test_family_comment_items_window_is_configurable():
engine, _ = engine_with([("A", "a1"), ("B", "b" * 9000)])
out = family_comment_items(
engine, ["99341"], "H", [], docket="D1", max_chars=6000, max_chunks=8
)
assert out == [("A", "a1"), ("B", "b" * 6000)]
def test_commenter_table_roundtrip(con):
def row(k, d="D", org="AAFP"):
return CommenterRow(
k, d, "c", org, "specialty_society", "", "", 0, "both", "Business", "regs"
)
assert write_commenters(con, [row("K1"), row("K2")]) == 2
assert write_commenters(con, [row("K1", org="AMA")]) == 1
got = read_commenters(con)
assert [(r.item_key, r.org_name) for r in got] == [("K1", "AMA"), ("K2", "AAFP")]
assert [r.item_key for r in read_commenters(con, ["K2"])] == ["K2"]
assert read_commenters(con, []) == []
assert write_commenters(con, []) == 0
# a docket rewrite drops items the rerun no longer selects
assert write_commenters(con, [row("K3")], docket="D") == 1
assert [r.item_key for r in read_commenters(con)] == ["K3"]
assert write_commenters(con, [row("K4", d="E")], docket="E") == 1
assert [r.item_key for r in read_commenters(con)] == ["K3", "K4"]
def test_category_map_values_are_org_types_and_ambiguous_unmapped():
assert set(CATEGORY_MAP.values()) <= set(ORG_TYPES)
assert CATEGORY_MAP["Physician - HC005"] == "individual_clinician"
assert CATEGORY_MAP["Hospital - HPA35"] == "health_system"
for ambiguous in (
"Health Care Professional/Association - Physician",
"Association - Other",
"Health Care Industry - PI015",
):
assert CATEGORY_MAP.get(ambiguous, "") == ""
def test_identity_input_is_the_opening_not_the_matching_chunk(tmp_path, con, store):
opening = {
"K1": ["## Inline comment", "tMED Coalition writes.", "second chunk"]
+ [f"filler {i}" for i in range(4)]
}
engine, _ = engine_with(
[("K1", "late matching chunk about 99341")], openings=opening
)
ask = FakeAsk(_reply(org_type="other"))
identify(
engine,
store,
con,
HOME,
docket="D",
mirror=FakeMirror(),
ask=ask,
log=RunLog(tmp_path / "l.jsonl"),
)
sent = ask.calls[0][1]
assert sent.startswith("tMED Coalition writes.\nsecond chunk\nfiller 0")
assert "late matching" not in sent and "##" not in sent
assert IDENTITY_PROMPT_VERSION == "identity-v5"
RULE_HDR = (
"## attachment_1.pdf DEPARTMENT OF HEALTH AND HUMAN SERVICES Centers for "
"Medicare & Medicaid Services 42 CFR Parts 405, 410, 411"
)
def rule_upload(key, n=20, mention="99341 home visits"):
"""An attachment that is a copy of the rule: the FR header, then pages
naming the code."""
out = [(key, "attachment_1.pdf", 0, "## attachment_1.pdf", False)]
out.append((key, "attachment_1.pdf", 1, RULE_HDR, False))
out += [
(key, "attachment_1.pdf", 2 + i, f"rule page {i} {mention}", True)
for i in range(n)
]
return out
def test_item_openings_headers_cap_and_rule_upload_left_out():
engine, conn = engine_with(
[],
openings={"A": ["## Inline comment", "x" * 4000], "B": ["a letter"]},
extra=rule_upload("B") + rule_upload("C"),
)
out = item_openings(engine, ["A", "B", "C", "Z"], docket="D1")
assert out["A"].text == "x" * 3000 and not out["A"].rule_copy
assert out["B"].text == "a letter" and out["B"].rule_copy
assert "C" not in out and "Z" not in out # only the rule: nothing of its own
params = conn.execute.call_args.args[1]
assert params["keys"] == ["A", "B", "C", "Z"] and params["docket"] == "D1"
def test_rule_copy_item_whose_inline_text_does_not_match_is_dropped(
tmp_path, con, store
):
engine, _ = engine_with(
[("K1", "a letter naming 99341")],
openings={"K2": ["A psychologists' form letter about medication."]},
extra=rule_upload("K2"),
)
rows, counts = identify(
engine,
store,
con,
HOME,
docket="D",
mirror=FakeMirror(),
ask=FakeAsk(_reply()),
log=RunLog(tmp_path / "l.jsonl"),
)
assert [r.item_key for r in rows] == ["K1"]
assert family_comment_items(engine, ["99341"], "H", [], docket="D") == [
("K1", "a letter naming 99341")
]
def test_rule_copy_item_whose_inline_text_names_the_code_is_kept_without_the_rule(
tmp_path, con, store
):
engine, _ = engine_with(
[("K2", "We support 99341 payment.")],
openings={"K2": ["tMED Coalition writes."]},
extra=rule_upload("K2"),
)
ask = FakeAsk(_reply(org_type="other"))
rows, _ = identify(
engine,
store,
con,
HOME,
docket="D",
mirror=FakeMirror(),
ask=ask,
log=RunLog(tmp_path / "l.jsonl"),
)
assert [r.item_key for r in rows] == ["K2"]
sent = ask.calls[0][1]
assert "tMED Coalition writes." in sent and "DEPARTMENT" not in sent
assert "rule page" not in sent
# selection text carries only the inline match, never a rule page
assert family_comment_items(engine, ["99341"], "H", [], docket="D") == [
("K2", "We support 99341 payment.")
]
def test_header_must_be_in_the_attachment_opening_not_a_late_citation():
late = [("A", "attachment_1.pdf", i, "page", False) for i in range(4)]
late.append(("A", "attachment_1.pdf", 4, RULE_HDR, False))
engine, _ = engine_with([], extra=late)
assert item_openings(engine, ["A"], docket="D")["A"].rule_copy is False
engine, _ = engine_with([], extra=[("B", "x", 0, "no section header here", False)])
assert item_openings(engine, ["B"], docket="D")["B"].rule_copy is False
def test_form_groups_use_openings(tmp_path, con, store):
letter = (
"I am writing to urge CMS to protect home visits for homebound older adults. "
"These services keep patients out of the hospital and must be paid fairly. "
"Please finalize the proposed rule without cuts to the home visit codes. "
)
keys = ["K1", "K2", "K3"]
# the matching excerpts differ; the openings are one form letter
selector = [(k, f"different excerpt {k} " * 20) for k in keys]
openings = {k: [letter + k] for k in keys}
rows, _ = identify(
engine_with(selector, openings=openings)[0],
store,
con,
HOME,
docket="D",
mirror=FakeMirror(),
ask=FakeAsk(None),
log=RunLog(tmp_path / "a.jsonl"),
)
assert [(r.form_group, r.form_size) for r in rows] == [("K1", 3)] * 3
def _status_error(code):
req = httpx.Request("GET", "https://x")
return httpx.HTTPStatusError(
"e", request=req, response=httpx.Response(code, request=req)
)
class SeqMirror:
"""Raises each queued exception in turn, then serves *attrs*."""
def __init__(self, errors, attrs=None):
self.errors = list(errors)
self.attrs = attrs or {"category": "Individual"}
self.calls = 0
def get_json(self, docket, cid):
self.calls += 1
if self.errors:
raise self.errors.pop(0)
return {"data": {"attributes": self.attrs}}
@pytest.fixture(autouse=True)
def no_sleep(monkeypatch):
monkeypatch.setattr("pfs.commenters.time.sleep", lambda s: None)
def test_mirror_retries_network_errors_and_5xx_twice():
for errs in (
[httpx.ConnectError("x"), httpx.ReadTimeout("y")],
[_status_error(503), _status_error(500)],
):
m = SeqMirror(errs)
assert regs_fields(m, "D", "c").category == "Individual" and m.calls == 3
m = SeqMirror([httpx.ConnectError("x")] * 3)
with pytest.raises(MirrorUnavailable):
regs_fields(m, "D", "c")
assert m.calls == 3
# a 404 or a parse problem is not retried
m = SeqMirror([_status_error(404)])
with pytest.raises(MirrorUnavailable):
regs_fields(m, "D", "c")
assert m.calls == 1
def test_blocked_item_becomes_unknown_row_without_model_call_then_is_replaced(
tmp_path, con, store
):
path = tmp_path / "l.jsonl"
ask = FakeAsk(_reply(org_type="payer"))
kw = dict(docket="D", ask=ask, log=RunLog(path))
rows, counts = identify(
engine_with([("K1", "a")])[0],
store,
con,
HOME,
mirror=FakeMirror(fail=True),
**kw,
)
assert counts["blocked"] == 1 and counts["pending"] == 0 and ask.calls == []
r = rows[0]
assert (r.item_key, r.org_name, r.org_type, r.specialty) == (
"K1",
"",
"unknown",
"",
)
assert r.extractor == "blocked:mirror" and not path.exists()
write_commenters(con, rows)
# the mirror reads on a later run: the row is replaced by the real one
rows, counts = identify(
engine_with([("K1", "a")])[0], store, con, HOME, mirror=FakeMirror(), **kw
)
assert counts["blocked"] == 0 and rows[0].org_type == "payer"
write_commenters(con, rows)
assert [r.org_type for r in read_commenters(con)] == ["payer"]
def test_retry_failed_reasks_failed_entries(tmp_path, con, store):
log = RunLog(tmp_path / "l.jsonl")
kw = dict(docket="D", mirror=FakeMirror(), log=log)
ask = FakeAsk(None)
_, counts = identify(engine_with([("K1", "a")])[0], store, con, HOME, ask=ask, **kw)
assert counts["failed"] == 1
_, counts = identify(engine_with([("K1", "a")])[0], store, con, HOME, ask=ask, **kw)
assert counts["failed_pending"] == 1 and len(ask.calls) == 1
ask.reply = _reply(org_type="payer")
rows, counts = identify(
engine_with([("K1", "a")])[0],
store,
con,
HOME,
ask=ask,
retry_failed=True,
**kw,
)
assert counts["done"] == 1 and rows[0].org_type == "payer"
def test_government_and_other_categories_defer_to_the_model():
for cat in CATEGORY_MAP:
assert not cat.startswith(
("Government", "Federal Government", "Other", "Academic", "Attorney")
)
assert "Government - Federal" not in CATEGORY_MAP
assert "other" not in CATEGORY_MAP.values()
def test_prompt_defines_vendor_and_sends_trade_associations_to_other():
ask = FakeAsk(_reply())
letterhead_identity("t", ask, specialties=())
system = ask.calls[0][0]
assert (
"vendor (only a for-profit company" in system
and "never an association" in system
)
assert "other (including trade associations, coalitions" in system
def test_family_rewrite_leaves_other_families_rows_alone(con):
def row(k, fam, org="AAFP"):
return CommenterRow(
k, "D", "c", org, "specialty_society", "", "", 0, "both", "", "regs", fam
)
write_commenters(con, [row("K1", "A"), row("K2", "A")], docket="D", family="A")
write_commenters(con, [row("K1", "B"), row("K3", "B")], docket="D", family="B")
assert [(r.item_key, r.family) for r in read_commenters(con)] == [
("K1", "A"),
("K1", "B"),
("K2", "A"),
("K3", "B"),
]
# rewriting A for D drops A's stale K2 but keeps all of B's
write_commenters(con, [row("K1", "A", org="AMA")], docket="D", family="A")
got = read_commenters(con)
assert [(r.item_key, r.family, r.org_name) for r in got] == [
("K1", "A", "AMA"),
("K1", "B", "AAFP"),
("K3", "B", "AAFP"),
]
assert [r.item_key for r in read_commenters(con, family="B")] == ["K1", "K3"]
assert [r.item_key for r in read_commenters(con, ["K1"], family="A")] == ["K1"]
def test_legacy_commenter_table_is_migrated_to_home_visits():
c = duckdb.connect(":memory:")
c.execute("CREATE SCHEMA pfs")
c.execute(
"CREATE TABLE pfs.commenter (item_key VARCHAR, docket VARCHAR, comment_id VARCHAR, "
"org_name VARCHAR, org_type VARCHAR, specialty VARCHAR, form_group VARCHAR, "
"form_size INTEGER, basis VARCHAR, reg_category VARCHAR, extractor VARCHAR)"
)
c.execute(
"INSERT INTO pfs.commenter VALUES ('K1','D','c','','unknown','','',0,'','','regs')"
)
ensure_tables(c)
assert [r.family for r in read_commenters(c)] == ["HOME-VISITS"]
# the name column is added too, blank for rows written before it existed
assert [r.submitter_name for r in read_commenters(c)] == [""]
def test_identify_sets_family(tmp_path, con, store):
rows, _ = identify(
engine_with([("K1", "a")])[0],
store,
con,
HOME,
docket="D",
mirror=FakeMirror(),
ask=FakeAsk(None),
log=RunLog(tmp_path / "l.jsonl"),
)
assert rows[0].family == "HOME-VISITS"
rows, _ = identify(
engine_with([("K1", "a")])[0],
store,
con,
HOME,
docket="D",
mirror=FakeMirror(fail=True),
ask=FakeAsk(None),
log=RunLog(tmp_path / "m.jsonl"),
)
assert rows[0].extractor == "blocked:mirror" and rows[0].family == "HOME-VISITS"