feat(etiket): görünen "Garanti" dilim adı site genelinde "Güvenli"

Yalnız kullanıcıya görünen etiket değişir; iç anahtar, tip, DB değeri, API
parametresi, mermaid classDef ve analitik değeri `garanti` olarak kalır
(veri göçü yok).

- risk.ts: tek kaynak DILIM_ETIKET + dilimdenFarkliRiskEtiketi; rapor
  listesi, yazdır tablosu, /sonuc sekmeleri, seçimlerim paneli ve
  meraklısına şeridi buradan okur
- "Güvenli · Güvenli" / "Güvenli (Güvenli)": satır detayında risk etiketi
  yalnız dilimin varsayılanından farklıysa, yazdır ve seçimlerimde yalnız
  kelime farklıysa yazılır
- LLM prompt'ları: `garanti` teknik anahtar, adaya "güvenli" denir sözlüğü
  (rapor, soru, tadımlık); liste özeti modele görünen dilim adıyla gider
- meraklısına şeması (SEMA düğümü, aria-label), 2. bölüm anlatımı ve
  AGENTS.md şema kuralı görünen adla eşitlendi
- "yerleşme garantisi vermez" türü vaat reddi cümleleri aynen kaldı

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-21 16:03:22 +03:00
parent d79758adc0
commit 5853b7d091
17 changed files with 103 additions and 51 deletions

View File

@@ -25,5 +25,5 @@ Funnel CTA butonlarında ("Listemi oluştur", "Tercihlerimi belirle", giriş/öd
# Meraklısına'daki mermaid şeması mimariyle senkron kalmalı # Meraklısına'daki mermaid şeması mimariyle senkron kalmalı
`/meraklisina` sayfasındaki sihirbaz akış şeması (`src/features/pazarlama/components/huni-semasi.tsx` içindeki `SEMA` sabiti) ürünün gerçek davranışının belgesidir, dekor değildir. Sihirbaz/listeleme mimarisinde davranış değiştiren her değişiklikte bu şema da aynı PR/commit içinde güncellenmeli: sihirbaz adımlarının sayısı-sırası-içeriği (alan → il → devlet/vakıf), aday havuzu kuralları, 24'lük liste iskeleti (hayal/dengeli/garanti dağılımı), havuz 24'ün altına düşünce filtre gevşetme sırası ve yapay zekânın havuz-dışına-çıkamama kuralı. Şemayı güncellerken çevresindeki unsurları da eşitle: bölümdeki anlatım metni (`src/features/pazarlama/components/meraklisina-icerik.tsx`, 4. bölüm), kabın `aria-label`'ı ve figcaption. Şema öğrenci diliyle kalmalı (SQL/prompt/teknik detay yok) ve ürünün risk renk dilini korumalı — kırmızı/sarı/yeşil her zaman metin etiketiyle birlikte, renk tek başına anlam taşımaz. `/meraklisina` sayfasındaki sihirbaz akış şeması (`src/features/pazarlama/components/huni-semasi.tsx` içindeki `SEMA` sabiti) ürünün gerçek davranışının belgesidir, dekor değildir. Sihirbaz/listeleme mimarisinde davranış değiştiren her değişiklikte bu şema da aynı PR/commit içinde güncellenmeli: sihirbaz adımlarının sayısı-sırası-içeriği (alan → il → devlet/vakıf), aday havuzu kuralları, 24'lük liste iskeleti (hayal/dengeli/güvenli dağılımı — üçüncü dilimin kullanıcıya görünen adı "Güvenli"dir; iç anahtar, tip, DB değeri, mermaid `classDef` ve analitik değeri `garanti` olarak kalır, görünen etiketin tek kaynağı `src/lib/risk.ts` içindeki `DILIM_ETIKET`), havuz 24'ün altına düşünce filtre gevşetme sırası ve yapay zekânın havuz-dışına-çıkamama kuralı. Şemayı güncellerken çevresindeki unsurları da eşitle: bölümdeki anlatım metni (`src/features/pazarlama/components/meraklisina-icerik.tsx`, 4. bölüm), kabın `aria-label`'ı ve figcaption. Şema öğrenci diliyle kalmalı (SQL/prompt/teknik detay yok) ve ürünün risk renk dilini korumalı — kırmızı/sarı/yeşil her zaman metin etiketiyle birlikte, renk tek başına anlam taşımaz.
<!-- END:nextjs-agent-rules --> <!-- END:nextjs-agent-rules -->

View File

@@ -70,6 +70,8 @@ KURALLAR (kesin):
- riskNotu: geçmiş taban verisine dayalı risk değerlendirmesi (TEK kısa cümle). - riskNotu: geçmiş taban verisine dayalı risk değerlendirmesi (TEK kısa cümle).
ASLA garanti/kesin/yüzde/olasılık/ihtimal deme; "güvenli / az riskli / ASLA garanti/kesin/yüzde/olasılık/ihtimal deme; "güvenli / az riskli /
riskli" etiketleriyle konuş. riskli" etiketleriyle konuş.
- Programın "dilim" alanındaki "garanti" değeri teknik anahtardır; adaya dönük
hiçbir metinde bu kelimeyi kullanma, o dilimi "güvenli" diye an.
- trendOzeti: son 5 yılın taban sıralaması hareketi, TEK cümle (ör. "2022'den - trendOzeti: son 5 yılın taban sıralaması hareketi, TEK cümle (ör. "2022'den
beri taban 45B'den 38B'ye indi, bölüm zorlaşıyor"). beri taban 45B'den 38B'ye indi, bölüm zorlaşıyor").
- Adayın sıralaması yaklaşıktır; kesin sıra telaffuz etme, banda göre konuş.`; - Adayın sıralaması yaklaşıktır; kesin sıra telaffuz etme, banda göre konuş.`;

View File

@@ -41,6 +41,9 @@ iş imkânları, tercih sırası) net ve dürüst yanıtlamak.
KURALLAR: KURALLAR:
- Cevaplarını adayın 24'lük listesi ve seçimleri bağlamında ver; listedeki - Cevaplarını adayın 24'lük listesi ve seçimleri bağlamında ver; listedeki
programlara referans verebilirsin. programlara referans verebilirsin.
- Dilimlerin adı Hayal, Dengeli ve Güvenli'dir. Üçüncü dilime her zaman
"Güvenli" de; "garanti dilimi", "garanti tercih" gibi ifadeler KULLANMA
(aday ya da eski mesajlar böyle dese bile sen "güvenli" diye düzelt).
- Listedeki her satırda dilim ve risk etiketi var: Güvenli (yeşil) = taban - Listedeki her satırda dilim ve risk etiketi var: Güvenli (yeşil) = taban
sıralaması adayın belirgin altında, Az riskli (sarı) = adayın sıralaması sıralaması adayın belirgin altında, Az riskli (sarı) = adayın sıralaması
civarında, Riskli (kırmızı) = geçen yıl adayın sıralamasının üzerinde civarında, Riskli (kırmızı) = geçen yıl adayın sıralamasının üzerinde

