"""Karşılaştırmalı başarım raporu (tek dosya HTML, dış bağımlılık yok).""" from __future__ import annotations import html import json from datetime import date from pathlib import Path from .errors import CATALOG ROOT = Path(__file__).resolve().parent.parent CSS = """ :root{--bg:#f7f7f5;--surface:#fcfcfb;--ink:#0b0b0b;--ink2:#52514e;--muted:#8a8984;--line:#e4e3de; --s1:#2a78d6;--s2:#eb6834;--track:#ecebe6;--good:#1c7c4a;--bad:#b42318} @media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#121211;--surface:#1a1a19;--ink:#fff; --ink2:#c3c2b7;--muted:#8f8e86;--line:#2e2e2b;--s1:#3987e5;--s2:#d95926;--track:#2a2a27;--good:#4cc38a;--bad:#f97066}} :root[data-theme="dark"]{--bg:#121211;--surface:#1a1a19;--ink:#fff;--ink2:#c3c2b7;--muted:#8f8e86;--line:#2e2e2b; --s1:#3987e5;--s2:#d95926;--track:#2a2a27;--good:#4cc38a;--bad:#f97066} *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif} main{max-width:1060px;margin:0 auto;padding:32px 16px 64px}h1{font-size:28px;margin:0 0 4px;letter-spacing:-.01em} h2{font-size:19px;margin:40px 0 10px;padding-top:8px;border-top:1px solid var(--line)}h3{font-size:15px;margin:20px 0 8px} .sub{color:var(--ink2);margin:0 0 24px}.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px} .tile{background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:14px 16px} .tile .k{font-size:12px;color:var(--ink2);text-transform:uppercase;letter-spacing:.04em}.tile .v{font-size:30px;font-weight:650;margin-top:2px} .tile .n{font-size:13px;color:var(--muted)} table{border-collapse:collapse;width:100%;background:var(--surface);border:1px solid var(--line);border-radius:10px;overflow:hidden;font-size:13.5px} th,td{padding:7px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}th{color:var(--ink2);font-weight:600;background:var(--bg)} td.num{text-align:right;font-variant-numeric:tabular-nums}.wrap{overflow-x:auto} .bars{background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:12px 14px} .bar{display:grid;grid-template-columns:minmax(120px,240px) 1fr 56px;gap:10px;align-items:center;margin:5px 0;font-size:13.5px} .track{background:var(--track);height:14px;border-radius:4px;position:relative} .fill{height:14px;border-radius:0 4px 4px 0;background:var(--s1)}.fill.alt{background:var(--s2)} .bar .val{text-align:right;font-variant-numeric:tabular-nums;color:var(--ink)} .note{color:var(--ink2);font-size:13.5px}.ok{color:var(--good);font-weight:600}.no{color:var(--bad);font-weight:600} code{font:12.5px ui-monospace,Menlo,monospace;background:var(--track);padding:1px 4px;border-radius:4px;word-break:break-word} .legend{display:flex;gap:16px;font-size:13px;color:var(--ink2);margin:4px 0 8px}.sw{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:6px;vertical-align:middle} @media (max-width:600px){.bar{grid-template-columns:110px 1fr 48px}} """ def e(x) -> str: return html.escape(str(x)) def pct(x) -> str: return "—" if x is None else f"%{str(round(x, 1)).replace('.', ',')}" def bars(rows: list[tuple[str, float | None]], alt_rows: list[float | None] | None = None, labels: tuple[str, str] | None = None) -> str: out = ['
'] if labels: out.append(f'
{e(labels[0])}' f'{e(labels[1])}
') for i, (name, v) in enumerate(rows): w = 0 if v is None else max(0.0, min(100.0, v)) out.append(f'
{e(name)}' f'
{pct(v)}
') if alt_rows is not None: a = alt_rows[i] wa = 0 if a is None else max(0.0, min(100.0, a)) out.append(f'
' f'
{pct(a)}
') out.append("
") return "\n".join(out) SYS_LABEL = { "kural_tr": "Kural tabanlı · TR normalizasyon · kendini düzeltme", "kural_tr_duzeltmesiz": "Kural tabanlı · TR normalizasyon · tek deneme", "kural_en_naif": "Kural tabanlı · naif (İngilizce yerel ayar)", "kural_metin_naif": "Kural tabanlı · naif (sayılar metin)", } def sys_label(name: str) -> str: if name in SYS_LABEL: return SYS_LABEL[name] for k, v in (("_tr_duzeltmesiz", " · TR · tek deneme"), ("_tr", " · TR · kendini düzeltme"), ("_en_naif", " · naif (İngilizce yerel ayar)"), ("_metin_naif", " · naif (sayılar metin)")): if name.endswith(k): return name[: -len(k)].capitalize() + v return name def _load_records(name: str) -> list[dict]: p = ROOT / "outputs" / "sonuclar" / f"sonuclar_{name}.jsonl" if not p.exists(): return [] return [json.loads(l) for l in p.read_text(encoding="utf-8").splitlines() if l.strip()] def write_report(ozet: dict, path: Path) -> Path: S = ozet["sistemler"] by = {s["sistem"]: s for s in S} main_s = by.get("kural_tr") or S[0] llm_main = next((s for s in S if s["sistem"].endswith("_tr") and not s["sistem"].startswith("kural")), None) naive = {r["kip"]: r for r in ozet["sayi_ablasyonu"]} P = [] P.append(f"

TR-TabloSQL Başarım Raporu

Dokümanlardaki tabloların yapılandırılmış bilgi tabanına " f"dönüştürülmesi ve Türkçe doğal dille sorgulanması · {date.today().strftime('%d.%m.%Y')} · " f"{ozet['soru_sayisi']} soru

") # ---- özet kutuları tiles = [("En iyi EX (TR kipi)", pct(max(s["EX"] for s in S if s["sayi_kipi"] == "tr")), "tüm sorular, kendini düzeltme açık"), ("Ayrılmış test EX", pct(main_s["bolum"].get("test")), f"{sys_label(main_s['sistem']).split(' ·')[0]}, " "geliştirmede kullanılmayan sorular")] if "en" in naive: tiles.append(("Sayı biçimi kaybı", pct(100 - naive["en"]["EX"]), "doğru SQL bile naif ayrıştırmada yanlış cevap veriyor")) tiles.append(("Cevapsız sorularda doğru red", pct(main_s["cevapsiz_dogru_red"]), "uydurma yerine açık 'cevap yok'")) P.append("
" + "".join( f"
{e(k)}
{v}
{e(n)}
" for k, v, n in tiles) + "
") if llm_main is None: P.append("

Not: Bu çalıştırmada LLM arka ucu kullanılmadı (API anahtarı yoktu). " "python scripts/run_all.py --claude, --openai veya --ollama " "ile LLM sistemleri aynı tabloya eklenir.

") # ---- sistem karşılaştırması P.append("

1. Sistem karşılaştırması (yürütme doğruluğu)

") P.append("
" "" "" "") for s in S: P.append(f"" f"" f"" f"" f"") P.append("
SistemSayı kipiEXGeliştirmeAyrılmış testİlk deneme EXCevapsız → doğru redYanlış redOrt. denemeOrt. süre
{e(sys_label(s['sistem']))}{e(s['sayi_kipi'])}{pct(s['EX'])}{pct(s['bolum'].get('gelistirme'))}{pct(s['bolum'].get('test'))}{pct(s['EX_ilk_deneme'])}{pct(s['cevapsiz_dogru_red'])}{pct(s['yanlis_red_orani'])}{str(s['ort_deneme']).replace('.', ',')}{str(s['ort_sure_sn']).replace('.', ',')} sn
") P.append(bars([(sys_label(s["sistem"]), s["EX"]) for s in S])) # ---- sayı biçimi P.append("

2. Türkçe sayı biçiminin etkisi ('1.234,56')

") P.append("

Aynı belgeler üç farklı sayı ayrıştırma kipiyle veritabanına aktarıldı. " "tr: doğru Türkçe yorum. en: yaygın naif yaklaşım — nokta ondalık, virgül binlik sanılır " "('1.346' → 1,346; '4,5' → 45; '258.107.640,00' ayrıştırılamaz → sütun metne düşer). " "text: hiç ayrıştırma yok; SQLite SUM('1.234,56') değerini 1,234 olarak toplar. " "Altın SQL sorguları bu veritabanlarında çalıştırılınca, sorgu doğru olsa bile kaç cevabın bozulduğu ölçüldü.

") P.append("
" "") st = {r["kip"]: r for r in ozet.get("sayi_istatistik", [])} for k, r in naive.items(): s2 = st.get(k, {}) P.append(f"" f"" f"") P.append("
KipAltın SQL EXBozulan cevapSayısal sütunMetin sütunAyrıştırılamayan hücre
{e(k)}{pct(r['EX'])}{len(r['bozulan'])} / {r['n']}{s2.get('sayisal_sutun', '—')}{s2.get('metin_sutun', '—')}{s2.get('ayristirilamayan_hucre', '—')}
") kip_sys = [(f"{sys_label(s['sistem'])}", s["EX"]) for s in S if s["sistem"].startswith("kural") and s["kendini_duzeltme"]] P.append("

Uçtan uca sistem, kip başına

" + bars(kip_sys)) tr_cat = main_s["kategori"].get("tr_sayi") P.append(f"

TR kipinde, Türkçe biçimli sayı içeren sorularda (tr_sayi, n={main_s['kategori_n'].get('tr_sayi')}) " f"EX {pct(tr_cat)}; naif kiplerdeki aynı alt kümedeki düşüş tabloda kategori satırlarında görülür.

") # ---- kategori P.append("

3. Soru kategorisine göre EX

") cats = sorted(main_s["kategori"].items(), key=lambda kv: (kv[1] is None, -(kv[1] or 0))) comp = by.get("kural_en_naif") P.append(bars([(f"{k} (n={main_s['kategori_n'][k]})", v) for k, v in cats], [comp["kategori"].get(k) for k, _ in cats] if comp else None, (sys_label(main_s["sistem"]), sys_label(comp["sistem"])) if comp else None)) P.append("

Zorluk düzeyi

" + "".join(f"" for z in ("kolay", "orta", "zor")) + "" + "".join(f"" + "".join( f"" for z in ("kolay", "orta", "zor")) + "" for s in S) + "
Sistem{z}
{e(sys_label(s['sistem']))}{pct(s['zorluk'].get(z))}
") # ---- hata kataloğu P.append("

4. Hata kataloğu

") P.append("

Başarısız örnekler aşağıdaki tiplere otomatik atandı (değerlendirme anında, altın sonuçla " "karşılaştırılarak). Çalışma zamanında ajan aynı tipleri statik denetim, yürütme ve sonuç denetimi " "adımlarında yakalayıp geri besleme olarak kullanır.

") types = list(CATALOG.keys()) P.append("
" + "".join(f"" for s in S) + "") for t in types: counts = [s["hata_tipleri"].get(t, 0) for s in S] if not any(counts) and t in ("YANLIS_SONUC", "YURUTME"): continue P.append(f"" + "".join(f"" for c in counts) + "") P.append("
Hata tipiTanım{e(sys_label(s['sistem']).split(' · ')[-1])}
{e(CATALOG[t]['ad'])}
{t}
{e(CATALOG[t]['tanim'])}" f"
Örnek: {e(CATALOG[t]['ornek'])} · Önlem: {e(CATALOG[t]['cozum'])}
{c}
") # ---- kendini düzeltme P.append("

5. Kendini düzeltme döngüsü

") sc = [s for s in S if s["kendini_duzeltme"] and s["sayi_kipi"] == "tr"] P.append(bars([(sys_label(s["sistem"]), s["EX"]) for s in sc], [s["EX_ilk_deneme"] for s in sc], ("Son deneme", "İlk deneme"))) P.append("

Kural tabanlı üreticide 'düzeltme', hata/boş sonuç sonrası sıradaki aday sorguya geçmektir; " "LLM arka uçlarında hata tipi, mesaj ve öneriler (ör. en yakın sütun adları, tablodaki gerçek değerler, ISO tarih " "dönüşümü) bir sonraki isteme eklenir.

") # ---- çıkarım P.append("

6. Tablo çıkarma doğruluğu

") ex = ozet["cikarim"] tabs = [r["tablo"] for r in next(iter(ex.values()))] P.append("
" + "".join(f"" for m in ex) + "") for i, t in enumerate(tabs): cells = [] for m in ex: r = ex[m][i] mark = "" if r["baslik_dogru"] else " •" cells.append(f"") P.append(f"{''.join(cells)}") P.append("" + "".join( f"" for m in ex) + "") P.append("
Tablo{e(m)}
{pct(100 * r['hucre_dogrulugu']) if r['bulundu'] else 'bulunamadı'}{mark}
{e(t)}
Ortalama hücre doğruluğu{pct(100 * sum(r['hucre_dogrulugu'] for r in ex[m]) / len(ex[m]))}

• = çok satırlı/birleşik başlık yapısı doğru çözülemedi. " "auto: pdfplumber (çizgili tablolar, hücre kutularından birleşim çözümleme) → bulamazsa Camelot-stream " "(çizgisiz tablolar). HTML tablolarında rowspan/colspan doğrudan okunur.

") # ---- hatalı örnekler recs = _load_records(main_s["sistem"]) bad = [r for r in recs if not r["dogru"]] P.append(f"

7. {e(sys_label(main_s['sistem']))}: başarısız örnekler ({len(bad)})

") if bad: P.append("
") for r in bad: pred = r["tahmin_sql"] or r["cevap"] P.append(f"" f"") P.append("
IDSoruHata tipiTahminAltın SQL
{e(r['id'])}{e(r['soru'])}{e(r['hata_tipi'])}{e(pred)}{e(r['altin_sql'] or 'CEVAPSIZ')}
") # ---- yöntem P.append("

8. Yöntem ve sınırlılıklar

") page = (f"" f"TR-TabloSQL Raporu
{''.join(P)}
") path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(page, encoding="utf-8") return path