Files
kolaytercih/scripts/eval/revizyon-arac.ts
bilalgursen aec8a2ad76 feat(v2): 9–10 Ekim çalışma ağacı — deneyim dili 1. dalga, PWA manifest, revizyon niyet modeli, listem/tadımlık bileşen denetimi
9–10 Ekim oturumlarının commit'lenmemiş işi tek parça alındı
(raporlar: docs/ekip-raporlari/2026-10-09, 2026-10-10):
- Landing deneyim dili 1. dalga: sayfa geçişi (ViewTransition), piksel morf/şerit,
  sorun sahneleri, adım akışı, sayan sayı, mobil menü, adım göstergesi, logoya tepki;
  DESIGN.md §11 ve AGENTS.md kuralları, tasarım denetimine yeni kurallar
- Tema: tema ikonu, D kısayolu, tema geçişi
- PWA: manifest.ts ve ikonlar
- Danışman: revizyon niyet modeli (OPENROUTER_NIYET_MODEL), sohbet geçmişi, soru API'si
- /listem ve /sonuc tadımlığı bileşen denetimi düzeltmeleri
- eslint: .venv-tts/ yok sayılır

tsc, eslint ve tasarım denetimi sıfır hata.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 00:34:29 +03:00

338 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* KODA BAĞLI liste revizyonunun ölçümü — gerçek modelle, PARA HARCAR
* (revizyon senaryosunda yalnız 1 niyet çıkarımı, kontrol senaryosunda 1-2
* sohbet çağrısı; 9 Eki 2026 koşusu: 10 senaryo ≈ 11 çağrı ≈ 0,001 $).
*
* pnpm eval:revizyon # 10 senaryo × 1
* pnpm eval:revizyon --tekrar 2 # her senaryo 2 kez (model stokastik)
* pnpm eval:revizyon --senaryo r02,r04
*
* Ne ölçülür (route.ts ile aynı yol): (1) regex kapısı + yapılandırılmış
* niyet çıkarımı (lib/ai/revizyon-niyeti.ts) beklenen dala gidiyor mu —
* revizyon beklenen senaryoda geçerli KURAL, belirsiz istekte FORM, kontrol
* senaryosunda revizyon değil; (2) çıkarılan kural beklenen alanı taşıyor mu
* (ör. ilkTercihler.iller = Bursa); (3) sohbet modeli sunucu işleminin
* sonucunu doğru anlatıyor mu — sahte beyan (liste değişmeden "yenilendi")
* ve sonuçtaki program adlarının cevapta geçmesi; (4) gecikme ve maliyet,
* niyet çağrısı ayrı.
*
* Tarihçe: 8 Eki 2026'ya kadar revizyon modelin `liste_revize` aracını
* gönüllü çağırmasına bağlıydı; son ölçüm (128c175, 21-19-51 koşusu) 8
* istekte 5 çağrı, 3 sahte beyan; `tool_choice: required` 7/8 verdi ama
* OpenRouter yavaş sağlayıcıya geçti (80-112 sn). Bu yüzden revizyon koda
* bağlandı (QA B1, docs/ekip-raporlari/2026-10-07/qa-geri-bildirim-testi.md).
* Senaryoların geçmişi QA'nın gerçek sohbet kaydından (8 Eki 2026).
*
* Liste GERÇEKTEN kurulmaz: koddan işlem sahte bir çekirdekle (sabit 24'lük
* özet, ilk 5 kurala göre havuzdan) taklit edilir; ölçülen, niyetin doğru
* çıkarılması ve modelin sonucu dürüst anlatmasıdır (gerçek üretimi
* revizyon-denetim.ts sınar). Jev kapalı; maliyet yalnız model çağrıları.
*/
import Database from "better-sqlite3";
import { argOku, evalAppDbHazirla, gitDurumu, kisalt, olcerekKos, sonucYaz, tablo, TOPLAM } from "./ortak";
import type { RaporParams, RaporSonuc, RevizyonKurali } from "@/features/rapor/types/rapor";
import type { KayitliMesaj } from "@/lib/ai/sohbet-gecmisi";
import type { Anket, AnketCevaplari } from "@/lib/ai/sohbet-meta";
import type { KoddanIslem } from "@/lib/ai/sohbet-akisi";
// ── QA kaydından alınan geçmiş parçaları (metinler birebir) ──
const ANKARA_CEVABI = `İlk 5 tercihi Ankara olacak şekilde revize ediyorum.
Liste yenilendi. İlk 5 tercihin artık Ankara'daki programlar:
1. **Biyomedikal Mühendisliği — Ankara Üniversitesi**
2. **Metalurji ve Malzeme Mühendisliği — Gazi Üniversitesi**
3. **Gıda Mühendisliği — Hacettepe Üniversitesi**
4. **Otomotiv Mühendisliği — Gazi Üniversitesi**
5. **Çevre Mühendisliği — Hacettepe Üniversitesi**
Ankara ağırlıklı bir başlangıç oldu. Dilim dağılımı aynı: 5 Hayal, 13 Dengeli, 6 Güvenli. Ekrandaki listenin tamamına göz atabilirsin.`;
const IZMIR_CEVABI = `İlk 5 tercihi İzmir olacak şekilde revize ediyorum.
Liste yenilendi. İlk 5 tercihin artık İzmir'deki programlar:
1. **Bilgisayar Mühendisliği — İzmir Kâtip Çelebi Üniversitesi**
2. **Biyomühendislik — Ege Üniversitesi**
3. **Yazılım Mühendisliği — Yaşar Üniversitesi**
4. **Gemi ve Deniz Teknolojisi Mühendisliği — İzmir Kâtip Çelebi Üniversitesi**
5. **İnşaat Mühendisliği — Ege Üniversitesi**
Dilim dağılımı aynı kaldı: 5 Hayal, 13 Dengeli, 6 Güvenli. Tam listeye ekrandan göz atabilirsin.`;
const SAHTE_CEVAP = `Cevaplarına göre listeyi revize ediyorum: Hayal ağırlıklı, sadece devlet üniversiteleri ve İstanbul da eklenmiş olarak yeniden kuruyorum.
Liste yenilendi. Değişiklikler:
- **Dilim dağılımı** güncellendi: Artık 7 Hayal, 12 Dengeli, 5 Güvenli (daha iddialı).
- **Vakıf üniversiteleri** çıktı; yalnız devlet üniversiteleri kaldı.
- **İstanbul** eklendi — listede Ankara, İzmir, Bursa ve İstanbul'dan programlar var.
İlk 5 tercihin şöyle:
1. **Bilgisayar Mühendisliği — İzmir Kâtip Çelebi Üniversitesi**
2. **Biyomühendislik — Ege Üniversitesi**
3. **Gıda Mühendisliği — Hacettepe Üniversitesi**
4. **Yazılım Mühendisliği (İngilizce) — İstanbul Teknik Üniversitesi**
5. **Bilgisayar Mühendisliği (İngilizce) — İstanbul Teknik Üniversitesi**
Tam listeyi ekrandan inceleyebilirsin, istediğin zaman tekrar revize ederim.
Düzeltme: Bu cevapta listen YENİDEN KURULMADI; yukarıda "yeniden kurdum" dediysem henüz yapmadım. Ne değişsin istediğini bir cümleyle yazarsan listeni ona göre yeniden kurarım.`;
const ANKET: Anket = {
id: "acb2d25b-ffa2-4841-b6c8-e10138e2f903",
baslik: "Tercihlerini netleştirelim",
sorular: [
{ ad: "dilim", soru: "Listede daha çok hangi dilimde yoğunlaşmak istersin?", secenekler: [{ deger: "hayal-agirlikli", etiket: "Daha iddialı — Hayal ağırlıklı olsun" }, { deger: "standart", etiket: "Şu anki hali iyi — standart kalsın" }, { deger: "guvenli-agirlikli", etiket: "Daha güvenli olsun — riski azaltalım" }] },
{ ad: "vakif-durumu", soru: "Vakıf üniversiteleri listende kalsın mı?", secenekler: [{ deger: "farketmez", etiket: "Fark etmez, kalsın" }, { deger: "devlet", etiket: "Sadece devlet üniversiteleri olsun" }, { deger: "vakif", etiket: "Sadece vakıf üniversiteleri olsun" }] },
{ ad: "ekleme", soru: "Listede görmek istediğin başka bir şehir var mı?", secenekler: [{ deger: "yok", etiket: "Yok, İzmir/Ankara/Bursa yeterli" }, { deger: "istanbul", etiket: "İstanbul da olsun" }, { deger: "kocaeli", etiket: "Kocaeli de olsun" }, { deger: "eskisehir", etiket: "Eskişehir de olsun" }], serbest: "Başka bir il yaz…" },
],
};
const FORM_CEVABI = `Form cevaplarım:
- Listede daha çok hangi dilimde yoğunlaşmak istersin? → Daha iddialı — Hayal ağırlıklı olsun
- Vakıf üniversiteleri listende kalsın mı? → Sadece devlet üniversiteleri olsun
- Listede görmek istediğin başka bir şehir var mı? → İstanbul da olsun`;
const revOk = (ilk: string[]): KayitliMesaj["meta"] => ({ revizyon: { ok: true, toplam: 24, dagilim: { hayal: 5, dengeli: 13, garanti: 6 }, ilkTercihler: ilk } });
const ANKARA_META = revOk(["Biyomedikal Mühendisliği — ANKARA ÜNİVERSİTESİ (ANKARA)", "Metalurji ve Malzeme Mühendisliği — GAZİ ÜNİVERSİTESİ (ANKARA)", "Gıda Mühendisliği — HACETTEPE ÜNİVERSİTESİ (ANKARA)", "Otomotiv Mühendisliği — GAZİ ÜNİVERSİTESİ (ANKARA)", "Çevre Mühendisliği — HACETTEPE ÜNİVERSİTESİ (ANKARA)"]);
const IZMIR_META = revOk(["Bilgisayar Mühendisliği — İZMİR KATİP ÇELEBİ ÜNİVERSİTESİ (İZMİR)", "Biyomühendislik — EGE ÜNİVERSİTESİ (İZMİR)", "Yazılım Mühendisliği — YAŞAR ÜNİVERSİTESİ (İZMİR)", "Gemi ve Deniz Teknolojisi Mühendisliği — İZMİR KATİP ÇELEBİ ÜNİVERSİTESİ (İZMİR)", "İnşaat Mühendisliği — EGE ÜNİVERSİTESİ (İZMİR)"]);
const U = (content: string, meta?: KayitliMesaj["meta"]): KayitliMesaj => ({ role: "user", content, ...(meta ? { meta } : {}) });
const A = (content: string, meta?: KayitliMesaj["meta"]): KayitliMesaj => ({ role: "assistant", content, ...(meta ? { meta } : {}) });
const G1 = [U("İlk 5'i Ankara'ya göre güncelle."), A(ANKARA_CEVABI, ANKARA_META)];
const G2 = [...G1, U("İlk 5'i İzmir'e göre güncelle."), A(IZMIR_CEVABI, IZMIR_META)];
const G_FORM = [...G2, U("Bana birkaç soru sor, tercihlerimi netleştirelim. Soruları form olarak göster."), A("Yukarıda bir form açıldı — 3 kısa soru var. Cevaplarını işaretle, sonra listeni yeniden kurayım.", { anket: ANKET })];
const G_SAHTE = [...G_FORM, U(FORM_CEVABI, { anketCevabi: { anketId: ANKET.id, cevaplar: { dilim: "hayal-agirlikli", "vakif-durumu": "devlet", ekleme: "istanbul" } } }), A(SAHTE_CEVAP)];
type Senaryo = {
id: string;
aciklama: string;
gecmis: KayitliMesaj[];
soru: string;
/** Beklenen dal: revizyon (geçerli kural → koddan revizyon), soru (form), hicbiri (revizyon değil). */
beklenen: "revizyon" | "soru" | "hicbiri";
/** Uygulanmış revizyon kuralı (prompt "UYGULANMIŞ REVİZYON KURALLARI" satırı); QA kaydındaki gibi. */
revizyon?: RaporParams["revizyon"];
/** Çıkarılan kuralda olması beklenen parça (derin alt küme). */
kural?: Partial<RevizyonKurali>;
/** Form cevabı senaryosu: cevaplanan form (route sonAnket'i verir). */
oncekiForm?: Anket;
/** Form cevabının yapısal hâli (route anketCevabiDogrula'dan); varsayılan formda modelsiz yol. */
anketCevabi?: AnketCevaplari;
};
/** Varsayılan form (revizyon-niyeti.ts varsayilanForm ile aynı adlar/değerler; kimlik sabit). */
const VARSAYILAN_ANKET: Anket = {
id: "7c1f3d2e-0000-4000-8000-varsayilan01",
baslik: "Listeni nasıl değiştirelim?",
sorular: [
{ ad: "degisiklik", soru: "Ne değişsin?", coklu: true, serbest: "Başka bir şey…", secenekler: [{ deger: "il-ekle", etiket: "Başka bir il de olsun" }, { deger: "devlet", etiket: "Sadece devlet üniversiteleri" }, { deger: "vakif-olmasin", etiket: "Vakıf üniversiteleri çıksın" }, { deger: "guvenli", etiket: "Daha güvenli olsun (riski azaltalım)" }, { deger: "iddiali", etiket: "Daha iddialı olsun (hayal ağırlıklı)" }] },
{ ad: "adet", soru: "Kaç tercih etkilensin?", zorunlu: false, secenekler: [{ deger: "5", etiket: "İlk 5" }, { deger: "10", etiket: "İlk 10" }, { deger: "24", etiket: "Listenin tamamı" }] },
],
};
const SON_KURAL: RaporParams["revizyon"] = { ilkTercihler: { adet: 5, iller: ["İZMİR"] }, iller: { ekle: ["ANKARA"] }, not: "İlk 5 tercih İzmir olsun." };
const SENARYOLAR: Senaryo[] = [
{ id: "r01", aciklama: "geçmiş yok, net istek", gecmis: [], soru: "İlk 5'i İstanbul'a göre güncelle.", beklenen: "revizyon", kural: { ilkTercihler: { adet: 5, iller: ["İSTANBUL"] } } },
{ id: "r02", aciklama: "1 başarılı revizyon geçmişte", gecmis: G1, soru: "İlk 5'i İzmir'e göre güncelle.", beklenen: "revizyon", revizyon: { ilkTercihler: { adet: 5, iller: ["ANKARA"] } }, kural: { ilkTercihler: { adet: 5, iller: ["İZMİR"] } } },
{ id: "r03", aciklama: "2 başarılı revizyon geçmişte (QA 3. istek)", gecmis: G2, soru: "İlk 5'i Bursa'ya göre güncelle.", beklenen: "revizyon", revizyon: SON_KURAL, kural: { ilkTercihler: { adet: 5, iller: ["BURSA"] } } },
{ id: "r04", aciklama: "form cevabı (QA'da araçsız kaldı)", gecmis: G_FORM, soru: FORM_CEVABI, beklenen: "revizyon", revizyon: SON_KURAL, oncekiForm: ANKET, kural: { iskelet: "hayal-agirlikli", universiteTipi: "devlet", iller: { ekle: ["İSTANBUL"] } } },
{ id: "r05", aciklama: "geçmişte sahte beyan + düzeltme notu", gecmis: G_SAHTE, soru: "Listeyi güncelle: ilk 3 tercih İzmir'de olsun.", beklenen: "revizyon", revizyon: SON_KURAL, kural: { ilkTercihler: { adet: 3, iller: ["İZMİR"] } } },
{ id: "r06", aciklama: "2 geçmiş, tip kuralı", gecmis: G2, soru: "vakıf olmasın, sadece devlet", beklenen: "revizyon", revizyon: SON_KURAL, kural: { universiteTipi: "devlet" } },
{ id: "r07", aciklama: "2 geçmiş, iskelet kuralı", gecmis: G2, soru: "listem daha güvenli olsun", beklenen: "revizyon", revizyon: SON_KURAL, kural: { iskelet: "guvenli-agirlikli" } },
{ id: "r08", aciklama: "2 geçmiş, il ekleme", gecmis: G2, soru: "Kocaeli de olsun", beklenen: "revizyon", revizyon: SON_KURAL, kural: { iller: { ekle: ["KOCAELİ"] } } },
{ id: "r09", aciklama: "2 geçmiş, belirsiz istek → form", gecmis: G2, soru: "listemi biraz değiştirmek istiyorum", beklenen: "soru", revizyon: SON_KURAL },
{ id: "r10", aciklama: "2 geçmiş, revizyon değil (kontrol)", gecmis: G2, soru: "listemdeki 1. programın kontenjanı kaç, geçen yıl kaç kişi yerleşmiş?", beklenen: "hicbiri", revizyon: SON_KURAL },
// 10 Eki 2026 (Bilal: paketli hesapta "liste oluşturma" isteği koşmadı; QA B1 "listemi değiştir" → diger):
// genel istek form açmalı, "diger"e düşmemeli; "yeniden oluştur" regex kapısından geçmeli.
{ id: "r11", aciklama: "genel istek, ne değişeceği yok → form (QA B1)", gecmis: G2, soru: "listemi değiştir", beklenen: "soru", revizyon: SON_KURAL },
{ id: "r12", aciklama: "listeyi baştan isteme → form", gecmis: [], soru: "listemi yeniden oluştur", beklenen: "soru" },
{ id: "r13", aciklama: "tercih güncelleme dili → form", gecmis: G2, soru: "tercihlerimi güncellemek istiyorum", beklenen: "soru", revizyon: SON_KURAL },
{ id: "r14", aciklama: "net çoklu kural tek cümlede", gecmis: [], soru: "yeni liste yap: ilk 10 tercihim İstanbul'dan olsun, vakıf olmasın", beklenen: "revizyon", kural: { ilkTercihler: { adet: 10, iller: ["İSTANBUL"] }, universiteTipi: "devlet" } },
// Varsayılan formun cevabı modelsiz kurala çevrilir (niyet sn ≈ 0 beklenir)
{ id: "r15", aciklama: "varsayılan form cevabı → modelsiz kural (il serbest metin + devlet, ilk 5)", gecmis: [...G2, U("listemi değiştir"), A("Birkaç kısa soru hazırladım; cevaplarsan listeni ona göre yeniden kurarım.", { anket: VARSAYILAN_ANKET })], soru: "Form cevaplarım:\n- Ne değişsin? → Başka bir il de olsun, Sadece devlet üniversiteleri, Kocaeli\n- Kaç tercih etkilensin? → İlk 5", beklenen: "revizyon", revizyon: SON_KURAL, oncekiForm: VARSAYILAN_ANKET, anketCevabi: { degisiklik: ["il-ekle", "devlet", "Kocaeli"], adet: "5" }, kural: { ilkTercihler: { adet: 5, iller: ["KOCAELİ"], universiteTipi: "devlet" }, iller: { ekle: ["KOCAELİ"] } } },
{ id: "r16", aciklama: "varsayılan form cevabı → modelsiz kural (güvenli, tamamı)", gecmis: [...G2, U("listemi değiştir"), A("Birkaç kısa soru hazırladım.", { anket: VARSAYILAN_ANKET })], soru: "Form cevaplarım:\n- Ne değişsin? → Daha güvenli olsun (riski azaltalım)\n- Kaç tercih etkilensin? → Listenin tamamı", beklenen: "revizyon", revizyon: SON_KURAL, oncekiForm: VARSAYILAN_ANKET, anketCevabi: { degisiklik: ["guvenli"], adet: "24" }, kural: { iskelet: "guvenli-agirlikli" } },
];
/** `beklenen` kural parçası `gercek`in alt kümesi mi (diziler sırasız, büyük-küçük harf duyarsız). */
function altKume(beklenen: unknown, gercek: unknown): boolean {
if (Array.isArray(beklenen)) {
if (!Array.isArray(gercek)) return false;
const g = gercek.map((x) => String(x).toLocaleLowerCase("tr-TR"));
return beklenen.every((b) => g.includes(String(b).toLocaleLowerCase("tr-TR")));
}
if (beklenen && typeof beklenen === "object") {
if (!gercek || typeof gercek !== "object") return false;
return Object.entries(beklenen).every(([k, v]) => altKume(v, (gercek as Record<string, unknown>)[k]));
}
return beklenen === gercek;
}
const PARAMS: RaporParams = { sira: 85000, tur: "say", kategoriler: ["Mühendislik & Teknoloji"], iller: ["ANKARA", "İZMİR", "BURSA"], universiteTipi: "farketmez", oncelikler: [], ozelDurum: [] };
async function main() {
const dbYolu = evalAppDbHazirla();
delete process.env.TYPESAFE_API_KEY;
const sqlite = new Database(dbYolu);
const { danismanAkisi } = await import("@/lib/ai/sohbet-akisi");
const { sistemPromptu } = await import("@/lib/ai/danisman-prompt");
const { kayitliMesajModelMetni, modelPenceresi, revizyonNiyetiVar } = await import("@/lib/ai/sohbet-gecmisi");
const { barajTablosu, listeRevizeSonucuBicimle, soruFormuSonucu } = await import("@/lib/ai/araclar");
const { revizyonNiyetiCikar } = await import("@/lib/ai/revizyon-niyeti");
const { listeOzetiCikar } = await import("@/lib/ai/rapor");
const { sohbetModeli, niyetModeli, saglayiciAdi, anahtarVar } = await import("@/lib/ai/client");
const { havuzOlustur, dilimHedefiHesapla, idealHedef } = await import("@/lib/rapor-havuzu");
const { kuralOzeti } = await import("@/lib/revizyon-kurali");
if (!anahtarVar()) {
console.error("Model anahtarı yok (AI_API_KEY / OPENROUTER_API_KEY): gerçek modelle koşulamadı.");
process.exit(2);
}
const secilenler = argOku("senaryo")?.split(",").filter(Boolean);
const tekrar = Math.max(1, Math.floor(Number(argOku("tekrar")) || 1));
const senaryolar = SENARYOLAR.filter((s) => !secilenler || secilenler.includes(s.id));
// Liste bağlamı deterministik (model çağrısı yok): her dilimden tabanı en iyi `hedef` kadar program
const { havuz } = havuzOlustur(PARAMS, {});
const hedef = dilimHedefiHesapla(havuz, idealHedef(PARAMS.oncelikler));
const secilen = (["hayal", "dengeli", "garanti"] as const).flatMap((d) =>
havuz.filter((x) => x.dilim === d).sort((a, b) => (a.efektifSira ?? 0) - (b.efektifSira ?? 0)).slice(0, hedef[d]),
);
const rapor = {
tercihler: secilen.map((x, i) => ({ programId: x.id, sira: i + 1, dilim: x.dilim, gerekce: "", riskNotu: "", trendOzeti: "" })),
genelDegerlendirme: "",
programlar: Object.fromEntries(secilen.map((x) => [x.id, { isim: x.isim, universite: x.universite, il: x.il, unitur: x.unitur, efektifSira: x.efektifSira }])),
} as RaporSonuc;
const listeOzeti = listeOzetiCikar(rapor, PARAMS.sira, { kimlik: true, gerekce: true });
const tr = (x: string) => x.toLocaleLowerCase("tr-TR");
/** Sahte çekirdek: ilk 5, kuralın ilkTercihler.iller'ine uyan havuz programlarından; yoksa listenin başı. */
const sahteOzet = (kural: RevizyonKurali) => {
const iller = (kural.ilkTercihler?.iller ?? []).map(tr);
const adet = Math.min(5, kural.ilkTercihler?.adet ?? 5);
const uyan = iller.length ? havuz.filter((x) => iller.includes(tr(x.il ?? ""))).slice(0, adet) : [];
const kalan = secilen.filter((x) => !uyan.includes(x)).slice(0, 5 - uyan.length);
const dagilim = kural.iskelet === "guvenli-agirlikli" ? { hayal: 4, dengeli: 12, garanti: 8 } : kural.iskelet === "hayal-agirlikli" ? { hayal: 7, dengeli: 12, garanti: 5 } : { hayal: 5, dengeli: 13, garanti: 6 };
return { ok: true as const, toplam: 24, dagilim, ilkTercihler: [...uyan, ...kalan].map((x) => `${x.isim} — ${x.universite} (${x.il})`) };
};
console.log(`\n== Koda bağlı revizyon eval — ${senaryolar.length} senaryo${tekrar > 1 ? ` × ${tekrar}` : ""}, sohbet ${sohbetModeli()}, niyet ${niyetModeli()} @ ${saglayiciAdi()} ==\n`);
const asilInfo = console.info;
console.info = () => {};
const kullaniciEkle = sqlite.prepare("INSERT OR REPLACE INTO user (id, name, email, email_verified, credit_balance, has_paket, created_at, updated_at) VALUES (?, 'Eval', ?, 1, 0, 1, ?, ?)");
const sonuclar: Record<string, unknown>[] = [];
for (const s of senaryolar) {
for (let t = 0; t < tekrar; t++) {
const params: RaporParams = { ...PARAMS, revizyon: s.revizyon };
const system = sistemPromptu({ params, listeOzeti, kilitli: false, profil: {}, barajlar: barajTablosu(), revizyon: { paketli: true } });
const mesajlar = modelPenceresi(s.gecmis.map(kayitliMesajModelMetni), s.soru);
const userId = `eval-rev-${process.pid}-${s.id}-${t}`;
kullaniciEkle.run(userId, `${userId}@eval.invalid`, Date.now(), Date.now());
// 1. Niyet: regex kapısı + yapılandırılmış çıkarım (route.ts ile aynı)
const regex = revizyonNiyetiVar(s.soru);
let dal: "revizyon" | "form" | "sohbet" = "sohbet";
let kural: RevizyonKurali | null = null;
let eksik: string[] = [];
let niyetAdi = "—";
let niyetSaglayici: string | null = null;
let islem: KoddanIslem | undefined;
let ozet: ReturnType<typeof sahteOzet> | null = null;
const t0 = Date.now();
const niyetOlcum = regex
? await olcerekKos(() => revizyonNiyetiCikar({ mesaj: s.soru, params, oncekiForm: s.oncekiForm ?? null, anketCevabi: s.anketCevabi ?? null, gozlem: (g) => (niyetSaglayici = g.saglayici) }))
: null;
const niyetMs = Date.now() - t0;
if (niyetOlcum) {
const n = niyetOlcum.sonuc;
niyetAdi = n.niyet;
if (n.niyet === "revizyon" && "kural" in n) {
dal = "revizyon";
kural = n.kural;
ozet = sahteOzet(n.kural);
const sonuc = listeRevizeSonucuBicimle({ ok: true, ozet, rapor });
islem = { ad: "liste_revize", girdi: n.kural, calistir: async () => sonuc, anlatim: { ozet: kuralOzeti(n.kural), ilkN: Boolean(n.kural.ilkTercihler) } };
} else if (n.niyet === "revizyon") {
dal = "form";
eksik = n.eksik;
const sonuc = soruFormuSonucu(n.form);
islem = { ad: "soru_sor", girdi: {}, calistir: async () => sonuc };
} else if (n.niyet === "belirsiz") niyetAdi = `belirsiz (${n.neden})`;
}
// 2. Sohbet: koddan işlem + model anlatımı
const araclar: string[] = [];
let metin = "";
let revizeNotu = false;
let hata: string | null = null;
const t1 = Date.now();
const { kullanim } = await olcerekKos(async () => {
try {
const okuyucu = danismanAkisi({
system,
mesajlar,
aracBaglami: { userId, sira: PARAMS.sira, tur: PARAMS.tur, hasPaket: true, profil: {}, oncelikler: [], ozelDurum: [], sihirbazIlVar: true },
islem,
onTamam: async (m) => {
metin = m;
},
onYarim: async (m) => {
metin = m;
hata = "yarım";
},
onDenetim: (d) => {
revizeNotu = d.revizeNotu;
},
onRevizeBeyani: async () => true,
}).getReader();
for (;;) {
const { done, value } = await okuyucu.read();
if (done) break;
const v = value as { type: string; toolName?: string; errorText?: string };
if (v.type === "tool-input-available") araclar.push(v.toolName ?? "?");
else if (v.type === "error") hata = v.errorText ?? "hata";
}
} catch (e) {
hata = e instanceof Error ? e.message : String(e);
}
});
const sohbetMs = Date.now() - t1;
const kuralTuttu = !s.kural || (kural != null && altKume(s.kural, kural));
const tuttu = s.beklenen === "revizyon" ? dal === "revizyon" && kuralTuttu : s.beklenen === "soru" ? dal === "form" : dal !== "revizyon";
const sahteBeyan = dal !== "revizyon" && revizeNotu;
// Anlatım: sonuçtaki ilk tercihlerin bölüm adları cevapta geçiyor mu (uydurma ad yerine sunucu sonucu)
const m = tr(metin);
const adEslesme = ozet ? ozet.ilkTercihler.filter((x) => m.includes(tr(x.split(" — ")[0]))).length : null;
const niyetMaliyet = niyetOlcum?.kullanim.maliyet ?? 0;
sonuclar.push({ id: s.id, tekrar: t, aciklama: s.aciklama, beklenen: s.beklenen, regex, niyet: niyetAdi, dal, kural, eksik, kuralTuttu, tuttu, sahteBeyan, adEslesme, araclar, hata, niyetMs, sohbetMs, ms: niyetMs + sohbetMs, niyetSaglayici, niyetCagri: niyetOlcum?.kullanim.cagri ?? 0, niyetMaliyet, sohbetCagri: kullanim.cagri, sohbetMaliyet: kullanim.maliyet, cevap: metin.trim() });
asilInfo(` ${tuttu ? "geçti" : "KALDI"} ${s.id}${tekrar > 1 ? `#${t + 1}` : ""} [${s.beklenen}] niyet ${niyetAdi} → ${dal}${kural ? ` ${kisalt(JSON.stringify(kural), 90)}` : ""}${!kuralTuttu ? " ⇒ KURAL BEKLENENDEN FARKLI" : ""} · niyet ${(niyetMs / 1000).toFixed(1)} sn + sohbet ${(sohbetMs / 1000).toFixed(1)} sn, ${(niyetOlcum?.kullanim.cagri ?? 0) + kullanim.cagri} çağrı, araç: ${araclar.join("+") || "—"}${adEslesme != null ? `, ad eşleşme ${adEslesme}/5` : ""}${sahteBeyan ? " ⇒ SAHTE BEYAN" : ""}${hata ? ` ⇒ hata: ${kisalt(hata, 60)}` : ""}\n ${kisalt(metin.replace(/\s+/g, " "), 160)}`);
}
}
console.info = asilInfo;
const rev = sonuclar.filter((x) => x.beklenen === "revizyon");
const kuruldu = rev.filter((x) => x.dal === "revizyon").length;
const kuralDogru = rev.filter((x) => x.dal === "revizyon" && x.kuralTuttu).length;
const sahte = sonuclar.filter((x) => x.sahteBeyan).length;
const niyetSureleri = sonuclar.filter((x) => x.regex).map((x) => x.niyetMs as number);
const ozet = {
sohbetModeli: sohbetModeli(),
niyetModeli: niyetModeli(),
saglayici: saglayiciAdi(),
n: sonuclar.length,
gecti: sonuclar.filter((x) => x.tuttu).length,
revizyonBeklenen: rev.length,
koddanRevizyon: kuruldu,
kuralDogru,
sahteBeyan: sahte,
niyetCagri: sonuclar.reduce((a, x) => a + (x.niyetCagri as number), 0),
niyetMaliyet: sonuclar.reduce((a, x) => a + (x.niyetMaliyet as number), 0),
niyetMsOrt: niyetSureleri.length ? Math.round(niyetSureleri.reduce((a, b) => a + b, 0) / niyetSureleri.length) : null,
harcama: { ...TOPLAM },
};
console.log(`\n${tablo(["senaryo", "beklenen", "niyet", "dal", "kural", "sonuç", "sahte", "niyet sn", "sohbet sn"], sonuclar.map((x) => [String(x.id), String(x.beklenen), String(x.niyet), String(x.dal), x.kuralTuttu ? "ok" : "FARKLI", x.tuttu ? "geçti" : "KALDI", x.sahteBeyan ? "EVET" : "-", ((x.niyetMs as number) / 1000).toFixed(1), ((x.sohbetMs as number) / 1000).toFixed(1)]))}`);
console.log(`\nkoddan revizyon (revizyon beklenen): ${kuruldu}/${rev.length}, kural beklenen alanı taşıyor: ${kuralDogru}/${rev.length} · sahte beyan: ${sahte} · geçen: ${ozet.gecti}/${ozet.n} · niyet çağrısı ort. ${ozet.niyetMsOrt ?? "—"} ms`);
console.log(`Harcama: ${TOPLAM.cagri} model çağrısı (${ozet.niyetCagri} niyet), ${TOPLAM.girdiToken} girdi + ${TOPLAM.ciktiToken} çıktı token, ${TOPLAM.maliyet.toFixed(4)} $ (niyet ${ozet.niyetMaliyet.toFixed(4)} $)${TOPLAM.maliyetBilinmeyenCagri ? ` (+${TOPLAM.maliyetBilinmeyenCagri} çağrının maliyeti bildirilmedi)` : ""}`);
const yol = sonucYaz("revizyon", { tur: "revizyon-koda-bagli", tarih: new Date().toISOString(), git: gitDurumu(), argv: process.argv.slice(2), ozet, senaryolar: sonuclar });
console.log(`Ham sonuç: ${yol}`);
sqlite.close();
process.exit(0);
}
main().catch((e) => {
console.error(e);
process.exit(2);
});