fix(havuz): B1 — tek dilime yığılan havuz 24'ün altına kırpılmasın [KARAR]

24 eşiği kırpmadan önce sayılıyor, sonra her dilim 20'ye iniyordu; havuzu
tek dilimde toplanan adayın listesi 20–22 satırda kalıyordu (SAY 800 devlet
sağlık: 617 güvenli program varken 22 satır). Kırpma artık dilim başına
20'yi korur ama toplam 24'ün altına düşecekse eksiği, adayı kalan
dilimlerden tamamlar. Dağıtım kuralı dilimHedefiHesapla ile aynı
(dengeli → güvenli → hayal) ve tek yardımcıda toplandı; 5/13/6 iskeleti ve
hedef hesabı değişmedi.

Kanıt (liste satırı, önce → sonra): a02 22 → 24 (1/1/22), a01 21 → 24,
a11/a12/a17/b05 20 → 24. Diğer 33 profilde havuz birebir aynı.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-05 01:13:57 +03:00
parent ef68f93463
commit c1d81cd1ec

View File

@@ -76,6 +76,35 @@ export type DilimHedef = Record<Dilim, number>;
// Listenin ideal iskeleti: 5 hayal / 13 dengeli / 6 garanti = 24 tercih. // Listenin ideal iskeleti: 5 hayal / 13 dengeli / 6 garanti = 24 tercih.
export const IDEAL_HEDEF: DilimHedef = { hayal: 5, dengeli: 13, garanti: 6 }; export const IDEAL_HEDEF: DilimHedef = { hayal: 5, dengeli: 13, garanti: 6 };
const DILIM_ONCELIGI: Dilim[] = ["dengeli", "garanti", "hayal"];
function dilimSay(havuz: AdayProgram[]): DilimHedef {
const mevcut: DilimHedef = { hayal: 0, dengeli: 0, garanti: 0 };
for (const p of havuz) mevcut[p.dilim]++;
return mevcut;
}
/**
* `pay` toplamı `toplam`ın altındaysa eksiği, adayı kalan dilimlere birer
* birer dağıtır (öncelik: dengeli → garanti → hayal — omurga önce, sigorta
* sonra, en riskli dilim en son). `pay`'i yerinde günceller.
*/
function eksigiDagit(pay: DilimHedef, mevcut: DilimHedef, toplam: number) {
let eksik = toplam - pay.hayal - pay.dengeli - pay.garanti;
while (eksik > 0) {
let dagitildi = false;
for (const d of DILIM_ONCELIGI) {
if (eksik <= 0) break;
if (mevcut[d] > pay[d]) {
pay[d]++;
eksik--;
dagitildi = true;
}
}
if (!dagitildi) break;
}
}
/** /**
* Havuzun gerçek dilim dağılımına uyarlanmış liste hedefi. İdeal 5/13/6'dan * Havuzun gerçek dilim dağılımına uyarlanmış liste hedefi. İdeal 5/13/6'dan
* başlar; bir dilimde yeterli aday yoksa eksik, kapasitesi kalan dilimlere * başlar; bir dilimde yeterli aday yoksa eksik, kapasitesi kalan dilimlere
@@ -84,30 +113,13 @@ export const IDEAL_HEDEF: DilimHedef = { hayal: 5, dengeli: 13, garanti: 6 };
* Deterministiktir: aynı havuz her zaman aynı hedefi üretir. * Deterministiktir: aynı havuz her zaman aynı hedefi üretir.
*/ */
export function dilimHedefiHesapla(havuz: AdayProgram[]): DilimHedef { export function dilimHedefiHesapla(havuz: AdayProgram[]): DilimHedef {
const mevcut: DilimHedef = { hayal: 0, dengeli: 0, garanti: 0 }; const mevcut = dilimSay(havuz);
for (const p of havuz) mevcut[p.dilim]++;
const toplamHedef = Math.min(HEDEF, havuz.length);
const hedef: DilimHedef = { const hedef: DilimHedef = {
hayal: Math.min(IDEAL_HEDEF.hayal, mevcut.hayal), hayal: Math.min(IDEAL_HEDEF.hayal, mevcut.hayal),
dengeli: Math.min(IDEAL_HEDEF.dengeli, mevcut.dengeli), dengeli: Math.min(IDEAL_HEDEF.dengeli, mevcut.dengeli),
garanti: Math.min(IDEAL_HEDEF.garanti, mevcut.garanti), garanti: Math.min(IDEAL_HEDEF.garanti, mevcut.garanti),
}; };
eksigiDagit(hedef, mevcut, Math.min(HEDEF, havuz.length));
const oncelik: Dilim[] = ["dengeli", "garanti", "hayal"];
let eksik = toplamHedef - hedef.hayal - hedef.dengeli - hedef.garanti;
while (eksik > 0) {
let dagitildi = false;
for (const d of oncelik) {
if (eksik <= 0) break;
if (mevcut[d] > hedef[d]) {
hedef[d]++;
eksik--;
dagitildi = true;
}
}
if (!dagitildi) break;
}
return hedef; return hedef;
} }
// Prompt'a giden nihai havuz: dilim başına aday sayısı. Arama dilimin tamamını // Prompt'a giden nihai havuz: dilim başına aday sayısı. Arama dilimin tamamını
@@ -116,10 +128,22 @@ export function dilimHedefiHesapla(havuz: AdayProgram[]): DilimHedef {
// modellerde prefill + isabet maliyeti yüksek (ölçüm: 6 Ağu 2026). // modellerde prefill + isabet maliyeti yüksek (ölçüm: 6 Ağu 2026).
const PROMPT_LIMIT_DILIM = 20; const PROMPT_LIMIT_DILIM = 20;
/** Dilim sırasını koruyarak her dilimden en fazla n aday bırakır. */ /**
* Dilim sırasını koruyarak her dilimden en fazla n aday bırakır. Havuz tek
* dilime yığılmışsa (ör. çok iyi derece: neredeyse hepsi güvenli) n'e kırpmak
* listeyi 24'ün altına düşürürdü; o durumda eksik, adayı kalan dilimlerden
* tamamlanır — yeterli program varken liste 24'ten kısa kalmaz.
*/
function dilimBasinaKirp(havuz: AdayProgram[], n: number): AdayProgram[] { function dilimBasinaKirp(havuz: AdayProgram[], n: number): AdayProgram[] {
const sayac: Record<Dilim, number> = { hayal: 0, dengeli: 0, garanti: 0 }; const mevcut = dilimSay(havuz);
return havuz.filter((p) => ++sayac[p.dilim] <= n); const pay: DilimHedef = {
hayal: Math.min(n, mevcut.hayal),
dengeli: Math.min(n, mevcut.dengeli),
garanti: Math.min(n, mevcut.garanti),
};
eksigiDagit(pay, mevcut, Math.min(HEDEF, havuz.length));
const sayac: DilimHedef = { hayal: 0, dengeli: 0, garanti: 0 };
return havuz.filter((p) => ++sayac[p.dilim] <= pay[p.dilim]);
} }
function dilimEkle(r: { function dilimEkle(r: {