fix(llm,pfs): I5/M1/M2 — probe the model pool once per classifier, strip FR citations before lineage pattern matching, drop unused extract_text param
I5: `closed_vocab_classifier` called `pool.check(cfg.instruct_model)` on every `classify()` call — a host-liveness probe, not per-request work. A code with hundreds of element lines re-probed the pool hundreds of times. Move the check to the closure's construction. M1: `fr_events` matched event patterns (crosswalk, replaces, …) against the raw FR paragraph text, so a citation like "91 FR 99490" inside a "crosswalk ... 91 FR 99490" sentence read as a real crosswalk event for 99490 — the citation's page number is not a mention of the code. Strip FR citations with the now-public `pfs.families.FR_CITE_RE` before both the presence check and every event pattern. M2: `extract_text`'s `classify` parameter was unused; no call site passed it. Drop it. Claude-Session: https://claude.ai/code/session_01Aum3pEMAM3yQVdFSdVe6Gc
This commit is contained in:
@@ -56,11 +56,15 @@ def _default_post(url: str, json: dict) -> dict:
|
||||
def closed_vocab_classifier(
|
||||
cfg: Any, pool: Any, *, post: Callable[..., dict] | None = None
|
||||
) -> Classifier:
|
||||
"""Return ``classify(text, choices) -> slug | None`` bound to *pool*."""
|
||||
"""Return ``classify(text, choices) -> slug | None`` bound to *pool*.
|
||||
|
||||
``pool.check`` runs once here, not per call (I5) — it's a host-liveness
|
||||
probe, not per-request work, and a code with hundreds of element lines
|
||||
used to re-probe the pool hundreds of times."""
|
||||
send = post or _default_post
|
||||
pool.check(cfg.instruct_model)
|
||||
|
||||
def classify(text: str, choices: Sequence[str]) -> str | None:
|
||||
pool.check(cfg.instruct_model)
|
||||
with pool.acquire_generation() as host:
|
||||
model = pick_model(cfg, pool, host)
|
||||
data = send(
|
||||
|
||||
@@ -80,9 +80,7 @@ def extract_run(
|
||||
return Extraction(run.code, tuple(ordered), tuple(reviews))
|
||||
|
||||
|
||||
def extract_text(
|
||||
code: str, text: str, *, year: int, source: str, classify: Classifier | None = None
|
||||
) -> Extraction:
|
||||
def extract_text(code: str, text: str, *, year: int, source: str) -> Extraction:
|
||||
rows = [
|
||||
ElementRow(code, year, e.type.value, e.value, e.detail, text, "", 0, 0, source)
|
||||
for e in parse_descriptor(text)
|
||||
|
||||
@@ -19,7 +19,7 @@ from typing import Any
|
||||
|
||||
from pfs.codetables import EventRow
|
||||
from pfs.descriptors import rule_year_of
|
||||
from pfs.families import find_codes
|
||||
from pfs.families import FR_CITE_RE, find_codes
|
||||
|
||||
RVU_KINDS = (
|
||||
"appeared",
|
||||
@@ -199,6 +199,11 @@ def fr_events(store: Any, code: str) -> list[EventRow]:
|
||||
out: list[EventRow] = []
|
||||
for r in rows:
|
||||
text = _expand_ranges(r["text"])
|
||||
# An FR page citation's number ("91 FR 99490") is not a mention of
|
||||
# the code — strip it before both the presence check and every
|
||||
# event pattern below (M1: a "crosswalk ... 91 FR 99490" sentence
|
||||
# used to read as a crosswalk event for 99490).
|
||||
text = FR_CITE_RE.sub(" ", text)
|
||||
if code not in text.upper():
|
||||
continue
|
||||
year = rule_year_of(r["title"], r["date_published"])
|
||||
|
||||
@@ -93,3 +93,14 @@ class TestClassifier:
|
||||
post=lambda url, json: {"message": {"content": "maybe consent?"}},
|
||||
)
|
||||
assert classify("x", ["consent"]) is None
|
||||
|
||||
def test_pool_check_runs_once_not_per_call(self):
|
||||
# I5: pool.check is a host-liveness probe, done once when the
|
||||
# classifier closure is built — not per classify() call.
|
||||
pool = _Pool()
|
||||
classify = closed_vocab_classifier(
|
||||
_Cfg(), pool, post=lambda url, json: {"message": {"content": "consent"}}
|
||||
)
|
||||
classify("a", ["consent"])
|
||||
classify("b", ["consent"])
|
||||
assert pool.checked == ["qwen2.5:14b"]
|
||||
|
||||
@@ -189,6 +189,23 @@ class TestFrEvents:
|
||||
kinds = {e.kind for e in fr_events(store, "99441")}
|
||||
assert {"deleted", "bundled", "telehealth_list"} <= kinds
|
||||
|
||||
def test_fr_citation_page_number_is_not_a_crosswalk_mention(self, store):
|
||||
# M1: an FR page citation ("91 FR 99490") is not a real mention of
|
||||
# 99490 — it must be stripped before pattern matching, or a
|
||||
# "crosswalk ... 91 FR 99490" sentence reads as a crosswalk event.
|
||||
store.con.execute(
|
||||
"INSERT INTO fr_anchors VALUES (?,?,?,?,?)",
|
||||
(
|
||||
"DE2VH9PD",
|
||||
1500,
|
||||
67999,
|
||||
1500,
|
||||
"We note the crosswalk methodology discussed at 91 FR 99490 for related codes.",
|
||||
),
|
||||
)
|
||||
ev = fr_events(store, "99490")
|
||||
assert not any(e.kind == "crosswalk" for e in ev)
|
||||
|
||||
def test_deleted_via_range_prefilter_middle_member(self, store):
|
||||
# 90868 is a middle member of a "90867-90869" range and appears
|
||||
# nowhere else in the paragraph literally — the SQL prefilter must
|
||||
|
||||
Reference in New Issue
Block a user