diff --git a/src/lib/db.ts b/src/lib/db.ts index f173092..ef55926 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -239,6 +239,28 @@ export function searchByRank( }; } +/** + * Aday havuzunun ham girdisi: üç dilimin TAMAMI, yakından uzağa sıralı + * (limit yok). Alan (kategori) DB'de kolon değil, JS'te isim+fakülteden + * türüyor; süzgeç limitten SONRA uygulanınca sıraya en yakın satırlar her + * alandan karışık geldiği için seçilen alan olduğundan az görünüyordu. Bu + * yüzden havuz dilimin tamamını alır, alan/il süzgecini kendisi uygular ve + * en son kırpar (bkz. lib/rapor-havuzu.ts). En büyük puan türü ~9 bin satır. + */ +export function havuzAdaylari( + sira: number, + turKey: PuanTuruKey, + opts: { uniturGrubu?: UniturGrubu; ozellik?: OzellikSuzgeci } = {} +): Record { + // LIMIT -1: SQLite'ta "sınırsız" + const ortak = { ...opts, limit: -1 }; + return { + hayal: dilimAra(sira, turKey, "hayal", ortak).programlar, + dengeli: dilimAra(sira, turKey, "dengeli", ortak).programlar, + garanti: dilimAra(sira, turKey, "garanti", ortak).programlar, + }; +} + /** * Programa yerleşen son kişinin yıl bazlı netleri, yeniden eskiye sıralı. * Veri scripts/detay.ts ile YÖK Atlas /api/netler/search'ten doldurulur. diff --git a/src/lib/rapor-havuzu.ts b/src/lib/rapor-havuzu.ts index 296fc44..fc33807 100644 --- a/src/lib/rapor-havuzu.ts +++ b/src/lib/rapor-havuzu.ts @@ -1,6 +1,6 @@ // server-only guard'ı bilinçli yok: scripts/tadimlik-uret.ts bu modülü düz // node (tsx) altında import ediyor; server-only paketi orada fırlar. -import { searchByRank, type OzellikSuzgeci } from "./db"; +import { havuzAdaylari, type OzellikSuzgeci } from "./db"; import type { DanismanProfili } from "./danisman-profil"; import { yakinIller } from "./mesafe"; import { @@ -70,7 +70,6 @@ export function profilSuzgeci( } const HEDEF = 24; -const LIMIT = 60; export type DilimHedef = Record; @@ -111,8 +110,8 @@ export function dilimHedefiHesapla(havuz: AdayProgram[]): DilimHedef { } return hedef; } -// Prompt'a giden nihai havuz: dilim başına aday sayısı. Arama LIMIT ile geniş -// yapılır (filtre/gevşetme zenginliği için), filtre sonrası buraya kırpılır — +// Prompt'a giden nihai havuz: dilim başına aday sayısı. Arama dilimin tamamını +// alır (filtre/gevşetme gerçek sayıya baksın diye), filtre sonrası buraya kırpılır — // 20×3=60 program ≈ 8k token; 180 program 24k token tutuyor ve lokal/ücretsiz // modellerde prefill + isabet maliyeti yüksek (ölçüm: 6 Ağu 2026). const PROMPT_LIMIT_DILIM = 20; @@ -141,6 +140,9 @@ function dilimEkle(r: { * profiline (dil, öğretim türü, burs, ücret, km) göre filtreler; havuz 24'ün * altına düşerse kademeli gevşetir (önce profil, sonra il, sonra kategori) ki * liste her zaman kurulabilsin. /meraklisina şeması bu sırayı anlatı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. */ export function havuzOlustur( params: RaporParams, @@ -160,6 +162,10 @@ export function havuzOlustur( kategoriler.length > 0 ? havuz.filter((p) => kategoriEslesir(p.isim, kategoriler, p.fakulte)) : havuz; + const ilFiltre = (havuz: AdayProgram[], liste: string[] | undefined) => + liste && liste.length > 0 + ? havuz.filter((p) => p.il != null && liste.includes(p.il)) + : havuz; // 0. Profil süzgeci (danışmana söylenenler) — yalnız yeterli havuz bırakırsa. // Bırakmazsa profil gevşetilir ve aşağıdaki sihirbaz akışı aynen sürer; @@ -167,13 +173,14 @@ export function havuzOlustur( const suzgec = profilSuzgeci(profil, iller.length > 0); const profilGevsetildi: Gevsetilen[] = []; if (suzgec.ozellik || suzgec.iller) { - const profilliHam = dilimEkle( - searchByRank(params.sira, params.tur, { - iller: iller.length > 0 ? iller : suzgec.iller, - uniturGrubu, - ozellik: suzgec.ozellik, - limitPerBucket: LIMIT, - }), + const profilliHam = ilFiltre( + dilimEkle( + havuzAdaylari(params.sira, params.tur, { + uniturGrubu, + ozellik: suzgec.ozellik, + }), + ), + iller.length > 0 ? iller : suzgec.iller, ); const profilliFiltreli = kategoriFiltre(profilliHam); if (profilliFiltreli.length >= HEDEF) { @@ -185,14 +192,11 @@ export function havuzOlustur( profilGevsetildi.push("profil"); } - // 1. Tam filtre: iller + üniversite tipi (SQL), kategori (JS) - const tamHam = dilimEkle( - searchByRank(params.sira, params.tur, { - iller: iller.length > 0 ? iller : undefined, - uniturGrubu, - limitPerBucket: LIMIT, - }), + // 1. Tam filtre: üniversite tipi (SQL), iller + kategori (JS) + const ilsizHam = dilimEkle( + havuzAdaylari(params.sira, params.tur, { uniturGrubu }), ); + const tamHam = ilFiltre(ilsizHam, iller); const tamFiltreli = kategoriFiltre(tamHam); if (tamFiltreli.length >= HEDEF) { return { @@ -212,12 +216,6 @@ export function havuzOlustur( // 3. İl gevşet: il filtresini kaldırıp kategoriye geri dön const gevsetildi: Gevsetilen[] = [...profilGevsetildi]; if (iller.length > 0) gevsetildi.push("il"); - const ilsizHam = dilimEkle( - searchByRank(params.sira, params.tur, { - uniturGrubu, - limitPerBucket: LIMIT, - }), - ); const ilsizFiltreli = kategoriFiltre(ilsizHam); if (ilsizFiltreli.length >= HEDEF) { return {