fix(revizyon): "ilk N" ölçütü seçimde kodla garanti ediliyor; mock yolu da kuralı uyguluyor

Model 24 geçerli satırı öncelikli satırsız doldurunca secimiOnar tamamlayacak
boşluk bulamıyor, ilkTercihleriOneAl öne alacak satır bulamıyordu ("ilk 5
İstanbul" istendi, liste değişmedi — 7 Eki 2026). Yeni saf fonksiyon
ilkTercihleriGarantile: ölçüte uyan ama seçilmemiş havuz programları, aynı
dilimdeki uymayan satırların (önce kodla gelen, sonra en düşük uyumlu; model
gerekçeli satırlar en son) yerine takasla girer, dilim adetleri değişmez.
rapor.ts: nihai seçimden sonra uygulanır, tani.ilkNEklenen alanı eklendi,
onarim.tamamlanan takas kadar artar, modelSecimi nihai seçimden yeniden
sayılır. Dev mock dilim içinde öncelikliyi önce alır ve ilkTercihleriOneAl
uygular.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-07 23:23:21 +03:00
parent bed839768c
commit 62994d70d1
2 changed files with 122 additions and 6 deletions

View File

@@ -42,6 +42,7 @@ import { ILK_YIL } from "../veri-yillari";
import {
cikarSuzgeci,
efektifParams,
ilkTercihleriGarantile,
ilkTercihleriOneAl,
kuralOzeti,
oncelikSuzgeci,
@@ -325,6 +326,8 @@ export type RaporTani = {
uyariAtilan: string[];
/** Koddan eklenen uyarı sayısı. */
uyariSablon: number;
/** Revizyonun "ilk N" ölçütü için kodla takas edilen satır sayısı (kural yoksa 0). */
ilkNEklenen: number;
};
// --- Deterministik satır metinleri: risk ve trend gerçek YÖK Atlas sıra
@@ -369,15 +372,22 @@ function mockRapor(
havuz: AdayProgram[],
params: RaporParams,
revizyonNotu?: string,
oncelikli?: ((p: AdayProgram) => boolean) | null,
): RaporSonuc {
// Prod ile aynı hedef hesabı: mock, uyarlanmış dağılımı da birebir taklit eder
const hedef = dilimHedefiHesapla(havuz, revizyonIskeleti(params));
const toplamHedef = hedef.hayal + hedef.dengeli + hedef.garanti;
const efektif = (p: AdayProgram) => p.efektifSira ?? 0;
// Revizyonun "ilk N" ölçütüne uyanlar dilim içinde önce gelir (prod'daki
// garanti takasının mock karşılığı); eşitte taban sırası iyiden kötüye
const dilimden = (dilim: AdayProgram["dilim"], adet: number) =>
havuz
.filter((p) => p.dilim === dilim)
.sort((a, b) => efektif(a) - efektif(b))
.sort(
(a, b) =>
Number(oncelikli?.(b) ?? false) - Number(oncelikli?.(a) ?? false) ||
efektif(a) - efektif(b),
)
.slice(0, adet);
const secilen = [
@@ -386,7 +396,7 @@ function mockRapor(
...dilimden("garanti", hedef.garanti),
];
const tercihler = secilen.map((p, i) => ({
const tercihlerHam = secilen.map((p, i) => ({
programId: p.id,
sira: i + 1,
dilim: p.dilim,
@@ -401,6 +411,9 @@ function mockRapor(
for (const p of secilen) {
programlar[p.id] = programBilgisi(p);
}
// Prod sırasıyla aynı: "ilk N" ölçütüne uyanlar başa (revizyon-kurali.ts)
const secilenMap = new Map(secilen.map((p) => [p.id, p]));
const { tercihler } = ilkTercihleriOneAl(tercihlerHam, params.revizyon, (id) => secilenMap.get(id));
return {
tercihler,
@@ -551,7 +564,7 @@ export async function raporUretAyrintili(
// Dev'de API key yoksa gerçek Yapay Zeka yerine mock rapor (üretim gecikmesi taklidi)
if (mockAktif()) {
await new Promise((r) => setTimeout(r, 1500));
return { rapor: mockRapor(havuz, params, revizyonNotu), tani: null };
return { rapor: mockRapor(havuz, params, revizyonNotu, oncelikli), tani: null };
}
const kullaniciMesaji = [
@@ -709,7 +722,31 @@ export async function raporUretAyrintili(
uyarilar: [],
};
}
const nihai = enIyi;
// "İlk N" garantisi (revizyon-kurali.ts): model öncelikli satırları
// atlamışsa ölçüte uyan havuz programları, aynı dilimdeki uymayan satırların
// yerine takasla girer; dilim adetleri değişmez. Prompt'taki "EN AZ N tane
// seç" ricadır, sonuç buraya bağlıdır. Takasla giren satırın gerekçesi
// şablondan basılır (kaynak "tamamlandi").
const garanti = ilkTercihleriGarantile(
enIyi.secim,
havuz,
kural,
uyum,
(p): SeciliSatir => ({ programId: p.id, gerekce: null, kaynak: "tamamlandi" }),
);
const nihai: Deneme = {
...enIyi,
secim: garanti.secim,
onarim: {
...enIyi.onarim,
tamamlanan: enIyi.onarim.tamamlanan + garanti.eklenen,
// Takas model satırı çıkarmış olabilir: sayı nihai seçimden yeniden okunur
modelSecimi: DILIMLER.reduce(
(n, d) => n + garanti.secim[d].filter((s) => s.kaynak !== "tamamlandi").length,
0,
),
},
};
// --- Metin denetimi (deterministik; ek model çağrısı yok). İhlalli metin
// kullanıcıya gitmez, koddan üretilen şablona düşer.
@@ -882,6 +919,7 @@ export async function raporUretAyrintili(
genelSablon,
uyariAtilan,
uyariSablon,
ilkNEklenen: garanti.eklenen,
},
};
}