arborist/scripts/backfill_concepts.py
russell@unturf.com 8d6961fcc1
aborist/arborist
modified:   .gitlab-ci.yml
	modified:   bench/qa_questions.txt
	modified:   bench/qa_sweep.py
	modified:   bench/run.sh
	modified:   docs/TICKETS.md
	modified:   docs/_source/README.md
	modified:   docs/_source/_ext/makefile_targets.py
	modified:   docs/_source/api/cli.rst
	modified:   docs/_source/api/distill.rst
	modified:   docs/_source/api/mesh.rst
	modified:   docs/_source/api/qa.rst
	modified:   docs/_source/api/retrieval.rst
	modified:   docs/_source/api/storage.rst
	modified:   docs/_source/api/substrate.rst
	modified:   docs/_source/concepts.rst
	modified:   docs/_source/conf.py
	modified:   docs/_source/cookbook.rst
	modified:   docs/_source/index.rst
	modified:   docs/_source/license.rst
	modified:   docs/_source/quickstart.rst
	modified:   docs/bench-maxing.md
	modified:   docs/benchmarks.md
	modified:   docs/cti-architecture.md
	modified:   docs/diagrams/aborist-modules.dot
	modified:   docs/diagrams/aborist-modules.svg
	modified:   docs/diagrams/mesh-data-flow.dot
	modified:   docs/diagrams/mesh-epoch-lifecycle.dot
	modified:   docs/diagrams/mesh-epoch-lifecycle.svg
	modified:   docs/diagrams/mesh-group-decisions.dot
	modified:   docs/diagrams/mesh-group-decisions.svg
	modified:   docs/diagrams/mesh-identity-stack.dot
	modified:   docs/diagrams/mesh-secret-envelope.dot
	modified:   docs/mesh.md
	modified:   docs/qa-modes-bench.md
	modified:   docs/seven-point-program.md
	modified:   docs/tickets/ticket-000001-retrieval-keywords-audit-gap.md
	modified:   docs/tickets/ticket-000002-reference-frame-polarity-contract.md
	modified:   docs/tickets/ticket-000003-anchor-class-warrant.md
	modified:   docs/tickets/ticket-000005-label-ladder-migration.md
	modified:   docs/tickets/ticket-000006-bench-emergent-findings.md
	modified:   docs/tickets/ticket-000007-query-layer-hyphen-fold.md
	modified:   docs/tickets/ticket-000008-broad-quantifier-preflight-guard.md
	modified:   docs/tickets/ticket-000009-quantifier-preflight-dag-binding.md
	modified:   docs/tickets/ticket-000010-metacognition-preflight-guard.md
	modified:   docs/tickets/ticket-000011-soft-preflight-hint-sidecar.md
	modified:   scripts/backfill_concepts.py
	modified:   scripts/bench_emergent.py
	modified:   tests/crawler/test_async_web_fetcher.py
	modified:   tests/crawler/test_bridge.py
	modified:   tests/crawler/test_web_fetch.py
	modified:   tests/test_bench_qa_sweep.py
	modified:   tests/test_burn.py
	modified:   tests/test_burn_doc.py
	modified:   tests/test_claim_lattice.py
	modified:   tests/test_cli_render.py
	modified:   tests/test_compress.py
	modified:   tests/test_concepts.py
	modified:   tests/test_dag.py
	modified:   tests/test_directives.py
	modified:   tests/test_distill.py
	modified:   tests/test_distill_recursive.py
	modified:   tests/test_evict.py
	modified:   tests/test_frame.py
	modified:   tests/test_grok_source.py
	modified:   tests/test_html_source.py
	modified:   tests/test_ingest.py
	modified:   tests/test_inspect.py
	modified:   tests/test_journal.py
	modified:   tests/test_keys.py
	modified:   tests/test_llm_context_base.py
	modified:   tests/test_merkle.py
	modified:   tests/test_mesh.py
	modified:   tests/test_mesh_aead.py
	modified:   tests/test_mesh_chain.py
	modified:   tests/test_mesh_cli.py
	modified:   tests/test_mesh_cli_pull.py
	modified:   tests/test_mesh_wire.py
	modified:   tests/test_mesh_wire_e2e.py
	modified:   tests/test_metacognition.py
	modified:   tests/test_migration_audit_mode.py
	modified:   tests/test_providence_source.py
	modified:   tests/test_qa.py
	modified:   tests/test_qa_quality_live.py
	modified:   tests/test_quantifier_caps.py
	modified:   tests/test_quantifier_classifier.py
	modified:   tests/test_quantifier_phase4.py
	modified:   tests/test_quantifier_reminder.py
	modified:   tests/test_query.py
	modified:   tests/test_reclassify.py
	modified:   tests/test_repair.py
	modified:   tests/test_resume.py
	modified:   tests/test_snapshot.py
	modified:   tests/test_soft_preflight.py
	modified:   tests/test_tfidf.py
	modified:   tests/test_vcs_source.py
	modified:   tests/test_verify.py
	modified:   tests/test_verify_json.py
	modified:   tests/test_versioned_ingest.py
	modified:   tests/test_warrant.py
	modified:   tests/test_wikipedia_old.py
	modified:   tests/test_wikipedia_xml.py
	modified:   tests/test_wikitext.py