View File

@@ -112,7 +112,7 @@ const steps = [
step: "3", step: "3",
title: "Dengeli 24'lük listeni al", title: "Dengeli 24'lük listeni al",
description: description:
"Hayal, dengeli ve garanti dilimlerine dağıtılmış, her satırı gerekçeli tercih listeni indir; aklına takılanı listen üzerinden yapay zekâya sor.", "Hayal, dengeli ve güvenli dilimlerine dağıtılmış, her satırı gerekçeli tercih listeni indir; aklına takılanı listen üzerinden yapay zekâya sor.",
}, },
]; ];
@@ -129,7 +129,7 @@ const faqs = [
{ {
question: "Yerleşme garantisi veriyor musunuz?", question: "Yerleşme garantisi veriyor musunuz?",
answer: answer:
"Hayır — ve verdiğini iddia eden herkesten uzak dur. Taban sıralamaları her yıl adayların davranışına göre değişir; kimse garanti veremez. Biz riski şeffaf gösterir, listeyi hayal/dengeli/garanti dilimleriyle kurarak boşta kalma ihtimalini en aza indiririz. Tercih listenin son hali ve ÖSYM başvurusu her zaman senin sorumluluğundadır.", "Hayır — ve verdiğini iddia eden herkesten uzak dur. Taban sıralamaları her yıl adayların davranışına göre değişir; kimse garanti veremez. Biz riski şeffaf gösterir, listeyi hayal/dengeli/güvenli dilimleriyle kurarak boşta kalma ihtimalini en aza indiririz. Tercih listenin son hali ve ÖSYM başvurusu her zaman senin sorumluluğundadır.",
}, },
{ {
question: "Fiyatlandırma nasıl çalışıyor?", question: "Fiyatlandırma nasıl çalışıyor?",

View File

@@ -20,7 +20,7 @@ import type {
PuanTuruKey, PuanTuruKey,
RankResultsListe, RankResultsListe,
} from "@/types/yokatlas"; } from "@/types/yokatlas";
import { riskHesapla, type RiskSeviyesi } from "@/lib/risk"; import { DILIM_ETIKET, riskHesapla, type RiskSeviyesi } from "@/lib/risk";
import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug"; import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug";
import { import {
ProgramSiraGecmisi, ProgramSiraGecmisi,
@@ -51,21 +51,21 @@ const DILIMLER: {
}[] = [ }[] = [
{ {
key: "hayal", key: "hayal",
etiket: "Hayal", etiket: DILIM_ETIKET.hayal,
icon: Rocket, icon: Rocket,
renk: "text-red-500", renk: "text-red-500",
renkAktif: "text-red-400", renkAktif: "text-red-400",
}, },
{ {
key: "dengeli", key: "dengeli",
etiket: "Dengeli", etiket: DILIM_ETIKET.dengeli,
icon: Scale, icon: Scale,
renk: "text-amber-500", renk: "text-amber-500",
renkAktif: "text-amber-400", renkAktif: "text-amber-400",
}, },
{ {
key: "garanti", key: "garanti",
etiket: "Garanti", etiket: DILIM_ETIKET.garanti,
icon: ShieldCheck, icon: ShieldCheck,
renk: "text-emerald-600", renk: "text-emerald-600",
renkAktif: "text-emerald-400", renkAktif: "text-emerald-400",

View File

@@ -13,7 +13,7 @@ import {
X, X,
} from "lucide-react"; } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { RISK_ETIKET, type RiskSeviyesi } from "@/lib/risk"; import { DILIM_ETIKET, RISK_ETIKET, type RiskSeviyesi } from "@/lib/risk";
import { sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler"; import { sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
import { import {
tercihDegerlendir, tercihDegerlendir,
@@ -156,9 +156,12 @@ export function SecimlerimPaneli() {
{ozet ? ( {ozet ? (
<dl className="mt-4 grid grid-cols-2 gap-3 sm:grid-cols-4"> <dl className="mt-4 grid grid-cols-2 gap-3 sm:grid-cols-4">
<Istatistik etiket="Hayal" deger={ozet.hayal} /> <Istatistik etiket={DILIM_ETIKET.hayal} deger={ozet.hayal} />
<Istatistik etiket="Dengeli" deger={ozet.dengeli} /> <Istatistik etiket={DILIM_ETIKET.dengeli} deger={ozet.dengeli} />
<Istatistik etiket="Daha güvenli" deger={ozet.dahaGuvenli} /> <Istatistik
etiket={DILIM_ETIKET.garanti}
deger={ozet.dahaGuvenli}
/>
<Istatistik etiket="Dikkat" deger={ozet.dikkat} /> <Istatistik etiket="Dikkat" deger={ozet.dikkat} />
</dl> </dl>
) : null} ) : null}
@@ -272,7 +275,9 @@ const SecimSatiri = memo(function SecimSatiri({
/> />
) : null} ) : null}
{degerlendirme.riskEtiketi} {degerlendirme.riskEtiketi}
{risk ? ( {/* Dilim adı ile risk etiketi aynı kelimeyse ("Güvenli")
parantez yazılmaz — "Güvenli (Güvenli)" olmasın. */}
{risk && RISK_ETIKET[risk] !== degerlendirme.riskEtiketi ? (
<span className="font-normal text-slate-500"> <span className="font-normal text-slate-500">
({RISK_ETIKET[risk]}) ({RISK_ETIKET[risk]})
</span> </span>

View File

@@ -6,6 +6,8 @@ import { ElCizimiSema } from "@/features/rehber/components/el-cizimi-sema";
// Renkler ürünün risk dilinden (kırmızı/sarı/yeşil) ve slate paletinden; // Renkler ürünün risk dilinden (kırmızı/sarı/yeşil) ve slate paletinden;
// renk tek başına anlam taşımaz, her düğüm metinle etiketlidir. // renk tek başına anlam taşımaz, her düğüm metinle etiketlidir.
// Üçüncü dilimin görünen adı "güvenli"dir; `classDef garanti` yalnızca iç
// renk sınıfıdır (render edilmez), iç anahtarla aynı kalır.
const SEMA = `--- const SEMA = `---
config: config:
look: handDrawn look: handDrawn
@@ -25,7 +27,7 @@ flowchart TD
R --> L("En fazla 24 tercihlik liste") R --> L("En fazla 24 tercihlik liste")
L --> L1("üstte hayal — hedef 5, havuz elverdiğince") L --> L1("üstte hayal — hedef 5, havuz elverdiğince")
L --> L2("ortada dengeli — hedef 13, havuz elverdiğince") L --> L2("ortada dengeli — hedef 13, havuz elverdiğince")
L --> L3("sonda garanti — hedef 6, havuz elverdiğince") L --> L3("sonda güvenli — hedef 6, havuz elverdiğince")
classDef havuz fill:#eff6ff,stroke:#3b82f6,color:#1e3a8a classDef havuz fill:#eff6ff,stroke:#3b82f6,color:#1e3a8a
classDef uyari fill:#fffbeb,stroke:#f59e0b,color:#92400e classDef uyari fill:#fffbeb,stroke:#f59e0b,color:#92400e
@@ -42,7 +44,7 @@ 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; 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 garanti 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 aday havuzuna daraltılır; 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."
figcaption={ figcaption={
<> <>
Her adım havuzu biraz daha daraltır; yapay zekâ listeyi bu havuzun{" "} Her adım havuzu biraz daha daraltır; yapay zekâ listeyi bu havuzun{" "}

View File

@@ -20,6 +20,7 @@ import { useTercihProfili } from "@/features/liste/hooks/use-tercih-profili";
import { PUAN_TURU_ETIKET, sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler"; import { PUAN_TURU_ETIKET, sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
import type { DilimKey, SihirbazFacetleri } from "@/types/yokatlas"; import type { DilimKey, SihirbazFacetleri } from "@/types/yokatlas";
import { onbellekliJsonGetir } from "@/lib/istek-cache"; import { onbellekliJsonGetir } from "@/lib/istek-cache";
import { DILIM_ETIKET } from "@/lib/risk";
export type DemoVeri = { export type DemoVeri = {
facetler: SihirbazFacetleri; facetler: SihirbazFacetleri;
@@ -146,7 +147,7 @@ export function DilimSeridiCanli() {
<svg <svg
viewBox="0 0 640 168" viewBox="0 0 640 168"
role="img" role="img"
aria-label={`Sıralama ekseni: ${fmt(sira)}. sıradaki aday için Hayal diliminde ${fmt(t.hayal)}, Dengeli diliminde ${fmt(t.dengeli)}, Garanti diliminde ${fmt(t.garanti)} program var.`} aria-label={`Sıralama ekseni: ${fmt(sira)}. sıradaki aday için ${DILIM_ETIKET.hayal} diliminde ${fmt(t.hayal)}, ${DILIM_ETIKET.dengeli} diliminde ${fmt(t.dengeli)}, ${DILIM_ETIKET.garanti} diliminde ${fmt(t.garanti)} program var.`}
className="w-full" className="w-full"
> >
{/* eksen */} {/* eksen */}
@@ -174,7 +175,7 @@ export function DilimSeridiCanli() {
{/* hayal */} {/* hayal */}
<rect x="20" y="70" width="150" height="32" rx="4" className="fill-red-100" /> <rect x="20" y="70" width="150" height="32" rx="4" className="fill-red-100" />
<text x="95" y="40" textAnchor="middle" className="fill-red-600 text-[12px] font-semibold"> <text x="95" y="40" textAnchor="middle" className="fill-red-600 text-[12px] font-semibold">
Hayal {DILIM_ETIKET.hayal}
</text> </text>
<text x="95" y="56" textAnchor="middle" className="fill-slate-600 text-[11px] font-medium tabular-nums"> <text x="95" y="56" textAnchor="middle" className="fill-slate-600 text-[11px] font-medium tabular-nums">
{fmt(t.hayal)} program {fmt(t.hayal)} program
@@ -192,7 +193,7 @@ export function DilimSeridiCanli() {
{/* dengeli */} {/* dengeli */}
<rect x="174" y="70" width="120" height="32" rx="4" className="fill-amber-100" /> <rect x="174" y="70" width="120" height="32" rx="4" className="fill-amber-100" />
<text x="234" y="40" textAnchor="middle" className="fill-amber-600 text-[12px] font-semibold"> <text x="234" y="40" textAnchor="middle" className="fill-amber-600 text-[12px] font-semibold">
Dengeli {DILIM_ETIKET.dengeli}
</text> </text>
<text x="234" y="56" textAnchor="middle" className="fill-slate-600 text-[11px] font-medium tabular-nums"> <text x="234" y="56" textAnchor="middle" className="fill-slate-600 text-[11px] font-medium tabular-nums">
{fmt(t.dengeli)} program {fmt(t.dengeli)} program
@@ -204,7 +205,7 @@ export function DilimSeridiCanli() {
{/* garanti */} {/* garanti */}
<rect x="296" y="70" width="324" height="32" rx="4" className="fill-emerald-100" /> <rect x="296" y="70" width="324" height="32" rx="4" className="fill-emerald-100" />
<text x="458" y="40" textAnchor="middle" className="fill-emerald-700 text-[12px] font-semibold"> <text x="458" y="40" textAnchor="middle" className="fill-emerald-700 text-[12px] font-semibold">
Garanti {DILIM_ETIKET.garanti}
</text> </text>
<text x="458" y="56" textAnchor="middle" className="fill-slate-600 text-[11px] font-medium tabular-nums"> <text x="458" y="56" textAnchor="middle" className="fill-slate-600 text-[11px] font-medium tabular-nums">
{fmt(t.garanti)} program {fmt(t.garanti)} program

View File

@@ -68,12 +68,12 @@ export function MeraklisinaIcerik() {
(listenin bel kemiği — sıralamanın yaklaşık yüzde kırk fazlasına (listenin bel kemiği — sıralamanın yaklaşık yüzde kırk fazlasına
kadar pay bırakılır), tabanı{" "} kadar pay bırakılır), tabanı{" "}
<strong>belirgin şekilde altında</strong> kalan her şey ise{" "} <strong>belirgin şekilde altında</strong> kalan her şey ise{" "}
<span className="font-semibold text-emerald-600">Garanti</span>{" "} <span className="font-semibold text-emerald-600">Güvenli</span>{" "}
dilimidir. dilimidir.
</p> </p>
<DilimSeridiCanli /> <DilimSeridiCanli />
<p className="mt-4"> <p className="mt-4">
Garanti diliminin bir sonu yok: 87. sıradaki bir adayın da 500 Güvenli diliminin bir sonu yok: 87. sıradaki bir adayın da 500
bininci sıradaki bir adayın da ulaşabildiği <em>bütün</em>{" "} bininci sıradaki bir adayın da ulaşabildiği <em>bütün</em>{" "}
programlar listededir. Liste sana en yakın tabandan başlar; aşağı programlar listededir. Liste sana en yakın tabandan başlar; aşağı
indikçe ve &ldquo;Daha fazla göster&rdquo;e bastıkça senden indikçe ve &ldquo;Daha fazla göster&rdquo;e bastıkça senden
@@ -131,7 +131,7 @@ export function MeraklisinaIcerik() {
<p className="mt-2"> <p className="mt-2">
Tüm bu hesaplar <strong>geçen yılların</strong>{" "} Tüm bu hesaplar <strong>geçen yılların</strong>{" "}
yerleştirme sonuçlarına dayanır. Kontenjanlar, tercih davranışları yerleştirme sonuçlarına dayanır. Kontenjanlar, tercih davranışları
ve puanlar her yıl değişir; geçen yıl &ldquo;garanti&rdquo; ve puanlar her yıl değişir; geçen yıl &ldquo;güvenli&rdquo;
görünen bir bölüm bu yıl dolabilir. Bu yüzden satırlarda son beş görünen bir bölüm bu yıl dolabilir. Bu yüzden satırlarda son beş
yılın taban seyrini de gösteririz — çizgi yukarı tırmanıyorsa yılın taban seyrini de gösteririz — çizgi yukarı tırmanıyorsa
bölüm zorlaşıyor demektir. KolayTercih karar <em>destek</em>{" "} bölüm zorlaşıyor demektir. KolayTercih karar <em>destek</em>{" "}

View File

@@ -29,17 +29,14 @@ import {
import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug"; import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug";
import type { RaporSonuc } from "@/features/rapor/types/rapor"; import type { RaporSonuc } from "@/features/rapor/types/rapor";
import { import {
DILIM_ETIKET,
RISK_ETIKET, RISK_ETIKET,
dilimdenFarkliRiskEtiketi,
dilimdenRisk, dilimdenRisk,
riskHesapla, riskHesapla,
type RiskSeviyesi, type RiskSeviyesi,
} from "@/lib/risk"; } from "@/lib/risk";
import type { Dilim } from "@/lib/rapor-havuzu";
export const DILIM_ETIKET: Record<string, string> = {
hayal: "Hayal",
dengeli: "Dengeli",
garanti: "Garanti",
};
// Risk renk dili: yeşil = güvenli, sarı = az riskli, kırmızı = riskli. // Risk renk dili: yeşil = güvenli, sarı = az riskli, kırmızı = riskli.
// Nokta hiçbir yerde tek başına durmaz; yanındaki etiket dar ekranda sr-only // Nokta hiçbir yerde tek başına durmaz; yanındaki etiket dar ekranda sr-only
@@ -75,12 +72,16 @@ export function RaporSatirDetayi({
trendOzeti: string; trendOzeti: string;
}) { }) {
const stil = RISK_STIL[risk]; const stil = RISK_STIL[risk];
// Risk etiketi yalnızca dilimin varsayılanından farklıysa yazılır; yoksa
// üçüncü dilimde başlık "Güvenli · Güvenli" olurdu (bkz. risk.ts).
const farkliRisk = dilimdenFarkliRiskEtiketi(dilim, risk);
return ( return (
<> <>
<div className="mb-2 flex flex-wrap items-center gap-2 text-xs text-slate-500"> <div className="mb-2 flex flex-wrap items-center gap-2 text-xs text-slate-500">
<stil.icon className="size-3.5" aria-hidden /> <stil.icon className="size-3.5" aria-hidden />
<span className="font-medium"> <span className="font-medium">
{DILIM_ETIKET[dilim]} · {RISK_ETIKET[risk]} {DILIM_ETIKET[dilim as Dilim] ?? dilim}
{farkliRisk ? ` · ${farkliRisk}` : null}
</span> </span>
{unitur ? <span>· {unitur}</span> : null} {unitur ? <span>· {unitur}</span> : null}
</div> </div>
@@ -345,7 +346,7 @@ export function RaporListesi({
<p className="mt-3 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900"> <p className="mt-3 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900">
Tercihlerine göre {rapor.kapsam.toplam} uygun program bulundu; listen{" "} Tercihlerine göre {rapor.kapsam.toplam} uygun program bulundu; listen{" "}
{rapor.kapsam.dagilim.hayal} hayal · {rapor.kapsam.dagilim.dengeli}{" "} {rapor.kapsam.dagilim.hayal} hayal · {rapor.kapsam.dagilim.dengeli}{" "}
dengeli · {rapor.kapsam.dagilim.garanti} garanti olarak kuruldu. Daha dengeli · {rapor.kapsam.dagilim.garanti} güvenli olarak kuruldu. Daha
fazla seçenek için alan veya il filtreni genişletebilirsin. fazla seçenek için alan veya il filtreni genişletebilirsin.
</p> </p>
) : null} ) : null}

View File

@@ -2,16 +2,19 @@ import Image from "next/image";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { verifySession, getCurrentUser } from "@/lib/session"; import { verifySession, getCurrentUser } from "@/lib/session";
import { kullanicininRaporu } from "../rapor-queries"; import { kullanicininRaporu } from "../rapor-queries";
import { PUAN_TURLERI } from "@/types/yokatlas"; import { PUAN_TURLERI, type DilimKey } from "@/types/yokatlas";
import { DILIM_ETIKET, RISK_ETIKET, dilimdenRisk } from "@/lib/risk";
import type { RaporParams, RaporSonuc } from "../types/rapor"; import type { RaporParams, RaporSonuc } from "../types/rapor";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { YazdirButonu } from "./yazdir-butonu"; import { YazdirButonu } from "./yazdir-butonu";
const DILIM_LABEL: Record<string, string> = { // Dilim sütunu: dilim adı + varsayılan riski. Üçüncü dilimde ikisi aynı
hayal: "Hayal (Riskli)", // kelime ("Güvenli") olduğu için parantez düşer — "Güvenli (Güvenli)" yazmaz.
dengeli: "Dengeli (Az riskli)", function dilimLabel(dilim: DilimKey): string {
garanti: "Garanti (Güvenli)", const ad = DILIM_ETIKET[dilim] ?? dilim;
}; const risk = RISK_ETIKET[dilimdenRisk(dilim)];
return ad === risk ? ad : `${ad} (${risk})`;
}
export async function RaporYazdir() { export async function RaporYazdir() {
const session = await verifySession("/rapor/yazdir"); const session = await verifySession("/rapor/yazdir");
@@ -50,7 +53,7 @@ export async function RaporYazdir() {
Not: Tercihlerine göre {rapor.kapsam.toplam} uygun program bulundu; Not: Tercihlerine göre {rapor.kapsam.toplam} uygun program bulundu;
liste {rapor.kapsam.dagilim.hayal} hayal ·{" "} liste {rapor.kapsam.dagilim.hayal} hayal ·{" "}
{rapor.kapsam.dagilim.dengeli} dengeli ·{" "} {rapor.kapsam.dagilim.dengeli} dengeli ·{" "}
{rapor.kapsam.dagilim.garanti} garanti olarak kuruldu. {rapor.kapsam.dagilim.garanti} güvenli olarak kuruldu.
</p> </p>
) : null} ) : null}
</header> </header>
@@ -91,7 +94,7 @@ export async function RaporYazdir() {
{p?.universite} · {p?.il ?? "—"} {p?.universite} · {p?.il ?? "—"}
</span> </span>
</td> </td>
<td className="py-2 pr-2">{DILIM_LABEL[t.dilim]}</td> <td className="py-2 pr-2">{dilimLabel(t.dilim)}</td>
<td className="py-2 pr-2">{t.gerekce}</td> <td className="py-2 pr-2">{t.gerekce}</td>
<td className="py-2 pr-2">{t.riskNotu}</td> <td className="py-2 pr-2">{t.riskNotu}</td>
<td className="py-2">{t.trendOzeti}</td> <td className="py-2">{t.trendOzeti}</td>

View File

@@ -76,7 +76,7 @@ export function RevizyonKutusu({ kalanHak }: { kalanHak: number }) {
onChange={(e) => setFeedback(e.target.value)} onChange={(e) => setFeedback(e.target.value)}
rows={2} rows={2}
maxLength={1000} maxLength={1000}
placeholder="ör. İlk 5 tercihte daha fazla İstanbul olsun, garanti dilimini genişlet…" placeholder="ör. İlk 5 tercihte daha fazla İstanbul olsun, güvenli dilimi genişlet…"
disabled={pending} disabled={pending}
className="w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50" className="w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
/> />

View File

@@ -150,7 +150,7 @@ export function CtaSiraForm({ baslik }: { baslik?: string }) {
</h2> </h2>
<p className="mt-2 text-sm leading-6 text-slate-600"> <p className="mt-2 text-sm leading-6 text-slate-600">
Sıralaman kayıtlı ({profil.sira.toLocaleString("tr-TR")}. sıra) — Sıralaman kayıtlı ({profil.sira.toLocaleString("tr-TR")}. sıra) —
gerçek YÖK Atlas verisiyle hayal, dengeli ve garanti dilimlerindeki gerçek YÖK Atlas verisiyle hayal, dengeli ve güvenli dilimlerindeki
programların hazır. programların hazır.
</p> </p>
<Link <Link
@@ -174,7 +174,7 @@ export function CtaSiraForm({ baslik }: { baslik?: string }) {
</h2> </h2>
<p className="mt-2 text-sm leading-6 text-slate-600"> <p className="mt-2 text-sm leading-6 text-slate-600">
YKS başarı sıralamanı gir; gerçek YÖK Atlas verisiyle sıralamana uygun YKS başarı sıralamanı gir; gerçek YÖK Atlas verisiyle sıralamana uygun
hayal, dengeli ve garanti dilimlerindeki programları anında listele. hayal, dengeli ve güvenli dilimlerindeki programları anında listele.
Ücretsiz, üyelik gerekmez. Ücretsiz, üyelik gerekmez.
</p> </p>
<Button <Button
@@ -217,7 +217,7 @@ export function CtaSiraForm({ baslik }: { baslik?: string }) {
</h2> </h2>
<p className="mt-2 max-w-xl text-sm leading-6 text-slate-600"> <p className="mt-2 max-w-xl text-sm leading-6 text-slate-600">
Puan türünü seç, başarı sıralamanı yaz; hayal, dengeli ve Puan türünü seç, başarı sıralamanı yaz; hayal, dengeli ve
garanti dilimlerindeki programları bu sayfadan ayrılmadan güvenli dilimlerindeki programları bu sayfadan ayrılmadan
gör. Ücretsiz, üyelik gerekmez. gör. Ücretsiz, üyelik gerekmez.
</p> </p>
</div> </div>

View File

@@ -322,7 +322,7 @@ export function SihirbazBolumu({
<p className="relative mx-auto mt-3 max-w-xl text-white/90"> <p className="relative mx-auto mt-3 max-w-xl text-white/90">
Tablodan elle eklemek her zaman serbest. Ama üç adımda ilgi Tablodan elle eklemek her zaman serbest. Ama üç adımda ilgi
alanlarını ve şehirlerini söylersen, yapay zekâ hayal, dengeli alanlarını ve şehirlerini söylersen, yapay zekâ hayal, dengeli
ve garanti dilimlerine dağıtılmış 24 tercihi — her satırı ve güvenli dilimlerine dağıtılmış 24 tercihi — her satırı
yerleşme riski ve gerekçesiyle — senin yerine dizer. yerleşme riski ve gerekçesiyle — senin yerine dizer.
</p> </p>
<Button <Button

View File

@@ -19,6 +19,7 @@ import {
} from "../rapor-havuzu"; } from "../rapor-havuzu";
import { secimOzeti } from "@/features/sihirbaz/sihirbaz-sabitler"; import { secimOzeti } from "@/features/sihirbaz/sihirbaz-sabitler";
import { import {
DILIM_ETIKET,
RISK_ETIKET, RISK_ETIKET,
dilimdenRisk, dilimdenRisk,
riskHesapla, riskHesapla,
@@ -77,6 +78,11 @@ KURALLAR (kesin):
- METİN DİYETİ: aday ${toplam} satır okuyacak; gerekce TEK kısa cümle (en fazla ~15 - METİN DİYETİ: aday ${toplam} satır okuyacak; gerekce TEK kısa cümle (en fazla ~15
kelime, Türkçe): bu programın bu aday için neden mantıklı olduğu. Dolgu söz kelime, Türkçe): bu programın bu aday için neden mantıklı olduğu. Dolgu söz
yok; ASLA "garanti", "kesin", "yüzde yüz" deme. yok; ASLA "garanti", "kesin", "yüzde yüz" deme.
- SÖZLÜK: "garanti" yalnızca üçüncü listenin teknik JSON alan adı ve havuzdaki
"dilim" değeridir. Adaya dönük HİÇBİR metinde (gerekce, genelDegerlendirme,
uyarilar) "garanti" kelimesini kullanma; bu dilimi adaya "güvenli dilim" /
"güvenli tercihler" diye an. Aday ya da önceki liste özeti "güvenli dilim"
derse kastedilen "garanti" listesidir.
- Adayın notlarındaki şehir/bölüm tercihlerine saygı göster ama dengeyi bozma. - Adayın notlarındaki şehir/bölüm tercihlerine saygı göster ama dengeyi bozma.
- genelDegerlendirme: listenin genel stratejisi, EN FAZLA 2 cümle. Uyarıları - genelDegerlendirme: listenin genel stratejisi, EN FAZLA 2 cümle. Uyarıları
buraya yazma. buraya yazma.
@@ -209,12 +215,12 @@ function mockRapor(
tercihler, tercihler,
genelDegerlendirme: `(Dev mock — Yapay Zeka anahtarı tanımlı değil, bu rapor gerçek Yapay Zeka çıktısı değildir.) ${params.sira.toLocaleString( genelDegerlendirme: `(Dev mock — Yapay Zeka anahtarı tanımlı değil, bu rapor gerçek Yapay Zeka çıktısı değildir.) ${params.sira.toLocaleString(
"tr-TR", "tr-TR",
)}. sıra için hayal/dengeli/garanti dağılımlı ${toplamHedef} tercihlik örnek liste kuruldu.${ )}. sıra için hayal/dengeli/güvenli dağılımlı ${toplamHedef} tercihlik örnek liste kuruldu.${
revizyonNotu ? ` Revizyon isteğin not alındı: "${revizyonNotu}".` : "" revizyonNotu ? ` Revizyon isteğin not alındı: "${revizyonNotu}".` : ""
}`, }`,
uyarilar: [ uyarilar: [
"(Dev mock) Tercih dönemi kapanmadan listeyi ÖSYM sistemine kendin gir.", "(Dev mock) Tercih dönemi kapanmadan listeyi ÖSYM sistemine kendin gir.",
"(Dev mock) Garanti dilimindeki son tercihleri asla silme.", "(Dev mock) Güvenli dilimindeki son tercihleri asla silme.",
], ],
programlar, programlar,
kapsam: kapsamUret(hedef), kapsam: kapsamUret(hedef),
@@ -406,7 +412,7 @@ export async function raporUret(
tercihler, tercihler,
genelDegerlendirme: genelDegerlendirme:
genel.trim() || genel.trim() ||
`Listen ${toplamHedef} tercihten oluşuyor; hayal, dengeli ve garanti dilimleri sıralamana göre dengelendi.`, `Listen ${toplamHedef} tercihten oluşuyor; hayal, dengeli ve güvenli dilimleri sıralamana göre dengelendi.`,
uyarilar: uyarilar.length > 0 ? uyarilar : undefined, uyarilar: uyarilar.length > 0 ? uyarilar : undefined,
programlar, programlar,
kapsam: kapsamUret(hedef), kapsam: kapsamUret(hedef),
@@ -424,7 +430,10 @@ export function listeOzetiCikar(rapor: RaporSonuc, adaySira?: number): string {
p?.efektifSira != null p?.efektifSira != null
? ` (taban sırası ~${p.efektifSira.toLocaleString("tr-TR")})` ? ` (taban sırası ~${p.efektifSira.toLocaleString("tr-TR")})`
: ""; : "";
return `${t.sira}. [${t.dilim} | ${RISK_ETIKET[risk]}] ${p?.isim ?? "?"} — ${p?.universite ?? "?"}${taban}`; // Modele ham `garanti` anahtarı değil görünen dilim adı gider; yoksa
// sohbet cevaplarına "garanti dilimi" sızıyor. Dilim ve risk aynı kelime
// olabildiği için ("Güvenli") alanlar adlandırılır.
return `${t.sira}. [dilim: ${DILIM_ETIKET[t.dilim] ?? t.dilim} | risk: ${RISK_ETIKET[risk]}] ${p?.isim ?? "?"} — ${p?.universite ?? "?"}${taban}`;
}) })
.join("\n"); .join("\n");
} }

View File

@@ -12,6 +12,18 @@ export const RISK_ETIKET: Record<RiskSeviyesi, string> = {
riskli: "Riskli", riskli: "Riskli",
}; };
/**
* Dilimin kullanıcıya GÖRÜNEN adı — tek kaynak. Üçüncü dilimin iç anahtarı
* (tip, DB değeri, API parametresi, mermaid classDef, analitik) `garanti`
* olarak kalır; ekranda "Güvenli" yazar. Ham anahtarı kullanıcıya basma,
* buradan oku.
*/
export const DILIM_ETIKET: Record<Dilim, string> = {
hayal: "Hayal",
dengeli: "Dengeli",
garanti: "Güvenli",
};
/** Harita pinlerinin SVG dolgu renkleri — risk renk dilinin oklch karşılığı. */ /** Harita pinlerinin SVG dolgu renkleri — risk renk dilinin oklch karşılığı. */
export const RISK_PIN_RENK: Record<RiskSeviyesi, string> = { export const RISK_PIN_RENK: Record<RiskSeviyesi, string> = {
guvenli: "oklch(0.696 0.17 162.5)", // emerald-500 guvenli: "oklch(0.696 0.17 162.5)", // emerald-500
@@ -43,6 +55,19 @@ export function dilimdenRisk(dilim: Dilim | string): RiskSeviyesi {
return "riskli"; return "riskli";
} }
/**
* Dilim başlığının yanına yazılacak risk etiketi. Yalnızca satırın riski
* dilimin varsayılanından FARKLIYSA döner; aynıysa null. Üçüncü dilimin adı
* risk etiketiyle aynı kelime olduğu için aksi hâlde "Güvenli · Güvenli"
* görünürdü.
*/
export function dilimdenFarkliRiskEtiketi(
dilim: Dilim | string,
risk: RiskSeviyesi,
): string | null {
return dilimdenRisk(dilim) === risk ? null : RISK_ETIKET[risk];
}
/** /**
* Risk seviyesinin tek cümlelik açıklaması (rapor satırları + tadımlık). * Risk seviyesinin tek cümlelik açıklaması (rapor satırları + tadımlık).
* Ürün yerleşme olasılığı HESAPLAMAZ; yalnızca son taban sıralamasını adayın * Ürün yerleşme olasılığı HESAPLAMAZ; yalnızca son taban sıralamasını adayın

View File

@@ -1,5 +1,5 @@
import { kategoriEslesir } from "./kategoriler"; import { kategoriEslesir } from "./kategoriler";
import { riskHesapla, type RiskSeviyesi } from "./risk"; import { DILIM_ETIKET, riskHesapla, type RiskSeviyesi } from "./risk";
import type { TercihProfili } from "@/features/sihirbaz/sihirbaz-sabitler"; import type { TercihProfili } from "@/features/sihirbaz/sihirbaz-sabitler";
export type DegerlendirilecekProgram = { export type DegerlendirilecekProgram = {
@@ -11,7 +11,8 @@ export type DegerlendirilecekProgram = {
export type TercihDegerlendirmesi = { export type TercihDegerlendirmesi = {
risk: RiskSeviyesi | null; risk: RiskSeviyesi | null;
riskEtiketi: "Hayal" | "Dengeli" | "Daha güvenli" | "Veri yetersiz"; /** Görünen dilim adı (DILIM_ETIKET) ya da "Veri yetersiz". */
riskEtiketi: string;
riskMetni: string; riskMetni: string;
olumlu: string[]; olumlu: string[];
celiskiler: string[]; celiskiler: string[];
@@ -65,11 +66,11 @@ export function tercihDegerlendir(
const riskEtiketi = const riskEtiketi =
risk === "riskli" risk === "riskli"
? "Hayal" ? DILIM_ETIKET.hayal
: risk === "az-riskli" : risk === "az-riskli"
? "Dengeli" ? DILIM_ETIKET.dengeli
: risk === "guvenli" : risk === "guvenli"
? "Daha güvenli" ? DILIM_ETIKET.garanti
: "Veri yetersiz"; : "Veri yetersiz";
const riskMetni = const riskMetni =
risk === "riskli" risk === "riskli"