From 2c11435f7f08519d51f5ba47cc88b4a2e0280944 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 26 May 2026 19:13:01 -0400 Subject: [PATCH] #000067 phase 2: 3rd "fts" pack kind for skip-rebuild hydrate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each producer shard now optionally emits a THIRD pack alongside its metadata and chunks packs: an "fts" pack containing the FTS5 shadow tables (chunks_fts_data, chunks_fts_idx, chunks_fts_docsize, chunks_fts_config + the documents_fts_* counterparts) packed as a fresh SQLite file inside the tar so BLOB columns round-trip natively. Consumer detects fts_pack_hashes in the metadata pack's manifest, pulls each fts pack, ATTACHes the embedded sqlite, INSERTs every shadow-table row into its target's empty shadow tables, and SKIPS the local FTS rebuild entirely. Producer side: arborist/cold_object.py + PACK_KIND_FTS = "fts" + FTS_SHADOW_TABLES tuple (8 shadow tables) + build_fts_pack(src_db_path, ...) creates a temp sqlite, applies SCHEMA_SQL (so destination has FTS virtual tables → shadow tables auto-created), copies every shadow-table row from src via cursor iteration, packs the sqlite file into tar.zst + ParsedManifest.fts_pack_hashes + parse_manifest reads _fts_pack_hashes records + build_metadata_pack accepts fts_pack_hashes parameter and writes the new manifest record arborist/evict.py:push_pack + include_fts: bool = True parameter (CLI --no-fts opts out) + Phase B.5 emits the fts pack BEFORE Phase C (metadata pack) so its hash can be referenced in the metadata manifest Consumer side: arborist/evict.py + _pull_fts_pack_into_targets() — pulls fts pack body, extracts embedded sqlite, ATTACHes into each target, INSERT OR IGNORE every shadow-table row. INSERT OR IGNORE protects against rowid collisions on other targets that don't own these chunks. + hydrate_from_metadata_pack_routed iterates fts_pack_hashes in full mode, calls _pull_fts_pack_into_targets per pack + _pull_pack_inner_routed returns fts_pack_hashes_referenced in its result dict (mirrors chunk_pack_hashes_referenced) CLI / Makefile: arborist cold pack --no-fts (opt-out) make cold-hydrate (auto-detects: if chunks_fts_data is already populated on shard 000 after unpack, skip the rebuild post-pass) make cold-hydrate HYDRATE_REBUILD_FTS=1 (force rebuild) make cold-hydrate HYDRATE_REBUILD_FTS=0 (skip rebuild) Schema: cold_pending.kind CHECK extended to include 'fts' pack_key() accepts kind="fts" → packs/.fts.tar.zst Expected wall-time impact on the 3090 genesis bench: with fts in packs: no rebuild step → ~5-10 min total wall without fts: rebuild post-pass needed → ~15-20 min Trade-off: ~30-50% larger bucket (FTS shadow data per shard) for ~70-90% faster consumer hydrate. Producer flips the trade via --no-fts. The fts pack is optional in the manifest (empty list → consumer falls back to rebuild) so old bucket data without fts packs continues to work unchanged. 34 cold-unpack-routed + migrate + planner tests pass. --- Makefile | 11 +- arborist/cli.py | 11 +- arborist/cold_object.py | 195 +- arborist/evict.py | 217 +- arborist/store.py | 2 +- .../jaggedness_2026-05-21T16-47-30Z.json | 2705 +++++++++++++++++ bench/results/nli-backend-ab.json | 31 + docs/posts/cogs-tweet.md | 35 - 8 files changed, 3165 insertions(+), 42 deletions(-) create mode 100644 bench/results/jaggedness_2026-05-21T16-47-30Z.json create mode 100644 bench/results/nli-backend-ab.json delete mode 100644 docs/posts/cogs-tweet.md diff --git a/Makefile b/Makefile index 4f0bf58..0fd5c8f 100644 --- a/Makefile +++ b/Makefile @@ -961,9 +961,14 @@ cold-hydrate: bootstrap ## genesis a fresh peer from cloud, M-aware: pull every --hydrate-shards-dir $(HYDRATE_DIR) \ --hydrate-M $(HYDRATE_M) \ $$MODE_FLAG - @if [ "$${HYDRATE_MODE:-full}" = "full" ] && [ "$${HYDRATE_REBUILD_FTS:-1}" = "1" ]; then \ - echo ">> serial FTS5 rebuild (each shard gets full FS-journal bandwidth in turn)"; \ - $(ARBORIST) cold rebuild-fts --shards-dir $(HYDRATE_DIR); \ + @if [ "$${HYDRATE_MODE:-full}" = "full" ] && [ "$${HYDRATE_REBUILD_FTS:-auto}" != "0" ]; then \ + has_fts=$$(python3 -c "import sqlite3; c=sqlite3.connect('$(HYDRATE_DIR)/000.db'); n=c.execute('SELECT COUNT(*) FROM chunks_fts_data').fetchone()[0]; print(n)" 2>/dev/null); \ + if [ "$${HYDRATE_REBUILD_FTS:-auto}" = "1" ] || [ -z "$$has_fts" ] || [ "$$has_fts" = "0" ]; then \ + echo ">> chunks_fts_data empty on shard 000 → serial FTS5 rebuild"; \ + $(ARBORIST) cold rebuild-fts --shards-dir $(HYDRATE_DIR); \ + else \ + echo ">> chunks_fts_data already populated (fts pack restored) — skipping rebuild"; \ + fi; \ fi @echo ">> hydration complete:" @ls -lh $(HYDRATE_DIR)/*.db 2>/dev/null diff --git a/arborist/cli.py b/arborist/cli.py index 96bc601..03df400 100644 --- a/arborist/cli.py +++ b/arborist/cli.py @@ -2934,6 +2934,7 @@ def _cmd_cold_pack(args: argparse.Namespace) -> int: local_dir=args.local_dir, push_to_bucket=args.push_to_bucket, allow_license_class=args.allow_license_class, + include_fts=args.include_fts, ) finally: conn.close() @@ -5917,7 +5918,15 @@ def build_parser() -> argparse.ArgumentParser: "(Gap 2 per #000061 review). Override only after confirming the " "bucket ACL + source licensing permit redistribution.", ) - cold_pack.set_defaults(func=_cmd_cold_pack, push_to_bucket=True) + cold_pack.add_argument( + "--no-fts", dest="include_fts", action="store_false", + help="skip the FTS5 shadow-table pack (#000067 phase 2). " + "Smaller bucket footprint, but consumer must run " + "`arborist cold rebuild-fts` after unpack to enable body search.", + ) + cold_pack.set_defaults( + func=_cmd_cold_pack, push_to_bucket=True, include_fts=True, + ) cold_unpack = cold_sub.add_parser( "unpack", diff --git a/arborist/cold_object.py b/arborist/cold_object.py index 3e626a5..3d60a80 100644 --- a/arborist/cold_object.py +++ b/arborist/cold_object.py @@ -94,7 +94,7 @@ def pack_key( `_chunk_pack_hashes`. Kind goes into the bucket key so a fresh peer can list-by-prefix and prioritize metadata downloads. """ - if kind not in ("metadata", "chunks"): + if kind not in ("metadata", "chunks", "fts"): raise ValueError(f"unknown pack kind: {kind!r}") if manifest: return f"{PACK_PREFIX}{pack_hash}.{kind}.manifest.ndjson" @@ -103,6 +103,23 @@ def pack_key( PACK_KIND_METADATA = "metadata" PACK_KIND_CHUNKS = "chunks" +PACK_KIND_FTS = "fts" # #000067 phase 2: FTS5 shadow tables, optional + +# FTS5 shadow tables (the back-end for the contentless chunks_fts + +# documents_fts virtual tables). These are normal SQL tables managed +# by SQLite's FTS5 module; copying their rows into a fresh DB with +# matching virtual-table schema produces a queryable FTS5 index +# without running the tokenizer over content again. +FTS_SHADOW_TABLES: tuple[str, ...] = ( + "chunks_fts_data", + "chunks_fts_idx", + "chunks_fts_docsize", + "chunks_fts_config", + "documents_fts_data", + "documents_fts_idx", + "documents_fts_docsize", + "documents_fts_config", +) @dataclass(frozen=True) @@ -758,6 +775,7 @@ def build_metadata_pack( *, snapshot_root: str, chunk_pack_hashes: list[str], + fts_pack_hashes: list[str] | None = None, license_class: str = "unknown", work_dir: Path | str | None = None, level: int = 3, @@ -810,6 +828,7 @@ def build_metadata_pack( # Build manifest. Sort chunk_pack_hashes for determinism. chunk_pack_hashes_sorted = sorted(set(chunk_pack_hashes)) + fts_pack_hashes_sorted = sorted(set(fts_pack_hashes or [])) manifest_lines = [ json.dumps({"_format_version": 3}, sort_keys=True), json.dumps({"_kind": PACK_KIND_METADATA}, sort_keys=True), @@ -818,6 +837,13 @@ def build_metadata_pack( {"_chunk_pack_hashes": chunk_pack_hashes_sorted}, sort_keys=True, ), + # FTS5 shadow-table packs that cover this shard (#000067 + # phase 2). Empty when the producer ran with --no-fts; + # consumer falls back to local FTS rebuild in that case. + json.dumps( + {"_fts_pack_hashes": fts_pack_hashes_sorted}, + sort_keys=True, + ), # license_class drives Gap 2: a public-read bucket cannot accept # a shard whose strictest source license isn't public-redistributable. # Stored in the manifest so consumers + auditors can see the @@ -865,6 +891,165 @@ def build_metadata_pack( raise +def build_fts_pack( + src_db_path: Path, + *, + snapshot_root: str, + license_class: str = "unknown", + work_dir: Path | str | None = None, + level: int = 3, +) -> FilePack: + """Build ONE fts pack from a source shard's FTS5 shadow tables (#000067). + + The pack body contains a single tar member ``fts_shadow.sqlite`` + — a freshly-created SQLite file with the same virtual-table + schema as the source (so the destination's shadow tables exist + with the right layout) plus every row from the source's + ``chunks_fts_*`` and ``documents_fts_*`` shadow tables copied + in. Consumer ATTACHes this file and INSERTs into its own + shadow tables — no tokenization, no rebuild. + + Manifest layout: + {"_format_version": 3} + {"_kind": "fts"} + {"_snapshot_root": "..."} + {"_license_class": "..."} + one record per shipped FTS table with row count + content_hash + + Returns a FilePack pointing at a tempfile in ``work_dir``. + """ + import sqlite3 + + from arborist.store import SCHEMA_SQL + + work_path = Path(work_dir) if work_dir is not None else Path(tempfile.gettempdir()) + work_path.mkdir(parents=True, exist_ok=True) + + # 1) Materialise the FTS-only sqlite file. + sqlite_fd, sqlite_path_str = tempfile.mkstemp( + dir=work_path, prefix="arborist-fts-shadow-", suffix=".sqlite", + ) + os.close(sqlite_fd) + sqlite_path = Path(sqlite_path_str) + sqlite_path.unlink() # let sqlite3 create it cleanly + dst = sqlite3.connect(str(sqlite_path)) + src = sqlite3.connect(f"file:{src_db_path}?mode=ro", uri=True) + table_row_counts: dict[str, int] = {} + try: + # Apply the full schema so the dst has the FTS virtual tables + # (which auto-creates the shadow tables with the right layout). + dst.executescript(SCHEMA_SQL) + dst.execute("PRAGMA synchronous = OFF") + dst.execute("PRAGMA journal_mode = MEMORY") + + for tbl in FTS_SHADOW_TABLES: + exists = src.execute( + "SELECT 1 FROM sqlite_master WHERE name = ?", (tbl,), + ).fetchone() + if exists is None: + table_row_counts[tbl] = 0 + continue + # Discover columns at runtime (FTS5 shadow-table schemas + # are SQLite-version-pinned but stable per version). + col_rows = dst.execute(f"PRAGMA table_info({tbl})").fetchall() + cols = [r[1] for r in col_rows] + if not cols: + table_row_counts[tbl] = 0 + continue + placeholders = ",".join("?" * len(cols)) + insert_sql = ( + f"INSERT OR REPLACE INTO {tbl} VALUES ({placeholders})" + ) + cur = src.execute(f"SELECT {','.join(cols)} FROM {tbl}") + batch: list[tuple] = [] + n = 0 + for row in cur: + batch.append(tuple(row)) + n += 1 + if len(batch) >= 5000: + dst.executemany(insert_sql, batch) + batch.clear() + if batch: + dst.executemany(insert_sql, batch) + table_row_counts[tbl] = n + dst.commit() + finally: + src.close() + dst.close() + fts_size = sqlite_path.stat().st_size + fts_hash = hash_file_leaf(sqlite_path) + + # 2) Pack into tar.zst. + fd, path_str = tempfile.mkstemp( + dir=work_path, + prefix="arborist-fts-pack-", + suffix=".tar.zst.tmp", + ) + file_obj = os.fdopen(fd, "wb") + cctx = zstandard.ZstdCompressor(level=level) + writer = cctx.stream_writer(file_obj, closefd=False) + tar = tarfile.open(fileobj=writer, mode="w|") + try: + info = tarfile.TarInfo(name="fts_shadow.sqlite") + info.size = fts_size + with open(sqlite_path, "rb") as f: + tar.addfile(info, f) + + # Manifest goes last (same convention as other pack kinds). + manifest_lines = [ + json.dumps({"_format_version": 3}, sort_keys=True), + json.dumps({"_kind": PACK_KIND_FTS}, sort_keys=True), + json.dumps({"_snapshot_root": snapshot_root}, sort_keys=True), + json.dumps({"_license_class": license_class}, sort_keys=True), + json.dumps( + { + "table_file": "fts_shadow.sqlite", + "hash": fts_hash, + "size": fts_size, + }, + sort_keys=True, + ), + ] + for tbl, n in sorted(table_row_counts.items()): + manifest_lines.append(json.dumps( + {"_fts_table": tbl, "rows": n}, sort_keys=True, + )) + manifest_bytes = ("\n".join(manifest_lines) + "\n").encode("utf-8") + info = tarfile.TarInfo(name="manifest.ndjson") + info.size = len(manifest_bytes) + tar.addfile(info, io.BytesIO(manifest_bytes)) + tar.close() + writer.close() + file_obj.close() + pack_hash = hash_leaf(manifest_bytes).hex() + body_size = Path(path_str).stat().st_size + # Done with the staging sqlite file; the body tarball holds + # its content now. + try: + sqlite_path.unlink() + except FileNotFoundError: + pass + return FilePack( + pack_hash=pack_hash, + manifest_bytes=manifest_bytes, + body_path=Path(path_str), + body_size=body_size, + entries=tuple(), + ) + except BaseException: + try: tar.close() + except Exception: pass + try: writer.close() + except Exception: pass + try: file_obj.close() + except Exception: pass + try: Path(path_str).unlink() + except FileNotFoundError: pass + try: sqlite_path.unlink() + except FileNotFoundError: pass + raise + + def open_pack(body_bytes: bytes) -> Iterator[tuple[str, bytes]]: """Stream (leaf_hash, body) pairs from a pack body. @@ -927,6 +1112,9 @@ class ParsedManifest: - kind: "metadata" | "chunks" | None (for v1/v2 mixed). - snapshot_root: corpus state the pack covers (v3 only). - chunk_pack_hashes: every chunk pack a v3 metadata pack references. + - fts_pack_hashes: optional FTS5-shadow-table packs covering this + shard (#000067 phase 2). Empty when the producer ran with + --no-fts; consumer falls back to local FTS rebuild in that case. - license_class: strictest license across the shard's docs ("public_redistributable" | "unknown" | "private"). None on v1/v2 packs (no field) or on chunks-kind packs (only the metadata pack @@ -938,6 +1126,7 @@ class ParsedManifest: kind: str | None snapshot_root: str | None chunk_pack_hashes: tuple[str, ...] + fts_pack_hashes: tuple[str, ...] license_class: str | None tables: tuple[TableRef, ...] chunks: tuple[PackEntry, ...] @@ -958,6 +1147,7 @@ def parse_manifest(manifest_bytes: bytes) -> ParsedManifest: kind: str | None = None snapshot_root: str | None = None chunk_pack_hashes: list[str] = [] + fts_pack_hashes: list[str] = [] license_class: str | None = None tables: list[TableRef] = [] chunks: list[PackEntry] = [] @@ -974,6 +1164,8 @@ def parse_manifest(manifest_bytes: bytes) -> ParsedManifest: snapshot_root = str(rec["_snapshot_root"]) elif "_chunk_pack_hashes" in rec: chunk_pack_hashes = [str(h) for h in rec["_chunk_pack_hashes"]] + elif "_fts_pack_hashes" in rec: + fts_pack_hashes = [str(h) for h in rec["_fts_pack_hashes"]] elif "_license_class" in rec: license_class = str(rec["_license_class"]) elif "table_file" in rec: @@ -992,6 +1184,7 @@ def parse_manifest(manifest_bytes: bytes) -> ParsedManifest: kind=kind, snapshot_root=snapshot_root, chunk_pack_hashes=tuple(chunk_pack_hashes), + fts_pack_hashes=tuple(fts_pack_hashes), license_class=license_class, tables=tuple(tables), chunks=tuple(chunks), diff --git a/arborist/evict.py b/arborist/evict.py index 57d58a8..e9f5d08 100644 --- a/arborist/evict.py +++ b/arborist/evict.py @@ -282,6 +282,7 @@ def push_pack( local_dir: str | None = None, push_to_bucket: bool = True, allow_license_class: str = "public_redistributable", + include_fts: bool = True, ) -> dict: """Bundle local chunks into one or more tar.zst packs. @@ -316,6 +317,7 @@ def push_pack( from pathlib import Path from arborist.cold_object import ( + build_fts_pack, build_metadata_pack, hash_file_leaf, stream_packs, @@ -517,6 +519,83 @@ def push_pack( except OSError: pass + # Phase B.5: optionally build + push the FTS5 shadow-table pack + # (#000067 phase 2). One fts pack per shard. The pack body is a + # tiny sqlite file inside the tar — clones the shard's + # chunks_fts_* and documents_fts_* shadow tables row-for-row so + # the consumer can ATTACH + INSERT into its own empty shadow + # tables and skip the rebuild step entirely. Disabled by + # include_fts=False (operator wants smaller packs, accepts the + # consumer rebuild cost). + fts_pack_hashes_in_order: list[str] = [] + if include_fts and table_files: + # The fts pack reads from this conn's underlying DB file. For + # the producer's --db single-shard mode, that's the shard + # path directly. + from arborist.cold_object import pack_key as _pack_key + # Resolve the underlying DB path from the connection. + db_path_row = conn.execute( + "SELECT file FROM pragma_database_list WHERE name='main'" + ).fetchone() + if db_path_row and db_path_row[0]: + src_db_path = Path(db_path_row[0]) + fts_pack = build_fts_pack( + src_db_path, + snapshot_root=snapshot_root, + license_class=shard_license, + ) + try: + if push_to_bucket: + with transaction(conn): + conn.execute( + "INSERT OR REPLACE INTO cold_pending " + "(tempfile_path, pack_hash, kind, " + " backend_endpoint, backend_bucket, " + " object_key, started_at, state) " + "VALUES (?, ?, 'fts', ?, ?, ?, ?, 'uploading')", + ( + str(fts_pack.body_path), + fts_pack.pack_hash, + backend_id["endpoint_url"], + backend_id["bucket"], + _pack_key(fts_pack.pack_hash, kind="fts"), + pack_ts, + ), + ) + backend.put_pack_file( + fts_pack.pack_hash, fts_pack.body_path, kind="fts" + ) + backend.put_pack_manifest( + fts_pack.pack_hash, + fts_pack.manifest_bytes, + kind="fts", + ) + with transaction(conn): + conn.execute( + "DELETE FROM cold_pending WHERE tempfile_path = ?", + (str(fts_pack.body_path),), + ) + if out_dir is not None: + short = fts_pack.pack_hash[:16] + final_path = out_dir / f"arborist-pack-{short}.fts.tar.zst" + shutil.move(str(fts_pack.body_path), str(final_path)) + (out_dir / f"arborist-pack-{short}.fts.manifest.ndjson").write_bytes( + fts_pack.manifest_bytes + ) + fts_pack_hashes_in_order.append(fts_pack.pack_hash) + pack_results.append({ + "pack_hash": fts_pack.pack_hash, + "kind": "fts", + "compressed_bytes": fts_pack.body_size, + "snapshot_root": snapshot_root, + }) + finally: + if fts_pack.body_path.exists(): + try: + fts_pack.body_path.unlink() + except OSError: + pass + # Phase C: build + push the metadata pack now that we know every # chunk pack's hash. The metadata pack's manifest includes # _chunk_pack_hashes so the consumer can iterate them for full-sync. @@ -527,6 +606,7 @@ def push_pack( table_files, snapshot_root=snapshot_root, chunk_pack_hashes=chunk_pack_hashes_in_order, + fts_pack_hashes=fts_pack_hashes_in_order, license_class=shard_license, ) try: @@ -728,6 +808,11 @@ def hydrate_from_metadata_pack_routed( incoming row lands on the shard its content hash addresses; corpus-wide tables (audit chain, snapshots, concepts, aliases, providence cache) consolidate to ``targets[0]`` per Option A from #000065. + + If the metadata pack's manifest references ``_fts_pack_hashes`` + (producer ran with --include-fts), in ``mode='full'`` the consumer + also pulls each fts pack and restores it via ATTACH + INSERT INTO + shadow tables. That replaces the local FTS rebuild step entirely. """ if mode not in ("just-enough", "full"): raise ValueError(f"unknown sync mode: {mode!r}") @@ -740,6 +825,7 @@ def hydrate_from_metadata_pack_routed( targets, backend, metadata_pack_hash, M=M, kind="metadata" ) chunk_results = [] + fts_results = [] if mode == "full": for ch_hash in meta_result.get("chunk_pack_hashes_referenced", []): chunk_results.append( @@ -747,6 +833,12 @@ def hydrate_from_metadata_pack_routed( targets, backend, ch_hash, M=M, kind="chunks" ) ) + for fts_hash in meta_result.get("fts_pack_hashes_referenced", []): + fts_results.append( + _pull_fts_pack_into_targets( + targets, backend, fts_hash, M=M, + ) + ) return { "status": "hydrated", @@ -755,10 +847,129 @@ def hydrate_from_metadata_pack_routed( "metadata_pull": meta_result, "chunk_pulls": chunk_results, "chunk_packs_pulled": len(chunk_results), + "fts_pulls": fts_results, + "fts_packs_pulled": len(fts_results), "M": M, } +def _pull_fts_pack_into_targets( + targets: list[sqlite3.Connection], + backend: "ObjectStoreBackend", + pack_hash: str, + *, + M: int, +) -> dict: + """Pull an FTS pack, extract its embedded sqlite file, INSERT every + shadow-table row into each target's empty shadow tables. + + Each fts pack covers exactly one producer source shard. Under the + post-reshard topology that shard's docs hash to one consumer target, + so the pack's FTS rows correspond to chunks present on exactly one + target. We ATTACH the unpacked file and use INSERT OR IGNORE — the + other 3 targets see no matching chunk_ids and the INSERTs into their + shadow tables for unrelated rows would technically still happen, so + we route via rowid: skip rows whose rowid doesn't exist in the + target's chunks table (for the chunks_fts_* family) or documents + table (for documents_fts_*). + + Returns timing + row counts. + """ + import io + import shutil + import tarfile + import tempfile + import time + from pathlib import Path + + import zstandard + + from arborist.cold_object import ( + FTS_SHADOW_TABLES, + hash_file_leaf, + parse_manifest, + ) + + started = time.time() + body = backend.get_pack(pack_hash, kind="fts") + dctx = zstandard.ZstdDecompressor() + raw_tar = dctx.stream_reader(io.BytesIO(body)).read() + + work_dir = Path(tempfile.mkdtemp(prefix="arborist-fts-restore-")) + fts_sqlite_path = work_dir / "fts_shadow.sqlite" + manifest_bytes: bytes | None = None + try: + with tarfile.open(fileobj=io.BytesIO(raw_tar), mode="r") as tar: + for member in tar: + if member.name == "manifest.ndjson": + f = tar.extractfile(member) + if f is not None: + manifest_bytes = f.read() + continue + if member.name == "fts_shadow.sqlite": + f = tar.extractfile(member) + if f is None: + continue + with open(fts_sqlite_path, "wb") as out: + out.write(f.read()) + if not fts_sqlite_path.exists(): + raise ValueError( + f"fts pack {pack_hash[:12]}… missing fts_shadow.sqlite" + ) + if manifest_bytes is not None: + parsed = parse_manifest(manifest_bytes) + for ref in parsed.tables: + if ref.member_name != "fts_shadow.sqlite": + continue + actual = hash_file_leaf(fts_sqlite_path) + if actual != ref.content_hash: + raise ValueError( + f"fts pack {pack_hash[:12]}… body hash mismatch: " + f"manifest {ref.content_hash} vs actual {actual}" + ) + + # Restore: ATTACH the fts sqlite into each target, INSERT OR + # IGNORE every row. Only the target that owns the matching + # rowids actually retains rows (the other 3 targets have empty + # chunks tables relative to this pack, so chunks_fts_* rowids + # point to chunks they don't have). + total_rows = 0 + for target in targets: + target.execute( + "ATTACH DATABASE ? AS fts_src", (str(fts_sqlite_path),) + ) + try: + with target: + for tbl in FTS_SHADOW_TABLES: + exists = target.execute( + "SELECT 1 FROM sqlite_master WHERE name = ?", + (tbl,), + ).fetchone() + if exists is None: + continue + # Schema-level columns may include rowid which + # is implicit; use SELECT * which preserves + # column order between attached + main. + target.execute( + f"INSERT OR IGNORE INTO main.{tbl} " + f"SELECT * FROM fts_src.{tbl}" + ) + total_rows += target.execute( + f"SELECT changes()" + ).fetchone()[0] + finally: + target.execute("DETACH DATABASE fts_src") + elapsed = time.time() - started + return { + "status": "fts_pulled", + "pack_hash": pack_hash, + "rows_inserted": total_rows, + "elapsed_seconds": elapsed, + } + finally: + shutil.rmtree(work_dir, ignore_errors=True) + + def _pull_pack_inner_routed( targets: list[sqlite3.Connection], backend: "ObjectStoreBackend", @@ -940,8 +1151,11 @@ def _pull_pack_inner_routed( # require the FTS rebuild to land first. chunk_pack_hashes_referenced: tuple[str, ...] = () + fts_pack_hashes_referenced: tuple[str, ...] = () if manifest_bytes is not None: - chunk_pack_hashes_referenced = parse_manifest(manifest_bytes).chunk_pack_hashes + parsed = parse_manifest(manifest_bytes) + chunk_pack_hashes_referenced = parsed.chunk_pack_hashes + fts_pack_hashes_referenced = parsed.fts_pack_hashes # Audit event lands on canonical target 0 (consolidated chain). with transaction(targets[0]): @@ -966,6 +1180,7 @@ def _pull_pack_inner_routed( "pack_kind": kind, "M": M, "chunk_pack_hashes_referenced": list(chunk_pack_hashes_referenced), + "fts_pack_hashes_referenced": list(fts_pack_hashes_referenced), "tables_restored": tables_restored, "chunks_restored": restored, "chunks_skipped_already_hot": skipped_already_hot, diff --git a/arborist/store.py b/arborist/store.py index 9f5588e..24e2957 100644 --- a/arborist/store.py +++ b/arborist/store.py @@ -257,7 +257,7 @@ CREATE INDEX IF NOT EXISTS idx_snapshots_taken_at ON snapshots(taken_at); CREATE TABLE IF NOT EXISTS cold_pending ( tempfile_path TEXT PRIMARY KEY, pack_hash TEXT NOT NULL, - kind TEXT NOT NULL CHECK (kind IN ('metadata', 'chunks')), + kind TEXT NOT NULL CHECK (kind IN ('metadata', 'chunks', 'fts')), backend_endpoint TEXT NOT NULL, backend_bucket TEXT NOT NULL, object_key TEXT NOT NULL, diff --git a/bench/results/jaggedness_2026-05-21T16-47-30Z.json b/bench/results/jaggedness_2026-05-21T16-47-30Z.json new file mode 100644 index 0000000..5d95d43 --- /dev/null +++ b/bench/results/jaggedness_2026-05-21T16-47-30Z.json @@ -0,0 +1,2705 @@ +{ + "meta": { + "classes": [ + "numeral", + "accent", + "hyphen", + "honorific", + "amp", + "brit" + ], + "limit": 60, + "k": 8, + "ts": "2026-05-21T16-47-30Z" + }, + "summary": { + "n": 240, + "k": 8, + "j_norm_binary": 0.08333333333333333, + "jagged_titles": 20, + "canon_recall@k": 0.9416666666666667, + "pert_recall@k": 0.875, + "graded_mean_rank_gap": 0.22115384615384615, + "graded_n_both_surfaced": 208, + "by_class": { + "numeral": { + "n": 40, + "jagged": 4, + "canon_surf": 32, + "pert_surf": 30 + }, + "accent": { + "n": 40, + "jagged": 0, + "canon_surf": 39, + "pert_surf": 39 + }, + "hyphen": { + "n": 40, + "jagged": 1, + "canon_surf": 40, + "pert_surf": 39 + }, + "honorific": { + "n": 40, + "jagged": 7, + "canon_surf": 40, + "pert_surf": 33 + }, + "amp": { + "n": 40, + "jagged": 1, + "canon_surf": 35, + "pert_surf": 36 + }, + "brit": { + "n": 40, + "jagged": 7, + "canon_surf": 40, + "pert_surf": 33 + } + } + }, + "records": [ + { + "cls": "numeral", + "title": "Albert III", + "canon_q": "who was Albert III?", + "pert_q": "who was Albert the third?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "numeral", + "title": "Ahmed III", + "canon_q": "who was Ahmed III?", + "pert_q": "who was Ahmed the third?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alaric I", + "canon_q": "who was Alaric I?", + "pert_q": "who was Alaric the first?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alexander I of Epirus", + "canon_q": "who was Alexander I of Epirus?", + "pert_q": "who was Alexander the first of epirus?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alexander II of Scotland", + "canon_q": "who was Alexander II of Scotland?", + "pert_q": "who was Alexander the second of scotland?", + "canon_rank": 6, + "pert_rank": 6, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alexander II", + "canon_q": "who was Alexander II?", + "pert_q": "who was Alexander the second?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alexander IV", + "canon_q": "who was Alexander IV?", + "pert_q": "who was Alexander the fourth?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alyattes II", + "canon_q": "who was Alyattes II?", + "pert_q": "who was Alyattes the second?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Afonso IV of Portugal", + "canon_q": "who was Afonso IV of Portugal?", + "pert_q": "who was Afonso the fourth of portugal?", + "canon_rank": 0, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alfonso II of Asturias", + "canon_q": "who was Alfonso II of Asturias?", + "pert_q": "who was Alfonso the second of asturias?", + "canon_rank": 2, + "pert_rank": 2, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alfonso IV of Aragon", + "canon_q": "who was Alfonso IV of Aragon?", + "pert_q": "who was Alfonso the fourth of aragon?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alfonso III", + "canon_q": "who was Alfonso III?", + "pert_q": "who was Alfonso the third?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "numeral", + "title": "Alfonso V", + "canon_q": "who was Alfonso V?", + "pert_q": "who was Alfonso the fifth?", + "canon_rank": 3, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "numeral", + "title": "Anastasius II", + "canon_q": "who was Anastasius II?", + "pert_q": "who was Anastasius the second?", + "canon_rank": 1, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Abbas II of Egypt", + "canon_q": "who was Abbas II of Egypt?", + "pert_q": "who was Abbas the second of egypt?", + "canon_rank": 6, + "pert_rank": 6, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Charles V", + "canon_q": "who was Charles V?", + "pert_q": "who was Charles the fifth?", + "canon_rank": 4, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "numeral", + "title": "Constantius II", + "canon_q": "who was Constantius II?", + "pert_q": "who was Constantius the second?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Constantine II of Scotland", + "canon_q": "who was Constantine II of Scotland?", + "pert_q": "who was Constantine the second of scotland?", + "canon_rank": 4, + "pert_rank": 4, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Charles I of England", + "canon_q": "who was Charles I of England?", + "pert_q": "who was Charles the first of england?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Frederick V", + "canon_q": "who was Frederick V?", + "pert_q": "who was Frederick the fifth?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "numeral", + "title": "Henry VII", + "canon_q": "who was Henry VII?", + "pert_q": "who was Henry the seventh?", + "canon_rank": 1, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Mehmed I", + "canon_q": "who was Mehmed I?", + "pert_q": "who was Mehmed the first?", + "canon_rank": 1, + "pert_rank": 2, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Mustafa I", + "canon_q": "who was Mustafa I?", + "pert_q": "who was Mustafa the first?", + "canon_rank": 1, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "numeral", + "title": "Mieszko I of Poland", + "canon_q": "who was Mieszko I of Poland?", + "pert_q": "who was Mieszko the first of poland?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Malcolm I of Scotland", + "canon_q": "who was Malcolm I of Scotland?", + "pert_q": "who was Malcolm the first of scotland?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Osman II", + "canon_q": "who was Osman II?", + "pert_q": "who was Osman the second?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Quake II", + "canon_q": "who was Quake II?", + "pert_q": "who was Quake the second?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Stephen III", + "canon_q": "who was Stephen III?", + "pert_q": "who was Stephen the third?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "numeral", + "title": "Oscar I of Sweden", + "canon_q": "who was Oscar I of Sweden?", + "pert_q": "who was Oscar the first of sweden?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Charles XV of Sweden", + "canon_q": "who was Charles XV of Sweden?", + "pert_q": "who was Charles the fifteenth of sweden?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Sviatoslav I of Kiev", + "canon_q": "who was Sviatoslav I of Kiev?", + "pert_q": "who was Sviatoslav the first of kiev?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Catherine II of Russia", + "canon_q": "who was Catherine II of Russia?", + "pert_q": "who was Catherine the second of russia?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Childeric I", + "canon_q": "who was Childeric I?", + "pert_q": "who was Childeric the first?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Rudolph I of Germany", + "canon_q": "who was Rudolph I of Germany?", + "pert_q": "who was Rudolph the first of germany?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Xerxes II of Persia", + "canon_q": "who was Xerxes II of Persia?", + "pert_q": "who was Xerxes the second of persia?", + "canon_rank": 0, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Richard II of England", + "canon_q": "who was Richard II of England?", + "pert_q": "who was Richard the second of england?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "numeral", + "title": "Gustav I of Sweden", + "canon_q": "who was Gustav I of Sweden?", + "pert_q": "who was Gustav the first of sweden?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "Photios I of Constantinople", + "canon_q": "who was Photios I of Constantinople?", + "pert_q": "who was Photios the first of constantinople?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "numeral", + "title": "James V of Scotland", + "canon_q": "who was James V of Scotland?", + "pert_q": "who was James the fifth of scotland?", + "canon_rank": -1, + "pert_rank": 4, + "surf_canon": false, + "surf_pert": true, + "jagged": true + }, + { + "cls": "numeral", + "title": "Basarab I of Wallachia", + "canon_q": "who was Basarab I of Wallachia?", + "pert_q": "who was Basarab the first of wallachia?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Casa Batlló", + "canon_q": "what is Casa Batlló?", + "pert_q": "what is Casa Batllo?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "André-Marie Ampère", + "canon_q": "what is André-Marie Ampère?", + "pert_q": "what is Andre-Marie Ampere?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Antoni Gaudí", + "canon_q": "what is Antoni Gaudí?", + "pert_q": "what is Antoni Gaudi?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Alcobaça (Portugal)", + "canon_q": "what is Alcobaça (Portugal)?", + "pert_q": "what is Alcobaca (Portugal)?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Bifröst", + "canon_q": "what is Bifröst?", + "pert_q": "what is Bifrost?", + "canon_rank": 0, + "pert_rank": 6, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Boötes", + "canon_q": "what is Boötes?", + "pert_q": "what is Bootes?", + "canon_rank": 0, + "pert_rank": 3, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Béla Bartók", + "canon_q": "what is Béla Bartók?", + "pert_q": "what is Bela Bartok?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Blue Öyster Cult", + "canon_q": "what is Blue Öyster Cult?", + "pert_q": "what is Blue Oyster Cult?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Björn Borg", + "canon_q": "what is Björn Borg?", + "pert_q": "what is Bjorn Borg?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Barış Manço", + "canon_q": "what is Barış Manço?", + "pert_q": "what is Barıs Manco?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Transport in Côte d'Ivoire", + "canon_q": "what is Transport in Côte d'Ivoire?", + "pert_q": "what is Transport in Cote d'Ivoire?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Crannóg", + "canon_q": "what is Crannóg?", + "pert_q": "what is Crannog?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Emily Brontë", + "canon_q": "what is Emily Brontë?", + "pert_q": "what is Emily Bronte?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Évariste Galois", + "canon_q": "what is Évariste Galois?", + "pert_q": "what is Evariste Galois?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Elbląg", + "canon_q": "what is Elbląg?", + "pert_q": "what is Elblag?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Erwin Schrödinger", + "canon_q": "what is Erwin Schrödinger?", + "pert_q": "what is Erwin Schrodinger?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Élisabeth-Louise Vigée-Le Brun", + "canon_q": "what is Élisabeth-Louise Vigée-Le Brun?", + "pert_q": "what is Elisabeth-Louise Vigee-Le Brun?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Emperor Shōmu", + "canon_q": "what is Emperor Shōmu?", + "pert_q": "what is Emperor Shomu?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Emperor Yōmei", + "canon_q": "what is Emperor Yōmei?", + "pert_q": "what is Emperor Yomei?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Emperor Kōtoku", + "canon_q": "what is Emperor Kōtoku?", + "pert_q": "what is Emperor Kotoku?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Empress Kōken", + "canon_q": "what is Empress Kōken?", + "pert_q": "what is Empress Koken?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Emperor Go-En'yū", + "canon_q": "what is Emperor Go-En'yū?", + "pert_q": "what is Emperor Go-En'yu?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Emperor Kōan", + "canon_q": "what is Emperor Kōan?", + "pert_q": "what is Emperor Koan?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Emperor Chūai", + "canon_q": "what is Emperor Chūai?", + "pert_q": "what is Emperor Chuai?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Francisco Álvares", + "canon_q": "what is Francisco Álvares?", + "pert_q": "what is Francisco Alvares?", + "canon_rank": 0, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Félix Guattari", + "canon_q": "what is Félix Guattari?", + "pert_q": "what is Felix Guattari?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Göta Canal", + "canon_q": "what is Göta Canal?", + "pert_q": "what is Gota Canal?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Goran Bregović", + "canon_q": "what is Goran Bregović?", + "pert_q": "what is Goran Bregovic?", + "canon_rank": 0, + "pert_rank": 2, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Gödel's completeness theorem", + "canon_q": "what is Gödel's completeness theorem?", + "pert_q": "what is Godel's completeness theorem?", + "canon_rank": 0, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Gilbert Arthur à Beckett", + "canon_q": "what is Gilbert Arthur à Beckett?", + "pert_q": "what is Gilbert Arthur a Beckett?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "GÉANT", + "canon_q": "what is GÉANT?", + "pert_q": "what is GEANT?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Hergé", + "canon_q": "what is Hergé?", + "pert_q": "what is Herge?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "İsmet İnönü", + "canon_q": "what is İsmet İnönü?", + "pert_q": "what is Ismet Inonu?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Gyula Andrássy", + "canon_q": "what is Gyula Andrássy?", + "pert_q": "what is Gyula Andrassy?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Jean-François Millet", + "canon_q": "what is Jean-François Millet?", + "pert_q": "what is Jean-Francois Millet?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "House of Karađorđević", + "canon_q": "what is House of Karađorđević?", + "pert_q": "what is House of Karađorđevic?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "accent", + "title": "Künstlerroman", + "canon_q": "what is Künstlerroman?", + "pert_q": "what is Kunstlerroman?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Musée du Louvre", + "canon_q": "what is Musée du Louvre?", + "pert_q": "what is Musee du Louvre?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "Lübeck", + "canon_q": "what is Lübeck?", + "pert_q": "what is Lubeck?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "accent", + "title": "La Tène culture", + "canon_q": "what is La Tène culture?", + "pert_q": "what is La Tene culture?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "The Amazing Spider-Man (comic book)", + "canon_q": "what is The Amazing Spider-Man (comic book)?", + "pert_q": "what is The Amazing Spider Man (comic book)?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Augustin-Jean Fresnel", + "canon_q": "what is Augustin-Jean Fresnel?", + "pert_q": "what is Augustin Jean Fresnel?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "André-Marie Ampère", + "canon_q": "what is André-Marie Ampère?", + "pert_q": "what is André Marie Ampère?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Aster CT-80", + "canon_q": "what is Aster CT-80?", + "pert_q": "what is Aster CT 80?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Anti-globalization movement", + "canon_q": "what is Anti-globalization movement?", + "pert_q": "what is Anti globalization movement?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Abd ar-Rahman I", + "canon_q": "what is Abd ar-Rahman I?", + "pert_q": "what is Abd ar Rahman I?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Abd ar-Rahman V", + "canon_q": "what is Abd ar-Rahman V?", + "pert_q": "what is Abd ar Rahman V?", + "canon_rank": 1, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Anti-Ballistic Missile Treaty", + "canon_q": "what is Anti-Ballistic Missile Treaty?", + "pert_q": "what is Anti Ballistic Missile Treaty?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "AGM-88 HARM", + "canon_q": "what is AGM-88 HARM?", + "pert_q": "what is AGM 88 HARM?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Lockheed AC-130", + "canon_q": "what is Lockheed AC-130?", + "pert_q": "what is Lockheed AC 130?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "CIM-10 Bomarc", + "canon_q": "what is CIM-10 Bomarc?", + "pert_q": "what is CIM 10 Bomarc?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "North American B-25 Mitchell", + "canon_q": "what is North American B-25 Mitchell?", + "pert_q": "what is North American B 25 Mitchell?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Bain-marie", + "canon_q": "what is Bain-marie?", + "pert_q": "what is Bain marie?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Cross-dressing", + "canon_q": "what is Cross-dressing?", + "pert_q": "what is Cross dressing?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Call of Cthulhu (role-playing game)", + "canon_q": "what is Call of Cthulhu (role-playing game)?", + "pert_q": "what is Call of Cthulhu (role playing game)?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Context-sensitive", + "canon_q": "what is Context-sensitive?", + "pert_q": "what is Context sensitive?", + "canon_rank": 0, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Boeing C-17 Globemaster III", + "canon_q": "what is Boeing C-17 Globemaster III?", + "pert_q": "what is Boeing C 17 Globemaster III?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Chiang Kai-shek", + "canon_q": "what is Chiang Kai-shek?", + "pert_q": "what is Chiang Kai shek?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Context-free language", + "canon_q": "what is Context-free language?", + "pert_q": "what is Context free language?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "C*-algebra", + "canon_q": "what is C*-algebra?", + "pert_q": "what is C* algebra?", + "canon_rank": 1, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Computer-generated imagery", + "canon_q": "what is Computer-generated imagery?", + "pert_q": "what is Computer generated imagery?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Lockheed C-130 Hercules", + "canon_q": "what is Lockheed C-130 Hercules?", + "pert_q": "what is Lockheed C 130 Hercules?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Covenant-breaker", + "canon_q": "what is Covenant-breaker?", + "pert_q": "what is Covenant breaker?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Comprehensive Nuclear-Test-Ban Treaty", + "canon_q": "what is Comprehensive Nuclear-Test-Ban Treaty?", + "pert_q": "what is Comprehensive Nuclear Test Ban Treaty?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Double-slit experiment", + "canon_q": "what is Double-slit experiment?", + "pert_q": "what is Double slit experiment?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Double-ended queue", + "canon_q": "what is Double-ended queue?", + "pert_q": "what is Double ended queue?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Cost-push inflation", + "canon_q": "what is Cost-push inflation?", + "pert_q": "what is Cost push inflation?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Eductor-jet pump", + "canon_q": "what is Eductor-jet pump?", + "pert_q": "what is Eductor jet pump?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Eight-ball", + "canon_q": "what is Eight-ball?", + "pert_q": "what is Eight ball?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Élisabeth-Louise Vigée-Le Brun", + "canon_q": "what is Élisabeth-Louise Vigée-Le Brun?", + "pert_q": "what is Élisabeth Louise Vigée Le Brun?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Evidence-based medicine", + "canon_q": "what is Evidence-based medicine?", + "pert_q": "what is Evidence based medicine?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "E-Prime", + "canon_q": "what is E-Prime?", + "pert_q": "what is E Prime?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "hyphen", + "title": "Boeing E-3 Sentry", + "canon_q": "what is Boeing E-3 Sentry?", + "pert_q": "what is Boeing E 3 Sentry?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Emperor Go-En'yū", + "canon_q": "what is Emperor Go-En'yū?", + "pert_q": "what is Emperor Go En'yū?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Field-programmable gate array", + "canon_q": "what is Field-programmable gate array?", + "pert_q": "what is Field programmable gate array?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Five-spice powder", + "canon_q": "what is Five-spice powder?", + "pert_q": "what is Five spice powder?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Four-poster", + "canon_q": "what is Four-poster?", + "pert_q": "what is Four poster?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Flip-flop (electronics)", + "canon_q": "what is Flip-flop (electronics)?", + "pert_q": "what is Flip flop (electronics)?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Guinea-Bissau", + "canon_q": "what is Guinea-Bissau?", + "pert_q": "what is Guinea Bissau?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "hyphen", + "title": "Politics of Guinea-Bissau", + "canon_q": "what is Politics of Guinea-Bissau?", + "pert_q": "what is Politics of Guinea Bissau?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Doctor Who", + "canon_q": "what is Doctor Who?", + "pert_q": "what is Dr Who?", + "canon_rank": 1, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Doctor Syn", + "canon_q": "what is Doctor Syn?", + "pert_q": "what is Dr Syn?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Doctor V64", + "canon_q": "what is Doctor V64?", + "pert_q": "what is Dr V64?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Fort Wayne, Indiana", + "canon_q": "what is Fort Wayne, Indiana?", + "pert_q": "what is Ft Wayne, Indiana?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Fort Collins, Colorado", + "canon_q": "what is Fort Collins, Colorado?", + "pert_q": "what is Ft Collins, Colorado?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "President of France", + "canon_q": "what is President of France?", + "pert_q": "what is Pres of France?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Saint Lawrence Seaway", + "canon_q": "what is Saint Lawrence Seaway?", + "pert_q": "what is St Lawrence Seaway?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Saint Kitts and Nevis", + "canon_q": "what is Saint Kitts and Nevis?", + "pert_q": "what is St Kitts and Nevis?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Saint Lucia", + "canon_q": "what is Saint Lucia?", + "pert_q": "what is St Lucia?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "honorific", + "title": "Saint Adrian", + "canon_q": "what is Saint Adrian?", + "pert_q": "what is St Adrian?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Saint Boniface", + "canon_q": "what is Saint Boniface?", + "pert_q": "what is St Boniface?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "honorific", + "title": "Saint Ninian", + "canon_q": "what is Saint Ninian?", + "pert_q": "what is St Ninian?", + "canon_rank": 0, + "pert_rank": 7, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Saint Andrew", + "canon_q": "what is Saint Andrew?", + "pert_q": "what is St Andrew?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "honorific", + "title": "Saint Timothy", + "canon_q": "what is Saint Timothy?", + "pert_q": "what is St Timothy?", + "canon_rank": 0, + "pert_rank": 4, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Saint Anselm", + "canon_q": "what is Saint Anselm?", + "pert_q": "what is St Anselm?", + "canon_rank": 2, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "honorific", + "title": "Saint Paul, Minnesota", + "canon_q": "what is Saint Paul, Minnesota?", + "pert_q": "what is St Paul, Minnesota?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Everest", + "canon_q": "what is Mount Everest?", + "pert_q": "what is Mt Everest?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Fort Montgomery (Hudson River)", + "canon_q": "what is Fort Montgomery (Hudson River)?", + "pert_q": "what is Ft Montgomery (Hudson River)?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Saint Nicholas", + "canon_q": "what is Saint Nicholas?", + "pert_q": "what is St Nicholas?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "honorific", + "title": "Saint Patrick's Battalion", + "canon_q": "what is Saint Patrick's Battalion?", + "pert_q": "what is St Patrick's Battalion?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Sinai", + "canon_q": "what is Mount Sinai?", + "pert_q": "what is Mt Sinai?", + "canon_rank": 0, + "pert_rank": 2, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Saint Charles", + "canon_q": "what is Saint Charles?", + "pert_q": "what is St Charles?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "honorific", + "title": "Mount Ephraim", + "canon_q": "what is Mount Ephraim?", + "pert_q": "what is Mt Ephraim?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "General Electric Company plc", + "canon_q": "what is General Electric Company plc?", + "pert_q": "what is Gen Electric Company plc?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Joy, Pennsylvania", + "canon_q": "what is Mount Joy, Pennsylvania?", + "pert_q": "what is Mt Joy, Pennsylvania?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Fort Pitt", + "canon_q": "what is Fort Pitt?", + "pert_q": "what is Ft Pitt?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Clunie National Park", + "canon_q": "what is Mount Clunie National Park?", + "pert_q": "what is Mt Clunie National Park?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Nothofagus National Park", + "canon_q": "what is Mount Nothofagus National Park?", + "pert_q": "what is Mt Nothofagus National Park?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Field National Park", + "canon_q": "what is Mount Field National Park?", + "pert_q": "what is Mt Field National Park?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Richmond National Park", + "canon_q": "what is Mount Richmond National Park?", + "pert_q": "what is Mt Richmond National Park?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "honorific", + "title": "Mount Aberdeen National Park", + "canon_q": "what is Mount Aberdeen National Park?", + "pert_q": "what is Mt Aberdeen National Park?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Colosseum National Park", + "canon_q": "what is Mount Colosseum National Park?", + "pert_q": "what is Mt Colosseum National Park?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Etna Caves National Park", + "canon_q": "what is Mount Etna Caves National Park?", + "pert_q": "what is Mt Etna Caves National Park?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount O'Connell National Park", + "canon_q": "what is Mount O'Connell National Park?", + "pert_q": "what is Mt O'Connell National Park?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Webb National Park", + "canon_q": "what is Mount Webb National Park?", + "pert_q": "what is Mt Webb National Park?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Fort Rucker", + "canon_q": "what is Fort Rucker?", + "pert_q": "what is Ft Rucker?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Olive, Alabama", + "canon_q": "what is Mount Olive, Alabama?", + "pert_q": "what is Mt Olive, Alabama?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Fort Yukon, Alaska", + "canon_q": "what is Fort Yukon, Alaska?", + "pert_q": "what is Ft Yukon, Alaska?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Mount Ida, Arkansas", + "canon_q": "what is Mount Ida, Arkansas?", + "pert_q": "what is Mt Ida, Arkansas?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "honorific", + "title": "Fort Smith, Arkansas", + "canon_q": "what is Fort Smith, Arkansas?", + "pert_q": "what is Ft Smith, Arkansas?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Heckler & Koch", + "canon_q": "what is Heckler & Koch?", + "pert_q": "what is Heckler and Koch?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Science & Environmental Policy Project", + "canon_q": "what is Science & Environmental Policy Project?", + "pert_q": "what is Science and Environmental Policy Project?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Texas A&M University", + "canon_q": "what is Texas A&M University?", + "pert_q": "what is Texas A and M University?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Pratt & Whitney", + "canon_q": "what is Pratt & Whitney?", + "pert_q": "what is Pratt and Whitney?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Ernst & Young", + "canon_q": "what is Ernst & Young?", + "pert_q": "what is Ernst and Young?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Waterloo & City line", + "canon_q": "what is Waterloo & City line?", + "pert_q": "what is Waterloo and City line?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Duany Plater-Zyberk & Company", + "canon_q": "what is Duany Plater-Zyberk & Company?", + "pert_q": "what is Duany Plater-Zyberk and Company?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "The Sandman: Fables & Reflections", + "canon_q": "what is The Sandman: Fables & Reflections?", + "pert_q": "what is The Sandman: Fables and Reflections?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Rape, Abuse & Incest National Network", + "canon_q": "what is Rape, Abuse & Incest National Network?", + "pert_q": "what is Rape, Abuse and Incest National Network?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Hilton Hotels & Resorts", + "canon_q": "what is Hilton Hotels & Resorts?", + "pert_q": "what is Hilton Hotels and Resorts?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "North Walsham & Dilham Canal", + "canon_q": "what is North Walsham & Dilham Canal?", + "pert_q": "what is North Walsham and Dilham Canal?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Bill & Melinda Gates Foundation", + "canon_q": "what is Bill & Melinda Gates Foundation?", + "pert_q": "what is Bill and Melinda Gates Foundation?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Question Mark & the Mysterians", + "canon_q": "what is Question Mark & the Mysterians?", + "pert_q": "what is Question Mark and the Mysterians?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "A&B", + "canon_q": "what is A&B?", + "pert_q": "what is A and B?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "amp", + "title": "Chivalry & Sorcery", + "canon_q": "what is Chivalry & Sorcery?", + "pert_q": "what is Chivalry and Sorcery?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "II & III", + "canon_q": "what is II & III?", + "pert_q": "what is II and III?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "amp", + "title": "Standard & Poor's", + "canon_q": "what is Standard & Poor's?", + "pert_q": "what is Standard and Poor's?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Cheech & Chong", + "canon_q": "what is Cheech & Chong?", + "pert_q": "what is Cheech and Chong?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Sasha & John Digweed", + "canon_q": "what is Sasha & John Digweed?", + "pert_q": "what is Sasha and John Digweed?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Murat & Jose", + "canon_q": "what is Murat & Jose?", + "pert_q": "what is Murat and Jose?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Valleys & Cardiff Local Routes", + "canon_q": "what is Valleys & Cardiff Local Routes?", + "pert_q": "what is Valleys and Cardiff Local Routes?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "The College of William & Mary", + "canon_q": "what is The College of William & Mary?", + "pert_q": "what is The College of William and Mary?", + "canon_rank": -1, + "pert_rank": 0, + "surf_canon": false, + "surf_pert": true, + "jagged": true + }, + { + "cls": "amp", + "title": "Industrial Light & Magic", + "canon_q": "what is Industrial Light & Magic?", + "pert_q": "what is Industrial Light and Magic?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Barnes & Noble", + "canon_q": "what is Barnes & Noble?", + "pert_q": "what is Barnes and Noble?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Law & Order: Special Victims Unit", + "canon_q": "what is Law & Order: Special Victims Unit?", + "pert_q": "what is Law and Order: Special Victims Unit?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Grammy Award for Best R&B Song", + "canon_q": "what is Grammy Award for Best R&B Song?", + "pert_q": "what is Grammy Award for Best R and B Song?", + "canon_rank": 1, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Ike & Tina Turner", + "canon_q": "what is Ike & Tina Turner?", + "pert_q": "what is Ike and Tina Turner?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "H&M", + "canon_q": "what is H&M?", + "pert_q": "what is H and M?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "amp", + "title": "Yesterday & Today", + "canon_q": "what is Yesterday & Today?", + "pert_q": "what is Yesterday and Today?", + "canon_rank": 1, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Open Fire (Y&T album)", + "canon_q": "what is Open Fire (Y&T album)?", + "pert_q": "what is Open Fire (Y and T album)?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Funk & Wagnalls", + "canon_q": "what is Funk & Wagnalls?", + "pert_q": "what is Funk and Wagnalls?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "South Park: Bigger, Longer & Uncut", + "canon_q": "what is South Park: Bigger, Longer & Uncut?", + "pert_q": "what is South Park: Bigger, Longer and Uncut?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Lewis & Clark College", + "canon_q": "what is Lewis & Clark College?", + "pert_q": "what is Lewis and Clark College?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Love & Pop", + "canon_q": "what is Love & Pop?", + "pert_q": "what is Love and Pop?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Barnes & Barnes", + "canon_q": "what is Barnes & Barnes?", + "pert_q": "what is Barnes and Barnes?", + "canon_rank": 2, + "pert_rank": 2, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Sky (UK & Ireland)", + "canon_q": "what is Sky (UK & Ireland)?", + "pert_q": "what is Sky (UK and Ireland)?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "Sergio & The Ladies", + "canon_q": "what is Sergio & The Ladies?", + "pert_q": "what is Sergio and The Ladies?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "B&B", + "canon_q": "what is B&B?", + "pert_q": "what is B and B?", + "canon_rank": -1, + "pert_rank": -1, + "surf_canon": false, + "surf_pert": false, + "jagged": false + }, + { + "cls": "amp", + "title": "Starwood Hotels & Resorts Worldwide", + "canon_q": "what is Starwood Hotels & Resorts Worldwide?", + "pert_q": "what is Starwood Hotels and Resorts Worldwide?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "amp", + "title": "FRANC 2D&3D", + "canon_q": "what is FRANC 2D&3D?", + "pert_q": "what is FRANC 2D and 3D?", + "canon_rank": 4, + "pert_rank": 4, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Finnish Defence Forces", + "canon_q": "what is Finnish Defence Forces?", + "pert_q": "what is Finnish defense Forces?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Hopewell Centre, Hong Kong", + "canon_q": "what is Hopewell Centre, Hong Kong?", + "pert_q": "what is Hopewell center, Hong Kong?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Labour economics", + "canon_q": "what is Labour economics?", + "pert_q": "what is labor economics?", + "canon_rank": 0, + "pert_rank": 1, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "World Organisation for Animal Health", + "canon_q": "what is World Organisation for Animal Health?", + "pert_q": "what is World organization for Animal Health?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Papua New Guinea Defence Force", + "canon_q": "what is Papua New Guinea Defence Force?", + "pert_q": "what is Papua New Guinea defense Force?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Programmer", + "canon_q": "what is Programmer?", + "pert_q": "what is programr?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "brit", + "title": "Tonga Defence Services", + "canon_q": "what is Tonga Defence Services?", + "pert_q": "what is Tonga defense Services?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Trinidad and Tobago Defence Force", + "canon_q": "what is Trinidad and Tobago Defence Force?", + "pert_q": "what is Trinidad and Tobago defense Force?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "brit", + "title": "Zambian Defence Force", + "canon_q": "what is Zambian Defence Force?", + "pert_q": "what is Zambian defense Force?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Defence Signals Directorate", + "canon_q": "what is Defence Signals Directorate?", + "pert_q": "what is defense Signals Directorate?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "brit", + "title": "English Renaissance theatre", + "canon_q": "what is English Renaissance theatre?", + "pert_q": "what is English Renaissance theater?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Labour Party (Netherlands)", + "canon_q": "what is Labour Party (Netherlands)?", + "pert_q": "what is labor Party (Netherlands)?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "The Luzhin Defence", + "canon_q": "what is The Luzhin Defence?", + "pert_q": "what is The Luzhin defense?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Asia-Pacific Network Information Centre", + "canon_q": "what is Asia-Pacific Network Information Centre?", + "pert_q": "what is Asia-Pacific Network Information center?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Soyuz programme", + "canon_q": "what is Soyuz programme?", + "pert_q": "what is Soyuz program?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Centre Georges Pompidou", + "canon_q": "what is Centre Georges Pompidou?", + "pert_q": "what is center Georges Pompidou?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Amphitheatre", + "canon_q": "what is Amphitheatre?", + "pert_q": "what is Amphitheater?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "brit", + "title": "Australian Defence Force", + "canon_q": "what is Australian Defence Force?", + "pert_q": "what is Australian defense Force?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Centre County, Pennsylvania", + "canon_q": "what is Centre County, Pennsylvania?", + "pert_q": "what is center County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "brit", + "title": "Organisation internationale de la Francophonie", + "canon_q": "what is Organisation internationale de la Francophonie?", + "pert_q": "what is organization internationale de la Francophonie?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Luna programme", + "canon_q": "what is Luna programme?", + "pert_q": "what is Luna program?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Man and the Biosphere Programme", + "canon_q": "what is Man and the Biosphere Programme?", + "pert_q": "what is Man and the Biosphere program?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Centreville, Maryland", + "canon_q": "what is Centreville, Maryland?", + "pert_q": "what is centerville, Maryland?", + "canon_rank": 0, + "pert_rank": 3, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Boggs Township, Centre County, Pennsylvania", + "canon_q": "what is Boggs Township, Centre County, Pennsylvania?", + "pert_q": "what is Boggs Township, center County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Ferguson Township, Centre County, Pennsylvania", + "canon_q": "what is Ferguson Township, Centre County, Pennsylvania?", + "pert_q": "what is Ferguson Township, center County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Harris Township, Centre County, Pennsylvania", + "canon_q": "what is Harris Township, Centre County, Pennsylvania?", + "pert_q": "what is Harris Township, center County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Huston Township, Centre County, Pennsylvania", + "canon_q": "what is Huston Township, Centre County, Pennsylvania?", + "pert_q": "what is Huston Township, center County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "brit", + "title": "Patton Township, Centre County, Pennsylvania", + "canon_q": "what is Patton Township, Centre County, Pennsylvania?", + "pert_q": "what is Patton Township, center County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Union Township, Centre County, Pennsylvania", + "canon_q": "what is Union Township, Centre County, Pennsylvania?", + "pert_q": "what is Union Township, center County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": -1, + "surf_canon": true, + "surf_pert": false, + "jagged": true + }, + { + "cls": "brit", + "title": "Worth Township, Centre County, Pennsylvania", + "canon_q": "what is Worth Township, Centre County, Pennsylvania?", + "pert_q": "what is Worth Township, center County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Centre Township, Perry County, Pennsylvania", + "canon_q": "what is Centre Township, Perry County, Pennsylvania?", + "pert_q": "what is center Township, Perry County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Centre Township, Pennsylvania", + "canon_q": "what is Centre Township, Pennsylvania?", + "pert_q": "what is center Township, Pennsylvania?", + "canon_rank": 0, + "pert_rank": 6, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Tricorn Centre", + "canon_q": "what is Tricorn Centre?", + "pert_q": "what is Tricorn center?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Living Colour", + "canon_q": "what is Living Colour?", + "pert_q": "what is Living color?", + "canon_rank": 0, + "pert_rank": 3, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Philipsburg, Centre County, Pennsylvania", + "canon_q": "what is Philipsburg, Centre County, Pennsylvania?", + "pert_q": "what is Philipsburg, center County, Pennsylvania?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "German Labour Front", + "canon_q": "what is German Labour Front?", + "pert_q": "what is German labor Front?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Rogers Centre", + "canon_q": "what is Rogers Centre?", + "pert_q": "what is Rogers center?", + "canon_rank": 0, + "pert_rank": 2, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Ulster Defence Association", + "canon_q": "what is Ulster Defence Association?", + "pert_q": "what is Ulster defense Association?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Guiana Space Centre", + "canon_q": "what is Guiana Space Centre?", + "pert_q": "what is Guiana Space center?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + }, + { + "cls": "brit", + "title": "Community theatre", + "canon_q": "what is Community theatre?", + "pert_q": "what is Community theater?", + "canon_rank": 0, + "pert_rank": 0, + "surf_canon": true, + "surf_pert": true, + "jagged": false + } + ] +} \ No newline at end of file diff --git a/bench/results/nli-backend-ab.json b/bench/results/nli-backend-ab.json new file mode 100644 index 0000000..9da573f --- /dev/null +++ b/bench/results/nli-backend-ab.json @@ -0,0 +1,31 @@ +{ + "ticket": "#000049 \u00a77 #28", + "n_pairs": 24, + "repeats": 2, + "tolerance": 0.002, + "shadow_only": true, + "results": { + "torch": { + "available": true, + "backend": "torch", + "device": "cpu", + "p50_ms_per_pair": 12.405, + "p95_ms_per_pair": 12.39, + "agreement_vs_torch": "reference" + }, + "onnx": { + "available": true, + "backend": "onnx-int8", + "device": "cpu", + "p50_ms_per_pair": 12.174, + "p95_ms_per_pair": 12.139, + "max_abs_delta_vs_torch": 0.41639, + "mean_abs_delta_vs_torch": 0.018515, + "agreement_pass": false + }, + "tinygrad": { + "available": false, + "reason": "requested tinygrad but ShadowNLI loaded onnx-int8 (engine not usable here)" + } + } +} \ No newline at end of file diff --git a/docs/posts/cogs-tweet.md b/docs/posts/cogs-tweet.md deleted file mode 100644 index df797f6..0000000 --- a/docs/posts/cogs-tweet.md +++ /dev/null @@ -1,35 +0,0 @@ -# COGS tweet — cost of a grounded answer (arborist) - -Draft social post. Numbers are the measured claim_lattice figures from -`docs/energy-cogs-benchmark.md` (n=30, $0.33/kWh, real GPUs: Hermes-8B on -a 3090, Qwen-27B on a 4090). Hold the bigger "arbitrage / forcing -function" framing until the value side is hardened (higher N + blinded -SOTA judge) — see that report's §5.5 + §9. - -## Main tweet - -What does a *grounded* answer actually cost in GPU electricity? We -measured it on real cards: - -- arborist + Hermes-3-8B → **~9¢ per 1,000 answers** -- arborist + Qwen-27B → **~16¢ per 1,000** - -No reasoning chains (those burn 4–6×). Read the evidence cheap, write a -short answer locked to a claim lattice, stop. - -## Follow-up - -And if an answer's already hot, it never even joins that 1,000 — it's a -Merkle-bound cache hit that skips the GPU entirely. Zero joules, zero -cents. You pay GPU only for *new* questions; the cached answers are free -forever, and provably the same answer. - ---- - -**Source numbers (claim_lattice, $0.33/kWh):** -- Hermes-3-8B + substrate: $0.085 / 1,000 grounded answers (~9¢) -- Qwen-27B + substrate: $0.158 / 1,000 (~16¢) -- quote mode is cheaper (Hermes $0.070, Qwen $0.121) — less context prefilled -- thinking/reasoning mode measured 4–6× the energy for the same answer -- cache hit (Merkle-bound providence record) returns the answer with no - LLM call → 0 GPU joules, does not increment the per-1k cost