feat: alan eşlemesine fakülte sinyali + il adımında tüm iller

Sihirbazın alan çipleri artık YÖK Atlas'ın kendi verisiyle süzülüyor:
kategoriBul program adı kurallarını fakülte kurallarıyla kesiştiriyor
(Siber Güvenlik Müh. artık Hukuk değil, Siyaset Bilimi artık Fen değil).
Aşırı geniş isim regexleri sıkılaştırıldı (bilim/güvenlik/programcı/çevre
+ gastronomi'nin içindeki "astronomi" tuzağı) — sayısalda Hukuk & Siyasal,
sözelde Mühendislik çipi tamamen kalktı.

İl adımındaki .slice(0,15) kırpması kaldırıldı: penceresinde programı olan
TÜM iller listelenir; KKTC/yurtdışı kampüsler ayrı çip grubunda. Haritanın
15-limit yüzünden yanlış "program yok" grilemesi kendiliğinden düzeldi.

Öncelik çiplerine şeffaflık notu eklendi (listeyi daraltmaz, AI'ya yön
verir). /meraklisina şeması ve anlatımı aynı davranışla senkronlandı.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-08-09 00:11:36 +03:00
parent ea85a053c5
commit 34c8f90e26
7 changed files with 176 additions and 36 deletions

View File

@@ -196,31 +196,38 @@ export function rankWindowFacets(
const satirlar = db
.prepare(
`SELECT isim, il, unitur FROM programs
`SELECT isim, fakulte, il, unitur FROM programs
WHERE tur = ? AND onlisans = ? AND ${EFEKTIF_SIRA} IS NOT NULL
AND ${EFEKTIF_SIRA} >= ?`
)
.all(tur, onlisans, altSinir) as {
isim: string;
fakulte: string | null;
il: string | null;
unitur: string | null;
}[];
// isim → kategori eşlemesi pahalı (12 regex); tekrar eden isimler için önbellek
// isim+fakülte → kategori eşlemesi pahalı (regexler); tekrar edenler için önbellek
const kategoriCache = new Map<string, string[]>();
const kategorileriGetir = (isim: string): string[] => {
let k = kategoriCache.get(isim);
const kategorileriGetir = (
isim: string,
fakulte: string | null
): string[] => {
const anahtar = `${isim}|${fakulte ?? ""}`;
let k = kategoriCache.get(anahtar);
if (!k) {
k = kategoriBul(isim);
kategoriCache.set(isim, k);
k = kategoriBul(isim, fakulte);
kategoriCache.set(anahtar, k);
}
return k;
};
const seciliKategoriler = secim.kategoriler ?? [];
const kategoriUyar = (isim: string) =>
const kategoriUyar = (isim: string, fakulte: string | null) =>
seciliKategoriler.length === 0 ||
kategorileriGetir(isim).some((k) => seciliKategoriler.includes(k));
kategorileriGetir(isim, fakulte).some((k) =>
seciliKategoriler.includes(k)
);
const seciliIller = new Set(secim.iller ?? []);
const ilUyar = (il: string | null) =>
seciliIller.size === 0 || (il != null && seciliIller.has(il));
@@ -230,12 +237,12 @@ export function rankWindowFacets(
let devlet = 0;
let vakif = 0;
for (const { isim, il, unitur } of satirlar) {
for (const { isim, fakulte, il, unitur } of satirlar) {
// 1. adım: kategoriler yalnızca pencereye bağlı (filtre uygulanmaz)
for (const k of kategorileriGetir(isim)) {
for (const k of kategorileriGetir(isim, fakulte)) {
kategoriSay.set(k, (kategoriSay.get(k) ?? 0) + 1);
}
if (!kategoriUyar(isim)) continue;
if (!kategoriUyar(isim, fakulte)) continue;
// 2. adım: iller seçili kategorilerle süzülür
if (il) ilSay.set(il, (ilSay.get(il) ?? 0) + 1);
if (!ilUyar(il)) continue;
@@ -251,10 +258,11 @@ export function rankWindowFacets(
.filter((k) => k.adet >= 3) // gürültüyü ele
.sort((a, b) => b.adet - a.adet);
// Penceredeki programı olan TÜM iller döner (81 il + KKTC/yurtdışı);
// sıralama/gruplama UI'da yapılır. Kırpma yok: kullanıcı her ili görebilmeli.
const iller = [...ilSay.entries()]
.map(([il, adet]) => ({ il, adet }))
.sort((a, b) => b.adet - a.adet)
.slice(0, 15);
.sort((a, b) => b.adet - a.adet);
const uniturler: { grup: UniturGrubu; adet: number }[] = [];
if (devlet > 0) uniturler.push({ grup: "devlet", adet: devlet });