fix(havuz): "ilk N" ölçütü yalnız ilk N satırı belirler; öncelikliye kırpmada N+pay kota
"İlk 5 İstanbul" isteğinde öncelikliler dilim içinde sınırsız öne alınınca havuz 60/60 İstanbul oluyor, 24 satırın hepsi İstanbul'a kayıyordu. Kırpma artık öncelikliye yalnız adet+2 yer ayırır (dilimlere sırayla), kalan kota sihirbaz süzgecine uyan adaylara gider. revizyon-denetim ilk5-istanbul senaryosu kalan 19'un çoğunluğunu sihirbaz illerinden bekliyor; 5/5 geçti. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -114,15 +114,24 @@ function eklenenEsit(r: Rapor, n: number): string[] {
|
|||||||
return r.tani?.ilkNEklenen === n ? [] : [`tani.ilkNEklenen ${r.tani?.ilkNEklenen} ≠ ${n}`];
|
return r.tani?.ilkNEklenen === n ? [] : [`tani.ilkNEklenen ${r.tani?.ilkNEklenen} ≠ ${n}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** İlk N'den sonraki satırların çoğunluğu sihirbaz illerinden mi ("ilk N" listenin kalanını kaydırmaz). */
|
||||||
|
function kalanSihirbazdan(r: Rapor, adet: number, iller: string[]): string[] {
|
||||||
|
const kalan = r.rapor.tercihler.slice(adet);
|
||||||
|
const sihirbaz = kalan.filter((t) => iller.map(tr).includes(tr(r.rapor.programlar[t.programId]?.il))).length;
|
||||||
|
return sihirbaz * 2 > kalan.length ? [] : [`kalan ${kalan.length} satırın yalnız ${sihirbaz}'i sihirbaz illerinden (${iller.join("/")})`];
|
||||||
|
}
|
||||||
|
|
||||||
const SENARYOLAR: Senaryo[] = [
|
const SENARYOLAR: Senaryo[] = [
|
||||||
{
|
{
|
||||||
// Bilal'in 7 Eki denemesi. Not: İstanbul her dilimde 20+ programla havuz
|
// Bilal'in 7 Eki denemesi. İstanbul her dilimde 20+ programla havuzu tek
|
||||||
// kırpmasının öncelikli kotasını tek başına doldurur → havuz 60/60 İstanbul
|
// başına doldurabilirdi (eskiden 60/60 İstanbul → 24/24 İstanbul); şimdi
|
||||||
// olur, sahte uç öncelikli olmayan satır bulamaz, takas gerekmez
|
// kırpma öncelikliye yalnız N + pay yer ayırır (rapor-havuzu.ts
|
||||||
// (ilkNEklenen 0). Garantinin takas yolu "ilk5-takas" senaryosunda ölçülür.
|
// oncelikliyeKotaAyir), kalan havuz Ankara/İzmir'den kurulur. Sahte uç
|
||||||
|
// İstanbul'u atlar, kod 5'ini takaslar; kalan 19'un çoğunluğu sihirbaz
|
||||||
|
// illerinden olmalı ("ilk N yalnız ilk N'yi belirler" ürün kararı).
|
||||||
ad: "ilk5-istanbul",
|
ad: "ilk5-istanbul",
|
||||||
params: taban({ ilkTercihler: { adet: 5, iller: ["İSTANBUL"] }, not: "ilk 5 İstanbul" }),
|
params: taban({ ilkTercihler: { adet: 5, iller: ["İSTANBUL"] }, not: "ilk 5 İstanbul" }),
|
||||||
dogrula: (r) => ilkNIl(r, 5, "İSTANBUL"),
|
dogrula: (r) => [...ilkNIl(r, 5, "İSTANBUL"), ...eklenenEsit(r, 5), ...kalanSihirbazdan(r, 5, ["ANKARA", "İZMİR"])],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Takas yolu: Yalova 100.000 say'da havuza 0/3/8 (hayal/dengeli/güvenli)
|
// Takas yolu: Yalova 100.000 say'da havuza 0/3/8 (hayal/dengeli/güvenli)
|
||||||
|
|||||||
@@ -177,6 +177,54 @@ function dilimBasinaKirp(havuz: AdayProgram[], n: number): AdayProgram[] {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DILIMLER: Dilim[] = ["hayal", "dengeli", "garanti"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kırpmada "ilk N" ölçütüne uyan programlara ayrılan yer: N + pay. Pay,
|
||||||
|
* modelin ya da garanti takasının (revizyon-kurali.ts) N'yi bulabilmesi için
|
||||||
|
* küçük bir esneklik; fazlası kalan satırları da ölçüte kaydırırdı.
|
||||||
|
*/
|
||||||
|
const ONCELIKLI_PAY = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "İlk N" ölçütü yalnız ilk N satırı belirler; kalan satırlar sihirbaz
|
||||||
|
* seçimine göre kalır (ürün kararı, 7 Eki 2026). Önceden öncelikliler dilim
|
||||||
|
* içinde sınırsız öne alınıyordu: her dilimde 20+ programı olan il (İstanbul)
|
||||||
|
* havuzu 60/60 tek başına dolduruyor, "ilk 5 İstanbul" isteği 24 satırın
|
||||||
|
* hepsini İstanbul yapıyordu. Şimdi öncelikliye toplam `kota` yer ayrılır —
|
||||||
|
* dilimlere sırayla birer birer, adayı kalan dilime (hayal → dengeli →
|
||||||
|
* güvenli) — kalan kota sihirbaz süzgecine uyan sıradan adaylara gider;
|
||||||
|
* sıradan yetmezse boşluk yine öncelikliden dolar (dilimBasinaKirp öndekini
|
||||||
|
* tutar). Dizi sırası korunur: katman, sonra uyum/yakınlık.
|
||||||
|
*/
|
||||||
|
function oncelikliyeKotaAyir(
|
||||||
|
havuz: AdayProgram[],
|
||||||
|
oncelikli: (p: AdayProgram) => boolean,
|
||||||
|
kota: number,
|
||||||
|
): AdayProgram[] {
|
||||||
|
const one: Record<Dilim, AdayProgram[]> = { hayal: [], dengeli: [], garanti: [] };
|
||||||
|
const siradan: Record<Dilim, AdayProgram[]> = { hayal: [], dengeli: [], garanti: [] };
|
||||||
|
for (const p of havuz) (oncelikli(p) ? one : siradan)[p.dilim].push(p);
|
||||||
|
const pay: DilimHedef = { hayal: 0, dengeli: 0, garanti: 0 };
|
||||||
|
let kalan = kota;
|
||||||
|
while (kalan > 0) {
|
||||||
|
let verildi = false;
|
||||||
|
for (const d of DILIMLER) {
|
||||||
|
if (kalan > 0 && one[d].length > pay[d]) {
|
||||||
|
pay[d]++;
|
||||||
|
kalan--;
|
||||||
|
verildi = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!verildi) break;
|
||||||
|
}
|
||||||
|
return DILIMLER.flatMap((d) => [
|
||||||
|
...one[d].slice(0, pay[d]),
|
||||||
|
...siradan[d],
|
||||||
|
...one[d].slice(pay[d]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/** Revizyonun "ekle" kimlikleri: dilim evreninde bulunanlar, katman 0 ile. */
|
/** Revizyonun "ekle" kimlikleri: dilim evreninde bulunanlar, katman 0 ile. */
|
||||||
function ekstraSec(evren: AdayProgram[], idler?: readonly string[]): AdayProgram[] {
|
function ekstraSec(evren: AdayProgram[], idler?: readonly string[]): AdayProgram[] {
|
||||||
if (!idler?.length) return [];
|
if (!idler?.length) return [];
|
||||||
@@ -282,7 +330,9 @@ function dilimEkle(r: {
|
|||||||
* Sohbet revizyonunun havuza dokunan kısmı (lib/ai/rapor.ts kurar):
|
* Sohbet revizyonunun havuza dokunan kısmı (lib/ai/rapor.ts kurar):
|
||||||
* `cikar` eşleşenler hiçbir katmana girmez; `oncelikli` eşleşenler kırpmadan
|
* `cikar` eşleşenler hiçbir katmana girmez; `oncelikli` eşleşenler kırpmadan
|
||||||
* önce dilim içinde öne alınır ("ilk 5 İstanbul olsun" için İstanbul havuzda
|
* önce dilim içinde öne alınır ("ilk 5 İstanbul olsun" için İstanbul havuzda
|
||||||
* kalsın); `ekstraIdler` adayın açıkça istediği programlar — sihirbaz
|
* kalsın) — ama yalnız `params.revizyon.ilkTercihler.adet` + pay kadar yer
|
||||||
|
* alır (oncelikliyeKotaAyir), kalan havuz sihirbaz seçiminden kurulur;
|
||||||
|
* `ekstraIdler` adayın açıkça istediği programlar — sihirbaz
|
||||||
* süzgecine takılsa bile (baraj ve özel kontenjan kuralı yine geçer) havuza
|
* süzgecine takılsa bile (baraj ve özel kontenjan kuralı yine geçer) havuza
|
||||||
* kendi dilimiyle eklenir.
|
* kendi dilimiyle eklenir.
|
||||||
*/
|
*/
|
||||||
@@ -339,14 +389,22 @@ export function havuzOlustur(
|
|||||||
const ekstra = ekstraSec(ilsizHam, secenekler.ekstraIdler);
|
const ekstra = ekstraSec(ilsizHam, secenekler.ekstraIdler);
|
||||||
// Revizyon önceliği gevşetme katmanından sonra, uyum puanından önce gelir:
|
// Revizyon önceliği gevşetme katmanından sonra, uyum puanından önce gelir:
|
||||||
// kararlı sıralama, eşleşenler dilim içinde öne (kırpma öndekileri tutar).
|
// kararlı sıralama, eşleşenler dilim içinde öne (kırpma öndekileri tutar).
|
||||||
const oncelikle = (havuz: AdayProgram[]) =>
|
// "İlk N" adedi biliniyorsa öncelikliye yalnız N + pay yer ayrılır; kalan
|
||||||
secenekler.oncelikli
|
// kırpma kotası sihirbaz seçimine uyan sıradan adaylara kalır.
|
||||||
? [...havuz].sort(
|
const ilkNAdet = params.revizyon?.ilkTercihler?.adet ?? 0;
|
||||||
(a, b) =>
|
const oncelikliKota = ilkNAdet > 0 ? ilkNAdet + ONCELIKLI_PAY : null;
|
||||||
(a.katman ?? 0) - (b.katman ?? 0) ||
|
const oncelikle = (havuz: AdayProgram[]) => {
|
||||||
Number(secenekler.oncelikli!(b)) - Number(secenekler.oncelikli!(a)),
|
const oncelikli = secenekler.oncelikli;
|
||||||
)
|
if (!oncelikli) return havuz;
|
||||||
: havuz;
|
const sirali = [...havuz].sort(
|
||||||
|
(a, b) =>
|
||||||
|
(a.katman ?? 0) - (b.katman ?? 0) ||
|
||||||
|
Number(oncelikli(b)) - Number(oncelikli(a)),
|
||||||
|
);
|
||||||
|
return oncelikliKota == null
|
||||||
|
? sirali
|
||||||
|
: oncelikliyeKotaAyir(sirali, oncelikli, oncelikliKota);
|
||||||
|
};
|
||||||
// Ekstralar katman 0 ile başa; ötekilerin gevşetme katmanı korunur
|
// Ekstralar katman 0 ile başa; ötekilerin gevşetme katmanı korunur
|
||||||
const ekstraIdKumesi = new Set(ekstra.map((p) => p.id));
|
const ekstraIdKumesi = new Set(ekstra.map((p) => p.id));
|
||||||
const kirpVe = (havuz: AdayProgram[]) =>
|
const kirpVe = (havuz: AdayProgram[]) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user