feat(pfs,llm): anchor metadata — families and elements stamped beside codes; GIN indexes (refs #688)

This commit is contained in:
kert
2026-09-09 15:37:19 -04:00
parent 07706f1b0f
commit ea0deeb5c6
6 changed files with 111 additions and 3 deletions

View File

@@ -14,7 +14,7 @@ import hashlib
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from pfs.families import find_codes from pfs.anchors import anchor_metadata
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -148,7 +148,7 @@ def chunk_doc(
# The heading is scanned with the piece: a section titled # The heading is scanned with the piece: a section titled
# "## G0556 — APCM" is about G0556 all the way down, but # "## G0556 — APCM" is about G0556 all the way down, but
# only its first chunk repeats the code. # only its first chunk repeats the code.
"codes": " ".join(find_codes(f"{heading} {piece}")), **anchor_metadata(f"{heading} {piece}"),
}, },
) )
for seq, (heading, piece) in enumerate(pieces) for seq, (heading, piece) in enumerate(pieces)
@@ -190,7 +190,7 @@ def _chunk_paragraphs(doc: Doc, target: int, overlap: int) -> list[Chunk]:
"item_key": doc.key, "item_key": doc.key,
"seq": str(seq), "seq": str(seq),
"section": "", "section": "",
"codes": " ".join(find_codes(text)), **anchor_metadata(text),
"p_id": str(first.p_id), "p_id": str(first.p_id),
"p_id_last": str(last.p_id), "p_id_last": str(last.p_id),
"page": str(first.page), "page": str(first.page),

View File

@@ -56,6 +56,12 @@ _HNSW_DDL = [
"(string_to_array(coalesce(cmetadata->>'codes',''), ' '))", "(string_to_array(coalesce(cmetadata->>'codes',''), ' '))",
"CREATE INDEX IF NOT EXISTS ix_embedding_collection " "CREATE INDEX IF NOT EXISTS ix_embedding_collection "
"ON langchain_pg_embedding (collection_id)", "ON langchain_pg_embedding (collection_id)",
"CREATE INDEX IF NOT EXISTS ix_embedding_families "
"ON langchain_pg_embedding USING gin "
"(string_to_array(coalesce(cmetadata->>'families',''), ' '))",
"CREATE INDEX IF NOT EXISTS ix_embedding_elements "
"ON langchain_pg_embedding USING gin "
"(string_to_array(coalesce(cmetadata->>'elements',''), ' '))",
] ]

39
src/pfs/anchors.py Normal file
View File

@@ -0,0 +1,39 @@
"""Anchor metadata for a piece of text: the codes it names, the families
those codes belong to, and the element slugs its phrases match. One
function, used by the indexer at chunk time and by the restamp backfill,
so both write identical values. Pure."""
from __future__ import annotations
from typing import Mapping
from pfs.elements import parse_descriptor
from pfs.families import FAMILIES, Family, find_codes
def anchor_metadata(
text: str, *, families: Mapping[str, Family] | None = None
) -> dict[str, str]:
"""Extract codes, families, and elements from text.
Returns a dict with keys:
- 'codes': space-joined sorted unique codes (from find_codes)
- 'families': space-joined sorted unique family keys
- 'elements': space-joined sorted unique element slugs (type=value, no detail)
Args:
text: The text to analyze
families: Optional mapping of family keys to Family objects. Defaults to pfs.families.FAMILIES
Returns:
dict with 'codes', 'families', and 'elements' keys
"""
fams = FAMILIES if families is None else families
codes = find_codes(text)
keys = sorted({k for k, f in fams.items() for c in codes if c in f.codes})
elements = sorted({f"{e.type.value}={e.value}" for e in parse_descriptor(text)})
return {
"codes": " ".join(codes),
"families": " ".join(keys),
"elements": " ".join(elements),
}

View File

@@ -184,3 +184,32 @@ class TestCodesMetadata:
) )
(chunk,) = chunk_doc(doc) (chunk,) = chunk_doc(doc)
assert chunk.metadata["codes"] == "G0556" assert chunk.metadata["codes"] == "G0556"
class TestAnchorMetadata:
def test_plain_doc_chunks_have_codes_families_elements_keys(self):
text = "Use 99439; consent; per calendar month."
chunks = chunk_doc(_doc(text))
assert len(chunks) == 1
chunk = chunks[0]
assert "codes" in chunk.metadata
assert "families" in chunk.metadata
assert "elements" in chunk.metadata
assert chunk.metadata["codes"] == "99439"
assert chunk.metadata["families"] == "CCM"
def test_paragraph_chunks_have_codes_families_elements_keys(self):
from llm.chunk import Paragraph
paras = (Paragraph(1, 100, 1, "G0556 APCM code with consent."),)
doc = Doc(
key="R", text="G0556 APCM code with consent.", metadata={}, paragraphs=paras
)
chunks = chunk_doc(doc)
assert len(chunks) == 1
chunk = chunks[0]
assert "codes" in chunk.metadata
assert "families" in chunk.metadata
assert "elements" in chunk.metadata
assert chunk.metadata["codes"] == "G0556"
assert chunk.metadata["families"] == "APCM"

View File

@@ -33,6 +33,13 @@ class TestDdl:
"ix_embedding_collection" in executed "ix_embedding_collection" in executed
and "langchain_pg_embedding (collection_id)" in executed and "langchain_pg_embedding (collection_id)" in executed
) )
# family and element indexes follow the same pattern
assert (
"ix_embedding_families" in executed and "cmetadata->>'families'" in executed
)
assert (
"ix_embedding_elements" in executed and "cmetadata->>'elements'" in executed
)
def test_fingerprint_column_and_docket_state(self): def test_fingerprint_column_and_docket_state(self):
assert "fingerprint" in migrate.INDEX_STATE_DDL assert "fingerprint" in migrate.INDEX_STATE_DDL

27
tests/pfs/test_anchors.py Normal file
View File

@@ -0,0 +1,27 @@
"""pfs.anchors — metadata for chunks: codes, families, and element slugs."""
from pfs.anchors import anchor_metadata
from pfs.families import Family
def test_codes_families_elements():
md = anchor_metadata(
"Use 99439 in conjunction with 99490; consent; per calendar month"
)
assert md["codes"] == "99439 99490"
assert md["families"] == "CCM"
assert (
"activity=consent" in md["elements"].split()
and "period=calendar-month" in md["elements"].split()
)
assert "relation=addon-of" in md["elements"].split() # detail dropped
def test_empty_and_unknown():
assert anchor_metadata("nothing here") == {
"codes": "",
"families": "",
"elements": "",
}
fams = {"X": Family("X", "X fam", ("12345",), ("x fam",))}
assert anchor_metadata("code 12345", families=fams)["families"] == "X"