feat(liste): danışman profili havuzu süzer (önce profil gevşetilir) + KVKK metin düzeltmeleri
- db.ts OzellikSuzgeci (dil, yalnız örgün, yalnız burslu, ücret üst sınırı) program_ozellik alt sorgusuyla; tablo yoksa süzgeç yok. - rapor-havuzu: profilSuzgeci (ev ili + km → il listesi; sihirbazda il seçiliyse km uygulanmaz); havuz 24'ün altına düşerse gevşetme sırası profil → il → kategori. rapor.ts prompt'a 'danışmana söylediği tercihler' ve profil gevşetme notu; rapor-actions profili okuyup geçirir. - /meraklisina şeması (huni-semasi SEMA + aria-label + figcaption) ve 4. bölüm metni yeni adım ve gevşetme sırasıyla eşitlendi (AGENTS.md kuralı). - /api/soru: profil kaydı öncesi 'kaydedeyim mi?' onayı (PROFIL_ONAY_SOR). - Gizlilik metni kodla eşitlendi: OpenRouter→DeepSeek (Anthropic değil), profil satırı, amaç+hukuki sebep, saklama süreleri, md. 11 hakları, 18 yaş altı notu; unvan/adres ve aktarım dayanağı TODO. Giriş kartı: koşullar 'kabul', gizlilik 'okudum'. Sohbet girişine kişisel bilgi uyarısı. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,13 @@ import { PUAN_TURLERI } from "@/types/yokatlas";
|
|||||||
import { ACIK_SATIR } from "@/lib/rapor-maske";
|
import { ACIK_SATIR } from "@/lib/rapor-maske";
|
||||||
|
|
||||||
const MAX_GECMIS = 12;
|
const MAX_GECMIS = 12;
|
||||||
|
/**
|
||||||
|
* Profil kaydı öncesi "kaydedeyim mi?" onayı (KVKK Üretken YZ Rehberi:
|
||||||
|
* kişiselleştirme ayrı işleme faaliyeti; onaysız otomatik kayıt avukata
|
||||||
|
* sorulacak — docs/arastirma/2026-09-25-kisisellestirme/hukuk-ve-rakipler.md
|
||||||
|
* C.3/10). Bedeli: "evet" cevabı bir kredi. Kapatılırsa model doğrudan kaydeder.
|
||||||
|
*/
|
||||||
|
const PROFIL_ONAY_SOR = true;
|
||||||
const MAX_MESAJ_UZUNLUK = 2000;
|
const MAX_MESAJ_UZUNLUK = 2000;
|
||||||
|
|
||||||
function sistemPromptu(opts: {
|
function sistemPromptu(opts: {
|
||||||
@@ -63,9 +70,14 @@ KURALLAR:
|
|||||||
aktar, yuvarlama dışında değiştirme. Bir soruda en fazla 2-3 araç çağır.
|
aktar, yuvarlama dışında değiştirme. Bir soruda en fazla 2-3 araç çağır.
|
||||||
- PROFİL: aday bir tercih söylerse (yaşadığı il, evden en fazla kaç km,
|
- PROFİL: aday bir tercih söylerse (yaşadığı il, evden en fazla kaç km,
|
||||||
vakıfta yıllık ücret sınırı, burs beklentisi, öğretim dili, uzaktan/açık
|
vakıfta yıllık ücret sınırı, burs beklentisi, öğretim dili, uzaktan/açık
|
||||||
kabul mü, kampüs büyüklüğü) profil_guncelle ile kaydet ve cevabının
|
kabul mü, kampüs büyüklüğü) ${PROFIL_ONAY_SOR
|
||||||
sonunda tek cümleyle söyle: "Şunu kaydettim: …". Emin değilsen önce sor,
|
? `HEMEN KAYDETME: cevabının sonunda tek cümleyle sor: "Bunu profiline
|
||||||
tahminle kaydetme. Bunların DIŞINDA hiçbir şeyi kaydetme ve sorma:
|
kaydedeyim mi? (Listen ve aramalar buna göre süzülür; istediğin zaman
|
||||||
|
silebilirsin.)" Aday evet/kaydet derse o zaman profil_guncelle ile kaydet
|
||||||
|
ve "Kaydettim: …" de. Aday açıkça "kaydet" diye yazdıysa sormadan kaydet.`
|
||||||
|
: `profil_guncelle ile kaydet ve cevabının sonunda tek cümleyle söyle:
|
||||||
|
"Şunu kaydettim: …". Emin değilsen önce sor, tahminle kaydetme.`}
|
||||||
|
Bunların DIŞINDA hiçbir şeyi kaydetme ve sorma:
|
||||||
sağlık/engel, din, aile durumu, gelir, cinsiyet, siyasi görüş. Aday
|
sağlık/engel, din, aile durumu, gelir, cinsiyet, siyasi görüş. Aday
|
||||||
bunları kendiliğinden yazsa bile profile YAZMA; yalnız cevabında dikkate al.
|
bunları kendiliğinden yazsa bile profile YAZMA; yalnız cevabında dikkate al.
|
||||||
- Kadın/erkek öğrenci oranı üniversite genelidir (bölüm bazında veri yok);
|
- Kadın/erkek öğrenci oranı üniversite genelidir (bölüm bazında veri yok);
|
||||||
|
|||||||
@@ -12,8 +12,16 @@ export const metadata: Metadata = sayfaMetadata({
|
|||||||
path: "/gizlilik",
|
path: "/gizlilik",
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOT: MVP taslağıdır; yayına almadan önce hukuki gözden geçirme gerekir.
|
// NOT: 25 Eyl 2026 taslağı — v2 dalı. Yayına almadan önce hukuki gözden
|
||||||
|
// geçirme gerekir (docs/arastirma/2026-09-25-kisisellestirme/hukuk-ve-rakipler.md
|
||||||
|
// C.2). Kodun gerçekte yaptığıyla birebir örtüşmesi zorunludur: sağlayıcı,
|
||||||
|
// alanlar ya da süreler değişirse bu metin aynı commit'te güncellenir.
|
||||||
|
// TODO(bilal): veri sorumlusunun ticari unvanı ve adresi (Tebliğ md. 5/1-a).
|
||||||
|
// TODO(avukat): yurt dışı aktarımın dayanağı (standart sözleşme / Türkiye'de
|
||||||
|
// model) kesinleşince "Hizmet sağlayıcılar" bölümüne yazılacak; saklama
|
||||||
|
// süreleri onaylanacak.
|
||||||
export default function GizlilikPage() {
|
export default function GizlilikPage() {
|
||||||
|
const baslik = "font-heading text-lg font-bold text-slate-900";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="mx-auto w-full max-w-2xl px-4 py-16">
|
<main className="mx-auto w-full max-w-2xl px-4 py-16">
|
||||||
@@ -22,7 +30,7 @@ export default function GizlilikPage() {
|
|||||||
Gizlilik ve Kişisel Verilerin Korunması
|
Gizlilik ve Kişisel Verilerin Korunması
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 text-sm text-slate-500">
|
<p className="mt-2 text-sm text-slate-500">
|
||||||
Son güncelleme: 2 Ağustos 2026
|
Son güncelleme: 25 Eylül 2026
|
||||||
</p>
|
</p>
|
||||||
</Parallax>
|
</Parallax>
|
||||||
|
|
||||||
@@ -33,66 +41,187 @@ export default function GizlilikPage() {
|
|||||||
strength={4}
|
strength={4}
|
||||||
className="mt-8 space-y-6 text-sm leading-7 text-slate-700"
|
className="mt-8 space-y-6 text-sm leading-7 text-slate-700"
|
||||||
>
|
>
|
||||||
<section>
|
<section className="rounded-2xl border border-slate-200 bg-slate-50 p-5">
|
||||||
<h2 className="font-heading text-lg font-bold text-slate-900">
|
<h2 className={baslik}>Kısa özet</h2>
|
||||||
Hangi verileri işliyoruz?
|
<ul className="mt-2 list-disc space-y-1 pl-5">
|
||||||
</h2>
|
<li>
|
||||||
<p className="mt-2">
|
Senden yalnız e-posta, başarı sıralaman, sihirbaz seçimlerin,
|
||||||
KolayTercih; e-posta adresini (hesap oluşturma ve giriş için), YKS
|
danışmana yazdıkların ve danışmana söyleyip kaydettiğin
|
||||||
başarı sıralamanı ve sihirbazda yaptığın seçimleri (tercih listeni
|
tercihler alınır. Ad, kimlik numarası, adres, cinsiyet, sağlık
|
||||||
üretmek için), danışman sohbetine yazdığın mesajları (sorularını
|
ya da gelir bilgisi istenmez.
|
||||||
yanıtlamak için) ve ödeme yaptığında sipariş kayıtlarını işler.
|
</li>
|
||||||
Kart bilgilerin bize hiç ulaşmaz; ödeme iyzico altyapısında
|
<li>
|
||||||
gerçekleşir.
|
Bunlar yalnız tercih listeni üretmek ve sorularını yanıtlamak
|
||||||
</p>
|
için kullanılır; satılmaz, reklam için kullanılmaz.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Danışmana yazdıkların yanıt üretilmesi için yurt dışındaki
|
||||||
|
yapay zekâ sağlayıcısına gider; adın ve e-postan gitmez.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Kaydedilen tercihlerini listem sayfasında görür, tek tıkla
|
||||||
|
silersin. Hesabını sildirmek için yazman yeterli.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
18 yaşından küçüksen bu metni velinle birlikte oku.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2 className="font-heading text-lg font-bold text-slate-900">
|
<h2 className={baslik}>Veri sorumlusu</h2>
|
||||||
Ne için kullanıyoruz?
|
|
||||||
</h2>
|
|
||||||
<p className="mt-2">
|
<p className="mt-2">
|
||||||
Verilerin yalnızca tercih önerileri üretmek, hesabını ve kredi
|
Bu sitedeki kişisel verilerin veri sorumlusu KolayTercih’tir
|
||||||
bakiyeni yönetmek ve sana hizmetle ilgili e-posta (giriş
|
[şirket unvanı ve adresi eklenecek]. Sorularını ve başvurularını{" "}
|
||||||
bağlantısı, ödeme bilgilendirmesi) göndermek için kullanılır.
|
|
||||||
Verilerini üçüncü taraflara satmayız, reklam amacıyla paylaşmayız.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2 className="font-heading text-lg font-bold text-slate-900">
|
|
||||||
Hizmet sağlayıcılar ve yurt dışına aktarım
|
|
||||||
</h2>
|
|
||||||
<p className="mt-2">
|
|
||||||
Hizmeti çalıştırabilmek için birkaç dış sağlayıcıdan yararlanıyoruz
|
|
||||||
ve verinin bir kısmı yalnızca ilgili iş için bu sağlayıcılara
|
|
||||||
iletilir. Giriş bağlantısı ve bilgilendirme e-postaları Resend
|
|
||||||
üzerinden gönderilir; e-posta adresin bu amaçla Avrupa
|
|
||||||
Birliği’ndeki (İrlanda) sunucularda işlenir. Ödemeler,
|
|
||||||
Türkiye’de yerleşik iyzico altyapısında gerçekleşir. Danışman
|
|
||||||
sohbetine yazdığın mesajlar, yanıt üretilebilmesi için
|
|
||||||
Anthropic’in yapay zekâ servisine (ABD) iletilir; bu mesajlar
|
|
||||||
sağlayıcının yapay zekâ modellerini eğitmek için kullanılmaz.
|
|
||||||
KVKK açısından bunlardan e-posta ve yapay zekâ sağlayıcısına yapılan
|
|
||||||
iletim yurt dışına aktarım sayılır ve hizmetin sunulabilmesi için
|
|
||||||
zorunludur.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2 className="font-heading text-lg font-bold text-slate-900">
|
|
||||||
Saklama ve silme
|
|
||||||
</h2>
|
|
||||||
<p className="mt-2">
|
|
||||||
Hesabını sildirmek veya verilerinin bir kopyasını istemek için{" "}
|
|
||||||
<Link href="/iletisim" className="text-primary underline">
|
<Link href="/iletisim" className="text-primary underline">
|
||||||
iletişim sayfasındaki
|
iletişim sayfasındaki
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
adresten bize yazabilirsin; talepler 30 gün içinde
|
e-posta adresine iletebilirsin.
|
||||||
sonuçlandırılır.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2 className="font-heading text-lg font-bold text-slate-900">
|
<h2 className={baslik}>Hangi verileri, ne için, hangi dayanakla işliyoruz?</h2>
|
||||||
Çerezler ve analitik
|
<ul className="mt-2 list-disc space-y-2 pl-5">
|
||||||
</h2>
|
<li>
|
||||||
|
<strong>E-posta adresin</strong> — hesap oluşturmak, giriş
|
||||||
|
bağlantısı ve ödeme bilgilendirmesi göndermek için.
|
||||||
|
Dayanak: seninle kurulan hizmet sözleşmesinin kurulması ve
|
||||||
|
ifası.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>YKS başarı sıralaman ve sihirbaz seçimlerin</strong>{" "}
|
||||||
|
(ilgi alanı, il, devlet/vakıf, öncelikler) — 24’lük tercih
|
||||||
|
listeni üretmek için. Dayanak: sözleşmenin ifası.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Danışman sohbetine yazdığın mesajlar</strong> —
|
||||||
|
sorularını listen bağlamında yanıtlamak için. Dayanak:
|
||||||
|
sözleşmenin ifası. Sohbete ad, telefon, kimlik numarası,
|
||||||
|
sağlık ya da aile bilgisi yazma; danışmanın bunlara ihtiyacı
|
||||||
|
yok ve bunları saklamak istemiyoruz.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Danışman profilin</strong> — sohbette söylediğin ve{" "}
|
||||||
|
<em>“kaydedeyim mi?”</em> sorusuna evet dediğin
|
||||||
|
tercihler: yaşadığın il, evden en fazla kaç km, vakıfta yıllık
|
||||||
|
ücret üst sınırı, burs beklentin, öğretim dili, uzaktan/açık
|
||||||
|
öğretim kabulün, kampüs büyüklüğü tercihin. Bu alanlar sabit
|
||||||
|
bir listedir; serbest metin kaydedilmez, sağlık, din, gelir ya
|
||||||
|
da cinsiyet gibi bilgiler bu profile hiçbir zaman yazılmaz.
|
||||||
|
Profil yalnız senin listeni ve danışman aramalarını süzmek için
|
||||||
|
kullanılır; reklam, pazarlama ya da e-posta için kullanılmaz.
|
||||||
|
Profil boşken de hizmet tam çalışır. Listem sayfasındaki
|
||||||
|
“Kaydettiklerim” çiplerinden her alanı görür ve tek
|
||||||
|
tıkla silersin. Dayanak: senin talimatınla sözleşmenin ifası.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Sipariş kayıtların</strong> (paket alırsan) — fatura
|
||||||
|
ve muhasebe yükümlülüğü için. Kart bilgilerin bize hiç ulaşmaz;
|
||||||
|
ödeme iyzico altyapısında gerçekleşir. Dayanak: hukuki
|
||||||
|
yükümlülük.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Kullanım ölçümü</strong> — hangi sayfaların
|
||||||
|
görüntülendiği ve hangi adımların tamamlandığı (aşağıda
|
||||||
|
“Çerezler ve analitik”). Dayanak: siteyi
|
||||||
|
geliştirmeye yönelik meşru menfaatimiz.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-2">
|
||||||
|
Verilerini üçüncü taraflara satmayız, reklam amacıyla
|
||||||
|
paylaşmayız. Program ve üniversite bilgileri (taban sıralamaları,
|
||||||
|
ücretler, öğrenci sayıları, kadın/erkek oranı) kişisel veri
|
||||||
|
değildir; YÖK Atlas ve YÖK İstatistik’ten alınır ve
|
||||||
|
sayfalarda kaynağıyla gösterilir.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className={baslik}>Hizmet sağlayıcılar ve yurt dışına aktarım</h2>
|
||||||
|
<p className="mt-2">
|
||||||
|
Hizmeti çalıştırabilmek için birkaç dış sağlayıcıdan yararlanıyoruz
|
||||||
|
ve verinin yalnız ilgili iş için gereken kısmı bu sağlayıcılara
|
||||||
|
iletilir:
|
||||||
|
</p>
|
||||||
|
<ul className="mt-2 list-disc space-y-2 pl-5">
|
||||||
|
<li>
|
||||||
|
<strong>Resend (İrlanda)</strong> — giriş bağlantısı ve
|
||||||
|
bilgilendirme e-postaları; e-posta adresin bu amaçla Avrupa
|
||||||
|
Birliği’ndeki sunucularda işlenir.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>iyzico (Türkiye)</strong> — ödeme.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Yapay zekâ sağlayıcısı</strong> — danışman sohbetine
|
||||||
|
yazdığın mesajlar, listendeki program bilgileri ve kaydettiğin
|
||||||
|
tercihler, yanıt üretilebilmesi için OpenRouter (ABD) aracılığıyla
|
||||||
|
kullandığımız dil modelinin sağlayıcısına (bugün DeepSeek)
|
||||||
|
iletilir. Adın ve e-postan modele gönderilmez. Tercih listeni
|
||||||
|
üreten yapay zekâ da aynı yoldan çalışır; ona sıralaman,
|
||||||
|
seçimlerin ve aday program havuzu gider.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-2">
|
||||||
|
KVKK açısından e-posta ve yapay zekâ sağlayıcısına yapılan iletim
|
||||||
|
yurt dışına aktarımdır ve hizmetin sunulabilmesi için gereklidir.
|
||||||
|
Sağlayıcı değişirse bu bölüm aynı gün güncellenir.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className={baslik}>Saklama ve silme</h2>
|
||||||
|
<ul className="mt-2 list-disc space-y-1 pl-5">
|
||||||
|
<li>
|
||||||
|
Sohbet mesajların ve danışman profilin: hesabın açık kaldığı
|
||||||
|
sürece; her ikisini de listem sayfasından kendin silebilirsin.
|
||||||
|
Tercih döneminin bitiminden 12 ay sonra sohbet mesajları ve
|
||||||
|
profil silinir.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Hesabını sildirme ya da verilerinin kopyasını isteme talebin
|
||||||
|
30 gün içinde sonuçlandırılır; işlenme sebebi ortadan kalkan
|
||||||
|
veri en geç 3 ay içinde silinir.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Sipariş ve fatura kayıtları vergi mevzuatı gereği 10 yıl
|
||||||
|
saklanır.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className={baslik}>Hakların</h2>
|
||||||
|
<p className="mt-2">
|
||||||
|
KVKK’nın 11. maddesi gereği bize başvurarak şunları
|
||||||
|
yapabilirsin: verilerinin işlenip işlenmediğini öğrenmek,
|
||||||
|
işlendiyse bilgi istemek, amacına uygun kullanılıp
|
||||||
|
kullanılmadığını öğrenmek, aktarıldığı üçüncü kişileri bilmek,
|
||||||
|
eksik ya da yanlış işlenmişse düzeltilmesini istemek,
|
||||||
|
silinmesini ya da yok edilmesini istemek, bu işlemlerin
|
||||||
|
aktarıldığı üçüncü kişilere bildirilmesini istemek, yalnızca
|
||||||
|
otomatik sistemlerle analiz edilmesi sonucu aleyhine bir sonuç
|
||||||
|
çıkmasına itiraz etmek ve kanuna aykırı işleme yüzünden zarara
|
||||||
|
uğradıysan tazminat istemek. Başvurunu{" "}
|
||||||
|
<Link href="/iletisim" className="text-primary underline">
|
||||||
|
iletişim sayfasındaki
|
||||||
|
</Link>{" "}
|
||||||
|
e-posta adresine yaz; en geç 30 gün içinde yanıtlarız.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className={baslik}>18 yaşından küçüksen</h2>
|
||||||
|
<p className="mt-2">
|
||||||
|
Bu metni velinle birlikte oku. Paket satın alınacaksa ödemeyi
|
||||||
|
velin yapmalı. Verilerin hiçbir şekilde pazarlama için
|
||||||
|
kullanılmaz.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className={baslik}>Çerezler ve analitik</h2>
|
||||||
<p className="mt-2">
|
<p className="mt-2">
|
||||||
Yalnızca oturumunu açık tutmak için zorunlu çerezler kullanılır;
|
Yalnızca oturumunu açık tutmak için zorunlu çerezler kullanılır;
|
||||||
reklam veya izleme çerezi kullanılmaz. Siteyi geliştirebilmek için
|
reklam veya izleme çerezi kullanılmaz. Siteyi geliştirebilmek için
|
||||||
@@ -105,7 +234,8 @@ export default function GizlilikPage() {
|
|||||||
yalnızca sorun giderme ile destek amacıyla kullanılır. Başarı
|
yalnızca sorun giderme ile destek amacıyla kullanılır. Başarı
|
||||||
sıralaman analitiğe ham hâliyle
|
sıralaman analitiğe ham hâliyle
|
||||||
gönderilmez; yalnızca “50-100 bin” gibi geniş bir
|
gönderilmez; yalnızca “50-100 bin” gibi geniş bir
|
||||||
aralık olarak kaydedilir.
|
aralık olarak kaydedilir. Danışman profilindeki değerler
|
||||||
|
analitiğe gitmez; yalnız hangi alanın kaydedildiği sayılır.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</Parallax>
|
</Parallax>
|
||||||
|
|||||||
@@ -77,15 +77,17 @@ export async function GirisKarti({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-6 text-center text-xs leading-relaxed text-slate-500">
|
<p className="mt-6 text-center text-xs leading-relaxed text-slate-500">
|
||||||
Giriş yaparak verilerinin tercih önerileri üretmek amacıyla{" "}
|
{/* Aydınlatma "okudum" (bilgilendirme), koşullar "kabul": KVKK İlke
|
||||||
<Link href="/gizlilik" className="underline hover:text-slate-700">
|
Kararı 2026/347 aydınlatma ile rızayı ayırır (hukuk-ve-rakipler.md C.2/8). */}
|
||||||
gizlilik metnindeki
|
Giriş yaparak{" "}
|
||||||
</Link>{" "}
|
|
||||||
gibi işlenmesini ve{" "}
|
|
||||||
<Link href="/kosullar" className="underline hover:text-slate-700">
|
<Link href="/kosullar" className="underline hover:text-slate-700">
|
||||||
kullanım koşullarını
|
kullanım koşullarını
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
kabul etmiş olursun. KolayTercih yerleşme garantisi vermez.
|
kabul etmiş ve verilerinin nasıl işlendiğini anlatan{" "}
|
||||||
|
<Link href="/gizlilik" className="underline hover:text-slate-700">
|
||||||
|
gizlilik metnini
|
||||||
|
</Link>{" "}
|
||||||
|
okumuş olursun. KolayTercih yerleşme garantisi vermez.
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ flowchart TD
|
|||||||
T("Sıralamanla açılan tüm programlar") --> A("1. adım · Alan seçimi<br/>alanlar bölümün adına ve bağlı olduğu fakülteye göre belirlenir, yalnızca ulaşabildiklerin listelenir")
|
T("Sıralamanla açılan tüm programlar") --> A("1. adım · Alan seçimi<br/>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<br/>programı olan tüm iller, seçtiğin alanlardaki program sayısına göre yeniden sayılır")
|
A --> B("2. adım · İl seçimi<br/>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<br/>sayılar hem alanı hem ili hesaba katar")
|
B --> C("3. adım · Devlet / Vakıf<br/>sayılar hem alanı hem ili hesaba katar")
|
||||||
C --> H("Aday havuzu<br/>elde kalan gerçek programlar — yalnızca en son tercih kılavuzunda yer alanlar<br/>kapanan ya da kodu değişip eski kaydı kalan programlar önerilmez")
|
C --> P("Danışmana söylediklerin<br/>öğ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<br/>elde kalan gerçek programlar — yalnızca en son tercih kılavuzunda yer alanlar<br/>kapanan ya da kodu değişip eski kaydı kalan programlar önerilmez")
|
||||||
H --> S{"Havuzda 24'ten az program mı kaldı?"}
|
H --> S{"Havuzda 24'ten az program mı kaldı?"}
|
||||||
S -- "evet" --> G("Filtreler adım adım gevşetilir<br/>önce il, sonra alan — listede açıkça belirtilir")
|
S -- "evet" --> G("Filtreler adım adım gevşetilir<br/>önce danışmana söylediklerin, sonra il, sonra alan — listede açıkça belirtilir")
|
||||||
G --> H
|
G --> H
|
||||||
H --> U("Havuz yine de dar kalırsa<br/>liste eldeki programlarla kurulur, dağılım havuza uyarlanır ve sana açıkça söylenir")
|
H --> U("Havuz yine de dar kalırsa<br/>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<br/>havuzda olmayan bölümü yazamaz, her satıra gerekçe yazar")
|
S -- "hayır" --> Y("Yapay zekâ havuzdan en fazla 24 tercih seçer<br/>havuzda olmayan bölümü yazamaz, her satıra gerekçe yazar")
|
||||||
@@ -34,7 +35,7 @@ flowchart TD
|
|||||||
classDef hayal fill:#fee2e2,stroke:#ef4444,color:#991b1b
|
classDef hayal fill:#fee2e2,stroke:#ef4444,color:#991b1b
|
||||||
classDef dengeli fill:#fef3c7,stroke:#f59e0b,color:#92400e
|
classDef dengeli fill:#fef3c7,stroke:#f59e0b,color:#92400e
|
||||||
classDef garanti fill:#d1fae5,stroke:#10b981,color:#065f46
|
classDef garanti fill:#d1fae5,stroke:#10b981,color:#065f46
|
||||||
class H,Y havuz
|
class H,Y,P havuz
|
||||||
class G,U uyari
|
class G,U uyari
|
||||||
class L1 hayal
|
class L1 hayal
|
||||||
class L2 dengeli
|
class L2 dengeli
|
||||||
@@ -44,12 +45,14 @@ export function HuniSemasi() {
|
|||||||
return (
|
return (
|
||||||
<ElCizimiSema
|
<ElCizimiSema
|
||||||
tanim={SEMA}
|
tanim={SEMA}
|
||||||
ariaLabel="Sihirbaz akış şeması: sıralamanla açılan programlar alan, il ve devlet/vakıf adımlarıyla aday havuzuna daraltılır; havuza yalnızca en son tercih kılavuzunda yer alan programlar girer, kapanan ya da kodu değişip eski kaydı kalan programlar önerilmez; alanlar bölümün adına ve bağlı olduğu fakülteye göre belirlenir, il adımında programı olan tüm iller listelenir; havuz 24'ün altına düşerse filtreler gevşetilir, yine de dar kalırsa liste eldeki programlarla kurulur ve dağılım havuza uyarlanır; yapay zekâ havuzdan üstte hayal, ortada dengeli, sonda güvenli olacak şekilde en fazla 24 tercih seçip her satıra gerekçe yazar; risk notu ve trend özeti gerçek sıralama geçmişinden hesaplanır."
|
ariaLabel="Sihirbaz akış şeması: sıralamanla açılan programlar alan, il ve devlet/vakıf adımlarıyla, danışmana söyleyip kaydettiğin tercihler (öğretim dili, uzaktan/örgün, burs, yıllık ücret sınırı, evden uzaklık) varsa onlarla da aday havuzuna daraltılır; havuza yalnızca en son tercih kılavuzunda yer alan programlar girer, kapanan ya da kodu değişip eski kaydı kalan programlar önerilmez; alanlar bölümün adına ve bağlı olduğu fakülteye göre belirlenir, il adımında programı olan tüm iller listelenir; havuz 24'ün altına düşerse filtreler önce danışmana söylediklerin, sonra il, sonra alan sırasıyla gevşetilir, yine de dar kalırsa liste eldeki programlarla kurulur ve dağılım havuza uyarlanır; yapay zekâ havuzdan üstte hayal, ortada dengeli, sonda güvenli olacak şekilde en fazla 24 tercih seçip her satıra gerekçe yazar; risk notu ve trend özeti gerçek sıralama geçmişinden hesaplanır."
|
||||||
figcaption={
|
figcaption={
|
||||||
<>
|
<>
|
||||||
Havuza yalnızca en son tercih kılavuzunda yer alan programlar girer;
|
Havuza yalnızca en son tercih kılavuzunda yer alan programlar girer;
|
||||||
kapanan ya da kodu değişip eski kaydı kalan programlar önerilmez. Her
|
kapanan ya da kodu değişip eski kaydı kalan programlar önerilmez. Her
|
||||||
adım havuzu biraz daha daraltır; yapay zekâ listeyi bu havuzun{" "}
|
adım havuzu biraz daha daraltır; danışmana söyleyip kaydettiğin
|
||||||
|
tercihler de (dil, ücret, uzaklık gibi) havuzu süzer. Yapay zekâ
|
||||||
|
listeyi bu havuzun{" "}
|
||||||
<span className="font-medium text-slate-700">
|
<span className="font-medium text-slate-700">
|
||||||
dışına asla çıkmadan
|
dışına asla çıkmadan
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
|
|||||||
@@ -114,15 +114,20 @@ export function MeraklisinaIcerik() {
|
|||||||
göre yeniden hesaplanarak listelenir; üçüncü adımdaki
|
göre yeniden hesaplanarak listelenir; üçüncü adımdaki
|
||||||
devlet/vakıf sayıları da hem alan hem il seçimini hesaba katar.
|
devlet/vakıf sayıları da hem alan hem il seçimini hesaba katar.
|
||||||
Seçtiğin il ve devlet/vakıf tercihi, sonuç sayfasındaki ücretsiz
|
Seçtiğin il ve devlet/vakıf tercihi, sonuç sayfasındaki ücretsiz
|
||||||
tabloya da uygulanır. Adımlar bitince elde kalan gerçek
|
tabloya da uygulanır. Danışmanla konuşurken söylediğin ve
|
||||||
|
“Kaydettiklerim” çiplerinde duran tercihler (öğretim
|
||||||
|
dili, uzaktan ya da örgün, burs, yıllık ücret sınırı, evden
|
||||||
|
uzaklık) varsa havuzu onlar da süzer; bir çipi silersen bir
|
||||||
|
sonraki liste onsuz kurulur. Adımlar bitince elde kalan gerçek
|
||||||
programlar bir <strong>aday havuzu</strong>{" "}
|
programlar bir <strong>aday havuzu</strong>{" "}
|
||||||
olur ve yapay zekâya öyle gider: ona boş bir sayfa değil, bu havuz
|
olur ve yapay zekâya öyle gider: ona boş bir sayfa değil, bu havuz
|
||||||
verilir. Havuza yalnızca{" "}
|
verilir. Havuza yalnızca{" "}
|
||||||
<strong>en son tercih kılavuzunda yer alan</strong>{" "}
|
<strong>en son tercih kılavuzunda yer alan</strong>{" "}
|
||||||
programlar girer: kapanmış ya da kodu değiştiği için eski kaydı
|
programlar girer: kapanmış ya da kodu değiştiği için eski kaydı
|
||||||
kalmış bir program sana önerilmez. Aşağıdaki şema akışın tamamını
|
kalmış bir program sana önerilmez. Aşağıdaki şema akışın tamamını
|
||||||
gösterir — havuz 24'ün altına düşerse filtrelerin nasıl
|
gösterir — havuz 24'ün altına düşerse filtrelerin hangi
|
||||||
gevşetildiği, yine de yetmezse listenin eldeki programlarla, sana
|
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.
|
||||||
</p>
|
</p>
|
||||||
<HuniSemasi />
|
<HuniSemasi />
|
||||||
|
|||||||
@@ -663,6 +663,11 @@ export function SohbetClient({
|
|||||||
</PromptInputAction>
|
</PromptInputAction>
|
||||||
</PromptInputActions>
|
</PromptInputActions>
|
||||||
</PromptInput>
|
</PromptInput>
|
||||||
|
{/* KVKK: sohbete kişisel bilgi yazılmasın (hukuk-ve-rakipler.md C.5/22) */}
|
||||||
|
<p className="mt-2 px-1 text-xs leading-5 text-slate-500">
|
||||||
|
Ad, telefon, kimlik numarası ya da sağlık bilgisi yazma; danışmanın
|
||||||
|
bunlara ihtiyacı yok.
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
uretimKilidiBirak,
|
uretimKilidiBirak,
|
||||||
} from "@/lib/uretim-kilidi";
|
} from "@/lib/uretim-kilidi";
|
||||||
import { getSession, getCurrentUser } from "@/lib/session";
|
import { getSession, getCurrentUser } from "@/lib/session";
|
||||||
|
import { profilGetir } from "@/lib/danisman-profil-db";
|
||||||
import { appDb, schema } from "@/lib/appdb";
|
import { appDb, schema } from "@/lib/appdb";
|
||||||
import { kullanicininRaporu } from "@/features/rapor/rapor-queries";
|
import { kullanicininRaporu } from "@/features/rapor/rapor-queries";
|
||||||
import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
|
import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
|
||||||
@@ -214,7 +215,9 @@ async function listeOlusturKilitli(
|
|||||||
|
|
||||||
let sonuc: RaporSonuc;
|
let sonuc: RaporSonuc;
|
||||||
try {
|
try {
|
||||||
sonuc = await raporUret(params);
|
// Danışmana söylenen tercihler (varsa) havuzu süzer; okunamazsa profilsiz üret
|
||||||
|
const profil = await profilGetir(user.id).catch(() => undefined);
|
||||||
|
sonuc = await raporUret(params, undefined, undefined, profil);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Üretim başarısız → krediyi iade et. Satır paketlide de yazılır (delta 0):
|
// Üretim başarısız → krediyi iade et. Satır paketlide de yazılır (delta 0):
|
||||||
// yukarıdaki DUPLICATE dalı "önceki deneme başarısız mıydı" sorusunu tam
|
// yukarıdaki DUPLICATE dalı "önceki deneme başarısız mıydı" sorusunu tam
|
||||||
@@ -347,10 +350,12 @@ export async function listeRevize(input: {
|
|||||||
|
|
||||||
let sonuc: RaporSonuc;
|
let sonuc: RaporSonuc;
|
||||||
try {
|
try {
|
||||||
|
const profil = await profilGetir(user.id).catch(() => undefined);
|
||||||
sonuc = await raporUret(
|
sonuc = await raporUret(
|
||||||
params,
|
params,
|
||||||
feedback.slice(0, 1000),
|
feedback.slice(0, 1000),
|
||||||
listeOzetiCikar(onceki, params.sira),
|
listeOzetiCikar(onceki, params.sira),
|
||||||
|
profil,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// İade satırı burada da yazılır (paketlide delta 0) — DUPLICATE dalındaki
|
// İade satırı burada da yazılır (paketlide delta 0) — DUPLICATE dalındaki
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { profilSatirlari, type DanismanProfili } from "@/lib/danisman-profil";
|
||||||
import "server-only";
|
import "server-only";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import type {
|
import type {
|
||||||
@@ -231,8 +232,10 @@ export async function raporUret(
|
|||||||
params: RaporParams,
|
params: RaporParams,
|
||||||
revizyonNotu?: string,
|
revizyonNotu?: string,
|
||||||
oncekiListeOzeti?: string,
|
oncekiListeOzeti?: string,
|
||||||
|
profil?: DanismanProfili,
|
||||||
): Promise<RaporSonuc> {
|
): Promise<RaporSonuc> {
|
||||||
const { havuz, gevsetildi } = havuzOlustur(params);
|
const { havuz, gevsetildi } = havuzOlustur(params, profil);
|
||||||
|
const profilSatir = profil ? profilSatirlari(profil) : [];
|
||||||
if (havuz.length < MIN_LISTE) {
|
if (havuz.length < MIN_LISTE) {
|
||||||
throw new RaporUretimHatasi(
|
throw new RaporUretimHatasi(
|
||||||
"Bu sıralama ve filtrelerle yeterli program bulunamadı. Daha geniş kategori veya il seçmeyi dene.",
|
"Bu sıralama ve filtrelerle yeterli program bulunamadı. Daha geniş kategori veya il seçmeyi dene.",
|
||||||
@@ -252,6 +255,11 @@ export async function raporUret(
|
|||||||
(params.universiteTipi && params.universiteTipi !== "farketmez");
|
(params.universiteTipi && params.universiteTipi !== "farketmez");
|
||||||
|
|
||||||
const gevsetmeNotu: string[] = [];
|
const gevsetmeNotu: string[] = [];
|
||||||
|
if (gevsetildi.includes("profil")) {
|
||||||
|
gevsetmeNotu.push(
|
||||||
|
"Adayın danışmana söylediği tercihlerle (aşağıda) yeterli program yoktu; havuz bu tercihler uygulanmadan kuruldu. Uyanları öne al ve uyarılarda bunu tek cümleyle söyle.",
|
||||||
|
);
|
||||||
|
}
|
||||||
if (gevsetildi.includes("il")) {
|
if (gevsetildi.includes("il")) {
|
||||||
gevsetmeNotu.push(
|
gevsetmeNotu.push(
|
||||||
`Adayın seçtiği illerde yeterli program yoktu; havuza başka illerden de program eklendi. Seçili illere öncelik ver ama listeyi ${toplamHedef}'e tamamlamak için diğerlerini de kullan.`,
|
`Adayın seçtiği illerde yeterli program yoktu; havuza başka illerden de program eklendi. Seçili illere öncelik ver ama listeyi ${toplamHedef}'e tamamlamak için diğerlerini de kullan.`,
|
||||||
@@ -280,6 +288,9 @@ export async function raporUret(
|
|||||||
})}\nSeçili kategori ve illere öncelik ver; havuzun elverdiği ölçüde bu seçimlere sadık kal.`
|
})}\nSeçili kategori ve illere öncelik ver; havuzun elverdiği ölçüde bu seçimlere sadık kal.`
|
||||||
: null,
|
: null,
|
||||||
params.notlar ? `Adayın ek notları: ${params.notlar}` : null,
|
params.notlar ? `Adayın ek notları: ${params.notlar}` : null,
|
||||||
|
profilSatir.length > 0
|
||||||
|
? `ADAYIN DANIŞMANA SÖYLEDİĞİ TERCİHLER (havuz buna göre süzüldü; gerekçelerde uyanı belirt):\n${profilSatir.map((s) => `- ${s.metin}`).join("\n")}`
|
||||||
|
: null,
|
||||||
gevsetmeNotu.length > 0 ? gevsetmeNotu.join("\n") : null,
|
gevsetmeNotu.length > 0 ? gevsetmeNotu.join("\n") : null,
|
||||||
revizyonNotu
|
revizyonNotu
|
||||||
? `REVİZYON İSTEĞİ: Aday mevcut listede şu değişikliği istiyor: ${revizyonNotu}\nÖnceki listenin özeti: ${oncekiListeOzeti ?? "yok"}`
|
? `REVİZYON İSTEĞİ: Aday mevcut listede şu değişikliği istiyor: ${revizyonNotu}\nÖnceki listenin özeti: ${oncekiListeOzeti ?? "yok"}`
|
||||||
|
|||||||
@@ -8,6 +8,11 @@
|
|||||||
* Sorulmayanlar: cinsiyet, sağlık/engel, gelir. Ücret üst sınırı sorulur, gelir
|
* Sorulmayanlar: cinsiyet, sağlık/engel, gelir. Ücret üst sınırı sorulur, gelir
|
||||||
* sorulmaz. Konum il düzeyindedir (adres/koordinat değil).
|
* sorulmaz. Konum il düzeyindedir (adres/koordinat değil).
|
||||||
*
|
*
|
||||||
|
* KULLANIM SINIRI (karar, 25 Eyl 2026): profil yalnız liste üretimi
|
||||||
|
* (rapor-havuzu) ve danışman aramalarında kullanılır; pazarlama, e-posta,
|
||||||
|
* segmentleme ya da analitik değerinde KULLANILMAZ — kullanılırsa açık rıza
|
||||||
|
* konusu olur (hukuk-ve-rakipler.md C.3/13). Profil boşken hizmet tam çalışır.
|
||||||
|
*
|
||||||
* Saf modül: client, server ve scripts aynı dosyayı okur.
|
* Saf modül: client, server ve scripts aynı dosyayı okur.
|
||||||
*/
|
*/
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|||||||
@@ -75,6 +75,48 @@ export function programSatiri(row: ProgramHamSatiri): Program {
|
|||||||
* sıralamasından küçükse geçen yıl oraya yerleşmek daha iyi dereceyle mümkündü
|
* sıralamasından küçükse geçen yıl oraya yerleşmek daha iyi dereceyle mümkündü
|
||||||
* demektir (hayal), büyükse daha güvenli demektir (garanti).
|
* demektir (hayal), büyükse daha güvenli demektir (garanti).
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Danışman profilinden gelen program özelliği süzgeci (program_ozellik
|
||||||
|
* tablosu; bkz. lib/program-ozellik.ts). Tablo yoksa süzgeç uygulanmaz.
|
||||||
|
* Devlet programlarında ücret/burs NULL'dur ve her zaman geçer.
|
||||||
|
*/
|
||||||
|
export type OzellikSuzgeci = {
|
||||||
|
dil?: "Türkçe" | "İngilizce";
|
||||||
|
yalnizOrgun?: boolean;
|
||||||
|
yalnizBurslu?: boolean;
|
||||||
|
ucretUst?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ozellikTablosuVar: boolean | null = null;
|
||||||
|
|
||||||
|
function ozellikFiltreSql(o?: OzellikSuzgeci): { sql: string; args: (string | number)[] } {
|
||||||
|
if (!o) return { sql: "", args: [] };
|
||||||
|
const kosul: string[] = [];
|
||||||
|
const args: (string | number)[] = [];
|
||||||
|
if (o.dil) {
|
||||||
|
kosul.push("ogrenim_dili LIKE ?");
|
||||||
|
args.push(`${o.dil}%`);
|
||||||
|
}
|
||||||
|
if (o.yalnizOrgun) kosul.push("(ogrenim_turu IS NULL OR ogrenim_turu = 'Örgün Öğretim')");
|
||||||
|
if (o.yalnizBurslu) kosul.push("(burs_orani IS NULL OR burs_orani = 'Burslu')");
|
||||||
|
if (o.ucretUst) {
|
||||||
|
kosul.push("(ucret IS NULL OR burs_orani = 'Burslu' OR ucret <= ?)");
|
||||||
|
args.push(o.ucretUst);
|
||||||
|
}
|
||||||
|
if (kosul.length === 0) return { sql: "", args: [] };
|
||||||
|
if (ozellikTablosuVar == null) {
|
||||||
|
ozellikTablosuVar =
|
||||||
|
getDb()
|
||||||
|
.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='program_ozellik'")
|
||||||
|
.get() !== undefined;
|
||||||
|
}
|
||||||
|
if (!ozellikTablosuVar) return { sql: "", args: [] };
|
||||||
|
return {
|
||||||
|
sql: `AND id IN (SELECT id FROM program_ozellik WHERE ${kosul.join(" AND ")})`,
|
||||||
|
args,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function uniturFiltreSql(grup?: UniturGrubu): { sql: string; args: string[] } {
|
function uniturFiltreSql(grup?: UniturGrubu): { sql: string; args: string[] } {
|
||||||
if (grup === "devlet") return { sql: "AND unitur = ?", args: ["DEVLET"] };
|
if (grup === "devlet") return { sql: "AND unitur = ?", args: ["DEVLET"] };
|
||||||
if (grup === "vakif")
|
if (grup === "vakif")
|
||||||
@@ -112,6 +154,7 @@ export function dilimAra(
|
|||||||
il?: string;
|
il?: string;
|
||||||
iller?: string[];
|
iller?: string[];
|
||||||
uniturGrubu?: UniturGrubu;
|
uniturGrubu?: UniturGrubu;
|
||||||
|
ozellik?: OzellikSuzgeci;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
} = {}
|
} = {}
|
||||||
@@ -132,16 +175,17 @@ export function dilimAra(
|
|||||||
? `AND il IN (${illerListe.map(() => "?").join(",")})`
|
? `AND il IN (${illerListe.map(() => "?").join(",")})`
|
||||||
: "";
|
: "";
|
||||||
const unitur = uniturFiltreSql(opts.uniturGrubu);
|
const unitur = uniturFiltreSql(opts.uniturGrubu);
|
||||||
|
const ozellik = ozellikFiltreSql(opts.ozellik);
|
||||||
|
|
||||||
const { alt, ust } = dilimAraligi(sira, dilim);
|
const { alt, ust } = dilimAraligi(sira, dilim);
|
||||||
if (ust != null && ust < alt) return { programlar: [], toplam: 0 };
|
if (ust != null && ust < alt) return { programlar: [], toplam: 0 };
|
||||||
const siraKosulu =
|
const siraKosulu =
|
||||||
ust != null ? `${EFEKTIF_SIRA} BETWEEN ? AND ?` : `${EFEKTIF_SIRA} >= ?`;
|
ust != null ? `${EFEKTIF_SIRA} BETWEEN ? AND ?` : `${EFEKTIF_SIRA} >= ?`;
|
||||||
const siraArgs = ust != null ? [alt, ust] : [alt];
|
const siraArgs = ust != null ? [alt, ust] : [alt];
|
||||||
const args = [tur, onlisans, ...illerListe, ...unitur.args, ...siraArgs];
|
const args = [tur, onlisans, ...illerListe, ...unitur.args, ...ozellik.args, ...siraArgs];
|
||||||
|
|
||||||
const base = `FROM programs
|
const base = `FROM programs
|
||||||
WHERE tur = ? AND onlisans = ? ${ilFiltre} ${unitur.sql}
|
WHERE tur = ? AND onlisans = ? ${ilFiltre} ${unitur.sql} ${ozellik.sql}
|
||||||
AND ${EFEKTIF_SIRA} IS NOT NULL
|
AND ${EFEKTIF_SIRA} IS NOT NULL
|
||||||
AND ${siraKosulu}`;
|
AND ${siraKosulu}`;
|
||||||
// hayal: sınıra en yakın (en büyük taban) önce; diğerleri en yakından uzağa
|
// hayal: sınıra en yakın (en büyük taban) önce; diğerleri en yakından uzağa
|
||||||
@@ -168,6 +212,7 @@ export function searchByRank(
|
|||||||
il?: string;
|
il?: string;
|
||||||
iller?: string[];
|
iller?: string[];
|
||||||
uniturGrubu?: UniturGrubu;
|
uniturGrubu?: UniturGrubu;
|
||||||
|
ozellik?: OzellikSuzgeci;
|
||||||
limitPerBucket?: number;
|
limitPerBucket?: number;
|
||||||
} = {}
|
} = {}
|
||||||
): RankResults {
|
): RankResults {
|
||||||
@@ -175,6 +220,7 @@ export function searchByRank(
|
|||||||
il: opts.il,
|
il: opts.il,
|
||||||
iller: opts.iller,
|
iller: opts.iller,
|
||||||
uniturGrubu: opts.uniturGrubu,
|
uniturGrubu: opts.uniturGrubu,
|
||||||
|
ozellik: opts.ozellik,
|
||||||
limit: opts.limitPerBucket ?? 12,
|
limit: opts.limitPerBucket ?? 12,
|
||||||
};
|
};
|
||||||
const hayal = dilimAra(sira, turKey, "hayal", ortak);
|
const hayal = dilimAra(sira, turKey, "hayal", ortak);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// server-only guard'ı bilinçli yok: scripts/tadimlik-uret.ts bu modülü düz
|
// 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.
|
// node (tsx) altında import ediyor; server-only paketi orada fırlar.
|
||||||
import { searchByRank } from "./db";
|
import { searchByRank, type OzellikSuzgeci } from "./db";
|
||||||
|
import type { DanismanProfili } from "./danisman-profil";
|
||||||
|
import { yakinIller } from "./mesafe";
|
||||||
import {
|
import {
|
||||||
PUAN_TURLERI,
|
PUAN_TURLERI,
|
||||||
type Program,
|
type Program,
|
||||||
@@ -16,10 +18,55 @@ export interface AdayProgram extends Program {
|
|||||||
dilim: Dilim;
|
dilim: Dilim;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Gevsetilen = "profil" | "kategori" | "il";
|
||||||
|
|
||||||
export interface HavuzSonuc {
|
export interface HavuzSonuc {
|
||||||
havuz: AdayProgram[];
|
havuz: AdayProgram[];
|
||||||
// Havuz 24'e ulaşmak için hangi filtreler gevşetildi
|
// Havuz 24'e ulaşmak için hangi filtreler gevşetildi (sırayla)
|
||||||
gevsetildi: ("kategori" | "il")[];
|
gevsetildi: Gevsetilen[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Danışman profilinden sert süzgeç: dil, öğretim türü, burs, ücret üst sınırı
|
||||||
|
* (program_ozellik) ve ev iline km sınırı (il listesi). Sihirbazda il
|
||||||
|
* seçilmişse km sınırı uygulanmaz — sihirbaz seçimi daha yeni ve açık.
|
||||||
|
*/
|
||||||
|
export function profilSuzgeci(
|
||||||
|
profil: DanismanProfili | undefined,
|
||||||
|
sihirbazIlVar: boolean,
|
||||||
|
): { ozellik?: OzellikSuzgeci; iller?: string[]; ozet: string[] } {
|
||||||
|
if (!profil) return { ozet: [] };
|
||||||
|
const ozellik: OzellikSuzgeci = {};
|
||||||
|
const ozet: string[] = [];
|
||||||
|
if (profil.dil === "ingilizce" || profil.dil === "turkce") {
|
||||||
|
ozellik.dil = profil.dil === "ingilizce" ? "İngilizce" : "Türkçe";
|
||||||
|
ozet.push(`öğretim dili ${ozellik.dil}`);
|
||||||
|
}
|
||||||
|
if (profil.ogretimTuru === "yalniz-orgun") {
|
||||||
|
ozellik.yalnizOrgun = true;
|
||||||
|
ozet.push("yalnız örgün öğretim");
|
||||||
|
}
|
||||||
|
if (profil.burs === "yalniz-burslu") {
|
||||||
|
ozellik.yalnizBurslu = true;
|
||||||
|
ozet.push("vakıfta yalnız burslu");
|
||||||
|
}
|
||||||
|
if (profil.ucretUst) {
|
||||||
|
ozellik.ucretUst = profil.ucretUst;
|
||||||
|
ozet.push(`yıllık ücret en fazla ${profil.ucretUst.toLocaleString("tr-TR")} TL`);
|
||||||
|
}
|
||||||
|
let iller: string[] | undefined;
|
||||||
|
if (!sihirbazIlVar && profil.evIl && profil.maxKm) {
|
||||||
|
const yakin = yakinIller(profil.evIl, profil.maxKm);
|
||||||
|
if (yakin.length > 0) {
|
||||||
|
iller = yakin;
|
||||||
|
ozet.push(`${profil.evIl} iline en fazla ${profil.maxKm} km`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ozellik: Object.keys(ozellik).length ? ozellik : undefined,
|
||||||
|
iller,
|
||||||
|
ozet,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const HEDEF = 24;
|
const HEDEF = 24;
|
||||||
@@ -89,12 +136,16 @@ function dilimEkle(r: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deterministik aday havuzu: Claude bu havuzdan SEÇER, asla uydurmaz.
|
* Deterministik aday havuzu: yapay zekâ bu havuzdan SEÇER, asla uydurmaz.
|
||||||
* Sihirbaz seçimlerine (kategori/il/üniversite tipi) göre filtreler; havuz
|
* Sihirbaz seçimlerine (kategori/il/üniversite tipi) ve varsa danışman
|
||||||
* 24'ün altına düşerse kademeli gevşetir (önce il, sonra kategori) ki liste
|
* profiline (dil, öğretim türü, burs, ücret, km) göre filtreler; havuz 24'ün
|
||||||
* her zaman kurulabilsin.
|
* 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.
|
||||||
*/
|
*/
|
||||||
export function havuzOlustur(params: RaporParams): HavuzSonuc {
|
export function havuzOlustur(
|
||||||
|
params: RaporParams,
|
||||||
|
profil?: DanismanProfili,
|
||||||
|
): HavuzSonuc {
|
||||||
const uniturGrubu =
|
const uniturGrubu =
|
||||||
params.universiteTipi && params.universiteTipi !== "farketmez"
|
params.universiteTipi && params.universiteTipi !== "farketmez"
|
||||||
? params.universiteTipi
|
? params.universiteTipi
|
||||||
@@ -110,6 +161,30 @@ export function havuzOlustur(params: RaporParams): HavuzSonuc {
|
|||||||
? havuz.filter((p) => kategoriEslesir(p.isim, kategoriler, p.fakulte))
|
? havuz.filter((p) => kategoriEslesir(p.isim, kategoriler, p.fakulte))
|
||||||
: havuz;
|
: 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;
|
||||||
|
// liste yine kurulur, gevşetme yapay zekâya ve adaya söylenir.
|
||||||
|
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 profilliFiltreli = kategoriFiltre(profilliHam);
|
||||||
|
if (profilliFiltreli.length >= HEDEF) {
|
||||||
|
return {
|
||||||
|
havuz: dilimBasinaKirp(profilliFiltreli, PROMPT_LIMIT_DILIM),
|
||||||
|
gevsetildi: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
profilGevsetildi.push("profil");
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Tam filtre: iller + üniversite tipi (SQL), kategori (JS)
|
// 1. Tam filtre: iller + üniversite tipi (SQL), kategori (JS)
|
||||||
const tamHam = dilimEkle(
|
const tamHam = dilimEkle(
|
||||||
searchByRank(params.sira, params.tur, {
|
searchByRank(params.sira, params.tur, {
|
||||||
@@ -122,7 +197,7 @@ export function havuzOlustur(params: RaporParams): HavuzSonuc {
|
|||||||
if (tamFiltreli.length >= HEDEF) {
|
if (tamFiltreli.length >= HEDEF) {
|
||||||
return {
|
return {
|
||||||
havuz: dilimBasinaKirp(tamFiltreli, PROMPT_LIMIT_DILIM),
|
havuz: dilimBasinaKirp(tamFiltreli, PROMPT_LIMIT_DILIM),
|
||||||
gevsetildi: [],
|
gevsetildi: profilGevsetildi,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,12 +205,12 @@ export function havuzOlustur(params: RaporParams): HavuzSonuc {
|
|||||||
if (kategoriler.length > 0 && tamHam.length >= HEDEF) {
|
if (kategoriler.length > 0 && tamHam.length >= HEDEF) {
|
||||||
return {
|
return {
|
||||||
havuz: dilimBasinaKirp(tamHam, PROMPT_LIMIT_DILIM),
|
havuz: dilimBasinaKirp(tamHam, PROMPT_LIMIT_DILIM),
|
||||||
gevsetildi: ["kategori"],
|
gevsetildi: [...profilGevsetildi, "kategori"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. İl gevşet: il filtresini kaldırıp kategoriye geri dön
|
// 3. İl gevşet: il filtresini kaldırıp kategoriye geri dön
|
||||||
const gevsetildi: ("kategori" | "il")[] = [];
|
const gevsetildi: Gevsetilen[] = [...profilGevsetildi];
|
||||||
if (iller.length > 0) gevsetildi.push("il");
|
if (iller.length > 0) gevsetildi.push("il");
|
||||||
const ilsizHam = dilimEkle(
|
const ilsizHam = dilimEkle(
|
||||||
searchByRank(params.sira, params.tur, {
|
searchByRank(params.sira, params.tur, {
|
||||||
|
|||||||
Reference in New Issue
Block a user