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
131 lines
4.2 KiB
Python
131 lines
4.2 KiB
Python
"""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())
|