Merges ~/ck3-knowledge and ~/kcd-knowledge, which were 90% duplicate code and
neither of which had ever been committed anywhere.
Works on any wiki, not just MediaWiki:
- sources.py splits the engine-specific parts (enumerate, fetch) behind a Source
protocol. MediaWikiSource is preferred whenever an api.php exists;
SitemapHtmlSource handles custom engines with no API (verified on Fextralife).
Everything downstream was already source-agnostic.
- scripts/detect_wiki.py probes /api.php, /w/api.php then /sitemap.xml and prints
the config to use. The API path and article URL vary independently — Fandom
serves /api.php but /wiki/{Title}.
- Profiles are plain env files, since config.py was already fully env-driven.
Per-wiki behaviour that must be measured rather than assumed:
- Subpage policy. On the CK3 wiki "A/B" is a mod; on the KCD wiki it is the
sequel, and rejecting subpages deletes 441 pages of wanted content.
- Noise sections. "Other languages" tables were 7% of KCD chunks and matched
English queries by repeating every title in eight languages.
- Variant tagging for wikis covering two editions, with scopes that exclude the
other edition but keep both/unknown pages.
Build performance: length bucketing in embed_efficiently is 2.7x (fastembed pads
each batch to its longest member; 184-char median vs 2000-char max). With noise
sections that is 61 min -> 20 min on a 6,140-page wiki at half the workers.
Fandom portable-infoboxes are transformed to tables before markdownify —
otherwise stat values survive but detach from their labels.
100% branch coverage, ruff clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
59 lines
1.8 KiB
Python
59 lines
1.8 KiB
Python
#!/usr/bin/env python
|
|
"""Verify search quality against concrete acceptance criteria.
|
|
|
|
DONE = every case's expected wiki page appears in the top-3 results, AND no
|
|
mod/subpage title appears in the top-5 of any probe query. Exits non-zero on
|
|
any failure so it can gate a build.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
from wikikb.scraper import is_core_title
|
|
from wikikb.search import search
|
|
|
|
# (query, one-of-these titles must be in the top 3)
|
|
CASES: list[tuple[str, set[str]]] = [
|
|
("domain limit rank", {"Titles", "Attributes", "Domain"}),
|
|
("genius intelligent trait skill bonus", {"Traits"}),
|
|
("primogeniture succession heir inheritance", {"Succession"}),
|
|
("congenital beauty attraction opinion", {"Traits"}),
|
|
("health aging death lifespan", {"Attributes", "Health"}),
|
|
]
|
|
|
|
# queries whose top-5 must contain NO mod/subpage results
|
|
NO_MOD_QUERIES = [
|
|
"domain limit",
|
|
"succession law",
|
|
"development growth",
|
|
"knight martial army",
|
|
"culture names",
|
|
]
|
|
|
|
|
|
def main() -> int:
|
|
ok = True
|
|
print("== relevance (expected page in top-3) ==")
|
|
for query, expected in CASES:
|
|
top3 = [r.title for r in search(query, k=5)[:3]]
|
|
hit = any(t in expected for t in top3)
|
|
ok &= hit
|
|
print(
|
|
f" [{'PASS' if hit else 'FAIL'}] {query!r} -> {top3} (want one of {sorted(expected)})"
|
|
)
|
|
|
|
print("== no mod/subpage pollution (top-5) ==")
|
|
for query in NO_MOD_QUERIES:
|
|
mods = [r.title for r in search(query, k=5) if not is_core_title(r.title)]
|
|
clean = not mods
|
|
ok &= clean
|
|
print(f" [{'PASS' if clean else 'FAIL'}] {query!r} -> {'clean' if clean else mods}")
|
|
|
|
print(f"\nSEARCH VERIFICATION: {'PASS' if ok else 'FAIL'}")
|
|
return 0 if ok else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|