diff --git a/src/lib/rapor-havuzu.ts b/src/lib/rapor-havuzu.ts index fc33807..aab3f16 100644 --- a/src/lib/rapor-havuzu.ts +++ b/src/lib/rapor-havuzu.ts @@ -76,6 +76,35 @@ export type DilimHedef = Record; // Listenin ideal iskeleti: 5 hayal / 13 dengeli / 6 garanti = 24 tercih. 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 * 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. */ export function dilimHedefiHesapla(havuz: AdayProgram[]): DilimHedef { - const mevcut: DilimHedef = { hayal: 0, dengeli: 0, garanti: 0 }; - for (const p of havuz) mevcut[p.dilim]++; - - const toplamHedef = Math.min(HEDEF, havuz.length); + const mevcut = dilimSay(havuz); const hedef: DilimHedef = { hayal: Math.min(IDEAL_HEDEF.hayal, mevcut.hayal), dengeli: Math.min(IDEAL_HEDEF.dengeli, mevcut.dengeli), garanti: Math.min(IDEAL_HEDEF.garanti, mevcut.garanti), }; - - 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; - } + eksigiDagit(hedef, mevcut, Math.min(HEDEF, havuz.length)); return hedef; } // 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). 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[] { - const sayac: Record = { hayal: 0, dengeli: 0, garanti: 0 }; - return havuz.filter((p) => ++sayac[p.dilim] <= n); + const mevcut = dilimSay(havuz); + 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: {