From d5235310d50f78d092dbb51d500fb962a96436e4 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Sun, 26 Jul 2026 02:35:20 +0300 Subject: [PATCH] =?UTF-8?q?Risk=20hesaplama=20katman=C4=B1:=20rapor=20list?= =?UTF-8?q?esi=20sat=C4=B1rlar=C4=B1=20aday=C4=B1n=20s=C4=B1ras=C4=B1na=20?= =?UTF-8?q?g=C3=B6re=20renklenir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sihirbaz-modal'ın kullandığı RaporListesi adaySira prop'u bu dosyalara bağlıydı; önceki commit'te eksik kalınca CI build'i kırıldı. Co-Authored-By: Claude Fable 5 --- src/app/rapor/yazdir/page.tsx | 6 +-- src/components/rapor-listesi.tsx | 84 ++++++++++++++++++++++++++------ src/lib/ai/rapor.ts | 28 ++++++++--- src/lib/risk.ts | 37 ++++++++++++++ 4 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 src/lib/risk.ts diff --git a/src/app/rapor/yazdir/page.tsx b/src/app/rapor/yazdir/page.tsx index 775b6de..e7dda03 100644 --- a/src/app/rapor/yazdir/page.tsx +++ b/src/app/rapor/yazdir/page.tsx @@ -13,9 +13,9 @@ export const metadata: Metadata = { }; const DILIM_LABEL: Record = { - hayal: "Hayal", - dengeli: "Dengeli", - garanti: "Garanti", + hayal: "Hayal (Riskli)", + dengeli: "Dengeli (Az riskli)", + garanti: "Garanti (Güvenli)", }; export default async function YazdirPage() { diff --git a/src/components/rapor-listesi.tsx b/src/components/rapor-listesi.tsx index 07ab1f3..2c4cd7d 100644 --- a/src/components/rapor-listesi.tsx +++ b/src/components/rapor-listesi.tsx @@ -1,25 +1,54 @@ import { Rocket, Scale, ShieldCheck } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import type { RaporSonuc } from "@/lib/ai/rapor"; +import { + RISK_ETIKET, + dilimdenRisk, + riskHesapla, + type RiskSeviyesi, +} from "@/lib/risk"; -export const DILIM_STIL: Record< - string, - { label: string; cls: string; icon: typeof Rocket } +export const DILIM_ETIKET: Record = { + hayal: "Hayal", + dengeli: "Dengeli", + garanti: "Garanti", +}; + +// Risk renk dili: yeşil = güvenli, sarı = az riskli, kırmızı = riskli +export const RISK_STIL: Record< + RiskSeviyesi, + { badge: string; kenar: string; icon: typeof Rocket } > = { - hayal: { label: "Hayal", cls: "bg-orange-100 text-orange-700", icon: Rocket }, - dengeli: { label: "Dengeli", cls: "bg-blue-100 text-blue-700", icon: Scale }, - garanti: { - label: "Garanti", - cls: "bg-emerald-100 text-emerald-700", + riskli: { + badge: "bg-red-100 text-red-700", + kenar: "border-l-red-500", + icon: Rocket, + }, + "az-riskli": { + badge: "bg-amber-100 text-amber-700", + kenar: "border-l-amber-500", + icon: Scale, + }, + guvenli: { + badge: "bg-emerald-100 text-emerald-700", + kenar: "border-l-emerald-500", icon: ShieldCheck, }, }; /** * 24'lük listenin gövdesi (genel değerlendirme + tercih kartları). - * Hem sonuç modalında hem PDF/yazdır sayfasında kullanılır. + * Hem sonuç modalında hem sohbet sayfasında kullanılır. Risk rengi + * adayın sıralamasına göre deterministik hesaplanır (adaySira verilirse); + * eski raporlarda dilimden türetilir. */ -export function RaporListesi({ rapor }: { rapor: RaporSonuc }) { +export function RaporListesi({ + rapor, + adaySira, +}: { + rapor: RaporSonuc; + adaySira?: number; +}) { return (
@@ -29,14 +58,36 @@ export function RaporListesi({ rapor }: { rapor: RaporSonuc }) {

-
    +
    + {(["guvenli", "az-riskli", "riskli"] as const).map((r) => ( + + + {RISK_ETIKET[r]} + + ))} +
    + +
      {rapor.tercihler.map((t) => { const p = rapor.programlar[t.programId]; - const stil = DILIM_STIL[t.dilim]; + const risk = + (adaySira != null + ? riskHesapla(p?.efektifSira, adaySira) + : null) ?? dilimdenRisk(t.dilim); + const stil = RISK_STIL[risk]; return (
    1. @@ -47,11 +98,14 @@ export function RaporListesi({ rapor }: { rapor: RaporSonuc }) {

      {p?.universite} · {p?.il ?? "—"} {p?.unitur ? ` · ${p.unitur}` : ""} + {p?.efektifSira != null + ? ` · taban ~${p.efektifSira.toLocaleString("tr-TR")}.` + : ""}

      - + - {stil.label} + {DILIM_ETIKET[t.dilim]} · {RISK_ETIKET[risk]}
diff --git a/src/lib/ai/rapor.ts b/src/lib/ai/rapor.ts index d7186fd..a7b6757 100644 --- a/src/lib/ai/rapor.ts +++ b/src/lib/ai/rapor.ts @@ -9,6 +9,7 @@ import { type AdayProgram, } from "../rapor-havuzu"; import { secimOzeti } from "../sihirbaz"; +import { RISK_ETIKET, dilimdenRisk, riskHesapla } from "../risk"; const TercihSchema = z.object({ programId: z.string(), @@ -26,9 +27,16 @@ const RaporSchema = z.object({ export type RaporSonuc = z.infer & { // Render için havuzdan zenginleştirilmiş program bilgisi + // efektifSira: COALESCE(sira2025, sira2024) — eski kayıtlı raporlarda yok programlar: Record< string, - { isim: string; universite: string; il: string | null; unitur: string | null } + { + isim: string; + universite: string; + il: string | null; + unitur: string | null; + efektifSira?: number | null; + } >; }; @@ -162,17 +170,25 @@ export async function raporUret( universite: p.universite, il: p.il, unitur: p.unitur, + efektifSira: p.sira2025 ?? p.sira2024, }; } return { ...sonuc, programlar }; } -export function listeOzetiCikar(rapor: RaporSonuc): string { +export function listeOzetiCikar(rapor: RaporSonuc, adaySira?: number): string { return rapor.tercihler - .map( - (t) => - `${t.sira}. [${t.dilim}] ${rapor.programlar[t.programId]?.isim ?? "?"} — ${rapor.programlar[t.programId]?.universite ?? "?"}`, - ) + .map((t) => { + const p = rapor.programlar[t.programId]; + const risk = + (adaySira != null ? riskHesapla(p?.efektifSira, adaySira) : null) ?? + dilimdenRisk(t.dilim); + const taban = + p?.efektifSira != null + ? ` (taban sırası ~${p.efektifSira.toLocaleString("tr-TR")})` + : ""; + return `${t.sira}. [${t.dilim} | ${RISK_ETIKET[risk]}] ${p?.isim ?? "?"} — ${p?.universite ?? "?"}${taban}`; + }) .join("\n"); } diff --git a/src/lib/risk.ts b/src/lib/risk.ts new file mode 100644 index 0000000..ed219df --- /dev/null +++ b/src/lib/risk.ts @@ -0,0 +1,37 @@ +import type { Dilim } from "./rapor-havuzu"; + +/** + * Satır bazlı yerleşme riski. Renk dili ürünün her yerinde aynı: + * yeşil = güvenli, sarı = az riskli, kırmızı = riskli. + */ +export type RiskSeviyesi = "guvenli" | "az-riskli" | "riskli"; + +export const RISK_ETIKET: Record = { + guvenli: "Güvenli", + "az-riskli": "Az riskli", + riskli: "Riskli", +}; + +/** + * Deterministik risk: programın efektif taban sıralaması (COALESCE 2025→2024) + * adayın sıralamasıyla kıyaslanır. Eşikler db.ts'teki dilim aralıklarıyla + * birebir aynı (dengeli: [sira, 1.4×sira], garanti: (1.4×sira, 3×sira]). + */ +export function riskHesapla( + efektifSira: number | null | undefined, + adaySira: number, +): RiskSeviyesi | null { + if (efektifSira == null || !Number.isFinite(adaySira) || adaySira < 1) { + return null; + } + if (efektifSira > Math.round(adaySira * 1.4)) return "guvenli"; + if (efektifSira >= adaySira) return "az-riskli"; + return "riskli"; +} + +/** Eski raporlar için (efektifSira kaydedilmemiş): dilimden risk türet. */ +export function dilimdenRisk(dilim: Dilim | string): RiskSeviyesi { + if (dilim === "garanti") return "guvenli"; + if (dilim === "dengeli") return "az-riskli"; + return "riskli"; +}