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:
bilalgursen
2026-09-25 01:23:14 +03:00
parent aa2051732d
commit 5a04db7e4b
11 changed files with 385 additions and 86 deletions

View File

@@ -77,15 +77,17 @@ export async function GirisKarti({
</div>
</div>
<p className="mt-6 text-center text-xs leading-relaxed text-slate-500">
Giriş yaparak verilerinin tercih önerileri üretmek amacıyla{" "}
<Link href="/gizlilik" className="underline hover:text-slate-700">
gizlilik metnindeki
</Link>{" "}
gibi işlenmesini ve{" "}
{/* Aydınlatma "okudum" (bilgilendirme), koşullar "kabul": KVKK İlke
Kararı 2026/347 aydınlatma ile rızayı ayırır (hukuk-ve-rakipler.md C.2/8). */}
Giriş yaparak{" "}
<Link href="/kosullar" className="underline hover:text-slate-700">
kullanım koşullarını
</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>
</>
);

View File

@@ -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")
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")
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ı?"}
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
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")
@@ -34,7 +35,7 @@ flowchart TD
classDef hayal fill:#fee2e2,stroke:#ef4444,color:#991b1b
classDef dengeli fill:#fef3c7,stroke:#f59e0b,color:#92400e
classDef garanti fill:#d1fae5,stroke:#10b981,color:#065f46
class H,Y havuz
class H,Y,P havuz
class G,U uyari
class L1 hayal
class L2 dengeli
@@ -44,12 +45,14 @@ export function HuniSemasi() {
return (
<ElCizimiSema
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={
<>
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
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">
dışına asla çıkmadan
</span>{" "}

View File

@@ -114,15 +114,20 @@ export function MeraklisinaIcerik() {
göre yeniden hesaplanarak listelenir; üçüncü adımdaki
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
tabloya da uygulanır. Adımlar bitince elde kalan gerçek
tabloya da uygulanır. Danışmanla konuşurken söylediğin ve
&ldquo;Kaydettiklerim&rdquo; ç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>{" "}
olur ve yapay zekâya öyle gider: ona boş bir sayfa değil, bu havuz
verilir. Havuza yalnızca{" "}
<strong>en son tercih kılavuzunda yer alan</strong>{" "}
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ı
gösterir — havuz 24&apos;ün altına düşerse filtrelerin nasıl
gevşetildiği, yine de yetmezse listenin eldeki programlarla, sana
gösterir — havuz 24&apos;ü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.
</p>
<HuniSemasi />

View File

@@ -663,6 +663,11 @@ export function SohbetClient({
</PromptInputAction>
</PromptInputActions>
</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>

View File

@@ -6,6 +6,7 @@ import {
uretimKilidiBirak,
} from "@/lib/uretim-kilidi";
import { getSession, getCurrentUser } from "@/lib/session";
import { profilGetir } from "@/lib/danisman-profil-db";
import { appDb, schema } from "@/lib/appdb";
import { kullanicininRaporu } from "@/features/rapor/rapor-queries";
import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
@@ -214,7 +215,9 @@ async function listeOlusturKilitli(
let sonuc: RaporSonuc;
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) {
// Ü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
@@ -347,10 +350,12 @@ export async function listeRevize(input: {
let sonuc: RaporSonuc;
try {
const profil = await profilGetir(user.id).catch(() => undefined);
sonuc = await raporUret(
params,
feedback.slice(0, 1000),
listeOzetiCikar(onceki, params.sira),
profil,
);
} catch (err) {
// İade satırı burada da yazılır (paketlide delta 0) — DUPLICATE dalındaki