refactor(pfs,llm,bib): one definition of the chunk-metadata array predicates (GIN-index expression), normalize_key for family keys everywhere, error-isolation docstring on code_cited_sources (refs #705)
Some checks failed
CI / lint (push) Successful in 43s
CI / notebooks-smoke (push) Successful in 1m27s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / notebooks (push) Successful in 59s
Infra CI / zotero (push) Successful in 16s
Infra CI / docs (push) Successful in 1m31s
Infra CI / api (push) Successful in 1m16s
Infra CI / llm (push) Successful in 47s
Infra CI / mc (push) Failing after 12s
Deploy / report (push) Successful in 14s
CI / test (push) Has been cancelled
Some checks failed
CI / lint (push) Successful in 43s
CI / notebooks-smoke (push) Successful in 1m27s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / notebooks (push) Successful in 59s
Infra CI / zotero (push) Successful in 16s
Infra CI / docs (push) Successful in 1m31s
Infra CI / api (push) Successful in 1m16s
Infra CI / llm (push) Successful in 47s
Infra CI / mc (push) Failing after 12s
Deploy / report (push) Successful in 14s
CI / test (push) Has been cancelled
This commit is contained in:
@@ -615,6 +615,7 @@ def _(code, mo, not_built, pl, store):
|
||||
|
||||
from llm.config import load as _load_llm_cfg
|
||||
from llm.config import pg_url as _pg_url
|
||||
from pfs.anchors import families_array_sql as _families_array_sql
|
||||
|
||||
_eng = _sa_engine(_pg_url(_load_llm_cfg()))
|
||||
with _eng.begin() as _conn:
|
||||
@@ -623,8 +624,7 @@ def _(code, mo, not_built, pl, store):
|
||||
"SELECT c.name, count(*) AS n "
|
||||
"FROM langchain_pg_embedding e "
|
||||
"JOIN langchain_pg_collection c ON c.uuid = e.collection_id "
|
||||
"WHERE string_to_array(COALESCE(e.cmetadata->>'families', ''), "
|
||||
"' ') && ARRAY[:key] "
|
||||
f"WHERE {_families_array_sql('e')} && ARRAY[:key] "
|
||||
"GROUP BY c.name ORDER BY c.name"
|
||||
),
|
||||
{"key": _key},
|
||||
|
||||
@@ -271,16 +271,19 @@ class Tag(BaseModel):
|
||||
Parameters
|
||||
----------
|
||||
key : str
|
||||
The family key exactly as registered (e.g. ``CCM``,
|
||||
``APCM``, or a derived ``pfs.code_family`` key) — never
|
||||
lower-cased, since derived keys aren't necessarily
|
||||
uppercase-safe round trips.
|
||||
The family key as registered (e.g. ``CCM``, ``APCM``, or a
|
||||
derived ``pfs.code_family`` key); normalised through
|
||||
``pfs.families.normalize_key`` (stripped, upper-case — every
|
||||
registry key is stored that way) so one spelling never
|
||||
becomes two tags.
|
||||
|
||||
Examples::
|
||||
|
||||
Tag.family("CCM") # family:CCM
|
||||
"""
|
||||
return cls(namespace="family", value=key)
|
||||
from pfs.families import normalize_key
|
||||
|
||||
return cls(namespace="family", value=normalize_key(key))
|
||||
|
||||
@classmethod
|
||||
def fn(cls, qualified_name: str) -> Tag:
|
||||
|
||||
@@ -42,7 +42,13 @@ from pfs.codetables import (
|
||||
)
|
||||
from pfs.cpt_load import ingest as cpt_ingest
|
||||
from pfs.extract import extract_code
|
||||
from pfs.families import FAMILIES, HAND_FAMILIES, derive_families, refresh_from
|
||||
from pfs.families import (
|
||||
FAMILIES,
|
||||
HAND_FAMILIES,
|
||||
derive_families,
|
||||
normalize_key,
|
||||
refresh_from,
|
||||
)
|
||||
from pfs.guidance import build as build_guidance
|
||||
from pfs.lineage import lineage, lineage_all
|
||||
from pfs.reaction import series as reaction_series
|
||||
@@ -136,7 +142,7 @@ def _codes_for(
|
||||
if families:
|
||||
refresh_from(con)
|
||||
for family in families:
|
||||
fam = FAMILIES.get(family.upper())
|
||||
fam = FAMILIES.get(normalize_key(family))
|
||||
if fam is None:
|
||||
# List only the hand families here (M3) — FAMILIES can hold
|
||||
# ~15k derived keys once `refresh_from` has run, and that
|
||||
@@ -475,7 +481,7 @@ def guidance(
|
||||
family's FR paragraphs and the CPT manual cite, with FR provenance."""
|
||||
if not family:
|
||||
raise typer.BadParameter("pass --family (repeatable)")
|
||||
keys = [f.upper() for f in family]
|
||||
keys = [normalize_key(f) for f in family]
|
||||
store = _store()
|
||||
if write:
|
||||
# Ruling A13 (mirrors `stack pfs reaction`): resolve every family
|
||||
@@ -559,7 +565,7 @@ def _reaction_targets(
|
||||
refresh_from(con)
|
||||
targets: list[tuple[str, tuple[str, ...], str | None]] = []
|
||||
for f in family:
|
||||
key = f.upper()
|
||||
key = normalize_key(f)
|
||||
fam = FAMILIES.get(key)
|
||||
if fam is None:
|
||||
raise typer.BadParameter(
|
||||
|
||||
@@ -24,6 +24,7 @@ import duckdb
|
||||
|
||||
from llm.config import LlmConfig
|
||||
from llm.links import as_source
|
||||
from pfs.anchors import codes_array_sql, families_array_sql
|
||||
from pfs.codetables import is_missing_table_error
|
||||
from pfs.families import FAMILIES, Detection, detect_codes, refresh_from
|
||||
from pfs.valuation import ValuationRow, valuation
|
||||
@@ -296,7 +297,7 @@ _CITED_SQL = (
|
||||
") AS rn "
|
||||
"FROM langchain_pg_embedding e "
|
||||
"JOIN unnest(CAST(:codes AS text[])) AS w(code) "
|
||||
"ON w.code = ANY(string_to_array(COALESCE(e.cmetadata->>'codes', ''), ' ')) "
|
||||
f"ON w.code = ANY({codes_array_sql('e')}) "
|
||||
"WHERE e.collection_id = (SELECT uuid FROM langchain_pg_collection "
|
||||
"WHERE name = :collection) "
|
||||
") t "
|
||||
@@ -332,7 +333,7 @@ _FAMILY_CITED_SQL = (
|
||||
") AS item_rn "
|
||||
"FROM langchain_pg_embedding e "
|
||||
"JOIN unnest(CAST(:families AS text[])) AS w(family) "
|
||||
"ON string_to_array(COALESCE(e.cmetadata->>'families', ''), ' ') && ARRAY[w.family] "
|
||||
f"ON {families_array_sql('e')} && ARRAY[w.family] "
|
||||
"WHERE e.collection_id = (SELECT uuid FROM langchain_pg_collection "
|
||||
"WHERE name = :collection) "
|
||||
") one_per_item "
|
||||
@@ -358,7 +359,7 @@ _DOCKET_CITED_SQL = (
|
||||
") AS rn "
|
||||
"FROM langchain_pg_embedding e "
|
||||
"JOIN unnest(CAST(:codes AS text[])) AS w(code) "
|
||||
"ON w.code = ANY(string_to_array(COALESCE(e.cmetadata->>'codes', ''), ' ')) "
|
||||
f"ON w.code = ANY({codes_array_sql('e')}) "
|
||||
"WHERE e.collection_id = (SELECT uuid FROM langchain_pg_collection "
|
||||
"WHERE name = :collection) "
|
||||
") t "
|
||||
|
||||
@@ -16,6 +16,32 @@ from pfs.elements import parse_descriptor
|
||||
from pfs.families import FAMILIES, Family, find_codes
|
||||
|
||||
|
||||
#: The chunk-metadata arrays every code/family predicate reads. ONE
|
||||
#: definition (#705 item 4): the ``&&`` GIN index on pgvector was built on
|
||||
#: exactly ``string_to_array(coalesce(cmetadata->>'families',''),' ')`` and a
|
||||
#: predicate that spells it differently silently loses the index.
|
||||
def codes_array_sql(alias: str = "e") -> str:
|
||||
"""SQL for the chunk's ``codes`` metadata as a text[] (space-joined)."""
|
||||
return f"string_to_array(COALESCE({alias}.cmetadata->>'codes', ''), ' ')"
|
||||
|
||||
|
||||
def families_array_sql(alias: str = "e") -> str:
|
||||
"""SQL for the chunk's ``families`` metadata as a text[] (space-joined)."""
|
||||
return f"string_to_array(COALESCE({alias}.cmetadata->>'families', ''), ' ')"
|
||||
|
||||
|
||||
def code_or_family_predicate(
|
||||
alias: str = "e", *, codes_param: str = ":codes", family_param: str = ":family_key"
|
||||
) -> str:
|
||||
"""``(codes && CAST(:codes AS text[])) OR (families && ARRAY[:family_key])``
|
||||
— the two-way match the chat's cited-source windows and the reaction
|
||||
series share."""
|
||||
return (
|
||||
f"({codes_array_sql(alias)} && CAST({codes_param} AS text[])) "
|
||||
f"OR ({families_array_sql(alias)} && ARRAY[{family_param}])"
|
||||
)
|
||||
|
||||
|
||||
def code_family_index(families: Mapping[str, Family]) -> dict[str, tuple[str, ...]]:
|
||||
"""Code -> sorted tuple of every qualifying family key, built once so
|
||||
``anchor_metadata`` doesn't scan every family for every chunk (a
|
||||
|
||||
@@ -28,6 +28,14 @@ _FR_CITE_RE = re.compile(r"\b\d{1,3}\s+FR\s+\d{3,6}\b", re.IGNORECASE)
|
||||
FR_CITE_RE = _FR_CITE_RE
|
||||
|
||||
|
||||
def normalize_key(key: str) -> str:
|
||||
"""The canonical form of a family key: stripped, upper-case. Every
|
||||
registry key (hand and derived) is stored this way; ``Tag.family``,
|
||||
the ``stack pfs`` commands and the chat all normalise through here
|
||||
(#705 item 5) so one spelling never becomes two tags or two families."""
|
||||
return (key or "").strip().upper()
|
||||
|
||||
|
||||
def find_codes(text: str) -> tuple[str, ...]:
|
||||
"""Sorted unique codes literally present in *text*, upper-cased.
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ from typing import Any, Callable, Sequence
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
from pfs.anchors import code_or_family_predicate
|
||||
from pfs.descriptors import rule_year_of
|
||||
from pfs.families import code_pattern, codes_in
|
||||
|
||||
@@ -56,8 +57,7 @@ _DOCKET_COUNTS_SQL = text(
|
||||
"count(DISTINCT item_key) AS n_total "
|
||||
"FROM ( "
|
||||
"SELECT e.cmetadata->>'docket' AS docket, e.cmetadata->>'item_key' AS item_key, "
|
||||
"(string_to_array(COALESCE(e.cmetadata->>'codes', ''), ' ') && CAST(:codes AS text[])) "
|
||||
"OR (string_to_array(COALESCE(e.cmetadata->>'families', ''), ' ') && ARRAY[:family_key]) "
|
||||
f"{code_or_family_predicate('e')} "
|
||||
"AS matched "
|
||||
"FROM langchain_pg_embedding e "
|
||||
"WHERE e.collection_id = (SELECT uuid FROM langchain_pg_collection WHERE name = :collection) "
|
||||
@@ -83,8 +83,7 @@ _STANCE_SAMPLE_SQL = text(
|
||||
"WHERE e.collection_id = (SELECT uuid FROM langchain_pg_collection WHERE name = :collection) "
|
||||
"AND e.cmetadata->>'docket' = :docket "
|
||||
"AND ( "
|
||||
"(string_to_array(COALESCE(e.cmetadata->>'codes', ''), ' ') && CAST(:codes AS text[])) "
|
||||
"OR (string_to_array(COALESCE(e.cmetadata->>'families', ''), ' ') && ARRAY[:family_key]) "
|
||||
f"{code_or_family_predicate('e')} "
|
||||
") "
|
||||
") one_per_item "
|
||||
"WHERE item_rn = 1 "
|
||||
|
||||
@@ -28,6 +28,7 @@ class TestTagFactories:
|
||||
|
||||
def test_family_label(self) -> None:
|
||||
assert Tag.family("CCM").label == "family:CCM"
|
||||
assert Tag.family(" ccm ").label == "family:CCM" # normalised (#705)
|
||||
|
||||
|
||||
class TestItemCodesFromChunks:
|
||||
|
||||
@@ -73,3 +73,33 @@ def test_single_code_family_never_stamped():
|
||||
assert (
|
||||
anchor_metadata("code 54321", families=fams, code_index=index)["families"] == ""
|
||||
)
|
||||
|
||||
|
||||
class TestSharedPredicateSql:
|
||||
"""#705 item 4: one definition of the metadata-array expressions the
|
||||
GIN index was built on; every predicate composes from it."""
|
||||
|
||||
def test_arrays_match_the_index_expression(self):
|
||||
from pfs.anchors import codes_array_sql, families_array_sql
|
||||
|
||||
assert codes_array_sql("e") == (
|
||||
"string_to_array(COALESCE(e.cmetadata->>'codes', ''), ' ')"
|
||||
)
|
||||
assert families_array_sql("x") == (
|
||||
"string_to_array(COALESCE(x.cmetadata->>'families', ''), ' ')"
|
||||
)
|
||||
|
||||
def test_predicate_composes_both_sides(self):
|
||||
from pfs.anchors import code_or_family_predicate
|
||||
|
||||
p = code_or_family_predicate("e")
|
||||
assert "&& CAST(:codes AS text[])" in p and "&& ARRAY[:family_key]" in p
|
||||
assert p.startswith("(string_to_array(COALESCE(e.cmetadata->>'codes'")
|
||||
|
||||
def test_consumers_use_the_shared_text(self):
|
||||
import llm.evidence as ev
|
||||
import pfs.reaction as rx
|
||||
from pfs.anchors import code_or_family_predicate, codes_array_sql
|
||||
|
||||
assert codes_array_sql("e") in ev._CITED_SQL
|
||||
assert code_or_family_predicate("e") in str(rx._DOCKET_COUNTS_SQL)
|
||||
|
||||
Reference in New Issue
Block a user