perf(pfs): code→family index for anchor_metadata (refs #688)

code_family_index(families) builds a code -> family key map once so a
restamp run of ~1.15M chunks doesn't rescan every family per code.
anchor_metadata gains an optional code_index kwarg that looks family
membership up through it instead.

Ruling A5: families with fewer than two codes are excluded from the
index and from the default unindexed scan — the derived registry is
~17,187 keys and all but a handful are single-code stem families,
where a 'families' anchor would just echo the code already in
'codes'. Only real groupings (today: ACP, CCM, PCM, TCM, APCM) get a
families anchor.
This commit is contained in:
kert
2026-09-09 16:01:40 -04:00
parent 0d3106368f
commit 8a0eef60b7
2 changed files with 75 additions and 6 deletions

View File

@@ -11,26 +11,67 @@ from pfs.elements import parse_descriptor
from pfs.families import FAMILIES, Family, find_codes from pfs.families import FAMILIES, Family, find_codes
def code_family_index(families: Mapping[str, Family]) -> dict[str, str]:
"""Code -> family key, built once so ``anchor_metadata`` doesn't scan
every family for every chunk (a restamp run calls it ~1.15M times).
Single-code families are excluded (Ruling A5): the derived registry
is ~17,187 keys, all but a handful stem families with exactly one
code, and a 'families' anchor on one of those would just echo the
code already in 'codes'. Only families with two or more codes (today:
ACP, CCM, PCM, TCM, APCM) are real groupings worth a separate anchor.
A code that belongs to two qualifying families keeps the first family
reached in *families*' iteration (dict) order — same tie-break a
caller would see from scanning in that order."""
out: dict[str, str] = {}
for key, fam in families.items():
if len(fam.codes) < 2:
continue
for code in fam.codes:
out.setdefault(code, key)
return out
def anchor_metadata( def anchor_metadata(
text: str, *, families: Mapping[str, Family] | None = None text: str,
*,
families: Mapping[str, Family] | None = None,
code_index: Mapping[str, str] | None = None,
) -> dict[str, str]: ) -> dict[str, str]:
"""Extract codes, families, and elements from text. """Extract codes, families, and elements from text.
Returns a dict with keys: Returns a dict with keys:
- 'codes': space-joined sorted unique codes (from find_codes) - 'codes': space-joined sorted unique codes (from find_codes)
- 'families': space-joined sorted unique family keys - 'families': space-joined sorted unique family keys (families with
only one code are excluded — see ``code_family_index``)
- 'elements': space-joined sorted unique element slugs (type=value, no detail) - 'elements': space-joined sorted unique element slugs (type=value, no detail)
Args: Args:
text: The text to analyze text: The text to analyze
families: Optional mapping of family keys to Family objects. Defaults to pfs.families.FAMILIES families: Optional mapping of family keys to Family objects. Defaults to pfs.families.FAMILIES
code_index: Optional precomputed ``code_family_index(families)``. When
given, family lookup goes through it instead of scanning every
family for every code — same result, built once per run by a
caller like the restamp backfill.
Returns: Returns:
dict with 'codes', 'families', and 'elements' keys dict with 'codes', 'families', and 'elements' keys
""" """
fams = FAMILIES if families is None else families fams = FAMILIES if families is None else families
codes = find_codes(text) codes = find_codes(text)
keys = sorted({k for k, f in fams.items() for c in codes if c in f.codes}) if code_index is not None:
keys = sorted({code_index[c] for c in codes if c in code_index})
else:
keys = sorted(
{
k
for k, f in fams.items()
if len(f.codes) >= 2
for c in codes
if c in f.codes
}
)
elements = sorted({f"{e.type.value}={e.value}" for e in parse_descriptor(text)}) elements = sorted({f"{e.type.value}={e.value}" for e in parse_descriptor(text)})
return { return {
"codes": " ".join(codes), "codes": " ".join(codes),

View File

@@ -1,7 +1,7 @@
"""pfs.anchors — metadata for chunks: codes, families, and element slugs.""" """pfs.anchors — metadata for chunks: codes, families, and element slugs."""
from pfs.anchors import anchor_metadata from pfs.anchors import anchor_metadata, code_family_index
from pfs.families import Family from pfs.families import FAMILIES, Family
def test_codes_families_elements(): def test_codes_families_elements():
@@ -23,5 +23,33 @@ def test_empty_and_unknown():
"families": "", "families": "",
"elements": "", "elements": "",
} }
fams = {"X": Family("X", "X fam", ("12345",), ("x fam",))} fams = {"X": Family("X", "X fam", ("12345", "23456"), ("x fam",))}
assert anchor_metadata("code 12345", families=fams)["families"] == "X" assert anchor_metadata("code 12345", families=fams)["families"] == "X"
def test_code_index_matches_unindexed_scan():
text = "Use 99439 in conjunction with 99490; consent; per calendar month"
index = code_family_index(FAMILIES)
assert anchor_metadata(text, code_index=index) == anchor_metadata(text)
def test_code_index_keeps_first_family_on_collision():
fams = {
"A": Family("A", "A fam", ("12345", "23456"), ("a fam",)),
"B": Family("B", "B fam", ("12345", "34567"), ("b fam",)),
}
index = code_family_index(fams)
assert index["12345"] == "A"
def test_single_code_family_never_stamped():
"""A stem-derived family of exactly one code (~17,182 of 17,187
derived keys) is not a grouping worth its own anchor — it would just
echo the code already in 'codes' (Ruling A5)."""
fams = {"SOLO": Family("SOLO", "Solo", ("54321",), ("solo",))}
assert "SOLO" not in code_family_index(fams)
assert anchor_metadata("code 54321", families=fams)["families"] == ""
index = code_family_index(fams)
assert (
anchor_metadata("code 54321", families=fams, code_index=index)["families"] == ""
)