spb/trouve-ka Public
Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com
Python 76.8%
TypeScript 15.7%
SQL 3.9%
Shell 1.4%
CSS 1.3%
Dockerfile 0.7%
1#!/usr/bin/env python32# Trouve-KA — évaluation du ranking contre le dataset (§16)3# Author: Simon-Pierre Boucher4# Contact: contact@spboucher.ai56"""Exécute le dataset d'évaluation contre l'API de recherche.78Usage : python3 scripts/eval/run-eval.py [--api http://localhost:8080]9Un cas passe si au moins un domaine attendu apparaît dans le top-k.10L'index grandit en continu : les scores s'améliorent avec la couverture.11"""1213import argparse14import json15import pathlib16import re17import urllib.parse18import urllib.request1920HERE = pathlib.Path(__file__).parent212223def load_cases() -> list[dict]:24 """Parse minimaliste du YAML du dataset (pas de dépendance PyYAML)."""25 cases, current = [], None26 for line in (HERE / "ranking-eval.yaml").read_text(encoding="utf-8").splitlines():27 if m := re.match(r'\s*- query: "(.*)"', line):28 current = {"query": m.group(1)}29 cases.append(current)30 elif m := re.match(r"\s*expect_any_of: \[(.*)\]", line):31 current["expect_any_of"] = [d.strip() for d in m.group(1).split(",")]32 elif m := re.match(r"\s*top_k: (\d+)", line):33 current["top_k"] = int(m.group(1))34 return cases353637def main() -> int:38 ap = argparse.ArgumentParser()39 ap.add_argument("--api", default="http://localhost:8080")40 args = ap.parse_args()4142 passed = 043 cases = load_cases()44 for case in cases:45 q = urllib.parse.quote(case["query"])46 url = f"{args.api}/api/search?q={q}&limit={case['top_k']}"47 try:48 with urllib.request.urlopen(url, timeout=10) as resp:49 data = json.load(resp)50 except Exception as exc:51 print(f"✗ {case['query']!r} — API injoignable : {exc}")52 continue53 domains = [r["domain"] for r in data["results"]]54 hit = next((d for d in domains if any(d == e or d.endswith("." + e) for e in case["expect_any_of"])), None)55 if hit:56 passed += 157 print(f"✓ {case['query']!r} → {hit} (rang {domains.index(hit) + 1}, {data['took_ms']} ms)")58 else:59 print(f"✗ {case['query']!r} → top-{case['top_k']}: {domains[:5]}")60 print(f"\n{passed}/{len(cases)} cas réussis")61 return 0 if passed == len(cases) else 1626364if __name__ == "__main__":65 raise SystemExit(main())66