Download t2s/report.py from SevdanurGenc/Text-to-SQL: direct link, hf CLI and curl.
- Browser
- Download file 16.8 kB
-
https://huggingface.co/SevdanurGenc/Text-to-SQL/resolve/main/t2s/report.py
- Command line
-
hf download hf://SevdanurGenc/Text-to-SQL/t2s/report.py
-
curl -L -o report.py https://huggingface.co/SevdanurGenc/Text-to-SQL/resolve/main/t2s/report.py
16.8 kB
| """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 = ['<div class="bars">'] | |
| if labels: | |
| out.append(f'<div class="legend"><span><i class="sw" style="background:var(--s1)"></i>{e(labels[0])}</span>' | |
| f'<span><i class="sw" style="background:var(--s2)"></i>{e(labels[1])}</span></div>') | |
| for i, (name, v) in enumerate(rows): | |
| w = 0 if v is None else max(0.0, min(100.0, v)) | |
| out.append(f'<div class="bar" title="{e(name)}: {pct(v)}"><span>{e(name)}</span>' | |
| f'<div class="track"><div class="fill" style="width:{w}%"></div></div><span class="val">{pct(v)}</span></div>') | |
| 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'<div class="bar" title="{e(name)} ({e(labels[1] if labels else "")}): {pct(a)}"><span></span>' | |
| f'<div class="track"><div class="fill alt" style="width:{wa}%"></div></div><span class="val">{pct(a)}</span></div>') | |
| out.append("</div>") | |
| 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"<h1>TR-TabloSQL Başarım Raporu</h1><p class='sub'>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</p>") | |
| # ---- ö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("<div class='tiles'>" + "".join( | |
| f"<div class='tile'><div class='k'>{e(k)}</div><div class='v'>{v}</div><div class='n'>{e(n)}</div></div>" | |
| for k, v, n in tiles) + "</div>") | |
| if llm_main is None: | |
| P.append("<p class='note'>Not: Bu çalıştırmada LLM arka ucu kullanılmadı (API anahtarı yoktu). " | |
| "<code>python scripts/run_all.py --claude</code>, <code>--openai</code> veya <code>--ollama</code> " | |
| "ile LLM sistemleri aynı tabloya eklenir.</p>") | |
| # ---- sistem karşılaştırması | |
| P.append("<h2>1. Sistem karşılaştırması (yürütme doğruluğu)</h2>") | |
| P.append("<div class='wrap'><table><tr><th>Sistem</th><th>Sayı kipi</th><th class='num'>EX</th>" | |
| "<th class='num'>Geliştirme</th><th class='num'>Ayrılmış test</th><th class='num'>İlk deneme EX</th>" | |
| "<th class='num'>Cevapsız → doğru red</th><th class='num'>Yanlış red</th><th class='num'>Ort. deneme</th>" | |
| "<th class='num'>Ort. süre</th></tr>") | |
| for s in S: | |
| P.append(f"<tr><td>{e(sys_label(s['sistem']))}</td><td>{e(s['sayi_kipi'])}</td><td class='num'><b>{pct(s['EX'])}</b></td>" | |
| f"<td class='num'>{pct(s['bolum'].get('gelistirme'))}</td><td class='num'>{pct(s['bolum'].get('test'))}</td>" | |
| f"<td class='num'>{pct(s['EX_ilk_deneme'])}</td><td class='num'>{pct(s['cevapsiz_dogru_red'])}</td>" | |
| f"<td class='num'>{pct(s['yanlis_red_orani'])}</td><td class='num'>{str(s['ort_deneme']).replace('.', ',')}</td>" | |
| f"<td class='num'>{str(s['ort_sure_sn']).replace('.', ',')} sn</td></tr>") | |
| P.append("</table></div>") | |
| P.append(bars([(sys_label(s["sistem"]), s["EX"]) for s in S])) | |
| # ---- sayı biçimi | |
| P.append("<h2>2. Türkçe sayı biçiminin etkisi ('1.234,56')</h2>") | |
| P.append("<p class='note'>Aynı belgeler üç farklı sayı ayrıştırma kipiyle veritabanına aktarıldı. " | |
| "<b>tr</b>: doğru Türkçe yorum. <b>en</b>: yaygın naif yaklaşım — nokta ondalık, virgül binlik sanılır " | |
| "(<code>'1.346'</code> → 1,346; <code>'4,5'</code> → 45; <code>'258.107.640,00'</code> ayrıştırılamaz → sütun metne düşer). " | |
| "<b>text</b>: hiç ayrıştırma yok; SQLite <code>SUM('1.234,56')</code> 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>") | |
| P.append("<div class='wrap'><table><tr><th>Kip</th><th class='num'>Altın SQL EX</th><th class='num'>Bozulan cevap</th>" | |
| "<th class='num'>Sayısal sütun</th><th class='num'>Metin sütun</th><th class='num'>Ayrıştırılamayan hücre</th></tr>") | |
| st = {r["kip"]: r for r in ozet.get("sayi_istatistik", [])} | |
| for k, r in naive.items(): | |
| s2 = st.get(k, {}) | |
| P.append(f"<tr><td>{e(k)}</td><td class='num'>{pct(r['EX'])}</td><td class='num'>{len(r['bozulan'])} / {r['n']}</td>" | |
| f"<td class='num'>{s2.get('sayisal_sutun', '—')}</td><td class='num'>{s2.get('metin_sutun', '—')}</td>" | |
| f"<td class='num'>{s2.get('ayristirilamayan_hucre', '—')}</td></tr>") | |
| P.append("</table></div>") | |
| kip_sys = [(f"{sys_label(s['sistem'])}", s["EX"]) for s in S if s["sistem"].startswith("kural") and s["kendini_duzeltme"]] | |
| P.append("<h3>Uçtan uca sistem, kip başına</h3>" + bars(kip_sys)) | |
| tr_cat = main_s["kategori"].get("tr_sayi") | |
| P.append(f"<p class='note'>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.</p>") | |
| # ---- kategori | |
| P.append("<h2>3. Soru kategorisine göre EX</h2>") | |
| 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("<h3>Zorluk düzeyi</h3><div class='wrap'><table><tr><th>Sistem</th>" + | |
| "".join(f"<th class='num'>{z}</th>" for z in ("kolay", "orta", "zor")) + "</tr>" + | |
| "".join(f"<tr><td>{e(sys_label(s['sistem']))}</td>" + "".join( | |
| f"<td class='num'>{pct(s['zorluk'].get(z))}</td>" for z in ("kolay", "orta", "zor")) + "</tr>" for s in S) | |
| + "</table></div>") | |
| # ---- hata kataloğu | |
| P.append("<h2>4. Hata kataloğu</h2>") | |
| P.append("<p class='note'>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.</p>") | |
| types = list(CATALOG.keys()) | |
| P.append("<div class='wrap'><table><tr><th>Hata tipi</th><th>Tanım</th>" + | |
| "".join(f"<th class='num'>{e(sys_label(s['sistem']).split(' · ')[-1])}</th>" for s in S) + "</tr>") | |
| 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"<tr><td><b>{e(CATALOG[t]['ad'])}</b><br><code>{t}</code></td><td>{e(CATALOG[t]['tanim'])}" | |
| f"<br><span class='note'>Örnek: {e(CATALOG[t]['ornek'])} · Önlem: {e(CATALOG[t]['cozum'])}</span></td>" + | |
| "".join(f"<td class='num'>{c}</td>" for c in counts) + "</tr>") | |
| P.append("</table></div>") | |
| # ---- kendini düzeltme | |
| P.append("<h2>5. Kendini düzeltme döngüsü</h2>") | |
| 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("<p class='note'>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.</p>") | |
| # ---- çıkarım | |
| P.append("<h2>6. Tablo çıkarma doğruluğu</h2>") | |
| ex = ozet["cikarim"] | |
| tabs = [r["tablo"] for r in next(iter(ex.values()))] | |
| P.append("<div class='wrap'><table><tr><th>Tablo</th>" + "".join(f"<th class='num'>{e(m)}</th>" for m in ex) + "</tr>") | |
| for i, t in enumerate(tabs): | |
| cells = [] | |
| for m in ex: | |
| r = ex[m][i] | |
| mark = "" if r["baslik_dogru"] else " <span class='no' title='başlık yapısı hatalı'>•</span>" | |
| cells.append(f"<td class='num'>{pct(100 * r['hucre_dogrulugu']) if r['bulundu'] else '<span class=no>bulunamadı</span>'}{mark}</td>") | |
| P.append(f"<tr><td>{e(t)}</td>{''.join(cells)}</tr>") | |
| P.append("<tr><td><b>Ortalama hücre doğruluğu</b></td>" + "".join( | |
| f"<td class='num'><b>{pct(100 * sum(r['hucre_dogrulugu'] for r in ex[m]) / len(ex[m]))}</b></td>" for m in ex) + "</tr>") | |
| P.append("</table></div><p class='note'>• = çok satırlı/birleşik başlık yapısı doğru çözülemedi. " | |
| "<b>auto</b>: pdfplumber (çizgili tablolar, hücre kutularından birleşim çözümleme) → bulamazsa Camelot-stream " | |
| "(çizgisiz tablolar). HTML tablolarında rowspan/colspan doğrudan okunur.</p>") | |
| # ---- hatalı örnekler | |
| recs = _load_records(main_s["sistem"]) | |
| bad = [r for r in recs if not r["dogru"]] | |
| P.append(f"<h2>7. {e(sys_label(main_s['sistem']))}: başarısız örnekler ({len(bad)})</h2>") | |
| if bad: | |
| P.append("<div class='wrap'><table><tr><th>ID</th><th>Soru</th><th>Hata tipi</th><th>Tahmin</th><th>Altın SQL</th></tr>") | |
| for r in bad: | |
| pred = r["tahmin_sql"] or r["cevap"] | |
| P.append(f"<tr><td>{e(r['id'])}</td><td>{e(r['soru'])}</td><td>{e(r['hata_tipi'])}</td>" | |
| f"<td><code>{e(pred)}</code></td><td><code>{e(r['altin_sql'] or 'CEVAPSIZ')}</code></td></tr>") | |
| P.append("</table></div>") | |
| # ---- yöntem | |
| P.append("<h2>8. Yöntem ve sınırlılıklar</h2><ul class='note'>" | |
| "<li><b>Veri:</b> Belgeler sentetiktir (gerçek kurum verisi değildir) ama Türk raporlarının yapısal zorluklarını " | |
| "taşır: birleşik başlıklar, rowspan'li gruplar, '1.234,56', parantezli negatifler, '%4,5', 'Ocak 2024', " | |
| "'Toplam' satırları, çizgisiz tablo.</li>" | |
| "<li><b>Test kümesi:</b> Spider (seçim, filtre, toplama, gruplama, sıralama, iç içe, çoklu tablo) ve BIRD " | |
| "(kanıt/formül, değer eşleme, birim) soru tiplerinden uyarlanmıştır; cevapsız sorular dahildir.</li>" | |
| "<li><b>EX:</b> Sistem SQL'i kendi çıkardığı veritabanında, altın SQL ise elle doğrulanmış referans veritabanında " | |
| "çalışır; böylece çıkarım hataları da ölçüme yansır. Sayısal tolerans ±(0,01 + %0,1).</li>" | |
| "<li><b>Önemli:</b> Kural tabanlı sistemin kuralları geliştirme bölümündeki sorular üzerinde ayarlandı; bu nedenle " | |
| "geliştirme skoru iyimserdir. Genellemeyi gösteren sayı <b>ayrılmış test</b> skorudur.</li>" | |
| "<li>Kural tabanlı sistem çoklu tablo ve alan bilgisi gerektiren formül sorularında (ör. brüt kâr marjı) zayıftır; " | |
| "bu sorular LLM arka uçlarının katkısını ölçmek için kümede tutulmuştur.</li></ul>") | |
| page = (f"<!doctype html><html lang='tr'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>" | |
| f"<title>TR-TabloSQL Raporu</title><style>{CSS}</style></head><body><main>{''.join(P)}</main></body></html>") | |
| path = Path(path) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| path.write_text(page, encoding="utf-8") | |
| return path | |