From 34c8f90e26d5e41dbeb40d5d1045074219a2d7e2 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Sun, 9 Aug 2026 00:11:36 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20alan=20e=C5=9Flemesine=20fak=C3=BClte?= =?UTF-8?q?=20sinyali=20+=20il=20ad=C4=B1m=C4=B1nda=20t=C3=BCm=20iller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../pazarlama/components/huni-semasi.tsx | 6 +- .../components/meraklisina-icerik.tsx | 11 +- .../sihirbaz/components/sihirbaz-adimlar.tsx | 45 ++++++- src/lib/db.ts | 34 ++++-- src/lib/kategoriler.ts | 112 ++++++++++++++++-- src/lib/rapor-havuzu.ts | 2 +- src/lib/tercih-degerlendirme.ts | 2 + 7 files changed, 176 insertions(+), 36 deletions(-) diff --git a/src/features/pazarlama/components/huni-semasi.tsx b/src/features/pazarlama/components/huni-semasi.tsx index 48ee426..85557c5 100644 --- a/src/features/pazarlama/components/huni-semasi.tsx +++ b/src/features/pazarlama/components/huni-semasi.tsx @@ -11,8 +11,8 @@ config: look: handDrawn --- flowchart TD - T("Sıralamanla açılan tüm programlar") --> A("1. adım · Alan seçimi
yalnızca ulaşabildiğin alanlar listelenir") - A --> B("2. adım · İl seçimi
iller, seçtiğin alanlardaki program sayısına göre yeniden sayılır") + T("Sıralamanla açılan tüm programlar") --> A("1. adım · Alan seçimi
alanlar bölümün adına ve bağlı olduğu fakülteye göre belirlenir, yalnızca ulaşabildiklerin listelenir") + A --> B("2. adım · İl seçimi
programı olan tüm iller, seçtiğin alanlardaki program sayısına göre yeniden sayılır") B --> C("3. adım · Devlet / Vakıf
sayılar hem alanı hem ili hesaba katar") C --> H("Aday havuzu
elde kalan gerçek programlar") H --> S{"Havuzda 24'ten az program mı kaldı?"} @@ -42,7 +42,7 @@ export function HuniSemasi() { return ( Her adım havuzu biraz daha daraltır; yapay zekâ listeyi bu havuzun{" "} diff --git a/src/features/pazarlama/components/meraklisina-icerik.tsx b/src/features/pazarlama/components/meraklisina-icerik.tsx index 362039e..eecd75c 100644 --- a/src/features/pazarlama/components/meraklisina-icerik.tsx +++ b/src/features/pazarlama/components/meraklisina-icerik.tsx @@ -107,11 +107,14 @@ export function MeraklisinaIcerik() { “Yapay Zeka listemi kur” sihirbazındaki üç adım birbirinden bağımsız değildir. İlk adımda yalnızca{" "} sıralamanla ulaşabildiğin{" "} - alanlar listelenir; ikinci adımdaki iller, seçtiğin alanlardaki - program sayısına göre yeniden hesaplanır; üçüncü adımdaki + alanlar listelenir — bir bölümün hangi alana girdiği, adına ve + bağlı olduğu fakülteye bakılarak belirlenir. İkinci adımda + programı olan tüm iller, seçtiğin alanlardaki program sayısına + göre yeniden hesaplanarak listelenir; üçüncü adımdaki devlet/vakıf sayıları da hem alan hem il seçimini hesaba katar. - Adımlar bitince elde kalan gerçek programlar bir{" "} - aday havuzu{" "} + Seçtiğin il ve devlet/vakıf tercihi, sonuç sayfasındaki ücretsiz + tabloya da uygulanır. Adımlar bitince elde kalan gerçek + programlar bir aday havuzu{" "} olur ve yapay zekâya öyle gider: ona boş bir sayfa değil, bu havuz verilir. Aşağıdaki şema akışın tamamını gösterir — havuz 24'ün altına düşerse filtrelerin nasıl gevşetildiği, yine de diff --git a/src/features/sihirbaz/components/sihirbaz-adimlar.tsx b/src/features/sihirbaz/components/sihirbaz-adimlar.tsx index cbe6df1..8952528 100644 --- a/src/features/sihirbaz/components/sihirbaz-adimlar.tsx +++ b/src/features/sihirbaz/components/sihirbaz-adimlar.tsx @@ -93,6 +93,16 @@ export function SihirbazAdimlar({ () => [...canliFacetler.iller].sort((a, b) => b.adet - a.adet), [canliFacetler.iller], ); + // Türkiye illeri ana çip grubu; KKTC/yurtdışı kampüsler haritada olmadığı + // için ayrı alt grupta listelenir. + const turkiyeIlleri = useMemo( + () => siraliIller.filter((i) => HARITA_ILLERI.has(i.il)), + [siraliIller], + ); + const disIller = useMemo( + () => siraliIller.filter((i) => !HARITA_ILLERI.has(i.il)), + [siraliIller], + ); const haritaIlleri = useMemo( () => canliFacetler.iller.filter((i) => HARITA_ILLERI.has(i.il)), [canliFacetler.iller], @@ -258,13 +268,14 @@ export function SihirbazAdimlar({ {/* Çipler birincil seçim yolu (haritadaki küçük iller mobilde güvenilir dokunma hedefi değil); iller seçili kategorilere göre - süzülmüş, program sayısına göre sıralı listelenir. */} + süzülmüş, program sayısına göre sıralı listelenir. Penceresinde + programı olan TÜM iller görünür (kırpma yok). */}
- {siraliIller.map((i) => ( + {turkiyeIlleri.map((i) => ( + {disIller.length > 0 ? ( +
+

+ KKTC & Yurt Dışı +

+
+ {disIller.map((i) => ( + listeDegistir(iller, setIller, i.il, 5)} + > + {trBaslikDuzeni(i.il)} + {i.adet} + + ))} +
+
+ ) : null} + {iller.length > 0 ? (
@@ -359,6 +394,10 @@ export function SihirbazAdimlar({ ))}
+

+ Öncelikler yapay zekânın gerekçelerine ve program seçimine yön + verir; listeyi daraltmaz. +

diff --git a/src/lib/db.ts b/src/lib/db.ts index 71e8552..b6195d1 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -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(); - 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 }); diff --git a/src/lib/kategoriler.ts b/src/lib/kategoriler.ts index 05c7ccc..1449f33 100644 --- a/src/lib/kategoriler.ts +++ b/src/lib/kategoriler.ts @@ -26,7 +26,7 @@ const KATEGORI_KURALLARI: { kategori: Kategori; kural: RegExp }[] = [ { kategori: "Mühendislik & Teknoloji", kural: - /mühendis|yazılım|bilgisayar|bilişim|elektrik|elektronik|mekatronik|makine|makina|otomotiv|inşaat|endüstri|metalurji|malzeme|maden|jeoloji|jeofizik|petrol|enerji|robot|yapay zeka|yapay zekâ|siber|ağ teknolojileri|kontrol|otomasyon|uçak|havacılık|uzay|gemi|deniz ulaştırma|teknoloji|teknolojisi|programcı|elektrik-elektronik|nükleer|tekstil müh/, + /mühendis|yazılım|bilgisayar|bilişim|elektrik|elektronik|mekatronik|makine|makina|otomotiv|inşaat|endüstri|metalurji|malzeme|maden|jeoloji|jeofizik|petrol|enerji|robot|yapay zeka|yapay zekâ|siber|ağ teknolojileri|kontrol|otomasyon|uçak|havacılık|uzay|gemi|deniz ulaştırma|teknoloji|teknolojisi|bilgisayar programcı|elektrik-elektronik|nükleer|tekstil müh/, }, { kategori: "Sağlık & Tıp", @@ -35,13 +35,15 @@ const KATEGORI_KURALLARI: { kategori: Kategori; kural: RegExp }[] = [ }, { kategori: "Hukuk & Siyasal", + // "güvenlik" bilinçli olarak dar: jenerik hali Siber/Sosyal/Bilgi + // Güvenliği'ni yanlışlıkla bu kategoriye alıyordu. kural: - /hukuk|adalet|siyaset|kamu yönetim|uluslararası ilişki|siyasal|güvenlik|ceza infaz|milletlerarası|diplomasi/, + /hukuk|adalet|siyaset|kamu yönetim|uluslararası ilişki|siyasal|özel güvenlik|ceza infaz|milletlerarası|diplomasi/, }, { kategori: "İktisat & İşletme", kural: - /işletme|iktisat|ekonomi|maliye|muhasebe|finans|bankacılık|sigortacılık|dış ticaret|lojistik|pazarlama|yönetim bilişim|insan kaynak|çalışma ekonomisi|uluslararası ticaret|bankacılık ve sigortacılık|halkla ilişkiler|reklamcılık|emlak|menkul|gümrük|büro yönetim|yönetici asistan/, + /işletme|iktisat|iktisadi|ekonomi|maliye|muhasebe|finans|bankacılık|sigortacılık|sosyal güvenlik|dış ticaret|elektronik ticaret|lojistik|pazarlama|yönetim bilişim|havacılık yönetimi|enerji yönetimi|bilgi yönetimi|insan kaynak|çalışma ekonomisi|uluslararası ticaret|bankacılık ve sigortacılık|halkla ilişkiler|reklamcılık|emlak|menkul|gümrük|büro yönetim|yönetici asistan/, }, { kategori: "Eğitim & Öğretmenlik", @@ -55,13 +57,15 @@ const KATEGORI_KURALLARI: { kategori: Kategori; kural: RegExp }[] = [ }, { kategori: "Fen & Temel Bilimler", + // Jenerik "bilim" yok: "Siyaset Bilimi" / "İletişim Bilimleri" / + // "Dilbilimi" gibi programları yanlışlıkla Fen yapıyordu. kural: - /matematik|fizik|kimya|biyoloji|istatistik|moleküler|genetik|biyoteknoloji|astronomi|aktüerya|fen bilim|bilim/, + /matematik|fizik|kimya|biyoloji|istatistik|moleküler|genetik|biyoteknoloji|\bastronomi|aktüerya|fen bilim|veri bilim|adli bilim/, }, { kategori: "Sosyal & Beşeri Bilimler", kural: - /sosyoloji|tarih|coğrafya|felsefe|edebiyat|dil ve edebiyat|filoloji|arkeoloji|antropoloji|ilahiyat|din kültürü|dilbilim|sosyal hizmet|mütercim|çeviribilim|tercümanlık|türk dili|dünya dilleri|sanat tarihi|halkbilim|dinî/, + /sosyoloji|tarih|coğrafya|felsefe|edebiyat|dil ve edebiyat|filoloji|arkeoloji|antropoloji|ilahiyat|din kültürü|dilbilim|sosyal hizmet|sosyal bilim|islam|mütercim|çeviribilim|tercümanlık|türk dili|dünya dilleri|sanat tarihi|halkbilim|dinî/, }, { kategori: "Sanat, Medya & İletişim", @@ -79,24 +83,108 @@ const KATEGORI_KURALLARI: { kategori: Kategori; kural: RegExp }[] = [ }, { kategori: "Tarım & Doğa", + // "çevre" bağlamlı: jenerik hali "İç Mimarlık ve Çevre Tasarımı"nı + // yanlışlıkla Tarım'a alıyordu. kural: - /ziraat|tarım|tarla|bahçe|bitki|hayvan|su ürünleri|gıda|orman|çevre|balıkçılık|peyzaj|tohum|toprak|zootekni/, + /ziraat|tarım|tarla|bahçe|bitki|hayvan|su ürünleri|gıda|orman|çevre müh|çevre sağlığı|çevre koruma|çevre temizli|çevresel|balıkçılık|peyzaj|tohum|toprak|zootekni/, }, ]; -/** Verilen program adının girdiği tüm kategorileri döner (0..n). */ -export function kategoriBul(isim: string): Kategori[] { - const lower = isim.toLocaleLowerCase("tr-TR"); +// YÖK Atlas'ın kendi alan sinyali: programın bağlı olduğu fakülte adı. +// Lisansta güçlü ("Mühendislik Fakültesi", "Hukuk Fakültesi"); önlisansta +// jenerik ("... Meslek Yüksekokulu") olduğundan hiçbir kurala eşleşmez ve +// isim kuralları tek başına devrede kalır. Bir fakülte birden çok kategoriye +// eşlenebilir (ör. Fen-Edebiyat → Fen + Sosyal); program bazında ayrım +// isim∩fakülte kesişimiyle yapılır (bkz. kategoriBul). +const FAKULTE_KURALLARI: { kategori: Kategori; kural: RegExp }[] = [ + { + kategori: "Mühendislik & Teknoloji", + kural: /mühendis|teknoloji|bilişim|havacılık ve uzay|denizcilik|uygulamalı bilimler/, + }, + { + kategori: "Sağlık & Tıp", + kural: /tıp|sağlık|diş|eczacılık|hemşirelik|veteriner|spor bilim/, + }, + { + kategori: "Hukuk & Siyasal", + kural: /hukuk|siyasal|iktisadi|idari|güvenlik/, + }, + { + kategori: "İktisat & İşletme", + kural: /iktisadi|işletme|yönetim|ticaret|bankacılık|uygulamalı bilimler|finans/, + }, + { kategori: "Eğitim & Öğretmenlik", kural: /eğitim/ }, + { + kategori: "Mimarlık & Tasarım", + kural: /mimarlık|tasarım|güzel sanatlar/, + }, + { + kategori: "Fen & Temel Bilimler", + kural: /\bfen\b|fen-edebiyat|doğa bilimleri|temel bilimler/, + }, + { + kategori: "Sosyal & Beşeri Bilimler", + kural: + /edebiyat|fen-edebiyat|insan ve toplum|sosyal bilim|ilahiyat|islami|beşeri/, + }, + { + kategori: "Sanat, Medya & İletişim", + kural: /iletişim|güzel sanatlar|sanat|konservatuvar|müzik/, + }, + { + kategori: "Psikoloji & Danışmanlık", + kural: /insan ve toplum|edebiyat|fen-edebiyat|sosyal bilim|eğitim/, + }, + { kategori: "Turizm & Gastronomi", kural: /turizm|gastronomi/ }, + { + kategori: "Tarım & Doğa", + kural: /ziraat|orman|su ürünleri|tarım|doğa bilimleri|veteriner/, + }, +]; + +function eslesenKategoriler( + kurallar: { kategori: Kategori; kural: RegExp }[], + metin: string, +): Kategori[] { + const lower = metin.toLocaleLowerCase("tr-TR"); const sonuc: Kategori[] = []; - for (const { kategori, kural } of KATEGORI_KURALLARI) { + for (const { kategori, kural } of kurallar) { if (kural.test(lower)) sonuc.push(kategori); } return sonuc; } +/** + * Verilen programın girdiği tüm kategorileri döner (0..n). + * + * Fakülte verilirse isim∩fakülte kesişimi alınır: "Siber Güvenlik + * Mühendisliği" isimden {Mühendislik, Hukuk} çıkarken Mühendislik + * Fakültesi'nde olduğu için Hukuk elenir. Kesişim boşsa ya da fakülte + * hiçbir kurala eşleşmiyorsa (jenerik MYO adları) isim kuralları geçerlidir; + * fakültesiz çağrı bugüne kadarki davranışla birebir aynıdır. + */ +export function kategoriBul( + isim: string, + fakulte?: string | null, +): Kategori[] { + const isimKats = eslesenKategoriler(KATEGORI_KURALLARI, isim); + if (fakulte && isimKats.length > 0) { + const fakulteKats = eslesenKategoriler(FAKULTE_KURALLARI, fakulte); + if (fakulteKats.length > 0) { + const kesisim = isimKats.filter((k) => fakulteKats.includes(k)); + if (kesisim.length > 0) return kesisim; + } + } + return isimKats; +} + /** Bir programın seçili kategorilerden en az biriyle eşleşip eşleşmediği. */ -export function kategoriEslesir(isim: string, secililer: string[]): boolean { +export function kategoriEslesir( + isim: string, + secililer: string[], + fakulte?: string | null, +): boolean { if (secililer.length === 0) return true; - const kats = kategoriBul(isim); + const kats = kategoriBul(isim, fakulte); return kats.some((k) => secililer.includes(k)); } diff --git a/src/lib/rapor-havuzu.ts b/src/lib/rapor-havuzu.ts index 933c914..5453485 100644 --- a/src/lib/rapor-havuzu.ts +++ b/src/lib/rapor-havuzu.ts @@ -106,7 +106,7 @@ export function havuzOlustur(params: RaporParams): HavuzSonuc { const kategoriFiltre = (havuz: AdayProgram[]) => kategoriler.length > 0 - ? havuz.filter((p) => kategoriEslesir(p.isim, kategoriler)) + ? havuz.filter((p) => kategoriEslesir(p.isim, kategoriler, p.fakulte)) : havuz; // 1. Tam filtre: iller + üniversite tipi (SQL), kategori (JS) diff --git a/src/lib/tercih-degerlendirme.ts b/src/lib/tercih-degerlendirme.ts index 7a54aed..b9157da 100644 --- a/src/lib/tercih-degerlendirme.ts +++ b/src/lib/tercih-degerlendirme.ts @@ -29,6 +29,8 @@ export function tercihDegerlendir( const olumlu: string[] = []; const celiskiler: string[] = []; + // Manuel liste satırında fakülte taşınmıyor (ProgramListeSatiri bilinçli + // omit ediyor); eşleşme yalnızca isim kurallarıyla yapılır. if (kategoriEslesir(program.isim, profil.secimler.kategoriler)) { olumlu.push("İlgi alanlarınla uyumlu"); } else {