Files
kolaytercih/scripts/eval/robot.ts
bilalgursen 065a9c102d feat(eval): tercih robotu ve danışman sohbeti için tekrar koşulabilir eval + ilk taban çizgisi
- scripts/eval/robot.ts: 40 profilde modelsiz havuz değişmezleri (CI, çıkış kodu) ve
  --model ile gerçek liste üretimi denetimi (şema, havuz-dışı, yasak ifade, il
  çelişkisi, sayı tutarlılığı, Jev gerekçe grubu, gecikme, maliyet)
- scripts/eval/sohbet.ts: 112 soru gerçek danismanAkisi ile; araç kuralları, metin
  kontrolü, sayı kaynağı, vaat regex'i, Jev danışman grubu
- scripts/eval/ortak.ts: kullanım ölçümü, Jev istemcisi, kod denetimleri
- package.json: eval:robot, eval:sohbet
- İlk koşu sonuçları ve rapor (04-eval-sonuclari.md)

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

410 lines
23 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.
/**
* 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 } from "@/lib/db";
import { kategoriEslesir } from "@/lib/kategoriler";
import { dilimHedefiHesapla, havuzOlustur, havuzuKompaktJson, turLabel, type AdayProgram, type DilimHedef } from "@/lib/rapor-havuzu";
import type { RaporParams, RaporSonuc } from "@/features/rapor/types/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<string, Ozellik> {
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<string, string> = { afyon: "AFYONKARAHİSAR", maraş: "KAHRAMANMARAŞ", urfa: "ŞANLIURFA", antep: "GAZİANTEP", izmit: "KOCAELİ", adapazarı: "SAKARYA" };
let ilKumesi: Map<string, string> | 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<string>();
// 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(/(?<![\p{L}])(\p{Lu}[\p{L}]+)['’][dt][ae](?:ki)?(?![\p{L}])/gu)) {
const il = ilKumesi.get(e[1].toLocaleLowerCase("tr-TR"));
if (il) bulunan.add(il);
}
return [...bulunan];
}
type Bulgu = { ad: string; durum: "ihlal" | "bilinen" | "duzeldi"; neden: string };
function degismezler(p: EvalProfil) {
const params = paramsYap(p);
const { havuz, gevsetildi } = havuzOlustur(params, p.danismanProfili);
const hedef = dilimHedefiHesapla(havuz);
const toplam = hedef.hayal + hedef.dengeli + hedef.garanti;
const oz = ozellikler(havuz.map((x) => 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})`);
degismez("alan-ilsiz-gevsedi", gevsetildi.includes("kategori") && s.iller.length === 0, ["B3"], `il seçilmeden alan gevşedi; 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,
ilIci: s.iller.length ? ilIci : null,
tipIci,
barajDisi: barajDisi.length,
ozelKontenjan: ozelKontenjan.length,
kktcYurtdisi,
yinelenen,
bulgular,
},
};
}
// ───────────────────────── --model: gerçek liste üretimi ─────────────────────────
const KURTARMA_GEREKCESI = "Sıralamana ve seçimlerine uygun olduğu için listene eklendi.";
type HamListe = { hayal?: { programId?: string }[]; dengeli?: { programId?: string }[]; garanti?: { programId?: string }[] };
/** Modelin HAM çıktısı (normalize öncesi): rapor.ts havuz-dışını sessizce atar, burada sayılır. */
function hamDenetle(ham: string, havuzMap: Map<string, AdayProgram>, 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 semaGecerli = DILIMLER.every((d) => Array.isArray(j[d]) && j[d]!.every((t) => typeof t?.programId === "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<string>();
for (const d of DILIMLER)
for (const t of j[d]!) {
const id = t.programId!;
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) => j[d]!.length === hedef[d]) };
}
async function modelleKos(p: EvalProfil, d: ReturnType<typeof degismezler>, raporUret: typeof import("@/lib/ai/rapor").raporUret) {
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 hata: string | null = null;
const { kullanim } = await olcerekKos(async () => {
try {
rapor = await raporUret(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 };
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;
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 (ham.length > 1) kontroller.push({ ad: "ikinci-deneme", durum: "gri", neden: `${ham.length} model denemesi gerekti` });
// 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 = r.tercihler.filter((t) => t.gerekce === KURTARMA_GEREKCESI).length;
if (kurtarma) kontroller.push({ ad: "kurtarma", durum: "gri", neden: `${kurtarma} satır kodla tamamlandı (model eksik döndü)` });
// 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
const yanlisIl = prog ? gerekcedekiIller(t.gerekce).filter((il) => il !== prog.il && !prog.universite.includes(il) && !prog.isim.toLocaleUpperCase("tr-TR").includes(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) },
};
const jev = t.gerekce === KURTARMA_GEREKCESI || 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, 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 { raporUret } = 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, raporUret);
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.ham.length > 1).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(` 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);
});