2026-05-07 09:31:49 -04:00

131 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Run every concept-layer extractor across every shard, in parallel.
Replaces the ad-hoc `python -c "..."` invocations the README + design
doc currently document. Wall-clock parallelism comes from a process
pool over the shard set — each shard's work is self-contained (separate
SQLite file, separate FTS5 index), so per-shard processes don't
contend.
Wired into `make backfill-concepts`. Usage:
python scripts/backfill_concepts.py --shards-dir ~/.arborist/shards
python scripts/backfill_concepts.py --shards-dir ~/.arborist/shards \
--extractors link_reciprocity,token_idf,documents_fts
python scripts/backfill_concepts.py --shards-dir ~/.arborist/shards \
--workers 8
Skips shards that aren't numeric-stem (`000.db`, `001.db`, …) by
default — the qa.db / snapshots.db / crawl_*.db shards rarely need
concept-layer backfill, and including them confuses progress reporting.
Pass --include-non-numeric to opt in.
"""
from __future__ import annotations
import argparse
import multiprocessing as mp
import sys
import time
from pathlib import Path
from arborist.concepts.extract import EXTRACTORS
from arborist.store import connect, discover_shards
# Default extractor order. link_reciprocity must run BEFORE token_idf
# (idf indexes the token set that link_reciprocity populated). Order
# matters when a shard has no concept_relations rows yet.
DEFAULT_EXTRACTORS = ("link_reciprocity", "token_idf", "documents_fts")
def _run_one_shard(args: tuple[Path, tuple[str, ...]]) -> dict:
shard, extractors = args
name = shard.stem
t0 = time.time()
results: dict[str, dict] = {}
conn = connect(shard)
try:
for kind in extractors:
fn = EXTRACTORS.get(kind)
if fn is None:
results[kind] = {"error": f"unknown extractor {kind!r}"}
continue
results[kind] = fn(conn, derived_from=f"backfill@{name}")
finally:
conn.close()
return {
"shard": str(shard),
"name": name,
"elapsed_s": round(time.time() - t0, 2),
"results": results,
}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__.strip().splitlines()[0])
parser.add_argument(
"--shards-dir",
type=Path,
default=Path.home() / ".arborist" / "shards",
help="Directory containing *.db shards.",
)
parser.add_argument(
"--extractors",
default=",".join(DEFAULT_EXTRACTORS),
help=(
"Comma-separated extractor names (registered in "
"arborist.concepts.extract.EXTRACTORS). Default: "
f"{','.join(DEFAULT_EXTRACTORS)}"
),
)
parser.add_argument(
"--workers",
type=int,
default=4,
help="Parallel shard workers. Default 4.",
)
parser.add_argument(
"--include-non-numeric",
action="store_true",
help=(
"Include shards whose stem isn't a digit run (qa.db, "
"snapshots.db, crawl_*.db). Off by default."
),
)
ns = parser.parse_args(argv)
extractors = tuple(e.strip() for e in ns.extractors.split(",") if e.strip())
for e in extractors:
if e not in EXTRACTORS:
parser.error(
f"unknown extractor {e!r}; available: "
f"{sorted(EXTRACTORS)}"
)
shards = [
s for s in discover_shards(ns.shards_dir)
if ns.include_non_numeric or s.stem.isdigit()
]
if not shards:
parser.error(f"no shards found under {ns.shards_dir}")
print(
f">> {len(shards)} shard(s) × {len(extractors)} extractor(s) "
f"× {ns.workers} workers",
flush=True,
)
grand_t0 = time.time()
work = [(s, extractors) for s in shards]
with mp.Pool(processes=ns.workers) as pool:
for outcome in pool.imap_unordered(_run_one_shard, work):
print(f"[{outcome['name']}] {outcome['elapsed_s']}s", flush=True)
for kind, result in outcome["results"].items():
print(f" {kind}: {result}", flush=True)
print(f"\n>> total wall-clock: {round(time.time() - grand_t0, 1)}s")
return 0
if __name__ == "__main__":
sys.exit(main())