Files
stack/tests/cli/test_pfs_cli.py
kert dfaaf43890 fix(pfs): stance R66a/R67/R68 — stance_eval JSON, method record survives reaction reruns, family-named relevance window, packaged golden set, load_duration log
- reaction --write carries stance_method/stance_agreement/stance_eval over from
  the stored docket row (it used to wipe the PRELIMINARY marker)
- pfs.code_reaction.stance_eval: {cell, recall, direction, n_items,
  n_positions, family, docket, in_sample}, stamped only for the golden set's
  family; new writes leave stance_agreement NULL, older rows stay readable
- the golden set moves to src/pfs/golden/stance_home_visits_cy2023.json
- R67: the +/-1-sentence window must name the event's subject keyword AND this
  family (family code, offered link code/alias, pair phrase); valuation needs a
  family code
- JsonExtractor logs Ollama load_duration at DEBUG
- stance-eval prints recall and direction
2026-11-12 16:04:19 -05:00

2575 lines
96 KiB
Python

"""stack pfs — elements / lineage / families / guidance / reaction / review."""
from __future__ import annotations
import dataclasses
import json
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock
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,
read_rounds,
write_cpt_edition,
write_elements,
write_events,
write_guidance,
)
from pfs.cpt_model import CptCode, CptEdition, CptSection
from pfs.extract import Extraction
from pfs.stance import Scores
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()
def _empty_extractions(store, con, codes, *, classify=None):
"""``pfs.extract.extract_codes``'s shape with nothing extracted — the
stand-in for tests that only care which codes the CLI targeted."""
return {code: Extraction(code, (), ()) for code in codes}
class TestElements:
def test_writes_rows_and_publishes(self, con, monkeypatch):
def fake_extract(store, con_, codes, *, classify=None):
return {
code: Extraction(
code,
(
ElementRow(
code,
2026,
"activity",
"consent",
"",
"Consent;",
"K",
1,
2,
"fr",
),
),
(),
)
for code in codes
}
monkeypatch.setattr(pfs_cli, "extract_codes", 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_summary_line_reports_confirmed_count_when_nonzero(self, con, monkeypatch):
# #703: the elements summary surfaces confirmed_by, but only when
# there's something to report.
def fake_extract(store, con_, codes, *, classify=None):
return {
code: Extraction(
code,
(
ElementRow(
code,
2026,
"activity",
"consent",
"",
"Consent;",
"K",
1,
2,
"fr",
"cpt:GQGTPGYV",
),
ElementRow(
code,
2026,
"activity",
"24-7-access",
"",
"24/7;",
"K",
3,
2,
"fr",
),
),
(),
)
for code in codes
}
monkeypatch.setattr(pfs_cli, "extract_codes", fake_extract)
res = runner.invoke(app, ["pfs", "elements", "--code", "g0556", "--no-llm"])
assert res.exit_code == 0, res.output
assert "G0556: 2 elements, 0 for review, 1 confirmed by a second source" in (
res.output
)
def test_summary_line_omits_confirmed_when_none(self, con, monkeypatch):
monkeypatch.setattr(pfs_cli, "extract_codes", _empty_extractions)
res = runner.invoke(app, ["pfs", "elements", "--code", "g0556", "--no-llm"])
assert res.exit_code == 0, res.output
assert "confirmed" not in res.output
def test_dry_run_does_not_write(self, con, monkeypatch):
monkeypatch.setattr(pfs_cli, "extract_codes", _empty_extractions)
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_codes",
lambda s, c, codes, *, classify=None: (
seen.extend(codes) or _empty_extractions(s, c, codes)
),
)
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_codes",
lambda s, c, codes, *, classify=None: (
seen.extend(codes) or _empty_extractions(s, c, codes)
),
)
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 target-count note is for ``elements``, whose
``--all-payable`` run makes one model call per unplaced line — not for
``lineage --all-payable``, which is pure SQL/regex."""
def test_warns_by_default(self, con, caplog):
pfs_cli._codes_for(con, [], [], True)
assert "targeting 1 codes" in caplog.text
def test_silent_when_warn_slow_is_false(self, con, caplog):
pfs_cli._codes_for(con, [], [], True, warn_slow=False)
assert "targeting" 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
def test_lineage_all_seen_targets_deleted_codes(self, con, monkeypatch):
con.execute(
"INSERT INTO pfs.rvu VALUES (?,?,?,?,?,?)",
["99343", None, "old home visit", "A", 1.0, 2022],
)
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)
monkeypatch.setattr(pfs_cli, "write_events", lambda c, code, rows: 0)
real_batch = pfs_cli._batch
monkeypatch.setattr(
pfs_cli, "_batch", lambda: (order.append(("batch",)), real_batch())[1]
)
res = runner.invoke(app, ["pfs", "lineage", "--all-seen", "--write"])
assert res.exit_code == 0, res.output
targets = order[0][1]
assert "99343" in targets and "G2058" in targets and "99999" in targets
assert order[1] == ("batch",)
assert con.published == [True]
def test_lineage_all_seen_limit_and_code_union(self, con, monkeypatch):
seen: list[tuple] = []
def fake(con_, store_, codes):
seen.append(tuple(codes))
return {c: [] for c in codes}
monkeypatch.setattr(pfs_cli, "lineage_all", fake)
res = runner.invoke(
app, ["pfs", "lineage", "--all-seen", "--limit", "1", "--code", "zzzzz"]
)
assert res.exit_code == 0, res.output
assert len(seen[0]) == 2 and "ZZZZZ" in seen[0]
def test_lineage_family_targets_curated_members(self, con, monkeypatch):
seen: list[tuple] = []
def fake(con_, store_, codes):
seen.append(tuple(codes))
return {c: [] for c in codes}
monkeypatch.setattr(pfs_cli, "lineage_all", fake)
res = runner.invoke(app, ["pfs", "lineage", "--family", "HOME-VISITS"])
assert res.exit_code == 0, res.output
assert len(seen[0]) == 20
assert "lineage: 20 codes targeted" in res.output
def test_lineage_unknown_family_is_rejected(self, con):
res = runner.invoke(app, ["pfs", "lineage", "--family", "NOPE-NOPE"])
assert res.exit_code != 0
def test_codes_for_with_no_targets_names_every_flag(self, con):
with pytest.raises(Exception, match="--all-seen"):
pfs_cli._codes_for(con, [], [], False)
def test_codes_for_all_seen_warns_unless_quiet(self, con, caplog):
import logging
with caplog.at_level(logging.WARNING):
out = pfs_cli._codes_for(con, [], [], False, all_seen=True)
assert "99490" in out and "--all-seen: targeting" in caplog.text
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 3, multi-code 2, cpt-named 0, hand 1, curated 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
def test_source_shown_when_present(self, con):
# #703: CPT-sourced review lines are distinguishable from FR ones.
from pfs.codetables import ReviewRow
write_elements(
con,
"G0556",
[],
[
ReviewRow("G0556", "Odd fr line;", "", "", "K", 9, "fr"),
ReviewRow("G0556", "Odd cpt line;", "", "", "GQGTPGYV", 0, "cpt"),
],
)
res = runner.invoke(app, ["pfs", "review"])
assert res.exit_code == 0, res.output
assert "(fr)" in res.output and "(cpt)" in res.output
def test_code_filter_scopes_the_query(self, con):
from pfs.codetables import ReviewRow
write_elements(
con, "G0556", [], [ReviewRow("G0556", "Odd line;", "", "", "K", 9, "fr")]
)
write_elements(
con, "99490", [], [ReviewRow("99490", "Other line;", "", "", "K", 3, "fr")]
)
res = runner.invoke(app, ["pfs", "review", "--code", "g0556"])
assert res.exit_code == 0, res.output
assert "Odd line" in res.output and "Other line" not in res.output
class TestExposure:
def test_write_builds_whole_calendar_and_publishes(self, con, monkeypatch):
from pfs.codetables import read_exposures
res = runner.invoke(app, ["pfs", "exposure", "--write"])
assert res.exit_code == 0, res.output
rows = read_exposures(con)
got = {(r.code, r.year, r.kind) for r in rows}
# 99490 appears (payable) in 2026 after being absent; G2058's only year is
# 2020 so it ends 2021; 99999 is B-status → never payable → no rows.
assert ("99490", 2026, "becomes-payable") in got
assert ("G2058", 2021, "ends") in got
assert not any(r.code == "99999" for r in rows)
assert con.published == [True]
assert "99490 2026-01-01 becomes-payable" in res.output
assert "exposures:" in res.output
def test_filters_only_affect_printing(self, con, monkeypatch):
from pfs.codetables import read_exposures
res = runner.invoke(app, ["pfs", "exposure", "--write", "--code", "g2058"])
assert res.exit_code == 0, res.output
assert len({r.code for r in read_exposures(con)}) == 2 # whole calendar written
assert "G2058" in res.output and "99490" not in res.output
def test_without_write_reads_and_never_takes_the_write_lock(self, con, monkeypatch):
runner.invoke(app, ["pfs", "exposure", "--write"])
con.published.clear()
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", "exposure", "--code", "99490"])
assert res.exit_code == 0, res.output
assert "99490 2026-01-01 becomes-payable" in res.output
assert con.published == []
def test_controls_lists_quiet_codes_in_the_same_band(self, con, monkeypatch):
# two more A-status codes in 2026 in the same (single) band, one of
# which is itself exposed that year → only the quiet one is a control
con.executemany(
"INSERT INTO pfs.rvu VALUES (?,?,?,?,?,?)",
[
# 11111 is payable across the whole file span → never treated
*[("11111", None, "quiet", "A", 1.0, y) for y in range(2020, 2027)],
("22222", None, "new", "A", 1.0, 2026), # becomes payable 2026 itself
],
)
runner.invoke(app, ["pfs", "exposure", "--write"])
res = runner.invoke(
app,
["pfs", "exposure", "--code", "99490", "--year", "2026", "--controls", "5"],
)
assert res.exit_code == 0, res.output
assert "11111 A band" in res.output
assert "22222" not in res.output
assert "controls for 99490 @ 2026" in res.output
def test_controls_requires_one_code_and_year(self, con):
res = runner.invoke(app, ["pfs", "exposure", "--controls", "5"])
assert res.exit_code != 0
def test_no_calendar_yet_is_a_friendly_no_op(self, monkeypatch):
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", "exposure"])
assert res.exit_code == 0, res.output
assert "no exposure calendar yet" in res.output
finally:
bare.close()
class TestUtilization:
_REC = {
"Rndrng_Prvdr_Geo_Lvl": "National",
"HCPCS_Cd": "99490",
"HCPCS_Desc": "CCM first 20 min",
"HCPCS_Drug_Ind": "N",
"Place_Of_Srvc": "O",
"Tot_Rndrng_Prvdrs": "10",
"Tot_Benes": "100",
"Tot_Srvcs": "1000",
"Tot_Bene_Day_Srvcs": "990",
"Avg_Sbmtd_Chrg": "100",
"Avg_Mdcr_Alowd_Amt": "60",
"Avg_Mdcr_Pymt_Amt": "45",
"Avg_Mdcr_Stdzd_Amt": "44",
}
@pytest.fixture
def env(self, con, monkeypatch, tmp_path):
from bib.store import Store
store = Store(":memory:", storage_dir=tmp_path / "storage")
monkeypatch.setattr(pfs_cli, "_store", lambda: store)
monkeypatch.setattr(
pfs_cli,
"utilization_cache_path",
lambda y, root=None: tmp_path / f"mup-phy-geo-{y}.json",
)
fetched: list[int] = []
def fake_fetch(client, y, **kw):
fetched.append(y)
return [self._REC, dict(self._REC, Place_Of_Srvc="F", Tot_Srvcs="5")]
monkeypatch.setattr(pfs_cli, "fetch_utilization_year", fake_fetch)
monkeypatch.setattr(pfs_cli, "_http", lambda: MagicMock())
try:
yield store, fetched, tmp_path
finally:
store.close()
def test_write_loads_cites_logs_and_publishes(self, con, env):
store, fetched, tmp_path = env
res = runner.invoke(app, ["pfs", "utilization", "--write", "--year", "2023"])
assert res.exit_code == 0, res.output
assert fetched == [2023]
assert con.execute(
"SELECT count(*), sum(n_services) FROM pfs.utilization WHERE year = 2023"
).fetchone() == (2, 1005.0)
key = con.execute("SELECT DISTINCT item_key FROM pfs.utilization").fetchone()[0]
item = store.get(key)
assert "CY2023" in item.title and "year:2023" in item.tags
log = con.execute(
"SELECT module, table_name, rows, pincite_key, source_file FROM cms.ingest_log"
).fetchone()
assert log[:4] == ("pfs.utilization", "pfs.utilization", 2, key)
assert log[4].endswith("mup-phy-geo-2023.json")
assert (tmp_path / "mup-phy-geo-2023.json").exists()
assert con.published == [True]
assert "2023: 2 records → 2 national rows" in res.output
def test_write_is_idempotent_and_offline_reads_the_cache(self, con, env):
store, fetched, tmp_path = env
runner.invoke(app, ["pfs", "utilization", "--write", "--year", "2023"])
res = runner.invoke(
app, ["pfs", "utilization", "--write", "--year", "2023", "--offline"]
)
assert res.exit_code == 0, res.output
assert fetched == [2023] # the offline run never fetched
assert con.execute("SELECT count(*) FROM pfs.utilization").fetchone()[0] == 2
def test_unknown_year_is_rejected_before_any_fetch(self, con, env):
store, fetched, _ = env
res = runner.invoke(app, ["pfs", "utilization", "--write", "--year", "1999"])
assert res.exit_code != 0
assert fetched == []
def test_code_reads_series_without_the_write_lock(self, con, env, monkeypatch):
runner.invoke(app, ["pfs", "utilization", "--write", "--year", "2023"])
con.published.clear()
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", "utilization", "--code", "99490"])
assert res.exit_code == 0, res.output
assert "99490 2023 services 1,005" in res.output
assert con.published == []
def test_no_table_yet_is_a_friendly_no_op(self, monkeypatch):
bare = duckdb.connect(":memory:")
monkeypatch.setattr(pfs_cli, "_read", lambda: bare)
try:
res = runner.invoke(app, ["pfs", "utilization", "--code", "99490"])
assert res.exit_code == 0, res.output
assert "no utilization table yet" in res.output
finally:
bare.close()
class TestFamiliesCurated:
def test_families_write_applies_curated(self, con, monkeypatch):
from pfs.codetables import FamilyRow
derived = [
FamilyRow(
"NEW-PATIENT", "New Patient", "99341", "base", 2015, None, "", 0, ""
)
]
monkeypatch.setattr(pfs_cli, "_derive_rows", lambda c: derived)
res = runner.invoke(app, ["pfs", "families", "--write"])
assert res.exit_code == 0, res.output
fams = read_families(con)
assert [r.key for r in fams if r.code == "99341"] == ["HOME-VISITS"]
assert len([r for r in fams if r.key == "HOME-VISITS"]) == 20
assert "curated 1" in res.output
def test_asker_builds_json_extractor_on_instruct_model(monkeypatch):
from llm import config as llm_config
from llm import pool as llm_pool
from llm.structured import JsonExtractor
class Cfg:
instruct_model = "qwen-test"
chat_num_ctx = 1024
class FakePool:
def check(self, model):
return ["http://h"]
monkeypatch.setattr(llm_config, "load", lambda: Cfg())
monkeypatch.setattr(
llm_pool.HostPool, "from_config", classmethod(lambda c, cfg: FakePool())
)
asker = pfs_cli._asker()
assert isinstance(asker, JsonExtractor) and asker.model == "qwen-test"
@dataclasses.dataclass
class _Cfg:
instruct_model: str = "qwen-14"
instruct_model_large: str = "qwen-32"
chat_num_ctx: int = 1024
def test_asker_large_pins_the_large_model(monkeypatch):
from llm import config as llm_config
from llm import pool as llm_pool
seen = []
class FakePool:
def check(self, model):
seen.append(model)
monkeypatch.setattr(llm_config, "load", lambda: _Cfg())
monkeypatch.setattr(
llm_pool.HostPool, "from_config", classmethod(lambda c, cfg: FakePool())
)
asker = pfs_cli._asker(large=True)
assert asker.model == "qwen-32" and seen == ["qwen-32"]
def test_asker_large_without_a_large_model(monkeypatch):
from llm import config as llm_config
monkeypatch.setattr(llm_config, "load", lambda: _Cfg(instruct_model_large=""))
with pytest.raises(Exception, match="no instruct_model_large"):
pfs_cli._asker(large=True)
class TestRounds:
def _row(self, **kw):
from pfs.codetables import RoundRow
base = dict(
family="HOME-VISITS",
code="99341",
rule_year=2023,
item_key="K",
rule_kind="final",
p_id=1,
page=2,
source="fr-para",
table_title="",
ruc_work_rvu=1.0,
ruc_percentile="25th",
survey_intra_min=None,
survey_total_min=None,
comparators="",
surveying_societies="",
requested_work_rvu=None,
proposed_work_rvu=1.0,
final_work_rvu=1.0,
decision="accept",
rationale_p_id=0,
rationale_quote="",
pe_note="",
extractor="regex",
)
return RoundRow(**{**base, **kw})
def _patch(self, monkeypatch, order, counts=None):
rows = [
self._row(),
self._row(rule_year=2008, rule_kind="final"),
self._row(code="99342", rule_year=2023, field_cites='{"decision": 1}'),
]
seen = {}
def fake(con, store, fam, **kw):
order.append("family_rounds")
seen.update(kw, key=fam.key)
return rows, counts or {
"done": 2,
"skipped": 1,
"failed": 0,
"failed_pending": 3,
}
monkeypatch.setattr(pfs_cli, "family_rounds", fake)
monkeypatch.setattr(pfs_cli, "_asker", lambda: "ASK")
monkeypatch.setattr(
pfs_cli, "ledger_path", lambda t, f: Path("/nonexistent") / f"{t}-{f}"
)
return seen
def test_rounds_cli_writes_after_extraction(self, con, monkeypatch):
order = []
seen = self._patch(monkeypatch, order)
orig_write = pfs_cli.write_rounds
def write(c, fam, rows):
order.append("write_rounds")
return orig_write(c, fam, rows)
class _B:
def __enter__(self):
order.append("_batch")
return con._real
def __exit__(self, *a):
return False
monkeypatch.setattr(pfs_cli, "write_rounds", write)
monkeypatch.setattr(pfs_cli, "_batch", lambda: _B())
monkeypatch.setattr(pfs_cli, "_publish", lambda: order.append("_publish"))
res = runner.invoke(
app,
[
"pfs",
"rounds",
"--family",
"home-visits",
"--limit",
"5",
"--retry-failed",
"--write",
],
)
assert res.exit_code == 0, res.output
assert order == ["family_rounds", "_batch", "write_rounds", "_publish"]
assert seen["ask"] == "ASK" and seen["limit"] == 5 and seen["retry_failed"]
assert seen["key"] == "HOME-VISITS" and not seen["tables_only"]
assert "CY2008 final: 1 rounds" in res.output
assert "CY2023 final: 2 rounds" in res.output
assert "done 2, skipped 1, failed 0, failed_pending 3" in res.output
assert "wrote 3 rounds" in res.output
got = read_rounds(con._real, "HOME-VISITS")
assert len(got) == 3
assert {r.code: r.field_cites for r in got}["99342"] == '{"decision": 1}'
def test_rounds_without_write_writes_nothing(self, con, monkeypatch):
order = []
self._patch(monkeypatch, order)
monkeypatch.setattr(pfs_cli, "_batch", lambda: 1 / 0)
res = runner.invoke(app, ["pfs", "rounds", "--family", "HOME-VISITS"])
assert res.exit_code == 0, res.output
assert order == ["family_rounds"] and "wrote" not in res.output
@pytest.mark.parametrize("flag", ["--no-llm", "--tables-only"])
def test_no_model_flags_build_no_asker(self, con, monkeypatch, flag):
seen = self._patch(monkeypatch, [])
monkeypatch.setattr(pfs_cli, "_asker", lambda: 1 / 0)
monkeypatch.setattr(pfs_cli, "_model_name", lambda large=False: "qwen-x")
res = runner.invoke(app, ["pfs", "rounds", "--family", "HOME-VISITS", flag])
assert res.exit_code == 0, res.output
assert seen["ask"] is None and seen["tables_only"] == (flag == "--tables-only")
# the finished ledger is replayed, so a --write cannot delete model rows
assert seen["replay_model"] == "qwen-x"
def test_model_run_does_not_replay(self, con, monkeypatch):
seen = self._patch(monkeypatch, [])
monkeypatch.setattr(pfs_cli, "_model_name", lambda: 1 / 0)
res = runner.invoke(app, ["pfs", "rounds", "--family", "HOME-VISITS"])
assert res.exit_code == 0, res.output
assert seen["replay_model"] is None
def test_model_name_reads_config_without_a_pool(self, monkeypatch):
from llm import config as llm_config
class Cfg:
instruct_model = "qwen-test"
monkeypatch.setattr(llm_config, "load", lambda: Cfg())
assert pfs_cli._model_name() == "qwen-test"
def test_rounds_unknown_family(self, con):
res = runner.invoke(app, ["pfs", "rounds", "--family", "NOPE"])
assert res.exit_code != 0 and "unknown curated family" in res.output
def test_rounds_dry_run_makes_no_model_calls(self, con, monkeypatch):
monkeypatch.setattr(
pfs_cli, "rounds_inventory", lambda store, fam: ([{}, {}, {}], [1, 2])
)
monkeypatch.setattr(pfs_cli, "_asker", lambda: 1 / 0)
monkeypatch.setattr(pfs_cli, "family_rounds", lambda *a, **k: 1 / 0)
monkeypatch.setattr(pfs_cli, "_batch", lambda: 1 / 0)
res = runner.invoke(
app, ["pfs", "rounds", "--family", "HOME-VISITS", "--dry-run"]
)
assert res.exit_code == 0, res.output
assert "3 candidate paragraphs, 2 table rows; no model calls made" in res.output
class TestOrigin:
def _row(self, **kw):
from pfs.codetables import OriginRow
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 _patch(self, monkeypatch, order):
rows = [
self._row(),
self._row(code="99350", kind="regulation", cite_kind="cfr"),
self._row(code="99342", cite_kind="cpt_book", item_key="B", p_id=0),
]
seen = {}
def fake(con, store, fam, **kw):
order.append("family_origin")
seen.update(kw, key=fam.key)
return rows, {"done": 2, "skipped": 1, "failed": 0, "failed_pending": 3}
monkeypatch.setattr(pfs_cli, "family_origin", fake)
monkeypatch.setattr(pfs_cli, "_asker", lambda: "ASK")
monkeypatch.setattr(
pfs_cli,
"resolve_book",
lambda store, key: ("Title " + key, Path("/x") / f"{key}.epub"),
)
monkeypatch.setattr(
pfs_cli, "ledger_path", lambda t, f: Path("/nonexistent") / f"{t}-{f}"
)
return seen
def test_origin_cli_write_after_extraction(self, con, monkeypatch):
from pfs.codetables import read_origin, write_origin
order = []
seen = self._patch(monkeypatch, order)
orig_write = write_origin
def write(c, fam, rows):
order.append("write_origin")
return orig_write(c, fam, rows)
class _B:
def __enter__(self):
order.append("_batch")
return con._real
def __exit__(self, *a):
return False
monkeypatch.setattr(pfs_cli, "write_origin", write)
monkeypatch.setattr(pfs_cli, "_batch", lambda: _B())
monkeypatch.setattr(pfs_cli, "_publish", lambda: order.append("_publish"))
res = runner.invoke(
app,
["pfs", "origin", "--family", "home-visits", "--limit", "4", "--write"],
)
assert res.exit_code == 0, res.output
assert order == ["family_origin", "_batch", "write_origin", "_publish"]
assert (
seen["ask"] == "ASK" and seen["limit"] == 4 and seen["key"] == "HOME-VISITS"
)
assert seen["books"] == [
("3ANRT854", "Title 3ANRT854", Path("/x/3ANRT854.epub"))
]
assert seen["replay_model"] is None
assert "statute: 2" in res.output and "regulation: 1" in res.output
assert "cpt_book: 1" in res.output and "fr_para: 1" in res.output
assert "done 2, skipped 1, failed 0, failed_pending 3" in res.output
assert "wrote 3 origins" in res.output
assert len(read_origin(con._real, "HOME-VISITS")) == 3
def test_origin_without_write_writes_nothing(self, con, monkeypatch):
order = []
seen = self._patch(monkeypatch, order)
monkeypatch.setattr(pfs_cli, "_batch", lambda: 1 / 0)
res = runner.invoke(
app,
["pfs", "origin", "--family", "HOME-VISITS", "--book", "A", "--book", "B"],
)
assert res.exit_code == 0, res.output
assert order == ["family_origin"] and "wrote" not in res.output
assert [b[0] for b in seen["books"]] == ["A", "B"]
def test_no_llm_replays_the_ledger(self, con, monkeypatch):
seen = self._patch(monkeypatch, [])
monkeypatch.setattr(pfs_cli, "_asker", lambda: 1 / 0)
monkeypatch.setattr(pfs_cli, "_model_name", lambda large=False: "qwen-x")
res = runner.invoke(
app, ["pfs", "origin", "--family", "HOME-VISITS", "--no-llm"]
)
assert res.exit_code == 0, res.output
assert seen["ask"] is None and seen["replay_model"] == "qwen-x"
def test_no_model_host_falls_back_to_replay(self, con, monkeypatch):
seen = self._patch(monkeypatch, [])
def down():
raise RuntimeError("no Ollama host serves qwen")
monkeypatch.setattr(pfs_cli, "_asker", down)
monkeypatch.setattr(pfs_cli, "_model_name", lambda large=False: "qwen-x")
res = runner.invoke(app, ["pfs", "origin", "--family", "HOME-VISITS"])
assert res.exit_code == 0, res.output
assert seen["ask"] is None and seen["replay_model"] == "qwen-x"
assert "replaying the ledger" in res.output
def test_origin_unknown_family_and_missing_book(self, con, monkeypatch):
res = runner.invoke(app, ["pfs", "origin", "--family", "NOPE"])
assert res.exit_code != 0 and "unknown curated family" in res.output
seen = self._patch(monkeypatch, [])
monkeypatch.setattr(pfs_cli, "resolve_book", lambda store, key: None)
# a book asked for by name must exist
res = runner.invoke(
app, ["pfs", "origin", "--family", "HOME-VISITS", "--book", "XX"]
)
assert res.exit_code != 0 and "no EPUB attachment" in res.output
# the default book missing only warns: the run goes on FR-only
res = runner.invoke(app, ["pfs", "origin", "--family", "HOME-VISITS"])
assert res.exit_code == 0, res.output
assert (
"warning: no EPUB attachment for 3ANRT854; skipping the book" in res.output
)
assert seen["books"] == []
def test_origin_dry_run_makes_no_model_calls(self, con, monkeypatch):
self._patch(monkeypatch, [])
monkeypatch.setattr(
pfs_cli,
"origin_inventory",
lambda store, fam, books: (
[{"unit": "a"}, {"unit": "b"}],
{"3ANRT854": ["12", "13", "20"]},
),
)
monkeypatch.setattr(pfs_cli, "_asker", lambda: 1 / 0)
monkeypatch.setattr(pfs_cli, "family_origin", lambda *a, **k: 1 / 0)
monkeypatch.setattr(pfs_cli, "_batch", lambda: 1 / 0)
res = runner.invoke(
app, ["pfs", "origin", "--family", "HOME-VISITS", "--dry-run"]
)
assert res.exit_code == 0, res.output
assert "2 candidate Federal Register paragraphs" in res.output
assert "3ANRT854: 3 pages" in res.output and "no model calls made" in res.output
class TestCmsSummaries:
def _rows(self):
from pfs.codetables import CmsSummaryRow
return [
CmsSummaryRow(
"HOME-VISITS",
"K1",
2023,
"final",
1679,
1680,
"",
"majority",
"support",
"acknowledged",
"regex",
),
CmsSummaryRow(
"HOME-VISITS",
"K1",
2023,
"final",
1681,
1682,
"99341",
"few",
"modify",
"finalize_as_proposed",
"regex",
),
CmsSummaryRow(
"HOME-VISITS",
"K0",
2008,
"final",
2241,
2242,
"99326",
"unspecified",
"oppose",
"finalize_modified",
"regex",
),
]
def _patch(self, monkeypatch, seen, counts=None):
def fake(store, fam, **kw):
seen.update(kw, key=fam.key)
kw["counts"].update(
{"done": 2, "skipped": 1, "failed": 0, "failed_pending": 3}
if counts is None
else counts
)
return self._rows()
monkeypatch.setattr(pfs_cli, "cms_summaries_rows", fake)
monkeypatch.setattr(
pfs_cli, "_classifier", lambda **kw: "CLS" if kw == {"pin": True} else 1 / 0
)
monkeypatch.setattr(pfs_cli, "_model_name", lambda large=False: "qwen-x")
monkeypatch.setattr(
pfs_cli, "ledger_path", lambda t, f: Path("/nonexistent") / f"{t}-{f}"
)
def test_writes_and_publishes(self, con, monkeypatch):
from pfs.codetables import read_cms_summaries
seen = {}
self._patch(monkeypatch, seen)
res = runner.invoke(
app,
[
"pfs",
"cms-summaries",
"--family",
"home-visits",
"--limit",
"4",
"--retry-failed",
"--write",
],
)
assert res.exit_code == 0, res.output
assert seen["classify"] == "CLS" and seen["model"] == "qwen-x"
assert (
seen["limit"] == 4 and seen["retry_failed"] and seen["replay_model"] is None
)
assert seen["key"] == "HOME-VISITS"
assert "CY2008 final: 1 summaries" in res.output
assert "CY2023 final: 2 summaries" in res.output
assert "done 2, skipped 1, failed 0, failed_pending 3" in res.output
assert "wrote 3 summaries" in res.output
assert con.published == [True]
assert len(read_cms_summaries(con._real, "HOME-VISITS")) == 3
def test_without_write_writes_nothing(self, con, monkeypatch):
self._patch(monkeypatch, {})
monkeypatch.setattr(pfs_cli, "_batch", lambda: 1 / 0)
res = runner.invoke(app, ["pfs", "cms-summaries", "--family", "HOME-VISITS"])
assert res.exit_code == 0, res.output
assert "wrote" not in res.output
def test_no_llm_builds_no_classifier_and_replays(self, con, monkeypatch):
seen = {}
self._patch(monkeypatch, seen, counts={})
monkeypatch.setattr(pfs_cli, "_classifier", lambda **kw: 1 / 0)
res = runner.invoke(
app, ["pfs", "cms-summaries", "--family", "HOME-VISITS", "--no-llm"]
)
assert res.exit_code == 0, res.output
assert seen["classify"] is None and seen["replay_model"] == "qwen-x"
assert "done 0, skipped 0, failed 0, failed_pending 0" in res.output
def test_unknown_family(self, con):
res = runner.invoke(app, ["pfs", "cms-summaries", "--family", "NOPE"])
assert res.exit_code != 0 and "unknown curated family" in res.output
class TestClassifierPin:
@pytest.mark.parametrize("pin,expect", [(True, "qwen-test"), (False, None)])
def test_classifier_pins_only_when_asked(self, monkeypatch, pin, expect):
import llm.classify as lc
import llm.pool as lp
from llm import config as llm_config
class Cfg:
instruct_model = "qwen-test"
got = {}
monkeypatch.setattr(llm_config, "load", lambda: Cfg())
monkeypatch.setattr(
lp.HostPool, "from_config", classmethod(lambda c, cfg: "POOL")
)
monkeypatch.setattr(
lc, "closed_vocab_classifier", lambda cfg, pool, **kw: got.update(kw) or "C"
)
assert pfs_cli._classifier(pin=pin) == "C"
assert got == {"model": expect}
class TestCommenters:
def _rows(self):
from pfs.codetables import CommenterRow
return [
CommenterRow(
"K1",
"D1",
"c1",
"AAFP",
"specialty_society",
"",
"",
0,
"both",
"Business",
"regs",
),
CommenterRow(
"K2",
"D1",
"c2",
"",
"individual",
"",
"",
0,
"regs_gov_fields",
"Individual",
"regs",
),
CommenterRow(
"K3",
"D1",
"c3",
"",
"individual",
"",
"",
0,
"regs_gov_fields",
"Individual",
"regs",
),
]
def _patch(self, monkeypatch, seen, pending=0):
def fake(engine, store, con_, fam, **kw):
seen.update(kw, key=fam.key)
kw["progress"]("K1", "done")
return self._rows(), {
"done": 1,
"skipped": 0,
"failed": 0,
"failed_pending": 0,
"pending": pending,
"blocked": 1,
}
monkeypatch.setattr(pfs_cli, "identify", fake)
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
monkeypatch.setattr(pfs_cli, "_mirror", lambda: "MIRROR")
monkeypatch.setattr(pfs_cli, "_asker", lambda: "ASK")
monkeypatch.setattr(
pfs_cli, "ledger_path", lambda t, f: Path("/nonexistent") / f"{t}-{f}"
)
def test_dry_run_lists_categories(self, con, monkeypatch):
from types import SimpleNamespace
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
monkeypatch.setattr(pfs_cli, "_mirror", lambda: "MIRROR")
monkeypatch.setattr(pfs_cli, "_asker", lambda: 1 / 0)
monkeypatch.setattr(
pfs_cli,
"family_comment_items",
lambda eng, codes, key, phrases, docket: [
("K1", "a"),
("K2", "b"),
("K3", "c"),
("K4", "d"),
("K5", "e"),
],
)
regs = dict(
zip(["K1", "K2", "K3", "K4"], ["Individual", "Individual", "", "Physician"])
)
monkeypatch.setattr(
pfs_cli,
"regs_for",
lambda store, mirror, docket, keys: (
{},
{k: SimpleNamespace(category=regs[k]) for k in keys if k in regs},
),
)
res = runner.invoke(
app,
[
"pfs",
"commenters",
"--family",
"home-visits",
"--docket",
"D1",
"--dry-run",
],
)
assert res.exit_code == 0, res.output
assert "D1: 5 items; no model calls made" in res.output
assert "unreadable mirror JSON: 1" in res.output
assert "category Individual: 2" in res.output
assert "category (none): 1" in res.output
assert "category Physician: 1" in res.output
def test_writes_and_publishes(self, con, monkeypatch):
from pfs.codetables import read_commenters
seen = {}
self._patch(monkeypatch, seen)
res = runner.invoke(
app,
[
"pfs",
"commenters",
"--family",
"home-visits",
"--docket",
"D1",
"--limit",
"5",
"--write",
],
)
assert res.exit_code == 0, res.output
assert seen["key"] == "HOME-VISITS" and seen["docket"] == "D1"
assert (
seen["limit"] == 5 and seen["ask"] == "ASK" and seen["mirror"] == "MIRROR"
)
assert "K1 done" in res.output
assert "individual: 2" in res.output and "specialty_society: 1" in res.output
assert "pending 0, blocked 1" in res.output
assert "wrote 3 commenters" in res.output
assert con.published == [True]
assert len(read_commenters(con._real)) == 3
def test_write_refused_while_pending(self, con, monkeypatch):
self._patch(monkeypatch, {}, pending=2)
monkeypatch.setattr(pfs_cli, "_batch", lambda: 1 / 0)
res = runner.invoke(
app,
[
"pfs",
"commenters",
"--family",
"HOME-VISITS",
"--docket",
"D1",
"--write",
],
)
assert res.exit_code != 0 and "2 comments not yet identified" in res.output
def test_without_write_writes_nothing(self, con, monkeypatch):
self._patch(monkeypatch, {})
monkeypatch.setattr(pfs_cli, "_batch", lambda: 1 / 0)
res = runner.invoke(
app, ["pfs", "commenters", "--family", "HOME-VISITS", "--docket", "D1"]
)
assert res.exit_code == 0 and "commenters: 3;" in res.output
def test_unknown_family(self, con):
res = runner.invoke(
app, ["pfs", "commenters", "--family", "NOPE", "--docket", "D1"]
)
assert res.exit_code != 0
def test_mirror_indirection(self):
from bib.regulations_gov import Mirror
assert isinstance(pfs_cli._mirror(), Mirror)
ASK = SimpleNamespace(model="qwen-x")
#: a golden-set score shaped like the CY2023 measurement (R66a)
SCORES = Scores(100, 83, 88, 24, 10, {}, (), positions=27, found=15, right=10)
class TestStance:
"""``stack pfs stance`` and the reaction overlay (task 9)."""
def _patch(self, con, monkeypatch, *, pending=0, seen=None):
from bib.dockets import Docket
from pfs.codetables import CommenterRow, StanceRow, write_commenters
seen = {} if seen is None else seen
write_commenters(
con._real,
[
CommenterRow(k, "D1", "C-" + k, "", "individual", "", "", 0, "regs_gov_fields", "", "regs", "HOME-VISITS")
for k in ("K1", "K2")
],
) # fmt: skip
store = type(
"S",
(),
{"dockets": lambda self: [Docket(id="D1", comment_end_date="2022-09-06")]},
)()
monkeypatch.setattr(pfs_cli, "_store", lambda: store)
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
monkeypatch.setattr(pfs_cli, "_asker", lambda large=False: ASK)
monkeypatch.setattr(
pfs_cli, "_golden_run", lambda data, method, **kw: (SCORES, {})
)
monkeypatch.setattr(pfs_cli, "_model_name", lambda large=False: "qwen-x")
from pfs.stance import EventChoice
monkeypatch.setattr(
pfs_cli,
"docket_events",
lambda con_, fam, year, store_: [
EventChoice("HOME-VISITS:2023:G0318", "g", year),
EventChoice("HOME-VISITS:2023:other", "o", year),
],
)
monkeypatch.setattr(
pfs_cli, "ledger_path", lambda t, f: Path("/nonexistent") / f"{t}-{f}"
)
monkeypatch.setattr(
pfs_cli,
"family_comment_items",
lambda eng, codes, key, phrases, docket, **kw: [("K1", "t1"), ("K2", "t2"), ("K9", "t9")],
) # fmt: skip
def series(engine, store_, family, codes, **kw):
return [
ReactionRow(family, "D1", "docket", 2023, 2, 9, 0, 0, 0, 0, "[]"),
ReactionRow(family, "R1", "fr-pairs", 2023, 3, 7, 0, 0, 0, 0, "[]"),
]
monkeypatch.setattr(pfs_cli, "reaction_series", series)
def fake(commenters, texts, events, ask, log, **kw):
seen.update(
keys=[c.item_key for c in commenters],
texts=texts,
events=[e.event_key for e in events],
ask=ask,
**kw,
)
kw["progress"]("K1", "done")
rows = [
StanceRow("HOME-VISITS", "K1", "D1", "HOME-VISITS:2023:other", "oppose", "", "", "x"),
StanceRow("HOME-VISITS", "K2", "D1", "HOME-VISITS:2023:other", "support", "", "", "x"),
] # fmt: skip
return rows, {
"done": 2, "skipped": 0, "failed": 0, "failed_pending": 0,
"pending": pending, "no_text": 0,
} # fmt: skip
monkeypatch.setattr(pfs_cli, "stances_for", fake)
return seen
def _args(self, *extra):
return ["pfs", "stance", "--family", "home-visits", "--docket", "D1", *extra]
def test_write_replaces_docket_row_and_keeps_fr_pairs(self, con, monkeypatch):
from pfs.codetables import read_stances, write_reaction
seen = self._patch(con, monkeypatch)
write_reaction(
con._real,
"HOME-VISITS",
[
ReactionRow("HOME-VISITS", "D1", "docket", 2023, 2, 9, 0, 0, 0, 0, "[]"),
ReactionRow("HOME-VISITS", "D0", "docket", 2020, 1, 4, 0, 0, 0, 0, "[]"),
ReactionRow("HOME-VISITS", "R1", "fr-pairs", 2023, 3, 7, 0, 0, 0, 0, "[]"),
],
) # fmt: skip
res = runner.invoke(app, self._args("--limit", "3", "--write"))
assert res.exit_code == 0, res.output
assert seen["keys"] == ["K1", "K2"] and seen["texts"] == {
"K1": "t1",
"K2": "t2",
}
assert seen["events"] == ["HOME-VISITS:2023:G0318", "HOME-VISITS:2023:other"]
assert seen["limit"] == 3 and seen["ask"] is ASK
assert "wrote 2 stances" in res.output and "K1 done" in res.output
assert (
"HOME-VISITS:2023:other: support 1, modify 0, oppose 1, unclear 0"
in res.output
)
assert len(read_stances(con._real, "HOME-VISITS")) == 2
rows = {r.period: r for r in read_reaction(con._real, "HOME-VISITS")}
assert set(rows) == {"D1", "D0", "R1"}
assert (rows["D1"].stance_support, rows["D1"].stance_oppose) == (1, 1)
assert (rows["D1"].n_classified, rows["D1"].n_commenters) == (2, 2)
assert (rows["D0"].n_classified, rows["D0"].n_commenters) == (None, None)
assert list(json.loads(rows["D1"].events_json)) == ["HOME-VISITS:2023:other"]
assert rows["D1"].n_items == 2 and rows["D1"].n_total == 9
assert (rows["D0"].stance_support, rows["R1"].stance_oppose) == (0, 0)
assert rows["D0"].events_json == "{}"
assert con.published == [True]
def test_no_llm_replays_the_ledger(self, con, monkeypatch):
seen = self._patch(con, monkeypatch)
monkeypatch.setattr(pfs_cli, "_asker", lambda: 1 / 0)
res = runner.invoke(app, self._args("--no-llm"))
assert res.exit_code == 0, res.output
assert seen["ask"] is None and seen["model"] == "qwen-x"
def test_write_with_no_stored_reaction_rows_uses_series(self, con, monkeypatch):
self._patch(con, monkeypatch)
res = runner.invoke(app, self._args("--write"))
assert res.exit_code == 0, res.output
assert {r.period for r in read_reaction(con._real, "HOME-VISITS")} == {
"D1",
"R1",
}
def test_write_refused_while_pending(self, con, monkeypatch):
self._patch(con, monkeypatch, pending=2)
monkeypatch.setattr(pfs_cli, "_batch", lambda: 1 / 0)
res = runner.invoke(app, self._args("--write"))
assert res.exit_code != 0 and "2 comments not yet classified" in res.output
def test_without_write_writes_nothing(self, con, monkeypatch):
self._patch(con, monkeypatch)
monkeypatch.setattr(pfs_cli, "_batch", lambda: 1 / 0)
res = runner.invoke(app, self._args())
assert res.exit_code == 0 and "stances: 2;" in res.output
def test_dry_run_makes_no_model_calls(self, con, monkeypatch):
self._patch(con, monkeypatch)
monkeypatch.setattr(pfs_cli, "_asker", lambda: 1 / 0)
monkeypatch.setattr(pfs_cli, "stances_for", lambda *a, **k: 1 / 0)
res = runner.invoke(app, self._args("--dry-run"))
assert res.exit_code == 0, res.output
assert "D1: 2 commenters, 2 with comment text, 2 events" in res.output
assert "HOME-VISITS:2023:other" in res.output
assert "no model calls made" in res.output
def test_no_commenter_rows(self, con, monkeypatch):
from pfs.codetables import write_commenters
self._patch(con, monkeypatch)
write_commenters(con._real, [], docket="D1", family="HOME-VISITS")
res = runner.invoke(app, self._args("--write"))
assert res.exit_code == 0 and "D1: no commenter rows" in res.output
def test_unknown_family_and_docket(self, con, monkeypatch):
self._patch(con, monkeypatch)
res = runner.invoke(
app, ["pfs", "stance", "--family", "NOPE", "--docket", "D1"]
)
assert res.exit_code != 0
res = runner.invoke(
app, ["pfs", "stance", "--family", "home-visits", "--docket", "ZZ"]
)
assert res.exit_code != 0 and "unknown docket" in res.output
def test_rollup_ignores_cms_summaries(self, con, monkeypatch):
from pfs.codetables import CmsSummaryRow, write_cms_summaries
self._patch(con, monkeypatch)
monkeypatch.setattr(pfs_cli, "reaction_series", lambda e, s, f, c, **kw: [
ReactionRow(f, "D1", "docket", 2023, 2, 9, 0, 0, 0, 0, "[]")
]) # fmt: skip
def counts():
r = [
x for x in read_reaction(con._real, "HOME-VISITS") if x.period == "D1"
][0]
return (
r.stance_support,
r.stance_modify,
r.stance_oppose,
r.stance_unclear,
r.events_json,
)
assert runner.invoke(app, self._args("--write")).exit_code == 0
before = counts()
write_cms_summaries(
con._real,
"HOME-VISITS",
[CmsSummaryRow("HOME-VISITS", "R1", 2023, "final", 1, 2, "99342", "a majority of commenters", "support", "finalize_as_proposed", "regex")],
) # fmt: skip
res = runner.invoke(
app, ["pfs", "reaction", "--family", "home-visits", "--write"]
)
assert res.exit_code == 0, res.output
assert counts() == before
assert runner.invoke(app, self._args("--write")).exit_code == 0
assert counts() == before == (1, 0, 1, 0, before[4])
def _reaction_patch(self, monkeypatch):
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
monkeypatch.setattr(
pfs_cli,
"reaction_series",
lambda e, s, f, c, **kw: [
ReactionRow(f, "D1", "docket", 2023, 5, 20, 0, 0, 0, 0, "[]"),
ReactionRow(f, "D2", "docket", 2024, 4, 10, 0, 0, 0, 0, "[]"),
],
)
def test_reaction_rerun_keeps_stance_counts(self, con, monkeypatch):
from pfs.codetables import StanceRow, write_stances
self._reaction_patch(monkeypatch)
from pfs.codetables import CommenterRow, write_commenters, write_reaction
write_commenters(
con._real,
[
CommenterRow(k, "D1", k, "", "individual", "", "", 0, "regs_gov_fields", "", "regs", "CCM")
for k in ("K1", "K2", "K3")
],
) # fmt: skip
ev = json.dumps({"cell": 0.83, "recall": 0.556, "in_sample": True})
write_reaction(
con._real,
"CCM",
[
ReactionRow("CCM", "D1", "docket", 2023, 5, 20, 0, 0, 0, 0, "[]", '{"CCM:2023:other": {}}',
stance_method="stance-v4:qwen2.5:32b", stance_agreement=0.83, stance_eval=ev),
],
) # fmt: skip
write_stances(
con._real,
"CCM",
"D1",
[
StanceRow("CCM", "K1", "D1", "CCM:2023:other", "modify", "", "", "x"),
StanceRow("CCM", "K2", "D1", "CCM:2023:other", "modify", "", "", "x"),
],
) # fmt: skip
res = runner.invoke(app, ["pfs", "reaction", "--family", "ccm", "--write"])
assert res.exit_code == 0, res.output
rows = {r.period: r for r in read_reaction(con._real, "CCM")}
assert rows["D1"].stance_modify == 2 and rows["D1"].n_items == 5
assert (rows["D1"].n_classified, rows["D1"].n_commenters) == (2, 3)
assert (rows["D2"].n_classified, rows["D2"].n_commenters) == (None, None)
assert list(json.loads(rows["D1"].events_json)) == ["CCM:2023:other"]
assert rows["D2"].stance_modify == 0 and rows["D2"].events_json == "{}"
# R66a: a reaction rerun keeps the stored row's stance method record
d1 = rows["D1"]
assert (d1.stance_method, d1.stance_agreement, d1.stance_eval) == (
"stance-v4:qwen2.5:32b",
0.83,
ev,
)
assert (rows["D2"].stance_method, rows["D2"].stance_eval) == (None, None)
def test_stance_sample_refused_when_stances_stored(self, con, monkeypatch):
from pfs.codetables import StanceRow, write_stances
self._reaction_patch(monkeypatch)
monkeypatch.setattr(pfs_cli, "_classifier", lambda **k: 1 / 0)
write_stances(
con._real, "CCM", "D1", [StanceRow("CCM", "K1", "D1", "CCM:2023:other", "modify", "", "", "x")]
) # fmt: skip
res = runner.invoke(
app, ["pfs", "reaction", "--family", "ccm", "--stance-sample", "5"]
)
assert res.exit_code != 0 and "stored stances" in res.output
def test_reaction_tolerates_a_missing_stance_table(self, con, monkeypatch):
self._reaction_patch(monkeypatch)
con._real.execute("DROP TABLE pfs.comment_stance")
res = runner.invoke(app, ["pfs", "reaction", "--family", "ccm"])
assert res.exit_code == 0, res.output
def test_stored_tolerates_only_a_missing_table(self):
import duckdb as dd
c = dd.connect(":memory:")
assert pfs_cli._stored(pfs_cli.read_stances, c, "F") == []
with pytest.raises(RuntimeError):
pfs_cli._stored(
lambda con, f: (_ for _ in ()).throw(RuntimeError("x")), c, "F"
)
def test_method_option_is_passed_and_validated(self, con, monkeypatch):
seen = self._patch(con, monkeypatch)
res = runner.invoke(app, self._args("--method", "per-event"))
assert res.exit_code == 0 and seen["method"] == "per-event", res.output
res = runner.invoke(app, self._args())
assert seen["method"] == pfs_cli.DEFAULT_METHOD
res = runner.invoke(app, self._args("--method", "nope"))
assert res.exit_code != 0 and "--method must be one of" in res.output
def test_text_window_is_the_wide_one(self, con, monkeypatch):
seen = self._patch(con, monkeypatch)
monkeypatch.setattr(
pfs_cli,
"family_comment_items",
lambda eng, codes, key, phrases, **kw: seen.update(window=kw) or [],
)
runner.invoke(app, self._args("--dry-run"))
assert seen["window"]["max_chars"] == pfs_cli.TEXT_CHARS == 6000
assert seen["window"]["max_chunks"] == pfs_cli.TEXT_CHUNKS
class TestStanceEval:
GOLDEN = {
"family": "HOME-VISITS",
"docket": "D1",
"items": [
{
"item_key": "K1",
"cells": {
"HOME-VISITS:2023:G0318": {"label": "support"},
"HOME-VISITS:2023:other": {"label": "none"},
},
},
{
"item_key": "K2",
"cells": {
"HOME-VISITS:2023:G0318": {"label": "oppose"},
"HOME-VISITS:2023:other": {"label": "none"},
},
},
],
}
def _patch(self, monkeypatch, tmp_path, *, pending=0, no_text=0, golden=None):
from bib.dockets import Docket
from pfs.codetables import StanceRow
from pfs.stance import EventChoice
path = tmp_path / "g.json"
path.write_text(json.dumps(golden or self.GOLDEN))
seen = {}
store = type(
"S",
(),
{"dockets": lambda self: [Docket(id="D1", comment_end_date="2022-09-06")]},
)()
monkeypatch.setattr(pfs_cli, "_store", lambda: store)
monkeypatch.setattr(pfs_cli, "_engine", lambda: "ENGINE")
monkeypatch.setattr(
pfs_cli,
"_asker",
lambda large=False: (
seen.update(large=large) or type("A", (), {"model": "qwen-x"})()
),
)
monkeypatch.setattr(
pfs_cli, "ledger_path", lambda t, f: Path("/nonexistent") / f"{t}-{f}"
)
monkeypatch.setattr(
pfs_cli,
"docket_events",
lambda con_, fam, year, store_: [
EventChoice("HOME-VISITS:2023:G0318", "g", year),
EventChoice("HOME-VISITS:2023:other", "o", year),
],
)
monkeypatch.setattr(
pfs_cli,
"family_comment_items",
lambda eng, codes, key, phrases, **kw: [("K1", "t1"), ("K2", "t2"), ("K9", "t")],
) # fmt: skip
def fake(commenters, texts, events, ask, log, **kw):
seen.update(keys=[c.item_key for c in commenters], texts=texts, **kw)
kw["progress"]("K1", "done")
rows = [
StanceRow("HOME-VISITS", "K1", "D1", "HOME-VISITS:2023:G0318", "support", "", "", "x"),
StanceRow("HOME-VISITS", "K2", "D1", "HOME-VISITS:2023:G0318", "support", "", "", "x"),
] # fmt: skip
return rows, {
"done": 2, "skipped": 0, "failed": 0, "failed_pending": 0,
"pending": pending, "no_text": no_text,
} # fmt: skip
monkeypatch.setattr(pfs_cli, "stances_for", fake)
return path, seen
def _run(self, path, *extra):
return runner.invoke(app, ["pfs", "stance-eval", "--golden", str(path), *extra])
def test_scores_and_prints_the_matrix(self, con, monkeypatch, tmp_path):
path, seen = self._patch(monkeypatch, tmp_path)
res = self._run(
path, "--method", "per-event", "--model", "large", "--limit", "7"
)
assert res.exit_code == 0, res.output
assert seen["large"] is True and seen["method"] == "per-event"
assert seen["limit"] == 7 and seen["keys"] == ["K1", "K2"]
assert seen["texts"] == {"K1": "t1", "K2": "t2"} # only golden items
assert "per-event on qwen-x: 2 new calls" in res.output
assert "cells 4: agreement 0.750 (3)" in res.output
assert "presence 1.000 (4)" in res.output
assert "firm accuracy 0.500 (1/2); gate FAIL" in res.output
assert "recall 1.000 (2/2); direction 0.500 (1/2)" in res.output
assert "confusion (rows golden, columns model)" in res.output
assert "disagree K2 G0318: golden oppose, model support" in res.output
def test_incomplete_run_prints_no_scores(self, con, monkeypatch, tmp_path):
path, _ = self._patch(monkeypatch, tmp_path, pending=3)
res = self._run(path)
assert res.exit_code == 0 and "incomplete: rerun to resume" in res.output
assert "agreement" not in res.output
def test_validation(self, con, monkeypatch, tmp_path):
path, _ = self._patch(monkeypatch, tmp_path)
assert "--method must be one of" in self._run(path, "--method", "x").output
assert (
"--model must be instruct or large"
in self._run(path, "--model", "x").output
)
path, _ = self._patch(
monkeypatch, tmp_path, golden={**self.GOLDEN, "family": "NOPE"}
)
assert "unknown curated family" in self._run(path).output
path, _ = self._patch(
monkeypatch, tmp_path, golden={**self.GOLDEN, "docket": "ZZ"}
)
assert "unknown docket" in self._run(path).output
class TestStanceMethodRecord:
def test_write_records_method_and_golden_agreement(self, con, monkeypatch):
t = TestStance()
seen = t._patch(con, monkeypatch)
res = runner.invoke(app, t._args("--model", "large", "--write"))
assert res.exit_code == 0, res.output
got = {r.period: r for r in read_reaction(con._real, "HOME-VISITS")}
d1 = got["D1"]
assert d1.stance_method == f"{pfs_cli.PROMPT_VERSIONS['multi']}:qwen-x"
# R66a: the measurement is the stance_eval JSON; the single figure is gone
assert d1.stance_agreement is None
assert json.loads(d1.stance_eval) == {
"cell": 0.83,
"recall": 0.556,
"direction": 0.667,
"n_items": 20,
"n_positions": 27,
"family": "HOME-VISITS",
"docket": "CMS-2022-0113",
"in_sample": True,
}
assert got["R1"].stance_method is None and got["R1"].stance_eval is None
assert seen["ask"] is ASK
res = runner.invoke(app, t._args("--model", "huge"))
assert res.exit_code != 0 and "--model must be instruct or large" in res.output
def test_eval_is_stamped_only_for_the_golden_family(
self, con, monkeypatch, tmp_path
):
"""A golden set of another family is never replayed nor stamped."""
t = TestStance()
t._patch(con, monkeypatch)
monkeypatch.setattr(pfs_cli, "_golden_run", lambda *a, **k: 1 / 0)
g = tmp_path / "g.json"
g.write_text(json.dumps({"family": "CCM", "docket": "D9", "items": []}))
res = runner.invoke(app, t._args("--golden", str(g), "--write"))
assert res.exit_code == 0, res.output
d1 = {r.period: r for r in read_reaction(con._real, "HOME-VISITS")}["D1"]
assert d1.stance_method and d1.stance_eval is None
assert d1.stance_agreement is None
def test_large_model_builds_the_large_asker(self, con, monkeypatch):
t = TestStance()
t._patch(con, monkeypatch)
calls = []
monkeypatch.setattr(
pfs_cli, "_asker", lambda large=False: calls.append(large) or ASK
)
runner.invoke(app, t._args("--model", "large"))
runner.invoke(app, t._args("--model", "instruct"))
runner.invoke(app, t._args()) # the default model is the large one
assert calls == [True, False, pfs_cli.DEFAULT_MODEL == "large"]
def test_eval_is_none_without_golden_or_with_pending(self, monkeypatch, tmp_path):
F = "HOME-VISITS"
assert pfs_cli._method_eval(tmp_path / "none.json", "multi", "m", F) is None
g = tmp_path / "g.json"
g.write_text(json.dumps({"family": F, "docket": "D1", "items": [{}]}))
monkeypatch.setattr(
pfs_cli, "_golden_run", lambda data, method, **kw: (None, {})
)
assert pfs_cli._method_eval(g, "multi", "m", F) is None
monkeypatch.setattr(
pfs_cli, "_golden_run", lambda data, method, **kw: (SCORES, {})
)
got = json.loads(pfs_cli._method_eval(g, "multi", "m", F))
assert (got["recall"], got["direction"], got["n_items"]) == (0.556, 0.667, 1)
assert pfs_cli._method_eval(g, "multi", "m", "CCM") is None
def test_model_of_and_slow_post(self, monkeypatch):
import httpx
assert pfs_cli._model_of(SimpleNamespace(model="a"), True) == "a"
monkeypatch.setattr(
pfs_cli, "_model_name", lambda large=False: "L" if large else "S"
)
assert pfs_cli._model_of(None, True) == "L"
assert pfs_cli._model_of(None, False) == "S"
class Client:
def __init__(self, timeout):
seen["timeout"] = timeout
def __enter__(self):
return self
def __exit__(self, *a):
return False
def post(self, url, json):
return SimpleNamespace(
raise_for_status=lambda: None, json=lambda: {"u": url}
)
seen = {}
monkeypatch.setattr(httpx, "Client", Client)
assert pfs_cli._slow_post("http://h", {}) == {"u": "http://h"}
assert seen["timeout"].read == 600.0
def test_model_name_large(self, monkeypatch):
from llm import config as llm_config
monkeypatch.setattr(llm_config, "load", lambda: _Cfg())
assert pfs_cli._model_name() == "qwen-14"
assert pfs_cli._model_name(large=True) == "qwen-32"
class TestStanceEvalOffline:
def test_no_llm_and_no_relevance_reach_the_run(self, monkeypatch, tmp_path):
seen = {}
def run(data, method, **kw):
seen.update(method=method, **kw)
return None, {
"done": 0,
"skipped": 0,
"failed": 0,
"failed_pending": 0,
"pending": 1,
"no_text": 0,
}
monkeypatch.setattr(pfs_cli, "_golden_run", run)
monkeypatch.setattr(pfs_cli, "_asker", lambda large=False: 1 / 0)
monkeypatch.setattr(
pfs_cli, "_model_name", lambda large=False: "L" if large else "S"
)
g = tmp_path / "g.json"
g.write_text("{}")
res = runner.invoke(
app,
[
"pfs",
"stance-eval",
"--golden",
str(g),
"--no-llm",
"--no-relevance",
"--model",
"large",
],
)
assert res.exit_code == 0, res.output
assert (
seen["ask"] is None and seen["name"] == "L" and seen["relevance"] is False
)
assert "multi on L (no relevance filter)" in res.output
assert "incomplete" in res.output