- 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>
410 lines
23 KiB
TypeScript
410 lines
23 KiB
TypeScript
/**
|
||
* 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);
|
||
});
|