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:
@@ -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),
|
||||||
|
|||||||
@@ -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"] == ""
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user