/** * Tercih robotu (24'lük liste) eval'i. * * pnpm eval:robot # modelsiz değişmezler, 40 profil, saniyeler; ihlalde çıkış kodu 1 * pnpm eval:robot --model # + gerçek liste üretimi (profil başına 1–2 model çağrısı, PARA HARCAR) * pnpm eval:robot --model --profil p05,a04 --limit 3 --eszaman 4 --butce 1 * * Değişmezler `profiller.ts` içindeki `beklenen` ile kıyaslanır; bir değişmez * bozuksa ve profilin `supheli` listesinde karşılığı (B1…B8) varsa "bilinen", * yoksa "ihlal" sayılır. `beklenen` sapması her zaman ihlaldir. */ import { argOku, bayrakVar, dayanaksizSayilar, genelKarar, gitDurumu, havuzdaKos, jevKontrolleri, jevMaliyeti, jevSor, JEV_TOPLAM, olcerekKos, sonucYaz, tablo, TOPLAM, vaatIfadeleri, yasakSozcukler, yuzdelik, type Kontrol, } from "./ortak"; import { JEV_EVAL_MODEL, JEV_SORU_GRUPLARI, type JevGerekceState } from "./jev-sorulari"; import { PROFILLER, type EvalProfil } from "./profiller"; import { getDb, havuzAdaylari } from "@/lib/db"; import { kategoriEslesir } from "@/lib/kategoriler"; import { dilimHedefiHesapla, havuzOlustur, havuzuKompaktJson, ozelKontenjanSatiriMi, turLabel, type AdayProgram, type DilimHedef } from "@/lib/rapor-havuzu"; import type { RaporParams, RaporSonuc } from "@/features/rapor/types/rapor"; import type { RaporTani } from "@/lib/ai/rapor"; const DILIMLER = ["hayal", "dengeli", "garanti"] as const; const IDEAL: DilimHedef = { hayal: 5, dengeli: 13, garanti: 6 }; const hedefYaz = (h: DilimHedef) => `${h.hayal}/${h.dengeli}/${h.garanti}`; const ayni = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b); export const paramsYap = (p: EvalProfil): RaporParams => ({ sira: p.sihirbaz.sira, tur: p.sihirbaz.tur, ...p.sihirbaz.secimler }); type Ozellik = { min_basari_sirasi: number | null; ucret: number | null; burs_orani: string | null; ogrenim_dili: string | null }; function ozellikler(idler: string[]): Map { if (idler.length === 0) return new Map(); const satirlar = getDb() .prepare(`SELECT id, min_basari_sirasi, ucret, burs_orani, ogrenim_dili FROM program_ozellik WHERE id IN (${idler.map(() => "?").join(",")})`) .all(...idler) as (Ozellik & { id: string })[]; return new Map(satirlar.map((s) => [s.id, s])); } /** Gerekçede "X'da/de" diye geçen il adları (tam ad ya da yaygın kısa ad); program başka ildeyse gerekçe başka programa ait demektir. */ const IL_KISA: Record = { afyon: "AFYONKARAHİSAR", maraş: "KAHRAMANMARAŞ", urfa: "ŞANLIURFA", antep: "GAZİANTEP", izmit: "KOCAELİ", adapazarı: "SAKARYA" }; let ilKumesi: Map | null = null; function gerekcedekiIller(metin: string): string[] { if (!ilKumesi) { const iller = (getDb().prepare("SELECT DISTINCT il FROM programs WHERE il IS NOT NULL").all() as { il: string }[]).map((r) => r.il); ilKumesi = new Map([...iller.map((il) => [il.toLocaleLowerCase("tr-TR"), il] as [string, string]), ...Object.entries(IL_KISA)]); } const bulunan = new Set(); // Yalnız BULUNMA ekiyle geçen il ("Konya'da", "Kayseri’de"): programın yerini iddia eder. // "İzmir'e yakın" gibi yönelme hâli adayın kendi ilidir, çelişki değildir. for (const e of metin.matchAll(/(? x.id)); const s = p.sihirbaz.secimler; const supheli = new Set(p.beklenen.supheli ?? []); const barajDisi = havuz.filter((x) => { const b = oz.get(x.id)?.min_basari_sirasi; return b != null && b < params.sira; }); const ozelKontenjan = havuz.filter((x) => /KKTC Uyruklu|M\.T\.O\.K\./.test(x.isim)); const alanIci = s.kategoriler.length ? havuz.filter((x) => kategoriEslesir(x.isim, s.kategoriler, x.fakulte)).length : havuz.length; const ilIci = s.iller.length ? havuz.filter((x) => x.il != null && s.iller.includes(x.il)).length : havuz.length; const tipIci = s.universiteTipi === "farketmez" ? havuz.length : havuz.filter((x) => (s.universiteTipi === "devlet" ? x.unitur === "DEVLET" : x.unitur !== "DEVLET")).length; const kktcYurtdisi = havuz.filter((x) => /KKTC|YURTDISI/.test(x.unitur ?? "")).length; const yinelenen = havuz.length - new Set(havuz.map((x) => x.id)).size; const dilimSayisi = { hayal: 0, dengeli: 0, garanti: 0 }; for (const x of havuz) dilimSayisi[x.dilim]++; const bulgular: Bulgu[] = []; // 1) `beklenen` ile kıyas (bugünkü davranışın kaydı; sapma = davranış değişti) if (!ayni(gevsetildi, p.beklenen.havuzGevsetmeBasamagi)) bulgular.push({ ad: "beklenen.gevsetme", durum: "ihlal", neden: `beklenen [${p.beklenen.havuzGevsetmeBasamagi}] gerçek [${gevsetildi}]` }); if (havuz.length < p.beklenen.minAday) bulgular.push({ ad: "beklenen.minAday", durum: "ihlal", neden: `beklenen ≥${p.beklenen.minAday} gerçek ${havuz.length}` }); if (!ayni(hedef, p.beklenen.hedef)) bulgular.push({ ad: "beklenen.hedef", durum: "ihlal", neden: `beklenen ${hedefYaz(p.beklenen.hedef)} gerçek ${hedefYaz(hedef)}` }); // 2) Değişmezler; `supheli`deki B numarası varsa "bilinen" const degismez = (ad: string, bozuk: boolean, bNo: string[], neden: string) => { const isaretli = bNo.some((b) => supheli.has(b)); if (bozuk) bulgular.push({ ad, durum: isaretli ? "bilinen" : "ihlal", neden: `${neden}${isaretli ? ` (${bNo.filter((b) => supheli.has(b)).join(",")})` : ""}` }); else if (isaretli && bNo.every((b) => supheli.has(b))) bulgular.push({ ad, durum: "duzeldi", neden: `${bNo.join(",")} işaretli ama artık görülmüyor` }); }; degismez("liste-24", toplam < 24, ["B1", "B6"], `liste ${toplam} satır`); degismez("baraj-disi", barajDisi.length > 0, ["B2"], `${barajDisi.length} program adayın yazamayacağı barajda (ör. ${barajDisi[0]?.isim} — ${barajDisi[0]?.universite})`); degismez("ozel-kontenjan", ozelKontenjan.length > 0, ["B8"], `${ozelKontenjan.length} KKTC Uyruklu/M.T.O.K. satırı (ör. ${ozelKontenjan[0]?.isim} — ${ozelKontenjan[0]?.universite})`); // İl seçilmeden alanın gevşemesi yalnız alanda adayın YAZABİLECEĞİ (baraj içi, özel kontenjan dışı) 24 program // varken bug'dır (B3). Alan baraj yüzünden gerçekten boşaldıysa (a07: SÖZ 310.000 + öğretmenlik) gevşetme meşrudur. let alanYazilabilir: number | null = null; if (gevsetildi.includes("kategori") && s.iller.length === 0) { const r = havuzAdaylari(params.sira, params.tur, { uniturGrubu: s.universiteTipi === "farketmez" ? undefined : s.universiteTipi }); alanYazilabilir = [...r.hayal, ...r.dengeli, ...r.garanti].filter((x) => !ozelKontenjanSatiriMi(x.isim) && kategoriEslesir(x.isim, s.kategoriler, x.fakulte)).length; } degismez("alan-ilsiz-gevsedi", alanYazilabilir != null && alanYazilabilir >= 24, ["B3"], `il seçilmeden alan gevşedi; alanda yazılabilir ${alanYazilabilir} program varken havuzda alan içi ${alanIci}/${havuz.length}`); degismez("il-korunmadi", gevsetildi.includes("il") && ilIci < Math.min(3, toplam), ["B5"], `il gevşedi; havuzda seçili ilden ${ilIci}/${havuz.length}`); if (tipIci < havuz.length) bulgular.push({ ad: "tip", durum: "ihlal", neden: `üniversite tipi dışı ${havuz.length - tipIci} program` }); if (yinelenen > 0) bulgular.push({ ad: "yinelenen", durum: "ihlal", neden: `${yinelenen} yinelenen program` }); return { params, havuz, oz, hedef, satir: { id: p.id, gevsetildi, havuz: havuz.length, havuzDilim: dilimSayisi, hedef, liste: toplam, ideal: ayni(hedef, IDEAL), alanIci, /** Alan ilsiz gevşediyse: alanda adayın yazabileceği program sayısı (<24 ise gevşetme meşru). */ alanYazilabilir, ilIci: s.iller.length ? ilIci : null, tipIci, barajDisi: barajDisi.length, ozelKontenjan: ozelKontenjan.length, kktcYurtdisi, yinelenen, bulgular, }, }; } // ───────────────────────── --model: gerçek liste üretimi ───────────────────────── // Modelin tel şeması 5 Eki 2026'da değişti: üçüncü liste `guvenli`, kimlik alanı `id` // (öncesi: `garanti`, `programId`). İkisi de okunur ki eski ham çıktılar da denetlenebilsin. // 5 Eki (2): satır tek dizgi, "id|gerekçe". type HamSatir = string | { id?: string; programId?: string }; type HamListe = { hayal?: HamSatir[]; dengeli?: HamSatir[]; garanti?: HamSatir[]; guvenli?: HamSatir[] }; /** Modelin HAM çıktısı (normalize öncesi): rapor.ts havuz-dışını sessizce atar, burada sayılır. */ function hamDenetle(ham: string, havuzMap: Map, hedef: DilimHedef) { let j: HamListe; try { j = JSON.parse(ham) as HamListe; } catch { return { semaGecerli: false, havuzDisi: 0, yinelenen: 0, yanlisDilim: 0, sayiTuttu: false }; } const liste = (d: (typeof DILIMLER)[number]) => (d === "garanti" ? (j.guvenli ?? j.garanti) : j[d]); const kimlik = (t: HamSatir) => (typeof t === "string" ? t.split("|")[0].trim() : (t?.id ?? t?.programId)); const semaGecerli = DILIMLER.every((d) => Array.isArray(liste(d)) && liste(d)!.every((t) => typeof kimlik(t) === "string")); if (!semaGecerli) return { semaGecerli, havuzDisi: 0, yinelenen: 0, yanlisDilim: 0, sayiTuttu: false }; let havuzDisi = 0; let yinelenen = 0; let yanlisDilim = 0; const gorulen = new Set(); for (const d of DILIMLER) for (const t of liste(d)!) { const id = kimlik(t)!; const p = havuzMap.get(id); if (!p) havuzDisi++; else if (gorulen.has(id)) yinelenen++; else if (p.dilim !== d) yanlisDilim++; gorulen.add(id); } return { semaGecerli, havuzDisi, yinelenen, yanlisDilim, sayiTuttu: DILIMLER.every((d) => liste(d)!.length === hedef[d]) }; } async function modelleKos(p: EvalProfil, d: ReturnType, raporUretAyrintili: typeof import("@/lib/ai/rapor").raporUretAyrintili) { const havuzMap = new Map(d.havuz.map((x) => [x.id, x])); const kompakt = new Map((JSON.parse(havuzuKompaktJson(d.havuz)) as { id: string }[]).map((x) => [x.id, JSON.stringify(x)])); const t0 = Date.now(); let rapor: RaporSonuc | null = null; let tani: RaporTani | null = null; let hata: string | null = null; const { kullanim } = await olcerekKos(async () => { try { ({ rapor, tani } = await raporUretAyrintili(d.params, undefined, undefined, p.danismanProfili)); } catch (e) { hata = e instanceof Error ? e.message.slice(0, 200) : String(e); } }); const ms = Date.now() - t0; const ham = kullanim.hamIcerik.map((h) => hamDenetle(h, havuzMap, d.hedef)); const kontroller: Kontrol[] = []; const temel = { id: p.id, ms, cagri: kullanim.cagri, girdiToken: kullanim.girdiToken, ciktiToken: kullanim.ciktiToken, maliyet: kullanim.maliyet, ham, tani: tani as RaporTani | null }; if (!rapor) { return { ...temel, karar: "kaldi" as const, kontroller: [{ ad: "uretim", durum: "kaldi" as const, neden: hata ?? "rapor yok" }], satirlar: [], liste: null }; } const r = rapor as RaporSonuc; const t_ = tani as RaporTani | null; // Gerekçesi koddan basılan satırlar (tamamlama + denetimde şablona düşenler) const sablonNedeni = new Map((t_?.sablonGerekce ?? []).map((x) => [x.programId, x.neden])); if (r.devMock) kontroller.push({ ad: "uretim", durum: "atlandi", neden: "anahtar yok: dev mock rapor (gerçek modelle koşulmadı)" }); // Ham çıktı (ilk deneme belirleyici: kullanıcı ikinci denemenin süresini bekler) const ilk = ham[0]; kontroller.push({ ad: "sema", durum: !ilk || ilk.semaGecerli ? "gecti" : "kaldi", neden: ilk ? undefined : "ham çıktı yakalanamadı" }); const hd = ham.reduce((a, h) => a + h.havuzDisi, 0); kontroller.push({ ad: "ham.havuz-disi", durum: hd ? "kaldi" : "gecti", neden: hd ? `${hd} havuz-dışı programId (normalize attı)` : undefined }); const hy = ham.reduce((a, h) => a + h.yinelenen, 0); kontroller.push({ ad: "ham.yinelenen", durum: hy ? "gri" : "gecti", neden: hy ? `${hy} yinelenen programId` : undefined }); const yd = ham.reduce((a, h) => a + h.yanlisDilim, 0); kontroller.push({ ad: "ham.yanlis-dilim", durum: yd ? "gri" : "gecti", neden: yd ? `${yd} program yanlış dilim listesinde` : undefined }); if ((t_?.modelCagrisi ?? ham.length) > 1) kontroller.push({ ad: "ikinci-deneme", durum: "gri", neden: `${t_?.modelCagrisi ?? ham.length} model denemesi gerekti` }); if (t_?.kaynak === "deterministik") kontroller.push({ ad: "deterministik-liste", durum: "gri", neden: "model zaman aşımı: liste tamamen koddan kuruldu (kredi iade edilmez)" }); // Nihai liste (kullanıcının gördüğü) const say = { hayal: 0, dengeli: 0, garanti: 0 }; for (const t of r.tercihler) say[t.dilim as keyof typeof say]++; const beklenenToplam = d.hedef.hayal + d.hedef.dengeli + d.hedef.garanti; kontroller.push({ ad: "satir-sayisi", durum: r.tercihler.length === beklenenToplam && ayni(say, d.hedef) ? "gecti" : "kaldi", neden: `${r.tercihler.length} satır ${hedefYaz(say)} (hedef ${hedefYaz(d.hedef)})`, }); const disari = r.tercihler.filter((t) => !havuzMap.has(t.programId)).length; kontroller.push({ ad: "havuz-disi", durum: disari ? "kaldi" : "gecti", neden: disari ? `${disari} satır havuz dışı` : undefined }); const yin = r.tercihler.length - new Set(r.tercihler.map((t) => t.programId)).size; kontroller.push({ ad: "yinelenen", durum: yin ? "kaldi" : "gecti", neden: yin ? `${yin}` : undefined }); const kurtarma = t_?.onarim.tamamlanan ?? 0; if (kurtarma) kontroller.push({ ad: "kurtarma", durum: "gri", neden: `${kurtarma} satır kodla tamamlandı (model eksik döndü)` }); const sablonDusen = (t_?.sablonGerekce.length ?? 0) - kurtarma; // Bilgi satırı: denetim çalıştı ve metin kullanıcıya gitmedi; karar "geçti" kalır, oran özetten izlenir if (sablonDusen > 0 || t_?.genelSablon || t_?.uyariAtilan.length) kontroller.push({ ad: "sablon", durum: "gecti", neden: `${sablonDusen} gerekçe şablona düştü${t_?.genelSablon ? `; genel değerlendirme şablon (${t_.genelSablon})` : ""}${t_?.uyariAtilan.length ? `; ${t_.uyariAtilan.length} uyarı atıldı` : ""}` }); // Liste düzeyi metinler const ustMetin = [r.genelDegerlendirme, ...(r.uyarilar ?? [])].join("\n"); const ustYasak = [...yasakSozcukler(ustMetin), ...vaatIfadeleri(ustMetin)]; kontroller.push({ ad: "ust-metin.yasak", durum: ustYasak.length ? "kaldi" : "gecti", neden: ustYasak.length ? ustYasak.join(" | ") : undefined }); // Satır gerekçeleri: yasak sözcük + sayı tutarlılığı (kod) + Jev gerekce grubu const adayMetni = `${d.params.sira} ${JSON.stringify(p.sihirbaz.secimler)} ${JSON.stringify(p.danismanProfili ?? {})}`; const satirlar = await Promise.all( r.tercihler.map(async (t) => { const prog = havuzMap.get(t.programId); const o = d.oz.get(t.programId); const yasak = [...new Set([...yasakSozcukler(t.gerekce), ...vaatIfadeleri(t.gerekce)])]; const sayi = prog ? dayanaksizSayilar(t.gerekce, [kompakt.get(t.programId) ?? "", adayMetni, JSON.stringify(o ?? {})]) : []; // Üniversite adında geçen il (ör. "Ankara Üniversitesi" İstanbul'da olmasa da) çelişki sayılmaz // "Kıbrıs'ta" KKTC şehirlerindeki (Lefke, Girne…) program için çelişki değildir (5 Eki: yanlış alarm düzeltildi) const yanlisIl = prog ? gerekcedekiIller(t.gerekce).filter((il) => il !== prog.il && !prog.universite.includes(il) && !prog.isim.toLocaleUpperCase("tr-TR").includes(il) && !(il === "KIBRIS" && KKTC_ILLERI.has(prog.il ?? ""))) : []; const state: JevGerekceState = { metin: t.gerekce, program: { ad: prog?.isim ?? null, universite: prog?.universite ?? null, il: prog?.il ?? null, tur: prog?.unitur ?? null, dil: o?.ogrenim_dili ?? null, burs: o?.burs_orani ?? null, ucret: o?.ucret ?? null, taban_sirasi: prog?.efektifSira ?? null, kontenjan: prog?.kontenjanSon ?? null, dilim: t.dilim === "garanti" ? "güvenli" : t.dilim, }, aday: { sira: d.params.sira, puan_turu: turLabel(d.params.tur) }, }; // Şablon gerekçe koddan gelir (modelin metni değil): hakeme sorulmaz, kod denetimleri yine koşar const sablon = sablonNedeni.get(t.programId) ?? null; const jev = sablon != null || r.devMock ? null : await jevSor(state, JEV_SORU_GRUPLARI.gerekce); const k: Kontrol[] = [ { ad: "yasak-ifade", durum: yasak.length ? "kaldi" : "gecti", neden: yasak.length ? yasak.join(" | ") : undefined }, { ad: "il-celiski", durum: yanlisIl.length ? "kaldi" : "gecti", neden: yanlisIl.length ? `gerekçe ${yanlisIl.join(", ")} diyor, program ${prog?.il}` : undefined }, { ad: "sayi", durum: sayi.length ? "gri" : "gecti", neden: sayi.length ? `kaynakta yok: ${sayi.join(", ")}` : undefined }, ...(jev ? jevKontrolleri(jev, null) : []), ]; return { sira: t.sira, programId: t.programId, dilim: t.dilim, program: prog ? `${prog.isim} — ${prog.universite}` : "?", taban: prog?.efektifSira ?? null, gerekce: t.gerekce, sablon, jev: jev?.durum === "ok" ? jev.p : null, karar: genelKarar(k), kontroller: k.filter((x) => x.durum !== "gecti") }; }), ); const kalan = satirlar.filter((s) => s.karar === "kaldi").length; const gri = satirlar.filter((s) => s.karar === "gri").length; kontroller.push({ ad: "gerekceler", durum: kalan ? "kaldi" : gri ? "gri" : "gecti", neden: `${kalan} kaldı, ${gri} gri / ${satirlar.length}` }); return { ...temel, karar: genelKarar(kontroller), kontroller, satirlar, // Sohbet eval'i bağlam olarak bunu kullanır (yalnız seçilen programlar; havuzun tamamı değil) liste: { tercihler: r.tercihler, genelDegerlendirme: r.genelDegerlendirme, uyarilar: r.uyarilar, programlar: Object.fromEntries(r.tercihler.map((t) => [t.programId, r.programlar[t.programId]])), kapsam: r.kapsam, devMock: r.devMock, } satisfies RaporSonuc, }; } // ───────────────────────── ana akış ───────────────────────── async function main() { const profilSuzgec = argOku("profil")?.split(",").filter(Boolean); const limit = Number(argOku("limit")) || Infinity; const profiller = PROFILLER.filter((p) => !profilSuzgec || profilSuzgec.some((s) => p.id.startsWith(s))).slice(0, limit); const t0 = Date.now(); const det = profiller.map((p) => ({ p, d: degismezler(p) })); const detMs = Date.now() - t0; console.log(`\n== Değişmezler (modelsiz) — ${profiller.length} profil, ${detMs} ms ==\n`); console.log( tablo( ["profil", "gevşetilen", "havuz", "hedef", "liste", "alan", "il", "baraj", "özelK", "sonuç"], det.map(({ d }) => { const s = d.satir; const ihlal = s.bulgular.filter((b) => b.durum === "ihlal").length; const bilinen = s.bulgular.filter((b) => b.durum === "bilinen").length; return [s.id.slice(0, 44), s.gevsetildi.join("→") || "—", s.havuz, hedefYaz(s.hedef), s.liste, `${s.alanIci}/${s.havuz}`, s.ilIci == null ? "–" : `${s.ilIci}/${s.havuz}`, s.barajDisi, s.ozelKontenjan, ihlal ? `İHLAL×${ihlal}` : bilinen ? `bilinen×${bilinen}` : "temiz"]; }), ), ); const ihlalli = det.filter(({ d }) => d.satir.bulgular.some((b) => b.durum === "ihlal")); const bilinenli = det.filter(({ d }) => !d.satir.bulgular.some((b) => b.durum === "ihlal") && d.satir.bulgular.some((b) => b.durum === "bilinen")); for (const { d } of det) for (const b of d.satir.bulgular) console.log(` [${b.durum}] ${d.satir.id}: ${b.ad} — ${b.neden}`); const detOzet = { profil: det.length, temiz: det.length - ihlalli.length - bilinenli.length, yalnizBilinen: bilinenli.length, ihlalli: ihlalli.length, liste24: det.filter(({ d }) => d.satir.liste === 24).length, ideal: det.filter(({ d }) => d.satir.ideal).length, barajDisiProfil: det.filter(({ d }) => d.satir.barajDisi > 0).length, ozelKontenjanProfil: det.filter(({ d }) => d.satir.ozelKontenjan > 0).length, ms: detMs, }; console.log(`\nÖzet: ${detOzet.temiz} temiz · ${detOzet.yalnizBilinen} yalnız bilinen bug · ${detOzet.ihlalli} ihlalli | 24 satır: ${detOzet.liste24}/${det.length} · 5/13/6: ${detOzet.ideal}/${det.length}`); let model: unknown = null; if (bayrakVar("model")) { const { raporUretAyrintili } = await import("@/lib/ai/rapor"); const { raporModeli, saglayiciAdi, anahtarVar } = await import("@/lib/ai/client"); const butce = Number(argOku("butce")) || 1; const eszaman = Number(argOku("eszaman")) || 4; console.log(`\n== Gerçek liste üretimi — ${det.length} profil, model ${raporModeli()} @ ${saglayiciAdi()}, eşzamanlılık ${eszaman}, bütçe ${butce} $ ==`); if (!anahtarVar()) console.log("UYARI: model anahtarı yok — dev mock rapor dönecek; sonuçlar 'gerçek modelle koşulmadı'."); const sonuclar = ( await havuzdaKos( det, eszaman, async ({ p, d }) => { const s = await modelleKos(p, d, raporUretAyrintili); console.log(` ${s.karar.padEnd(6)} ${p.id} ${(s.ms / 1000).toFixed(1)} sn, ${s.cagri} çağrı, ${s.maliyet == null ? "?" : s.maliyet.toFixed(4)} $ ${s.kontroller.filter((k) => k.durum !== "gecti").map((k) => `${k.ad}${k.neden ? `(${k.neden})` : ""}`).join("; ")}`); return s; }, () => TOPLAM.maliyet >= butce, ) ).filter((s) => s != null); const sureler = sonuclar.map((s) => s.ms); const say = (k: string) => sonuclar.filter((s) => s.karar === k).length; const kontrolKalan = (ad: string) => sonuclar.filter((s) => s.kontroller.some((k) => k.ad === ad && k.durum === "kaldi")).length; const tumSatir = sonuclar.flatMap((s) => s.satirlar); const ozet = { model: raporModeli(), saglayici: saglayiciAdi(), jevModel: JEV_EVAL_MODEL, profil: sonuclar.length, atlananProfil: det.length - sonuclar.length, gecti: say("gecti"), gri: say("gri"), kaldi: say("kaldi"), semaGecersiz: kontrolKalan("sema"), uretimHatasi: kontrolKalan("uretim"), hamHavuzDisiProgram: sonuclar.reduce((a, s) => a + s.ham.reduce((b, h) => b + h.havuzDisi, 0), 0), ikinciDenemeGereken: sonuclar.filter((s) => (s.tani?.modelCagrisi ?? s.cagri) > 1).length, // Üst sağlayıcı (OpenRouter'ın yönlendirdiği): çağrı sayısı ve reasoning kapalı isteğine uymayanlar saglayicilar: (() => { const m: Record = {}; for (const c of sonuclar.flatMap((s) => s.tani?.cagrilar ?? [])) { const k = (m[c.saglayici ?? "?"] ??= { cagri: 0, dusunen: 0, ortMs: 0, tokSn: 0 }); k.cagri++; if (c.dusunmeToken > 0) k.dusunen++; k.ortMs += c.ms; k.tokSn += c.ciktiToken; } for (const k of Object.values(m)) { k.tokSn = Math.round(k.tokSn / (k.ortMs / 1000)); k.ortMs = Math.round(k.ortMs / k.cagri); } return m; })(), deterministikListe: sonuclar.filter((s) => s.tani?.kaynak === "deterministik").length, zamanAsimi: sonuclar.filter((s) => s.tani?.zamanAsimi).length, // Kod onarımı (ikinci model çağrısı yerine): kaç listede, kaç satır onarim: { liste: sonuclar.filter((s) => s.tani && s.tani.onarim.havuzDisi + s.tani.onarim.yinelenen + s.tani.onarim.yanlisDilim + s.tani.onarim.tamamlanan > 0).length, havuzDisi: sonuclar.reduce((a, s) => a + (s.tani?.onarim.havuzDisi ?? 0), 0), yinelenen: sonuclar.reduce((a, s) => a + (s.tani?.onarim.yinelenen ?? 0), 0), yanlisDilimTasinan: sonuclar.reduce((a, s) => a + (s.tani?.onarim.yanlisDilim ?? 0), 0), tamamlanan: sonuclar.reduce((a, s) => a + (s.tani?.onarim.tamamlanan ?? 0), 0), }, // Şablona düşme: çıktı denetiminin bedeli (çok yüksekse prompt/model sorunu) sablon: { gerekce: sonuclar.reduce((a, s) => a + (s.tani?.sablonGerekce.length ?? 0), 0), gerekceTamamlama: sonuclar.reduce((a, s) => a + (s.tani?.onarim.tamamlanan ?? 0), 0), gerekceYasak: sonuclar.reduce((a, s) => a + (s.tani?.sablonGerekce.filter((x) => x.neden.startsWith("yasak")).length ?? 0), 0), gerekceYer: sonuclar.reduce((a, s) => a + (s.tani?.sablonGerekce.filter((x) => x.neden.startsWith("başka programa")).length ?? 0), 0), gerekceSayi: sonuclar.reduce((a, s) => a + (s.tani?.sablonGerekce.filter((x) => x.neden.startsWith("dayanaksız")).length ?? 0), 0), genel: sonuclar.filter((s) => s.tani?.genelSablon).length, uyariAtilan: sonuclar.reduce((a, s) => a + (s.tani?.uyariAtilan.length ?? 0), 0), uyariEklenen: sonuclar.reduce((a, s) => a + (s.tani?.uyariSablon ?? 0), 0), listeUyariAtilan: sonuclar.filter((s) => s.tani?.uyariAtilan.length).length, }, satirSayisiTutmayan: kontrolKalan("satir-sayisi"), nihaiHavuzDisi: kontrolKalan("havuz-disi"), gerekce: { toplam: tumSatir.length, yasakIfade: tumSatir.filter((s) => s.kontroller.some((k) => k.ad === "yasak-ifade")).length, ilCeliski: tumSatir.filter((s) => s.kontroller.some((k) => k.ad === "il-celiski")).length, sayiAdayi: tumSatir.filter((s) => s.kontroller.some((k) => k.ad === "sayi")).length, jevIhlal: tumSatir.filter((s) => s.kontroller.some((k) => k.ad.startsWith("jev:") && k.durum === "kaldi")).length, jevGri: tumSatir.filter((s) => s.kontroller.some((k) => k.ad.startsWith("jev:") && k.durum === "gri")).length, jevAtlanan: tumSatir.filter((s) => s.kontroller.some((k) => k.ad === "jev" && k.durum === "atlandi")).length, }, gecikmeMs: { p50: yuzdelik(sureler, 50), p95: yuzdelik(sureler, 95), max: Math.max(0, ...sureler) }, harcama: { ...TOPLAM, jev: { ...JEV_TOPLAM, maliyet: jevMaliyeti() } }, }; console.log(`\nÖzet (model): ${ozet.gecti} geçti · ${ozet.gri} gri · ${ozet.kaldi} kaldı / ${ozet.profil}${ozet.atlananProfil ? ` (bütçe freni: ${ozet.atlananProfil} profil koşulmadı)` : ""}`); console.log(` ham havuz-dışı program: ${ozet.hamHavuzDisiProgram} · şema geçersiz: ${ozet.semaGecersiz} · 2. deneme: ${ozet.ikinciDenemeGereken} · satır sayısı tutmayan: ${ozet.satirSayisiTutmayan}`); console.log(` gerekçe ${ozet.gerekce.toplam}: yasak ifade ${ozet.gerekce.yasakIfade} · il çelişkisi ${ozet.gerekce.ilCeliski} · sayı adayı ${ozet.gerekce.sayiAdayi} · Jev ihlal ${ozet.gerekce.jevIhlal} · Jev gri ${ozet.gerekce.jevGri} · Jev atlanan ${ozet.gerekce.jevAtlanan}`); console.log(` onarım: ${ozet.onarim.liste} liste (yinelenen ${ozet.onarim.yinelenen}, taşınan ${ozet.onarim.yanlisDilimTasinan}, tamamlanan ${ozet.onarim.tamamlanan}) · deterministik liste ${ozet.deterministikListe} · zaman aşımı ${ozet.zamanAsimi}`); console.log(` şablon: gerekçe ${ozet.sablon.gerekce}/${ozet.gerekce.toplam} (tamamlama ${ozet.sablon.gerekceTamamlama}, yasak ${ozet.sablon.gerekceYasak}, yer ${ozet.sablon.gerekceYer}, sayı ${ozet.sablon.gerekceSayi}) · genel ${ozet.sablon.genel}/${ozet.profil} · uyarı atılan ${ozet.sablon.uyariAtilan} (${ozet.sablon.listeUyariAtilan} liste)`); console.log(` üst sağlayıcılar: ${Object.entries(ozet.saglayicilar).map(([ad, k]) => `${ad} ${k.cagri} çağrı/ort ${(k.ortMs / 1000).toFixed(1)} sn/${k.tokSn} tok·sn${k.dusunen ? `/DÜŞÜNEN ${k.dusunen}` : ""}`).join(" · ")}`); console.log(` gecikme p50 ${ozet.gecikmeMs.p50} ms · p95 ${ozet.gecikmeMs.p95} ms`); console.log(` harcama: ${TOPLAM.cagri} model çağrısı, ${TOPLAM.girdiToken} girdi + ${TOPLAM.ciktiToken} çıktı token, ${TOPLAM.maliyet.toFixed(4)} $${TOPLAM.maliyetBilinmeyenCagri ? ` (+${TOPLAM.maliyetBilinmeyenCagri} çağrının maliyeti bildirilmedi)` : ""}; Jev ${JEV_TOPLAM.cagri} çağrı ≈ ${jevMaliyeti().toFixed(4)} $`); // Dosya küçük kalsın: geçen satırların gerekçesi zaten `liste.tercihler` içinde model = { ozet, profiller: sonuclar.map((s) => ({ ...s, satirSayisi: s.satirlar.length, satirlar: s.satirlar.filter((x) => x.karar !== "gecti") })) }; } // Modelsiz koşu (CI) dosya bırakmaz; taban çizgisi için --kaydet if (!bayrakVar("model") && !bayrakVar("kaydet")) process.exit(ihlalli.length > 0 ? 1 : 0); const yol = sonucYaz("robot", { tur: "robot", tarih: new Date().toISOString(), git: gitDurumu(), argv: process.argv.slice(2), degismezler: { ozet: detOzet, profiller: det.map(({ d }) => d.satir) }, model, }); console.log(`\nHam sonuç: ${yol}`); process.exit(ihlalli.length > 0 ? 1 : 0); } main().catch((e) => { console.error(e); process.exit(2); });