Two compounding crashes/hangs in the corpus-derived synonym layer
that the bench surfaced 2026-05-02:
(1) Union-find chained reciprocal-link clusters into one giant
connected component (54,538 tokens for any seed in the 4-shard
Wikipedia corpus). One query token expanded to the entire
synonym alphabet, tripping SQLite's expression-tree-depth=1000
limit on the OR-clause in `_search_titles`. Replaced with
direct-neighbor adjacency only.
(2) Even direct-neighbor expansion was too noisy on generic tokens:
a 19-token query expanded to 419 tokens (every token had ~22
reciprocal-link neighbors averaging out to topic-adjacency
noise). Title-LIKE on 419 patterns × 3.47M docs × 4 shards
hung indefinitely.
Fix: split index by evidence_kind, cap derived expansion only.
- ``manual_index`` — manual_legacy + manual rows. Curated; ALWAYS
expand regardless of per-token degree. The brain-tech / AMD-
family / Mac / Linux / etc. seed groups have legitimately many
members per token after seed.py started writing clique edges.
- ``derived_index`` — link_reciprocity & corpus-extracted edges.
Subject to MAX_NEIGHBORS_PER_TOKEN=8 cap. Generic tokens
("person", "thoughts") have huge degree from Wikipedia link
noise; specific named entities have small focused neighborhoods
that pass the cap.
- ``MAX_TOTAL_TOKENS=50`` overall cap on expanded set. Bounds
the SQL clause count so title-LIKE search stays tractable.
seed.py: write CLIQUE edges within each legacy group (every
member-pair, not just anchor→member). Preserves the legacy
frozenset semantic where any member retrieves every other
member. Quadratic in group size but groups stay small (largest
is the 30-member brain-tech → 435 pairs).
Live shard 000 re-seeded: 80 → 747 manual_legacy rows. Idempotent
re-seed via INSERT OR IGNORE — re-running adds nothing new.
Verified:
- 19-token query: 419 → 50 expanded (cap saturated)
- thoughts: 30+ tokens incl. brain-tech members preserved
- athlon: full AMD-family clique (amd, duron, opteron, ryzen, …)
Tests: 14/14 concept tests pass.
132 lines
5.2 KiB
Python
132 lines
5.2 KiB
Python
"""One-time migration: legacy frozensets → concept_relations rows.
|
|
|
|
Pre-2026-05-01 the synonym & rivalry data lived as hand-curated frozensets
|
|
in ``aborist.qa.concepts``. This module preserves those tuples & writes
|
|
them as ``evidence_kind='manual_legacy'`` rows so the DB-backed lookup
|
|
returns the same answers the frozenset lookup did.
|
|
|
|
Idempotent: re-running ``seed_legacy_concepts`` is safe because the
|
|
INSERT OR IGNORE in ``add_concept_relation`` falls through on duplicates.
|
|
|
|
The legacy data writes to a designated ``source_root`` value
|
|
(``__legacy__concepts__``) since these relations were not derived from
|
|
any specific document. New extractors writing relations from a real
|
|
document use the document's actual ``document_root``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
|
|
from aborist.concepts.store import add_concept_relation
|
|
|
|
# Sentinel source_root for legacy seed data. Real concept relations
|
|
# from corpus extractors use the document's actual document_root so
|
|
# the per-source-root provenance stays intact.
|
|
LEGACY_SOURCE_ROOT = "__legacy__concepts__"
|
|
LEGACY_EVIDENCE_KIND = "manual_legacy"
|
|
|
|
|
|
# Snapshot of the frozenset groups as of commit bde1bd6 (2026-05-01).
|
|
# Each group becomes a fully-connected synonym cluster: every pair of
|
|
# tokens within the group becomes a synonym edge. Union-find at lookup
|
|
# time collapses these back into the same equivalence class.
|
|
LEGACY_SYNONYM_GROUPS: list[tuple[str, ...]] = [
|
|
# AMD CPU family
|
|
("amd", "athlon", "duron", "opteron", "ryzen", "epyc",
|
|
"thunderbird", "palomino", "thoroughbred", "barton",
|
|
"k6", "k7", "k8", "k10", "5x86"),
|
|
# Intel CPU family
|
|
("intel", "pentium", "celeron", "xeon", "itanium",
|
|
"i7", "i5", "i3", "i9",
|
|
"core2", "coreduo", "skylake", "haswell", "ivy",
|
|
"8086", "80286", "80386", "80486"),
|
|
# HTTP / web protocol family
|
|
("http", "https", "hypertext", "rfc2068", "rfc2616"),
|
|
# FTP / file-transfer family
|
|
("ftp", "sftp", "ftps"),
|
|
# Mac family
|
|
("macintosh", "mac", "macos", "osx", "apple"),
|
|
# Windows family
|
|
("windows", "microsoft", "win32", "winnt", "win9x"),
|
|
# Linux family
|
|
("linux", "gnu", "ubuntu", "debian", "fedora", "redhat", "kernel"),
|
|
# Mind-reading / brain-computer-interface family (added 2026-05-01)
|
|
("telepathy", "telepathic", "neurotechnology", "neuroscience",
|
|
"neuroimaging", "neural", "neuron", "neurons",
|
|
"brain", "brains", "mind", "minds", "cognition", "cognitive",
|
|
"consciousness", "thoughts", "thinking", "thought",
|
|
"psychic", "psychokinesis", "esp", "clairvoyance",
|
|
"fmri", "eeg", "fnirs", "meg", "ecog",
|
|
"tms", "transcranial", "bci"),
|
|
]
|
|
|
|
# Legacy rivalry pairs: a single edge between any token in group A and
|
|
# any token in group B captures the rivalry; lookup-time closure
|
|
# expands it to the whole synonym cluster on each side. We pick the
|
|
# canonical first token of each group as the representative.
|
|
LEGACY_RIVALRY_REPRESENTATIVES: list[tuple[str, str]] = [
|
|
("amd", "intel"), # AMD ↔ Intel
|
|
("apple", "windows"), # Mac ↔ Windows (group reps)
|
|
]
|
|
|
|
|
|
def seed_legacy_concepts(conn: sqlite3.Connection) -> dict:
|
|
"""Seed the legacy frozensets into ``concept_relations`` on ``conn``.
|
|
|
|
Returns ``{"synonyms_inserted": N, "rivalries_inserted": M,
|
|
"synonyms_skipped": K, "rivalries_skipped": L}``. Skipped counts
|
|
indicate rows that already existed (idempotent re-seed).
|
|
"""
|
|
syn_ins = syn_skip = riv_ins = riv_skip = 0
|
|
|
|
# Synonyms: emit a clique edge for every (a, b) pair within each
|
|
# group. Lookup is direct-neighbor (no transitive closure across
|
|
# groups) so we need the full N*(N-1)/2 pairs to preserve the
|
|
# legacy frozenset semantic where every member retrieved every
|
|
# other member. Quadratic in group size but groups stay small
|
|
# (largest is the 30-member brain-tech set → 435 pairs).
|
|
for group in LEGACY_SYNONYM_GROUPS:
|
|
if len(group) < 2:
|
|
continue
|
|
members = list(group)
|
|
for i, a in enumerate(members):
|
|
for b in members[i + 1:]:
|
|
inserted = add_concept_relation(
|
|
conn,
|
|
source_root=LEGACY_SOURCE_ROOT,
|
|
relation_kind="synonym",
|
|
token=a,
|
|
target=b,
|
|
evidence_kind=LEGACY_EVIDENCE_KIND,
|
|
derived_from="aborist.qa.concepts (legacy frozensets)",
|
|
)
|
|
if inserted:
|
|
syn_ins += 1
|
|
else:
|
|
syn_skip += 1
|
|
|
|
# Rivalries: one row per representative pair. Group closure happens
|
|
# at lookup time via the synonym index.
|
|
for a, b in LEGACY_RIVALRY_REPRESENTATIVES:
|
|
inserted = add_concept_relation(
|
|
conn,
|
|
source_root=LEGACY_SOURCE_ROOT,
|
|
relation_kind="rivalry",
|
|
token=a,
|
|
target=b,
|
|
evidence_kind=LEGACY_EVIDENCE_KIND,
|
|
derived_from="aborist.qa.concepts (legacy frozensets)",
|
|
)
|
|
if inserted:
|
|
riv_ins += 1
|
|
else:
|
|
riv_skip += 1
|
|
|
|
conn.commit()
|
|
return {
|
|
"synonyms_inserted": syn_ins,
|
|
"rivalries_inserted": riv_ins,
|
|
"synonyms_skipped": syn_skip,
|
|
"rivalries_skipped": riv_skip,
|
|
}
|