Files
bilalgursen a3cb058a3b
All checks were successful
Deploy / deploy (push) Successful in 7m45s
feat: update app.db and BACKLOG.md with new decisions and adjustments
- Updated the app.db file to reflect recent changes in the application state.
- Modified BACKLOG.md to include new entries regarding the removal of certain wizard steps, adjustments to priority chips, and the introduction of special quota conditions for candidates.
- Added details on the decision to link priority chips to data sources and clarified the implications for user experience and data handling.
2026-10-06 17:17:49 +03:00

481 lines
29 KiB
TypeScript
Raw Permalink 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, havuzAdaylari } from "@/lib/db";
import { kategoriEslesir } from "@/lib/kategoriler";
import { dilimHedefiHesapla, havuzOlustur, havuzuKompaktJson, idealHedef, 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<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];
}
const KKTC_ILLERI = new Set(["LEFKOŞA", "GİRNE", "GAZİMAĞUSA", "GÜZELYURT", "LEFKE"]);
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, idealHedef(params.oncelikler));
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})`);
// İ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<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 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<string>();
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<typeof degismezler>, 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,
// Koddan hesaplanan "öne çıkan" veriler (uyum-puani.ts): gerekçedeki TÜİK %, URAP sırası vb. dayanaklıdır
one_cikan: t.oneCikan?.length ? t.oneCikan.join(" · ") : null,
},
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<string, { cagri: number; dusunen: number; ortMs: number; tokSn: number }> = {};
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);
});