scripts/xkb_evict_report.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""Report current demotion from namespace-scoped relevance verdicts. Legacy cosine statistics remain in knowledge_usage and do not drive this report."""
from __future__ import annotations
import argparse
import json
import sqlite3
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import xkb_console
import xkb_eviction as ev
import xkb_frontmatter
import xkb_paths
from xkb_evidence import IDENTITY_PREFIX, identity_source
xkb_console.use_utf8()
def describe(record_id: str) -> tuple[str, str]:
"""record_id 的標題與**真正的**分類。
record_id 的前綴是檔案放在哪個 bookmarks 資料夾(`02-seo-geo/2032...`),而那
不是這張卡的分類——1,754 筆書籤裡有 650 筆的 frontmatter `category` 跟所在目錄
不一致,而召回、wiki 路由、健檢讀的全都是 frontmatter。目錄只是位址。
2026-09-12 我自己就被這個前綴騙過:看到 `02-seo-geo/` 就推論那張卡「被歸錯類
所以被 SEO 問題撈出來」。它其實是日文的 AI 生圖 prompt 合集,frontmatter 寫
04-ai-tools-agents,而它被撈出來是內容嵌入弱相關,跟資料夾毫無關係。報告印出
前綴卻不印真正的分類,就是在邀請這個推論。
"""
record_id = identity_source(record_id)
stem = record_id.rsplit("/", 1)[-1]
card = xkb_paths.CARDS_DIR / f"{stem}.md"
if not card.is_file():
return "", ""
try:
text = card.read_text(encoding="utf-8", errors="replace")
except OSError:
return "", ""
title = ""
for line in text.splitlines():
if line.startswith("# "):
title = line[2:].strip()
break
return title, (xkb_frontmatter.get(text, "category") or "")
def load_rows(namespace: str = "private") -> list[dict]:
"""Read the versioned merged-recall counters, never infer from cosine."""
if not xkb_paths.SERVICE_DB.exists():
return []
from contextlib import closing
with closing(sqlite3.connect(f"file:{xkb_paths.SERVICE_DB}?mode=ro", uri=True)) as db:
db.row_factory = sqlite3.Row
if not db.execute("SELECT 1 FROM sqlite_master WHERE name='recall_usage'").fetchone():
return []
return [dict(r) for r in db.execute(
"SELECT * FROM recall_usage WHERE namespace=? AND record_id LIKE ? ORDER BY judged_count DESC", (namespace, IDENTITY_PREFIX + "%"))]
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--json", action="store_true")
parser.add_argument("--namespace", default="private")
parser.add_argument("--after", type=int, default=ev.DEMOTE_AFTER_CONSIDERED)
parser.add_argument("--limit", type=int, default=20)
args = parser.parse_args()
rows = load_rows(args.namespace)
demoted = [r for r in rows if ev.is_demoted(r["judged_count"], r["relevant_count"], after=args.after)]
if args.json:
print(json.dumps({"measurement_basis": "explicit_relevance_v2", "namespace": args.namespace,
"after": args.after, "tracked": len(rows), "demoted": len(demoted),
"records": demoted}, ensure_ascii=False, indent=2))
return 0
print(f"Tracked {len(rows)} records; {len(demoted)} repeatedly judged irrelevant.")
for r in demoted[:args.limit]:
title, category = describe(r["record_id"])
print(f"{r['judged_count']} judgements / {r['returned_count']} returned: [{category}] {title or r['record_id']}")
print("A relevant verdict revives the record. Returned does not imply used in an answer.")
return 0
import xkb_usage # noqa: E402 — 量測誰在跑,見 scripts/xkb_usage.py
if __name__ == "__main__":
xkb_usage.record(__file__)
raise SystemExit(main())