diff --git a/src/features/pazarlama/components/huni-semasi.tsx b/src/features/pazarlama/components/huni-semasi.tsx index ee1f190..0bfc4f1 100644 --- a/src/features/pazarlama/components/huni-semasi.tsx +++ b/src/features/pazarlama/components/huni-semasi.tsx @@ -19,7 +19,7 @@ flowchart TD C --> P("Danışmana söylediklerin
öğretim dili, uzaktan/örgün, burs, yıllık ücret sınırı, evden uzaklık — sohbette söyleyip kaydettiklerin varsa havuzu bunlar da süzer") P --> H("Aday havuzu
elde kalan gerçek programlar — yalnızca en son tercih kılavuzunda yer alanlar
kapanan ya da kodu değişip eski kaydı kalan programlar önerilmez") H --> S{"Havuzda 24'ten az program mı kaldı?"} - S -- "evet" --> G("Filtreler adım adım gevşetilir
önce danışmana söylediklerin, sonra il, sonra alan — listede açıkça belirtilir") + S -- "evet" --> G("Filtreler adım adım gevşetilir
önce danışmana söylediklerin, sonra il, sonra alan — listede açıkça belirtilir
seçimine uyan programlar havuzda kalır, boşluk sıralamana en yakın diğer programlarla dolar") G --> H H --> U("Havuz yine de dar kalırsa
liste eldeki programlarla kurulur, dağılım havuza uyarlanır ve sana açıkça söylenir") S -- "hayır" --> Y("Yapay zekâ havuzdan en fazla 24 tercih seçer
havuzda olmayan bölümü yazamaz, her satıra gerekçe yazar") @@ -45,7 +45,7 @@ export function HuniSemasi() { return ( Havuza yalnızca en son tercih kılavuzunda yer alan programlar girer; diff --git a/src/features/pazarlama/components/meraklisina-icerik.tsx b/src/features/pazarlama/components/meraklisina-icerik.tsx index 947ccac..dabdec5 100644 --- a/src/features/pazarlama/components/meraklisina-icerik.tsx +++ b/src/features/pazarlama/components/meraklisina-icerik.tsx @@ -128,7 +128,10 @@ export function MeraklisinaIcerik() { gösterir — havuz 24'ün altına düşerse filtrelerin hangi sırayla gevşetildiği (önce danışmana söylediklerin, sonra il, sonra alan), yine de yetmezse listenin eldeki programlarla, sana - açıkça belirtilerek nasıl kurulduğu da dahil. + açıkça belirtilerek nasıl kurulduğu da dahil. Bir filtre + gevşediğinde seçimine uyan programlar havuzdan çıkmaz: önce onlar + alınır, kalan boşluk sıralamana en yakın diğer programlarla + doldurulur.

diff --git a/src/lib/rapor-havuzu.ts b/src/lib/rapor-havuzu.ts index aab3f16..afedc75 100644 --- a/src/lib/rapor-havuzu.ts +++ b/src/lib/rapor-havuzu.ts @@ -128,11 +128,14 @@ export function dilimHedefiHesapla(havuz: AdayProgram[]): DilimHedef { // modellerde prefill + isabet maliyeti yüksek (ölçüm: 6 Ağu 2026). const PROMPT_LIMIT_DILIM = 20; +const DILIM_SIRASI: Record = { hayal: 0, dengeli: 1, garanti: 2 }; + /** - * 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. + * Her dilimden en fazla n aday bırakır; dizideki sıra önceliktir (öndeki + * kalı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. Sonuç dilim sırasına ve dilim içinde yakından uzağa dizilir. */ function dilimBasinaKirp(havuz: AdayProgram[], n: number): AdayProgram[] { const mevcut = dilimSay(havuz); @@ -143,7 +146,34 @@ function dilimBasinaKirp(havuz: AdayProgram[], n: number): AdayProgram[] { }; 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]); + // hayal: sınıra en yakın (en büyük taban) önce; diğerleri en küçük taban önce + const yakinlik = (p: AdayProgram) => + p.dilim === "hayal" ? -(p.efektifSira ?? 0) : (p.efektifSira ?? 0); + return havuz + .filter((p) => ++sayac[p.dilim] <= pay[p.dilim]) + .sort( + (a, b) => + DILIM_SIRASI[a.dilim] - DILIM_SIRASI[b.dilim] || + yakinlik(a) - yakinlik(b), + ); +} + +/** + * Katmanları öncelik sırasıyla, tekrarsız birleştirir. Gevşetmede kullanılır: + * gevşeyen filtreye UYAN programlar önce gelir ve kırpmada korunur, kalan + * boşluk sonraki katmanlardan (sıraya en yakın diğer programlar) dolar. + */ +function oncelikliBirlestir(...katmanlar: AdayProgram[][]): AdayProgram[] { + const gorulen = new Set(); + const sonuc: AdayProgram[] = []; + for (const katman of katmanlar) { + for (const p of katman) { + if (gorulen.has(p.id)) continue; + gorulen.add(p.id); + sonuc.push(p); + } + } + return sonuc; } function dilimEkle(r: { @@ -167,6 +197,8 @@ function dilimEkle(r: { * * Süzgeçlerin hepsi dilimin TAMAMINA uygulanır, kırpma en sonda yapılır: * "24'ten az" kararı o alanda/ilde gerçekten kaç program olduğuna bakar. + * Bir filtre gevşediğinde ona uyan programlar havuzdan düşmez: önce onlar + * alınır, boşluk sıraya en yakın diğer programlarla dolar. */ export function havuzOlustur( params: RaporParams, @@ -196,6 +228,8 @@ export function havuzOlustur( // liste yine kurulur, gevşetme yapay zekâya ve adaya söylenir. const suzgec = profilSuzgeci(profil, iller.length > 0); const profilGevsetildi: Gevsetilen[] = []; + // Profil gevşerse profile uyan (24'ten az) programlar yine en önde tutulur + let profilliFiltreli: AdayProgram[] = []; if (suzgec.ozellik || suzgec.iller) { const profilliHam = ilFiltre( dilimEkle( @@ -206,7 +240,7 @@ export function havuzOlustur( ), iller.length > 0 ? iller : suzgec.iller, ); - const profilliFiltreli = kategoriFiltre(profilliHam); + profilliFiltreli = kategoriFiltre(profilliHam); if (profilliFiltreli.length >= HEDEF) { return { havuz: dilimBasinaKirp(profilliFiltreli, PROMPT_LIMIT_DILIM), @@ -222,35 +256,40 @@ export function havuzOlustur( ); const tamHam = ilFiltre(ilsizHam, iller); const tamFiltreli = kategoriFiltre(tamHam); + const kirp = (...katmanlar: AdayProgram[][]) => + dilimBasinaKirp( + oncelikliBirlestir(profilliFiltreli, ...katmanlar), + PROMPT_LIMIT_DILIM, + ); if (tamFiltreli.length >= HEDEF) { - return { - havuz: dilimBasinaKirp(tamFiltreli, PROMPT_LIMIT_DILIM), - gevsetildi: profilGevsetildi, - }; + return { havuz: kirp(tamFiltreli), gevsetildi: profilGevsetildi }; } - // 2. Kategori gevşet: aynı illerdeki kategori-dışı programlarla doldur + // 2. Kategori gevşet: seçili alandakiler kalır, boşluk aynı illerdeki + // kategori-dışı programlarla dolar if (kategoriler.length > 0 && tamHam.length >= HEDEF) { return { - havuz: dilimBasinaKirp(tamHam, PROMPT_LIMIT_DILIM), + havuz: kirp(tamFiltreli, tamHam), gevsetildi: [...profilGevsetildi, "kategori"], }; } - // 3. İl gevşet: il filtresini kaldırıp kategoriye geri dön + // 3. İl gevşet: seçili ildekiler kalır, boşluk aynı kategorideki başka + // illerin programlarıyla dolar const gevsetildi: Gevsetilen[] = [...profilGevsetildi]; if (iller.length > 0) gevsetildi.push("il"); const ilsizFiltreli = kategoriFiltre(ilsizHam); if (ilsizFiltreli.length >= HEDEF) { - return { - havuz: dilimBasinaKirp(ilsizFiltreli, PROMPT_LIMIT_DILIM), - gevsetildi, - }; + return { havuz: kirp(tamFiltreli, ilsizFiltreli), gevsetildi }; } - // 4. Hem il hem kategori gevşet: elde ne varsa + // 4. Hem il hem kategori gevşet: önce iki seçime de uyanlar, sonra alana + // uyanlar, sonra ile uyanlar, en son elde ne varsa if (kategoriler.length > 0) gevsetildi.push("kategori"); - return { havuz: dilimBasinaKirp(ilsizHam, PROMPT_LIMIT_DILIM), gevsetildi }; + return { + havuz: kirp(tamFiltreli, ilsizFiltreli, tamHam, ilsizHam), + gevsetildi, + }; } // Prompt'a veri penceresinin tamamı değil son 4 yılı gider (yeniden eskiye):