Files
stack/tests/cli/test_pfs_cli.py
kert 6897557999 fix(cli): _codes_for's slow-SQL warning is elements-only, not lineage --all-payable (refs #691 #692)
Minor from the final fix wave: _codes_for's "~20 min of SQL before any
model call" warning describes elements' per-code LLM classification
pass. lineage --all-payable also calls _codes_for but its own
--all-payable path (lineage_all) is one inverted pass over
fr_anchors — seconds, not minutes — so the warning there was
misleading. New warn_slow keyword (default True, elements' two call
sites unchanged; lineage --all-payable passes warn_slow=False).
2026-09-10 13:32:34 -04:00

933 lines
33 KiB
Python

"""stack pfs — elements / lineage / families / guidance / reaction / review."""
from __future__ import annotations
import duckdb
import pytest
from typer.testing import CliRunner
import cli.pfs as pfs_cli
from cli import app
from pfs.codetables import (
ElementRow,
EventRow,
GuidanceRow,
ReactionRow,
ensure_tables,
read_elements,
read_events,
read_families,
read_guidance,
read_reaction,
write_cpt_edition,
write_elements,
write_events,
write_guidance,
)
from pfs.cpt_model import CptCode, CptEdition, CptSection
from pfs.extract import Extraction
runner = CliRunner()
@pytest.fixture
def restore_families():
"""Snapshot ``pfs.families.FAMILIES`` and restore it in teardown, even
if the test body raises — a test that reaches ``refresh_from`` must
not leave the live registry clobbered for the rest of the session."""
from pfs import families as mod
before = dict(mod.FAMILIES)
try:
yield mod
finally:
mod.FAMILIES.clear()
mod.FAMILIES.update(before)
@pytest.fixture
def con(monkeypatch, restore_families):
c = duckdb.connect(":memory:")
ensure_tables(c)
c.execute(
"CREATE TABLE pfs.rvu (hcpcs VARCHAR, mod VARCHAR, description VARCHAR, status_code VARCHAR, non_fac_total DOUBLE, year INTEGER)"
)
c.executemany(
"INSERT INTO pfs.rvu VALUES (?,?,?,?,?,?)",
[
("99490", None, "Chrnc care mgmt staff 1st 20", "A", 1.0, 2026),
("G2058", None, "Ccm add 20min", "A", 1.0, 2020),
("99999", None, "bundled thing", "B", 0.0, 2026),
],
)
class _Batch:
def __enter__(self):
return c
def __exit__(self, *a):
return False
monkeypatch.setattr(pfs_cli, "_batch", lambda: _Batch())
# A DuckDB cursor, not `c` itself: `stack pfs reaction` (unlike the
# other commands here) calls `_read()` and then, on `--write`,
# `_batch()` in the same invocation — closing a `.close()`d `c`
# would break the batch. A cursor shares `c`'s catalog/data and
# closes independently of it, matching how `_read()`/`_batch()` are
# genuinely separate connections in production.
monkeypatch.setattr(pfs_cli, "_read", lambda: c.cursor())
monkeypatch.setattr(pfs_cli, "_store", lambda: object())
published = []
monkeypatch.setattr(pfs_cli, "_publish", lambda: published.append(True))
class _ConProxy:
"""``DuckDBPyConnection`` has no ``__dict__`` (C extension type), so
a plain ``con.published = []`` fails; wrap it to attach the
publish-tracking list the tests assert on while forwarding
everything else (execute, executemany, ...) to the real connection."""
def __init__(self, real, published):
object.__setattr__(self, "_real", real)
object.__setattr__(self, "published", published)
def __getattr__(self, name):
return getattr(object.__getattribute__(self, "_real"), name)
try:
yield _ConProxy(c, published)
finally:
c.close()
class TestElements:
def test_writes_rows_and_publishes(self, con, monkeypatch):
def fake_extract(store, con_, code, *, classify=None):
return Extraction(
code,
(
ElementRow(
code,
2026,
"activity",
"consent",
"",
"Consent;",
"K",
1,
2,
"fr",
),
),
(),
)
monkeypatch.setattr(pfs_cli, "extract_code", fake_extract)
res = runner.invoke(app, ["pfs", "elements", "--code", "g0556", "--no-llm"])
assert res.exit_code == 0, res.output
assert "G0556: 1 elements, 0 for review" in res.output
assert [r.value for r in read_elements(con, "G0556")] == ["consent"]
assert con.published == [True]
def test_dry_run_does_not_write(self, con, monkeypatch):
monkeypatch.setattr(
pfs_cli,
"extract_code",
lambda s, c, code, *, classify=None: Extraction(code, (), ()),
)
def fail_batch():
raise AssertionError("--dry-run must not open a RW duckdb_batch connection")
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
read_calls = []
orig_read = pfs_cli._read
def spy_read():
read_calls.append(True)
return orig_read()
monkeypatch.setattr(pfs_cli, "_read", spy_read)
res = runner.invoke(
app, ["pfs", "elements", "--code", "G0556", "--no-llm", "--dry-run"]
)
assert res.exit_code == 0, res.output
assert con.published == []
assert read_calls == [True]
def test_all_payable_selects_art_status_in_newest_year(self, con, monkeypatch):
seen = []
monkeypatch.setattr(
pfs_cli,
"extract_code",
lambda s, c, code, *, classify=None: (
seen.append(code) or Extraction(code, (), ())
),
)
res = runner.invoke(
app, ["pfs", "elements", "--all-payable", "--no-llm", "--dry-run"]
)
assert res.exit_code == 0 and seen == ["99490"]
def test_repeated_family_option_expands_every_family(self, con, monkeypatch):
# #684 regression: --family used to be a single str, so a second
# --family silently discarded the first. It must now be repeatable
# and expand every family named.
seen = []
monkeypatch.setattr(
pfs_cli,
"extract_code",
lambda s, c, code, *, classify=None: (
seen.append(code) or Extraction(code, (), ())
),
)
res = runner.invoke(
app,
[
"pfs",
"elements",
"--family",
"CCM",
"--family",
"APCM",
"--no-llm",
"--dry-run",
],
)
assert res.exit_code == 0, res.output
from pfs.families import HAND_FAMILIES
expected = set(HAND_FAMILIES["CCM"].codes) | set(HAND_FAMILIES["APCM"].codes)
assert set(seen) == expected
class TestCodesForWarnSlow:
"""``_codes_for``'s "~20 min of SQL" warning describes the
per-code LLM classification pass ``elements`` runs — it does not
apply to ``lineage --all-payable`` (one inverted pass, seconds)."""
def test_warns_by_default(self, con, caplog):
pfs_cli._codes_for(con, [], [], True)
assert "20 min" in caplog.text
def test_silent_when_warn_slow_is_false(self, con, caplog):
pfs_cli._codes_for(con, [], [], True, warn_slow=False)
assert "20 min" not in caplog.text
class TestLineage:
def test_prints_and_writes(self, con, monkeypatch):
ev = EventRow(
"G2058",
2021,
"replaced_by",
"",
"99439",
"YBM4IZUS",
1578,
84639,
"fr",
True,
"",
)
monkeypatch.setattr(pfs_cli, "lineage", lambda c, s, code: [ev])
res = runner.invoke(app, ["pfs", "lineage", "--code", "G2058", "--write"])
assert res.exit_code == 0, res.output
assert "2021 replaced_by" in res.output and "YBM4IZUS ¶1578" in res.output
assert read_events(con, "G2058")[0].to_codes == "99439"
assert con.published == [True]
def test_prints_cpt_event_with_cpt_anchor_text(self, con, monkeypatch):
ev = EventRow(
"99490",
2015,
"cpt_changed",
"",
"",
"GQGTPGYV",
0,
0,
"cpt",
True,
"CPT Changes 2015",
)
monkeypatch.setattr(pfs_cli, "lineage", lambda c, s, code: [ev])
res = runner.invoke(app, ["pfs", "lineage", "--code", "99490", "--write"])
assert res.exit_code == 0, res.output
assert "2015 cpt_changed" in res.output
assert "cpt 2015 GQGTPGYV" in res.output
def test_without_write_reads_and_never_takes_the_write_lock(self, con, monkeypatch):
# I4: `stack pfs lineage` without --write must read the replica,
# not open a RW duckdb_batch connection (the DuckDB single-writer
# lock a notebook may be holding, #508-#514).
def fail_batch():
raise AssertionError(
"lineage without --write must not open a RW duckdb_batch connection"
)
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
monkeypatch.setattr(pfs_cli, "lineage", lambda c, s, code: [])
read_calls = []
orig_read = pfs_cli._read
def spy_read():
read_calls.append(True)
return orig_read()
monkeypatch.setattr(pfs_cli, "_read", spy_read)
res = runner.invoke(app, ["pfs", "lineage", "--code", "G2058"])
assert res.exit_code == 0, res.output
assert read_calls == [True]
assert con.published == []
def test_requires_code_or_all_payable(self, con):
res = runner.invoke(app, ["pfs", "lineage"])
assert res.exit_code != 0
def test_all_payable_limit_write_calls_writer_once_per_code_after_build(
self, con, monkeypatch
):
# Extra A/R/T, newest-year codes so --limit has something to
# trim: sorted, the newest-year A/R/T targets are
# 99490 < A0001 < B0002 ('9' sorts before letters); 99999 is
# status B (excluded) and G2058 is a stale year (excluded).
con.execute(
"INSERT INTO pfs.rvu VALUES (?,?,?,?,?,?)",
["A0001", None, "extra a", "A", 1.0, 2026],
)
con.execute(
"INSERT INTO pfs.rvu VALUES (?,?,?,?,?,?)",
["B0002", None, "extra b", "R", 1.0, 2026],
)
order: list[tuple] = []
def fake_lineage_all(con_, store_, codes):
order.append(("lineage_all", tuple(codes)))
return {c: [] for c in codes}
monkeypatch.setattr(pfs_cli, "lineage_all", fake_lineage_all)
written: list[str] = []
def fake_write_events(con_, code, rows):
order.append(("write", code))
written.append(code)
return 0
monkeypatch.setattr(pfs_cli, "write_events", fake_write_events)
real_batch = pfs_cli._batch
def recording_batch():
order.append(("batch",))
return real_batch()
monkeypatch.setattr(pfs_cli, "_batch", recording_batch)
res = runner.invoke(
app, ["pfs", "lineage", "--all-payable", "--limit", "2", "--write"]
)
assert res.exit_code == 0, res.output
assert order[0] == ("lineage_all", ("99490", "A0001"))
assert order[1] == ("batch",)
assert order[2:] == [("write", "99490"), ("write", "A0001")]
assert written == ["99490", "A0001"]
assert con.published == [True]
def test_all_payable_without_write_never_opens_the_batch(self, con, monkeypatch):
monkeypatch.setattr(
pfs_cli, "lineage_all", lambda c, s, codes: {c: [] for c in codes}
)
def fail_batch():
raise AssertionError(
"lineage --all-payable without --write must not open a RW "
"duckdb_batch connection"
)
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
res = runner.invoke(app, ["pfs", "lineage", "--all-payable"])
assert res.exit_code == 0, res.output
assert con.published == []
assert "lineage: 1 codes targeted" in res.output
class TestFamilies:
def test_derives_from_tables_and_writes(self, con):
write_elements(
con,
"99439",
[
ElementRow(
"99439", 2021, "relation", "addon-of", "99490", "", "K", 1, 1, "fr"
)
],
[],
)
write_events(
con,
"G2058",
[
EventRow(
"G2058", 2021, "replaced_by", "", "99439", "K", 2, 1, "fr", True, ""
)
],
)
res = runner.invoke(app, ["pfs", "families", "--write"])
assert res.exit_code == 0, res.output
fams = read_families(con)
assert {r.code for r in fams if r.key == "CCM"} >= {"99490", "99439", "G2058"}
# Minor: --write prints the summary line by default; the
# per-family dump ("CCM" among them) is --verbose only.
assert "CCM" not in res.output
# No pfs.cpt_* rows seeded in this test — the summary line must
# still print, with cpt-named at 0 (I4: read-only-tolerant path
# also applies to the write path when the CPT tables are empty).
assert "families: total 2, multi-code 1, cpt-named 0, hand 1, other 1" in (
res.output
)
def test_verbose_prints_the_per_family_dump(self, con):
write_elements(
con,
"99439",
[
ElementRow(
"99439", 2021, "relation", "addon-of", "99490", "", "K", 1, 1, "fr"
)
],
[],
)
res = runner.invoke(app, ["pfs", "families", "--write", "--verbose"])
assert res.exit_code == 0, res.output
assert "CCM" in res.output
assert "families: total" in res.output
def _ccm_cpt_edition(self, year=2024):
section = CptSection(
sec_id="sec_ccm",
level=3,
title="Chronic Care Management Services",
path=(
"Evaluation and Management",
"Care Management Services",
"Chronic Care Management Services",
),
code_lo="99490",
code_hi="99439",
guideline="",
)
codes = (
CptCode(
code="99490",
sec_id="sec_ccm",
descriptor="Chronic care management services, first 20 minutes.",
stem="Chronic care management services",
elements=(),
tail="first 20 minutes.",
addon=False,
resequenced=False,
new=False,
revised=False,
telemedicine=False,
parent="",
mod51_exempt=False,
audio_only=False,
fda_pending=False,
pla=False,
category="I",
),
CptCode(
code="99439",
sec_id="sec_ccm",
descriptor="each additional 20 minutes.",
stem="Chronic care management services",
elements=(),
tail="each additional 20 minutes.",
addon=True,
resequenced=False,
new=False,
revised=False,
telemedicine=False,
parent="99490",
mod51_exempt=False,
audio_only=False,
fda_pending=False,
pla=False,
category="I",
),
)
return CptEdition(
year=year,
sections=(section,),
codes=codes,
instructions=(),
references=(),
crosswalks=(),
lists=(),
)
def test_feeds_cpt_inputs_when_present_and_prints_summary_line(self, con):
write_cpt_edition(con, self._ccm_cpt_edition(), "ITEM0001")
res = runner.invoke(app, ["pfs", "families", "--write"])
assert res.exit_code == 0, res.output
fams = read_families(con)
ccm = {r.code: r for r in fams if r.key == "CCM"}
assert {"99490", "99439"} <= set(ccm)
assert ccm["99490"].note == (
"Evaluation and Management > Care Management Services > "
"Chronic Care Management Services"
)
assert "families: total" in res.output
assert "cpt-named" in res.output
def test_since_spans_all_ingested_editions_not_just_the_newest(self, con):
# Ruling C12: _cpt_inputs' cpt_presence comes from a full
# SELECT code, edition_year FROM pfs.cpt_code (every edition
# write_cpt_edition has ever written), so `since` is the
# earliest edition, not clamped to whichever one is newest.
write_cpt_edition(con, self._ccm_cpt_edition(year=2019), "ITEM_2019")
write_cpt_edition(con, self._ccm_cpt_edition(year=2024), "ITEM_2024")
res = runner.invoke(app, ["pfs", "families", "--write"])
assert res.exit_code == 0, res.output
fams = read_families(con)
ccm = {r.code: r for r in fams if r.key == "CCM"}
assert ccm["99490"].since == 2019
assert ccm["99490"].until is None
def test_write_tolerates_null_rvu_description(self, con):
# #687 regression: a real pfs.rvu row can carry a NULL description
# (a corpus parsing artifact) at the current max year with a
# payable status — that row lands in the derivation scope and used
# to crash `families --write` with an AttributeError.
con.execute(
"INSERT INTO pfs.rvu VALUES (?,?,?,?,?,?)",
("Z9999", None, None, "A", 1.0, 2026),
)
res = runner.invoke(app, ["pfs", "families", "--write"])
assert res.exit_code == 0, res.output
def test_without_write_reads_and_never_takes_the_write_lock(self, con, monkeypatch):
# I4: `stack pfs families` without --write must read the replica,
# not open a RW duckdb_batch connection.
def fail_batch():
raise AssertionError(
"families without --write must not open a RW duckdb_batch connection"
)
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
read_calls = []
orig_read = pfs_cli._read
def spy_read():
read_calls.append(True)
return orig_read()
monkeypatch.setattr(pfs_cli, "_read", spy_read)
res = runner.invoke(app, ["pfs", "families"])
assert res.exit_code == 0, res.output
assert read_calls == [True]
assert con.published == []
def test_no_derived_tables_yet_is_a_friendly_no_op(self, monkeypatch):
# I4: a fresh replica with no pfs.code_element/code_event/code_family
# (nothing has run --write yet) is not a bug — print a friendly
# message and exit 0, without ever taking the write lock.
bare = duckdb.connect(":memory:")
monkeypatch.setattr(pfs_cli, "_read", lambda: bare)
def fail_batch():
raise AssertionError("must not open a RW duckdb_batch connection")
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
try:
res = runner.invoke(app, ["pfs", "families"])
assert res.exit_code == 0, res.output
assert "no derived tables yet" in res.output
finally:
bare.close()
def test_unknown_family_error_lists_hand_families_only(self, con, monkeypatch):
# M3: FAMILIES can hold ~15k derived keys once `refresh_from` has
# run — the error text must stay short and human-scannable.
pfs_cli.FAMILIES["ZZZZ-99999"] = pfs_cli.FAMILIES["CCM"]
res = runner.invoke(
app, ["pfs", "elements", "--family", "NOPE", "--no-llm", "--dry-run"]
)
assert res.exit_code != 0
assert "ZZZZ-99999" not in res.output
for key in pfs_cli.HAND_FAMILIES:
assert key in res.output
class TestCptIngest:
def test_dry_run_never_opens_batch(self, con, monkeypatch):
seen = {}
def fake_ingest(store, con_, *, years=None, dry_run=False):
seen["store"] = store
seen["con"] = con_
seen["years"] = years
seen["dry_run"] = dry_run
return {2024: {"sections": 3, "codes": 5}}
monkeypatch.setattr(pfs_cli, "cpt_ingest", fake_ingest)
def fail_batch():
raise AssertionError("--dry-run must not open a RW duckdb_batch connection")
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
res = runner.invoke(
app, ["pfs", "cpt-ingest", "--edition", "2024", "--dry-run"]
)
assert res.exit_code == 0, res.output
assert seen["dry_run"] is True
assert seen["con"] is None
assert seen["years"] == [2024]
assert "2024: sections=3, codes=5" in res.output
assert con.published == []
def test_all_flag_passes_years_none(self, con, monkeypatch):
seen = {}
monkeypatch.setattr(
pfs_cli,
"cpt_ingest",
lambda store, con_, *, years=None, dry_run=False: (
seen.update(years=years) or {}
),
)
res = runner.invoke(app, ["pfs", "cpt-ingest", "--all", "--dry-run"])
assert res.exit_code == 0, res.output
assert seen["years"] is None
def test_requires_edition_or_all(self, con):
res = runner.invoke(app, ["pfs", "cpt-ingest"])
assert res.exit_code != 0
def test_write_path_opens_batch_ensures_tables_and_publishes(
self, con, monkeypatch
):
seen = {}
def fake_ingest(store, con_, *, years=None, dry_run=False):
seen["con"] = con_
seen["dry_run"] = dry_run
return {2024: {"sections": 1, "codes": 2}}
monkeypatch.setattr(pfs_cli, "cpt_ingest", fake_ingest)
res = runner.invoke(app, ["pfs", "cpt-ingest", "--edition", "2024"])
assert res.exit_code == 0, res.output
assert seen["dry_run"] is False
assert seen["con"] is not None
assert "2024: sections=1, codes=2" in res.output
assert con.published == [True]
class TestGuidance:
def _row(self, family="CCM", code="99490"):
return GuidanceRow(
family, code, "cfr", "42 CFR 410.78(a)(3)", "SEC", "K", 398, 32389
)
def test_write_builds_writes_and_publishes(self, con, monkeypatch):
seen = {}
def fake_build(con_, store_, codes, *, family):
seen["con"] = con_
seen["store"] = store_
seen["codes"] = codes
seen["family"] = family
return [self._row(family=family)]
monkeypatch.setattr(pfs_cli, "build_guidance", fake_build)
res = runner.invoke(app, ["pfs", "guidance", "--family", "ccm", "--write"])
assert res.exit_code == 0, res.output
assert seen["family"] == "CCM"
assert seen["con"] is not None
assert seen["store"] is not None
assert set(seen["codes"]) == set(pfs_cli.HAND_FAMILIES["CCM"].codes)
got = read_guidance(con, "CCM")
assert [r.code for r in got] == ["99490"]
assert con.published == [True]
assert "CCM cfr 42 CFR 410.78(a)(3) SEC ← K ¶398" in res.output
def test_write_scopes_per_family(self, con, monkeypatch):
monkeypatch.setattr(
pfs_cli,
"build_guidance",
lambda con_, store_, codes, *, family: [self._row(family=family)],
)
res = runner.invoke(
app, ["pfs", "guidance", "--family", "CCM", "--family", "APCM", "--write"]
)
assert res.exit_code == 0, res.output
assert [r.family for r in read_guidance(con, "CCM")] == ["CCM"]
assert [r.family for r in read_guidance(con, "APCM")] == ["APCM"]
def test_without_write_reads_and_never_takes_the_write_lock(self, con, monkeypatch):
write_guidance(con, "CCM", [self._row()])
def fail_batch():
raise AssertionError("must not open a RW duckdb_batch connection")
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
res = runner.invoke(app, ["pfs", "guidance", "--family", "CCM"])
assert res.exit_code == 0, res.output
assert "CCM cfr 42 CFR 410.78(a)(3) SEC ← K ¶398" in res.output
assert con.published == []
def test_no_derived_guidance_yet_is_a_friendly_no_op(self, monkeypatch):
# I4: a fresh replica with no pfs.code_guidance (guidance --write
# hasn't run yet) is not a bug — print a friendly message per
# family and exit 0, without ever taking the write lock.
bare = duckdb.connect(":memory:")
monkeypatch.setattr(pfs_cli, "_read", lambda: bare)
def fail_batch():
raise AssertionError("must not open a RW duckdb_batch connection")
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
try:
res = runner.invoke(app, ["pfs", "guidance", "--family", "CCM"])
assert res.exit_code == 0, res.output
assert "no derived guidance yet" in res.output
finally:
bare.close()
def test_unknown_family_error_lists_hand_families_only(self, con, monkeypatch):
pfs_cli.FAMILIES["ZZZZ-99999"] = pfs_cli.FAMILIES["CCM"]
res = runner.invoke(app, ["pfs", "guidance", "--family", "NOPE", "--write"])
assert res.exit_code != 0
assert "ZZZZ-99999" not in res.output
for key in pfs_cli.HAND_FAMILIES:
assert key in res.output
def test_unknown_key_among_several_errors_before_any_batch(self, con, monkeypatch):
# Every --family key must be resolved and validated up front — an
# unknown key later in the list must fail before build_guidance
# runs for any family and before the write lock is ever opened.
built: list[str] = []
monkeypatch.setattr(
pfs_cli,
"build_guidance",
lambda con_, store_, codes, *, family: built.append(family) or [],
)
def fail_batch():
raise AssertionError("must not open a RW duckdb_batch connection")
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
res = runner.invoke(
app, ["pfs", "guidance", "--family", "CCM", "--family", "NOPE", "--write"]
)
assert res.exit_code != 0
assert built == []
def test_write_builds_every_family_before_opening_the_batch(self, con, monkeypatch):
order: list[str] = []
def fake_build(con_, store_, codes, *, family):
order.append("build")
return [self._row(family=family)]
real_batch = pfs_cli._batch
def recording_batch():
order.append("batch")
return real_batch()
monkeypatch.setattr(pfs_cli, "build_guidance", fake_build)
monkeypatch.setattr(pfs_cli, "_batch", recording_batch)
res = runner.invoke(
app, ["pfs", "guidance", "--family", "CCM", "--family", "APCM", "--write"]
)
assert res.exit_code == 0, res.output
assert order == ["build", "build", "batch"]
def test_requires_family(self, con):
res = runner.invoke(app, ["pfs", "guidance"])
assert res.exit_code != 0
class TestReaction:
def _row(
self, family="CCM", period="CMS-2023-0121", period_kind="docket", year=2023
):
return ReactionRow(family, period, period_kind, year, 5, 20, 1, 0, 0, 1, "[]")
def test_write_builds_writes_and_publishes(self, con, monkeypatch):
seen = {}
def fake_series(
engine,
store_,
family,
codes,
*,
classify=None,
stance_sample_n=0,
name=None,
):
seen["engine"] = engine
seen["store"] = store_
seen["family"] = family
seen["codes"] = codes
seen["classify"] = classify
seen["n"] = stance_sample_n
seen["name"] = name
return [self._row(family=family)]
monkeypatch.setattr(pfs_cli, "reaction_series", fake_series)
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
res = runner.invoke(app, ["pfs", "reaction", "--family", "ccm", "--write"])
assert res.exit_code == 0, res.output
assert seen["family"] == "CCM"
assert seen["engine"] == "ENGINE"
assert seen["classify"] is None # no --stance-sample: no LLM at all
assert seen["n"] == 0
assert seen["name"] == pfs_cli.HAND_FAMILIES["CCM"].name
assert set(seen["codes"]) == set(pfs_cli.HAND_FAMILIES["CCM"].codes)
got = read_reaction(con, "CCM")
assert [r.period for r in got] == ["CMS-2023-0121"]
assert con.published == [True]
assert "CCM CMS-2023-0121 (2023) 5/20 commenters" in res.output
def test_code_makes_a_single_code_pseudo_family_with_no_display_name(
self, con, monkeypatch
):
seen = {}
def fake_series(
engine,
store_,
family,
codes,
*,
classify=None,
stance_sample_n=0,
name=None,
):
seen["family"] = family
seen["codes"] = codes
seen["name"] = name
return [self._row(family=family)]
monkeypatch.setattr(pfs_cli, "reaction_series", fake_series)
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
res = runner.invoke(app, ["pfs", "reaction", "--code", "g2211", "--write"])
assert res.exit_code == 0, res.output
assert seen["family"] == "G2211"
assert seen["codes"] == ("G2211",)
assert seen["name"] is None
assert [r.family for r in read_reaction(con, "G2211")] == ["G2211"]
def test_stance_sample_builds_the_classifier(self, con, monkeypatch):
seen = {}
monkeypatch.setattr(
pfs_cli,
"reaction_series",
lambda engine, store_, family, codes, *, classify=None, stance_sample_n=0, name=None: ( # noqa: E501
seen.update(classify=classify, n=stance_sample_n) or []
),
)
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
monkeypatch.setattr(pfs_cli, "_classifier", lambda: "CLASSIFIER")
res = runner.invoke(
app, ["pfs", "reaction", "--family", "CCM", "--stance-sample", "5"]
)
assert res.exit_code == 0, res.output
assert seen["classify"] == "CLASSIFIER"
assert seen["n"] == 5
def test_write_computes_classifier_and_series_before_opening_the_batch(
self, con, monkeypatch
):
# Ruling A13: the classifier and every family's series must be
# fully computed BEFORE the DuckDB write lock is taken — the
# batch block only writes and publishes.
order: list[str] = []
def fake_classifier():
order.append("classifier")
return "CLASSIFIER"
def fake_series(
engine,
store_,
family,
codes,
*,
classify=None,
stance_sample_n=0,
name=None,
):
order.append("series")
assert classify == "CLASSIFIER" # already built by the time series runs
return [self._row(family=family)]
real_batch = pfs_cli._batch
def recording_batch():
order.append("batch")
return real_batch()
monkeypatch.setattr(pfs_cli, "_classifier", fake_classifier)
monkeypatch.setattr(pfs_cli, "reaction_series", fake_series)
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
monkeypatch.setattr(pfs_cli, "_batch", recording_batch)
res = runner.invoke(
app,
["pfs", "reaction", "--family", "CCM", "--stance-sample", "5", "--write"],
)
assert res.exit_code == 0, res.output
assert order == ["classifier", "series", "batch"]
def test_without_write_never_opens_a_batch_connection(self, con, monkeypatch):
monkeypatch.setattr(
pfs_cli,
"reaction_series",
lambda engine, store_, family, codes, *, classify=None, stance_sample_n=0, name=None: [ # noqa: E501
self._row(family=family)
],
)
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
def fail_batch():
raise AssertionError("must not open a RW duckdb_batch connection")
monkeypatch.setattr(pfs_cli, "_batch", fail_batch)
res = runner.invoke(app, ["pfs", "reaction", "--family", "CCM"])
assert res.exit_code == 0, res.output
assert "CCM CMS-2023-0121 (2023) 5/20 commenters" in res.output
assert con.published == [] # nothing persisted, batch never opened
def test_unknown_family_error_lists_hand_families_only(self, con, monkeypatch):
pfs_cli.FAMILIES["ZZZZ-99999"] = pfs_cli.FAMILIES["CCM"]
res = runner.invoke(app, ["pfs", "reaction", "--family", "NOPE", "--write"])
assert res.exit_code != 0
assert "ZZZZ-99999" not in res.output
for key in pfs_cli.HAND_FAMILIES:
assert key in res.output
def test_requires_family_or_code(self, con):
res = runner.invoke(app, ["pfs", "reaction"])
assert res.exit_code != 0
class TestReview:
def test_lists_queue(self, con):
from pfs.codetables import ReviewRow
write_elements(
con, "G0556", [], [ReviewRow("G0556", "Odd line;", "", "", "K", 9)]
)
res = runner.invoke(app, ["pfs", "review"])
assert res.exit_code == 0 and "G0556" in res.output and "Odd line" in res.output