asa / scripts /lib /term_extraction.py
defenestrate's picture
ASA Space: squashed history (instructions and settings live in the private pack)
526950e
Raw History Blame Contribute Delete
2.21 kB
"""Term detection over text using term_registry.words[] aliases.
Builds one compiled regex from all aliases (case-insensitive, word-bounded).
Maps matches back to canonical slugs. Supports 1-hop expansion via depends_on.
"""
from __future__ import annotations
import re
def build_regex(registry: dict) -> re.Pattern:
"""Compile a single regex matching any alias in registry.terms[*].words[].
Aliases are sorted longest-first so 'elasticity of demand' matches
before 'elasticity'. Word boundaries enforced. Case-insensitive.
"""
aliases: list[tuple[str, str]] = [] # (alias, canonical_slug)
for slug, term in registry.get("terms", {}).items():
for word in term.get("words", []) or []:
aliases.append((word, slug))
# Longest first to prevent shorter aliases shadowing longer ones
aliases.sort(key=lambda p: len(p[0]), reverse=True)
if not aliases:
return re.compile(r"$^") # never matches
pattern = r"\b(" + "|".join(re.escape(a[0]) for a in aliases) + r")\b"
return re.compile(pattern, re.IGNORECASE)
def find_terms(text: str, regex: re.Pattern, registry: dict) -> list[str]:
"""Return list of canonical slugs whose aliases appear in text.
Deduped, ordered by first appearance.
"""
# Build alias -> slug lookup (case-folded)
alias_to_slug: dict[str, str] = {}
for slug, term in registry.get("terms", {}).items():
for word in term.get("words", []) or []:
alias_to_slug[word.lower()] = slug
seen: list[str] = []
for m in regex.finditer(text):
slug = alias_to_slug.get(m.group(0).lower())
if slug and slug not in seen:
seen.append(slug)
return seen
def expand_one_hop(seed_slugs: list[str], registry: dict) -> list[str]:
"""Return 1-hop neighbors (via depends_on) of seed_slugs, excluding seeds.
Output is deterministic: deduped, sorted alphabetically.
"""
seeds = set(seed_slugs)
neighbors: set[str] = set()
terms = registry.get("terms", {})
for seed in seeds:
for n in terms.get(seed, {}).get("depends_on", []) or []:
if n not in seeds:
neighbors.add(n)
return sorted(neighbors)