merge: veri/2026-pb — okuyan taraf yılsız Program tipine geçti
This commit is contained in:
@@ -38,6 +38,7 @@ import {
|
|||||||
} from "../src/lib/tadimlik-havuzu";
|
} from "../src/lib/tadimlik-havuzu";
|
||||||
import { anahtarVar } from "../src/lib/ai/client";
|
import { anahtarVar } from "../src/lib/ai/client";
|
||||||
import { yapilandirilmisUret } from "../src/lib/ai/cagri";
|
import { yapilandirilmisUret } from "../src/lib/ai/cagri";
|
||||||
|
import { ILK_YIL, VERI_YIL_SAYISI } from "../src/lib/veri-yillari";
|
||||||
|
|
||||||
// Next dışında koşuyoruz; .env dosyalarını elle yükle (mevcut env kazanır).
|
// Next dışında koşuyoruz; .env dosyalarını elle yükle (mevcut env kazanır).
|
||||||
function envYukle(dosya: string): void {
|
function envYukle(dosya: string): void {
|
||||||
@@ -72,8 +73,8 @@ KURALLAR (kesin):
|
|||||||
riskli" etiketleriyle konuş.
|
riskli" etiketleriyle konuş.
|
||||||
- Programın "dilim" alanındaki "garanti" değeri teknik anahtardır; adaya dönük
|
- 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.
|
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 ${VERI_YIL_SAYISI} yılın taban sıralaması hareketi, TEK cümle
|
||||||
beri taban 45B'den 38B'ye indi, bölüm zorlaşıyor").
|
(ör. "2022'den 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ş.`;
|
||||||
|
|
||||||
function mockAktif(): boolean {
|
function mockAktif(): boolean {
|
||||||
@@ -87,27 +88,30 @@ function mockMetin(p: Program, temsiliSira: number, kategori: string | null) {
|
|||||||
riskNotu:
|
riskNotu:
|
||||||
"(Dev mock) Son taban sıralaması sırana yakın; küçük bir kayma sonucu değiştirebilir.",
|
"(Dev mock) Son taban sıralaması sırana yakın; küçük bir kayma sonucu değiştirebilir.",
|
||||||
trendOzeti:
|
trendOzeti:
|
||||||
"(Dev mock) Son 5 yılda taban sıralaması görece stabil seyretti.",
|
`(Dev mock) Son ${VERI_YIL_SAYISI} yılda taban sıralaması görece stabil seyretti.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** rapor.ts programBilgisi ile aynı snapshot şekli. */
|
/** rapor.ts programBilgisi ile aynı snapshot şekli. */
|
||||||
function programBilgisi(p: Program) {
|
function programBilgisi(p: Program) {
|
||||||
const gecmis = [p.sira2021, p.sira2022, p.sira2023, p.sira2024, p.sira2025];
|
const gecmis = p.siraGecmisi;
|
||||||
|
const gecmisVar = gecmis.some((s) => s != null);
|
||||||
return {
|
return {
|
||||||
isim: p.isim,
|
isim: p.isim,
|
||||||
universite: p.universite,
|
universite: p.universite,
|
||||||
il: p.il,
|
il: p.il,
|
||||||
unitur: p.unitur,
|
unitur: p.unitur,
|
||||||
efektifSira: p.sira2025 ?? p.sira2024,
|
efektifSira: p.efektifSira,
|
||||||
siraGecmisi: gecmis.some((s) => s != null) ? gecmis : undefined,
|
siraGecmisi: gecmisVar ? gecmis : undefined,
|
||||||
|
ilkYil: gecmisVar ? ILK_YIL : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deterministik program seçimi: kovanın temsili sırası için "dengeli" dilimden
|
* Deterministik program seçimi: kovanın temsili sırası için "dengeli" dilimden
|
||||||
* (boşsa "garanti") kategoriye ve üniversite tipine uyan, 2025'te en çok
|
* (boşsa "garanti") kategoriye ve üniversite tipine uyan, son yerleştirme
|
||||||
* yerleşen alan program. Tanınır/dolu programlar tadımlıkta daha ikna edici.
|
* yılında en çok yerleşen alan program. Tanınır/dolu programlar tadımlıkta
|
||||||
|
* daha ikna edici.
|
||||||
*/
|
*/
|
||||||
function programSec(
|
function programSec(
|
||||||
temsiliSira: number,
|
temsiliSira: number,
|
||||||
@@ -125,7 +129,7 @@ function programSec(
|
|||||||
: programlar;
|
: programlar;
|
||||||
if (uygunlar.length === 0) continue;
|
if (uygunlar.length === 0) continue;
|
||||||
const secilen = [...uygunlar].sort(
|
const secilen = [...uygunlar].sort(
|
||||||
(a, b) => (b.yerlesen2025 ?? -1) - (a.yerlesen2025 ?? -1),
|
(a, b) => (b.yerlesenSon ?? -1) - (a.yerlesenSon ?? -1),
|
||||||
)[0];
|
)[0];
|
||||||
return { program: secilen, dilim };
|
return { program: secilen, dilim };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
import { TrendingDown, TrendingUp } from "lucide-react";
|
import { TrendingDown, TrendingUp } from "lucide-react";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import { ILK_YIL } from "@/lib/veri-yillari";
|
||||||
|
|
||||||
/** Taban sıra sütunlarının yapısal alt kümesi: hem DB programı hem de
|
/** Taban sıra geçmişinin yapısal alt kümesi: hem DB programı hem de rapordaki
|
||||||
* rapordaki kaydedilmiş sıra geçmişi (bkz. raporSiraSerisi) bu tipe uyar. */
|
* kaydedilmiş sıra geçmişi (bkz. raporSiraSerisi) bu tipe uyar. Dizi `ilkYil`'a
|
||||||
export type SiraSerisi = Pick<
|
* (yoksa veri penceresinin ilk yılına) demirlidir: eleman yılı
|
||||||
Program,
|
* `ilkYil + indeks`. Uzunluk sabit DEĞİL — eski kayıtlı raporlar daha kısa
|
||||||
"sira2021" | "sira2022" | "sira2023" | "sira2024" | "sira2025"
|
* dizi taşır; yardımcılar dizinin kendi son iki elemanıyla çalışır. */
|
||||||
>;
|
export type SiraSerisi = {
|
||||||
|
siraGecmisi: readonly (number | null)[];
|
||||||
/** Rapordaki [2021..2025] dizisini sütunlu seriye çevirir. */
|
ilkYil?: number;
|
||||||
export function raporSiraSerisi(gecmis?: (number | null)[]): SiraSerisi {
|
|
||||||
const g = gecmis ?? [];
|
|
||||||
return {
|
|
||||||
sira2021: g[0] ?? null,
|
|
||||||
sira2022: g[1] ?? null,
|
|
||||||
sira2023: g[2] ?? null,
|
|
||||||
sira2024: g[3] ?? null,
|
|
||||||
sira2025: g[4] ?? null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Rapordaki kayıtlı diziyi seriye çevirir. Dizi DOLDURULMAZ: 5 elemanlı eski
|
||||||
|
* kayıt 5 elemanlı kalır, "son iki yıl" kaydın kendi son iki yılıdır. */
|
||||||
|
export function raporSiraSerisi(
|
||||||
|
gecmis?: (number | null)[],
|
||||||
|
ilkYil?: number,
|
||||||
|
): SiraSerisi {
|
||||||
|
return { siraGecmisi: gecmis ?? [], ilkYil };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Serinin son iki yılı: [bir önceki, son]. */
|
||||||
|
function sonIkiSira(program: SiraSerisi): [number | null, number | null] {
|
||||||
|
const g = program.siraGecmisi;
|
||||||
|
return [g[g.length - 2] ?? null, g[g.length - 1] ?? null];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function programEtkinSira(program: SiraSerisi): number | null {
|
export function programEtkinSira(program: SiraSerisi): number | null {
|
||||||
return program.sira2025 ?? program.sira2024;
|
const [onceki, son] = sonIkiSira(program);
|
||||||
|
return son ?? onceki;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 2021–2025 taban sıralamasını sonuç listesindeki kompakt çizgiyle gösterir. */
|
/** Taban sıralaması geçmişini sonuç listesindeki kompakt çizgiyle gösterir. */
|
||||||
export function ProgramSiraGecmisi({ program }: { program: SiraSerisi }) {
|
export function ProgramSiraGecmisi({ program }: { program: SiraSerisi }) {
|
||||||
// Satır başına render edilen bileşen: filter/map/min/max/map/map'in 6 ayrı
|
// Satır başına render edilen bileşen: filter/map/min/max/map/map'in 6 ayrı
|
||||||
// geçişi tek döngüde toplanır (js-combine-iterations, js-min-max-loop)
|
// geçişi tek döngüde toplanır (js-combine-iterations, js-min-max-loop)
|
||||||
@@ -32,14 +39,10 @@ export function ProgramSiraGecmisi({ program }: { program: SiraSerisi }) {
|
|||||||
let min = Infinity;
|
let min = Infinity;
|
||||||
let max = -Infinity;
|
let max = -Infinity;
|
||||||
const ozetParcalari: string[] = [];
|
const ozetParcalari: string[] = [];
|
||||||
for (const [yil, sira] of [
|
const ilkYil = program.ilkYil ?? ILK_YIL;
|
||||||
[2021, program.sira2021],
|
for (const [indeks, sira] of program.siraGecmisi.entries()) {
|
||||||
[2022, program.sira2022],
|
|
||||||
[2023, program.sira2023],
|
|
||||||
[2024, program.sira2024],
|
|
||||||
[2025, program.sira2025],
|
|
||||||
] as const) {
|
|
||||||
if (sira == null) continue;
|
if (sira == null) continue;
|
||||||
|
const yil = ilkYil + indeks;
|
||||||
yillar.push([yil, sira]);
|
yillar.push([yil, sira]);
|
||||||
if (sira < min) min = sira;
|
if (sira < min) min = sira;
|
||||||
if (sira > max) max = sira;
|
if (sira > max) max = sira;
|
||||||
@@ -89,12 +92,13 @@ export function ProgramSiraGecmisi({ program }: { program: SiraSerisi }) {
|
|||||||
|
|
||||||
/** Son iki yılın tabanı %10'dan fazla değiştiğinde yönü işaretler. */
|
/** Son iki yılın tabanı %10'dan fazla değiştiğinde yönü işaretler. */
|
||||||
export function ProgramTabanTrendi({ program }: { program: SiraSerisi }) {
|
export function ProgramTabanTrendi({ program }: { program: SiraSerisi }) {
|
||||||
if (program.sira2025 == null || program.sira2024 == null) return null;
|
const [onceki, son] = sonIkiSira(program);
|
||||||
|
if (son == null || onceki == null) return null;
|
||||||
|
|
||||||
const oran = (program.sira2024 - program.sira2025) / program.sira2024;
|
const oran = (onceki - son) / onceki;
|
||||||
if (Math.abs(oran) < 0.1) return null;
|
if (Math.abs(oran) < 0.1) return null;
|
||||||
|
|
||||||
const gecis = `${program.sira2024.toLocaleString("tr-TR")} → ${program.sira2025.toLocaleString("tr-TR")}`;
|
const gecis = `${onceki.toLocaleString("tr-TR")} → ${son.toLocaleString("tr-TR")}`;
|
||||||
if (oran > 0) {
|
if (oran > 0) {
|
||||||
return (
|
return (
|
||||||
<TrendingUp
|
<TrendingUp
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ import { VeriKaynagi } from "./veri-kaynagi";
|
|||||||
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
||||||
import { SiteFooter } from "@/components/site-footer";
|
import { SiteFooter } from "@/components/site-footer";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import {
|
||||||
|
BASLIK_YILI,
|
||||||
|
ONCEKI_YIL,
|
||||||
|
SON_YIL,
|
||||||
|
TERCIH_YILI,
|
||||||
|
} from "@/lib/veri-yillari";
|
||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||||
|
|
||||||
@@ -65,20 +71,20 @@ function bolumSayfaDilimi(bolum: BolumDetay, sayfa: number) {
|
|||||||
let minSira: number | null = null;
|
let minSira: number | null = null;
|
||||||
let maxSira: number | null = null;
|
let maxSira: number | null = null;
|
||||||
let verisiz = 0;
|
let verisiz = 0;
|
||||||
// Sırası 2025'te olmayıp 2024'ten gelen satırlar: aralık metni yılı dürüstçe
|
// Sırası son yılda olmayıp bir önceki yıldan gelen satırlar: aralık metni
|
||||||
// söylesin (tablonun Sıralama sütunu bu satırlarda "—" gösterir).
|
// yılı dürüstçe söylesin (tablonun Sıralama sütunu bu satırlarda "—" gösterir).
|
||||||
let yalniz2024 = 0;
|
let yalnizOnceki = 0;
|
||||||
for (const p of dilim) {
|
for (const p of dilim) {
|
||||||
const efektif = p.sira2025 ?? p.sira2024;
|
const efektif = p.efektifSira;
|
||||||
if (efektif == null) {
|
if (efektif == null) {
|
||||||
verisiz += 1;
|
verisiz += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (p.sira2025 == null) yalniz2024 += 1;
|
if (p.siraSon == null) yalnizOnceki += 1;
|
||||||
if (minSira == null || efektif < minSira) minSira = efektif;
|
if (minSira == null || efektif < minSira) minSira = efektif;
|
||||||
if (maxSira == null || efektif > maxSira) maxSira = efektif;
|
if (maxSira == null || efektif > maxSira) maxSira = efektif;
|
||||||
}
|
}
|
||||||
return { tumProgramlar, dilim, minSira, maxSira, verisiz, yalniz2024 };
|
return { tumProgramlar, dilim, minSira, maxSira, verisiz, yalnizOnceki };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** <title> için kısa sıra: 2.425.061 → "2,43 mn", 95.400 → "95 bin". */
|
/** <title> için kısa sıra: 2.425.061 → "2,43 mn", 95.400 → "95 bin". */
|
||||||
@@ -99,13 +105,13 @@ function siraAraligiMetni({
|
|||||||
minSira,
|
minSira,
|
||||||
maxSira,
|
maxSira,
|
||||||
verisiz,
|
verisiz,
|
||||||
yalniz2024,
|
yalnizOnceki,
|
||||||
}: ReturnType<typeof bolumSayfaDilimi>): string | null {
|
}: ReturnType<typeof bolumSayfaDilimi>): string | null {
|
||||||
if (dilim.length === 0) return null;
|
if (dilim.length === 0) return null;
|
||||||
if (minSira == null || maxSira == null) {
|
if (minSira == null || maxSira == null) {
|
||||||
return "taban verisi olmayan programlar";
|
return "taban verisi olmayan programlar";
|
||||||
}
|
}
|
||||||
const yil = siraYiliEtiketi(yalniz2024, dilim.length - verisiz);
|
const yil = siraYiliEtiketi(yalnizOnceki, dilim.length - verisiz);
|
||||||
const aralik =
|
const aralik =
|
||||||
minSira === maxSira
|
minSira === maxSira
|
||||||
? `${yil} taban sırası ${sayi(minSira)} olan programlar`
|
? `${yil} taban sırası ${sayi(minSira)} olan programlar`
|
||||||
@@ -134,8 +140,8 @@ export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
|
|||||||
const meta = sayfaMetadata({
|
const meta = sayfaMetadata({
|
||||||
title: baslik,
|
title: baslik,
|
||||||
description: aralik
|
description: aralik
|
||||||
? `${bolum.ad} taban puanları, sayfa ${sayfa}: ${aralik}. Toplam ${stats.toplam} programın son yerleştirme verileri (2025; 2025'te verisi olmayanlarda 2024) — taban puanı, başarı sıralaması ve kontenjan.`
|
? `${bolum.ad} taban puanları, sayfa ${sayfa}: ${aralik}. Toplam ${stats.toplam} programın son yerleştirme verileri (${SON_YIL}; o yıl verisi olmayanlarda ${ONCEKI_YIL}) — taban puanı, başarı sıralaması ve kontenjan.`
|
||||||
: `${bolum.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${stats.toplam} programın 2025 yerleştirme verileri.`,
|
: `${bolum.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${stats.toplam} programın ${SON_YIL} yerleştirme verileri.`,
|
||||||
path: `/bolum/${slug}/sayfa/${sayfa}`,
|
path: `/bolum/${slug}/sayfa/${sayfa}`,
|
||||||
openGraph: {
|
openGraph: {
|
||||||
images: ustSegmentOgGorseli(
|
images: ustSegmentOgGorseli(
|
||||||
@@ -151,8 +157,8 @@ export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
|
|||||||
? ` En iyi taban sıralaması ${sayi(stats.minSira)}.`
|
? ` En iyi taban sıralaması ${sayi(stats.minSira)}.`
|
||||||
: "";
|
: "";
|
||||||
return sayfaMetadata({
|
return sayfaMetadata({
|
||||||
title: `${bolum.ad} Taban Puanları ve Başarı Sıralamaları 2026`,
|
title: `${bolum.ad} Taban Puanları ve Başarı Sıralamaları ${BASLIK_YILI}`,
|
||||||
description: `${bolum.ad} 2025 yerleştirme verileri: ${stats.toplam} programın taban puanı ve sıralaması, ${stats.devlet} devlet / ${stats.vakif} vakıf.${enIyi} 2026 tercih rehberi.`,
|
description: `${bolum.ad} ${SON_YIL} yerleştirme verileri: ${stats.toplam} programın taban puanı ve sıralaması, ${stats.devlet} devlet / ${stats.vakif} vakıf.${enIyi} ${TERCIH_YILI} tercih rehberi.`,
|
||||||
path: `/bolum/${slug}`,
|
path: `/bolum/${slug}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -230,7 +236,7 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
|||||||
</h1>
|
</h1>
|
||||||
{ilkSayfa ? (
|
{ilkSayfa ? (
|
||||||
<p className="mt-3 max-w-2xl text-sm leading-6 text-muted-foreground sm:text-base">
|
<p className="mt-3 max-w-2xl text-sm leading-6 text-muted-foreground sm:text-base">
|
||||||
{sayi(tumProgramlar.length)} üniversite programının 2025 taban
|
{sayi(tumProgramlar.length)} üniversite programının {SON_YIL} taban
|
||||||
puanlarını, başarı sıralamalarını ve kontenjanlarını incele.
|
puanlarını, başarı sıralamalarını ve kontenjanlarını incele.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Link from "next/link";
|
|||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
import { trBaslikDuzeni } from "@/lib/slug";
|
import { trBaslikDuzeni } from "@/lib/slug";
|
||||||
|
import { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
||||||
import { Sayfalama, SAYFA_BOYU } from "./sayfalama";
|
import { Sayfalama, SAYFA_BOYU } from "./sayfalama";
|
||||||
import {
|
import {
|
||||||
ProgramSiraGecmisi,
|
ProgramSiraGecmisi,
|
||||||
@@ -24,14 +25,14 @@ import {
|
|||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||||
|
|
||||||
type SiralamaAlani = "sira2025" | "puan2025" | "kontenjan2025";
|
type SiralamaAlani = "siraSon" | "puanSon" | "kontenjanSon";
|
||||||
type SiralamaYonu = "artan" | "azalan";
|
type SiralamaYonu = "artan" | "azalan";
|
||||||
export type BolumProgrami = Program & { universiteSlug: string | null };
|
export type BolumProgrami = Program & { universiteSlug: string | null };
|
||||||
|
|
||||||
const ALAN_ETIKETLERI: Record<SiralamaAlani, string> = {
|
const ALAN_ETIKETLERI: Record<SiralamaAlani, string> = {
|
||||||
sira2025: "Sıralama",
|
siraSon: "Sıralama",
|
||||||
puan2025: "Puan",
|
puanSon: "Puan",
|
||||||
kontenjan2025: "Kontenjan",
|
kontenjanSon: "Kontenjan",
|
||||||
};
|
};
|
||||||
|
|
||||||
function SiralamaDugmesi({
|
function SiralamaDugmesi({
|
||||||
@@ -157,11 +158,11 @@ export function BolumProgramListesi({
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Üniversite</TableHead>
|
<TableHead>Üniversite</TableHead>
|
||||||
<TableHead className="hidden text-right md:table-cell">
|
<TableHead className="hidden text-right md:table-cell">
|
||||||
Son 5 yıl
|
Son {VERI_YIL_SAYISI} yıl
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="hidden text-right lg:table-cell">
|
<TableHead className="hidden text-right lg:table-cell">
|
||||||
<SiralamaDugmesi
|
<SiralamaDugmesi
|
||||||
alan="puan2025"
|
alan="puanSon"
|
||||||
aktifAlan={aktifAlan}
|
aktifAlan={aktifAlan}
|
||||||
yon={yon}
|
yon={yon}
|
||||||
onSirala={sirala}
|
onSirala={sirala}
|
||||||
@@ -169,7 +170,7 @@ export function BolumProgramListesi({
|
|||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="hidden text-right sm:table-cell">
|
<TableHead className="hidden text-right sm:table-cell">
|
||||||
<SiralamaDugmesi
|
<SiralamaDugmesi
|
||||||
alan="kontenjan2025"
|
alan="kontenjanSon"
|
||||||
aktifAlan={aktifAlan}
|
aktifAlan={aktifAlan}
|
||||||
yon={yon}
|
yon={yon}
|
||||||
onSirala={sirala}
|
onSirala={sirala}
|
||||||
@@ -177,7 +178,7 @@ export function BolumProgramListesi({
|
|||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="text-right">
|
<TableHead className="text-right">
|
||||||
<SiralamaDugmesi
|
<SiralamaDugmesi
|
||||||
alan="sira2025"
|
alan="siraSon"
|
||||||
aktifAlan={aktifAlan}
|
aktifAlan={aktifAlan}
|
||||||
yon={yon}
|
yon={yon}
|
||||||
onSirala={sirala}
|
onSirala={sirala}
|
||||||
@@ -243,17 +244,17 @@ export function BolumProgramListesi({
|
|||||||
<ProgramSiraGecmisi program={p} />
|
<ProgramSiraGecmisi program={p} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 lg:table-cell">
|
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 lg:table-cell">
|
||||||
{p.puan2025 != null
|
{p.puanSon != null
|
||||||
? p.puan2025.toFixed(2).replace(".", ",")
|
? p.puanSon.toFixed(2).replace(".", ",")
|
||||||
: "—"}
|
: "—"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 sm:table-cell">
|
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 sm:table-cell">
|
||||||
{p.kontenjan2025 != null ? `${sayi(p.kontenjan2025)} kişi` : "—"}
|
{p.kontenjanSon != null ? `${sayi(p.kontenjanSon)} kişi` : "—"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right text-xs tabular-nums text-slate-500">
|
<TableCell className="text-right text-xs tabular-nums text-slate-500">
|
||||||
<span className="inline-flex items-center gap-1">
|
<span className="inline-flex items-center gap-1">
|
||||||
<ProgramTabanTrendi program={p} />
|
<ProgramTabanTrendi program={p} />
|
||||||
{p.sira2025 != null ? `~${sayi(p.sira2025)}.` : "—"}
|
{p.siraSon != null ? `~${sayi(p.siraSon)}.` : "—"}
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="pr-3 text-right">
|
<TableCell className="pr-3 text-right">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
|
import { ONCEKI_YIL, SON_YIL } from "@/lib/veri-yillari";
|
||||||
|
|
||||||
/** Programatik tablo sayfaları için satır dilimi boyutu. */
|
/** Programatik tablo sayfaları için satır dilimi boyutu. */
|
||||||
export const SAYFA_BOYU = 50;
|
export const SAYFA_BOYU = 50;
|
||||||
@@ -13,10 +14,19 @@ export function sayfaDilimi<T>(liste: readonly T[], sayfa: number): T[] {
|
|||||||
return liste.slice((sayfa - 1) * SAYFA_BOYU, sayfa * SAYFA_BOYU);
|
return liste.slice((sayfa - 1) * SAYFA_BOYU, sayfa * SAYFA_BOYU);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Aralıktaki sıraların hangi yıldan geldiği: hepsi 2025 / hepsi 2024 / karışık. */
|
/**
|
||||||
export function siraYiliEtiketi(yalniz2024: number, siraliSayi: number): string {
|
* Aralıktaki sıraların hangi yıldan geldiği: hepsi son yıl / hepsi bir önceki
|
||||||
if (yalniz2024 === 0) return "2025";
|
* yıl / karışık. `yalnizOnceki`: sırası son yılda olmayıp bir önceki yıldan
|
||||||
return yalniz2024 === siraliSayi ? "2024" : "2024–2025";
|
* gelen satır sayısı; `siraliSayi`: sırası olan toplam satır.
|
||||||
|
*/
|
||||||
|
export function siraYiliEtiketi(
|
||||||
|
yalnizOnceki: number,
|
||||||
|
siraliSayi: number,
|
||||||
|
): string {
|
||||||
|
if (yalnizOnceki === 0) return String(SON_YIL);
|
||||||
|
return yalnizOnceki === siraliSayi
|
||||||
|
? String(ONCEKI_YIL)
|
||||||
|
: `${ONCEKI_YIL}–${SON_YIL}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { UniLogo } from "@/components/uni-logo";
|
|||||||
import { uniLogoYolu } from "@/lib/uni-logolar";
|
import { uniLogoYolu } from "@/lib/uni-logolar";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
|
import { BASLIK_YILI, SON_YIL, TERCIH_YILI } from "@/lib/veri-yillari";
|
||||||
|
|
||||||
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
const sayi = (n: number) => n.toLocaleString("tr-TR");
|
||||||
|
|
||||||
@@ -91,13 +92,13 @@ function fakulteOzeti({
|
|||||||
if (adlar.length === 0) return null;
|
if (adlar.length === 0) return null;
|
||||||
if (adlar.length === 1) {
|
if (adlar.length === 1) {
|
||||||
const siralar = dilim
|
const siralar = dilim
|
||||||
.map((p) => p.sira2025 ?? p.sira2024)
|
.map((p) => p.efektifSira)
|
||||||
.filter((n): n is number => n != null);
|
.filter((n): n is number => n != null);
|
||||||
if (siralar.length === 0) return adlar[0];
|
if (siralar.length === 0) return adlar[0];
|
||||||
const min = Math.min(...siralar);
|
const min = Math.min(...siralar);
|
||||||
const max = Math.max(...siralar);
|
const max = Math.max(...siralar);
|
||||||
const yil = siraYiliEtiketi(
|
const yil = siraYiliEtiketi(
|
||||||
dilim.filter((p) => p.sira2025 == null && p.sira2024 != null).length,
|
dilim.filter((p) => p.siraSon == null && p.efektifSira != null).length,
|
||||||
siralar.length,
|
siralar.length,
|
||||||
);
|
);
|
||||||
return min === max
|
return min === max
|
||||||
@@ -116,10 +117,10 @@ export function uniSayfaMetadata(slug: string, sayfa: number): Metadata {
|
|||||||
// farklılaştırıcı (o sayfadaki birimler) açıklamaya ve gövdeye gider.
|
// farklılaştırıcı (o sayfadaki birimler) açıklamaya ve gövdeye gider.
|
||||||
const birimler = fakulteOzeti(uniSayfaDilimi(uni, sayfa));
|
const birimler = fakulteOzeti(uniSayfaDilimi(uni, sayfa));
|
||||||
return sayfaMetadata({
|
return sayfaMetadata({
|
||||||
title: `${uni.ad} Taban Puanları 2026 (Sayfa ${sayfa})`,
|
title: `${uni.ad} Taban Puanları ${BASLIK_YILI} (Sayfa ${sayfa})`,
|
||||||
description: birimler
|
description: birimler
|
||||||
? `${uni.ad} taban puanları, sayfa ${sayfa}: ${birimler}. Toplam ${uni.programSayisi} programın 2025 yerleştirme verileri — taban puanı, başarı sıralaması ve kontenjan.`
|
? `${uni.ad} taban puanları, sayfa ${sayfa}: ${birimler}. Toplam ${uni.programSayisi} programın ${SON_YIL} yerleştirme verileri — taban puanı, başarı sıralaması ve kontenjan.`
|
||||||
: `${uni.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${uni.programSayisi} programın 2025 yerleştirme verileri.`,
|
: `${uni.ad} taban puanları ve başarı sıralamaları tablosunun ${sayfa}. sayfası — toplam ${uni.programSayisi} programın ${SON_YIL} yerleştirme verileri.`,
|
||||||
path: `/universite/${slug}/sayfa/${sayfa}`,
|
path: `/universite/${slug}/sayfa/${sayfa}`,
|
||||||
openGraph: {
|
openGraph: {
|
||||||
images: ustSegmentOgGorseli(
|
images: ustSegmentOgGorseli(
|
||||||
@@ -130,8 +131,8 @@ export function uniSayfaMetadata(slug: string, sayfa: number): Metadata {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return sayfaMetadata({
|
return sayfaMetadata({
|
||||||
title: `${uni.ad} Taban Puanları ve Başarı Sıralamaları 2026`,
|
title: `${uni.ad} Taban Puanları ve Başarı Sıralamaları ${BASLIK_YILI}`,
|
||||||
description: `${uni.ad} 2025 yerleştirme verileri: ${uni.programSayisi} programın taban puanları, başarı sıralamaları ve kontenjanları${uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""}. 2026 tercih rehberi.`,
|
description: `${uni.ad} ${SON_YIL} yerleştirme verileri: ${uni.programSayisi} programın taban puanları, başarı sıralamaları ve kontenjanları${uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""}. ${TERCIH_YILI} tercih rehberi.`,
|
||||||
path: `/universite/${slug}`,
|
path: `/universite/${slug}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -241,7 +242,7 @@ export function UniversiteIcerik({
|
|||||||
<div className="mt-4 flex items-center gap-4">
|
<div className="mt-4 flex items-center gap-4">
|
||||||
<UniLogo slug={slug} ad={uni.ad} boy="lg" />
|
<UniLogo slug={slug} ad={uni.ad} boy="lg" />
|
||||||
<h1 className="font-heading text-3xl font-bold text-slate-900 sm:text-4xl">
|
<h1 className="font-heading text-3xl font-bold text-slate-900 sm:text-4xl">
|
||||||
{uni.ad} Taban Puanları 2026
|
{uni.ad} Taban Puanları {BASLIK_YILI}
|
||||||
{ilkSayfa ? "" : ` — Sayfa ${sayfa}`}
|
{ilkSayfa ? "" : ` — Sayfa ${sayfa}`}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -250,7 +251,7 @@ export function UniversiteIcerik({
|
|||||||
<>
|
<>
|
||||||
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
<p className="mt-4 max-w-3xl text-sm leading-7 text-slate-600">
|
||||||
{uni.ad}
|
{uni.ad}
|
||||||
{uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""}, 2025 YKS
|
{uni.il ? ` (${trBaslikDuzeni(uni.il)})` : ""}, {SON_YIL} YKS
|
||||||
yerleştirme sonuçlarına göre{" "}
|
yerleştirme sonuçlarına göre{" "}
|
||||||
<strong>{sayi(uni.programSayisi)} programda</strong> öğrenci aldı
|
<strong>{sayi(uni.programSayisi)} programda</strong> öğrenci aldı
|
||||||
{uni.fakulteSayisi > 1
|
{uni.fakulteSayisi > 1
|
||||||
@@ -267,10 +268,11 @@ export function UniversiteIcerik({
|
|||||||
{uni.stats.toplamKontenjan > 0 ? (
|
{uni.stats.toplamKontenjan > 0 ? (
|
||||||
<> Toplam kontenjan {sayi(uni.stats.toplamKontenjan)}.</>
|
<> Toplam kontenjan {sayi(uni.stats.toplamKontenjan)}.</>
|
||||||
) : null}{" "}
|
) : null}{" "}
|
||||||
Aşağıdaki tablolar 2026 tercihleri için rehber niteliğindedir;{" "}
|
Aşağıdaki tablolar {TERCIH_YILI} tercihleri için rehber
|
||||||
|
niteliğindedir;{" "}
|
||||||
<em>
|
<em>
|
||||||
2026 taban puanları ve sıralamaları, yerleştirme sonuçları
|
{TERCIH_YILI} taban puanları ve sıralamaları, yerleştirme
|
||||||
açıklandığında netleşir.
|
sonuçları açıklandığında netleşir.
|
||||||
</em>
|
</em>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
|
import { SON_YIL, VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
||||||
import {
|
import {
|
||||||
ProgramSiraGecmisi,
|
ProgramSiraGecmisi,
|
||||||
ProgramTabanTrendi,
|
ProgramTabanTrendi,
|
||||||
@@ -48,10 +49,10 @@ export function UniversiteProgramTablosu({
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Program</TableHead>
|
<TableHead>Program</TableHead>
|
||||||
<TableHead className="hidden text-right md:table-cell">
|
<TableHead className="hidden text-right md:table-cell">
|
||||||
Son 5 yıl
|
Son {VERI_YIL_SAYISI} yıl
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="hidden text-right lg:table-cell">
|
<TableHead className="hidden text-right lg:table-cell">
|
||||||
2025 puan
|
{SON_YIL} puan
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="hidden text-right sm:table-cell">
|
<TableHead className="hidden text-right sm:table-cell">
|
||||||
Kontenjan
|
Kontenjan
|
||||||
@@ -93,13 +94,13 @@ export function UniversiteProgramTablosu({
|
|||||||
<ProgramSiraGecmisi program={p} />
|
<ProgramSiraGecmisi program={p} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 lg:table-cell">
|
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 lg:table-cell">
|
||||||
{p.puan2025 != null
|
{p.puanSon != null
|
||||||
? p.puan2025.toFixed(2).replace(".", ",")
|
? p.puanSon.toFixed(2).replace(".", ",")
|
||||||
: "—"}
|
: "—"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 sm:table-cell">
|
<TableCell className="hidden text-right text-xs tabular-nums text-slate-500 sm:table-cell">
|
||||||
{p.kontenjan2025 != null
|
{p.kontenjanSon != null
|
||||||
? `${sayi(p.kontenjan2025)} kişi`
|
? `${sayi(p.kontenjanSon)} kişi`
|
||||||
: "—"}
|
: "—"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right text-xs tabular-nums text-slate-500">
|
<TableCell className="text-right text-xs tabular-nums text-slate-500">
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ type ProgramOzet = Pick<
|
|||||||
| "il"
|
| "il"
|
||||||
| "unitur"
|
| "unitur"
|
||||||
| "tur"
|
| "tur"
|
||||||
| "sira2025"
|
| "efektifSira"
|
||||||
| "sira2024"
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const PROFILSIZ_EKLEME_TOAST_ID = "profilsiz-ekleme";
|
const PROFILSIZ_EKLEME_TOAST_ID = "profilsiz-ekleme";
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import type {
|
|||||||
} from "@/types/yokatlas";
|
} from "@/types/yokatlas";
|
||||||
import { DILIM_ETIKET, 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 { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
||||||
import {
|
import {
|
||||||
ProgramSiraGecmisi,
|
ProgramSiraGecmisi,
|
||||||
ProgramTabanTrendi,
|
ProgramTabanTrendi,
|
||||||
@@ -239,7 +240,7 @@ export function ProgramTablosu({
|
|||||||
<TableHead className="w-8" />
|
<TableHead className="w-8" />
|
||||||
<TableHead>Program</TableHead>
|
<TableHead>Program</TableHead>
|
||||||
<TableHead className="hidden text-right md:table-cell">
|
<TableHead className="hidden text-right md:table-cell">
|
||||||
Son 5 yıl
|
Son {VERI_YIL_SAYISI} yıl
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="hidden text-right sm:table-cell">
|
<TableHead className="hidden text-right sm:table-cell">
|
||||||
Kontenjan
|
Kontenjan
|
||||||
@@ -357,19 +358,19 @@ function ProgramSatiri({
|
|||||||
<TableCell
|
<TableCell
|
||||||
className="hidden text-right text-xs tabular-nums text-slate-500 sm:table-cell"
|
className="hidden text-right text-xs tabular-nums text-slate-500 sm:table-cell"
|
||||||
title={
|
title={
|
||||||
p.kontenjan2025 != null && p.yerlesen2025 != null
|
p.kontenjanSon != null && p.yerlesenSon != null
|
||||||
? `${p.kontenjan2025} kontenjan, ${p.yerlesen2025} yerleşen`
|
? `${p.kontenjanSon} kontenjan, ${p.yerlesenSon} yerleşen`
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{p.kontenjan2025 != null ? (
|
{p.kontenjanSon != null ? (
|
||||||
<span className="inline-flex items-center gap-1.5">
|
<span className="inline-flex items-center gap-1.5">
|
||||||
{p.yerlesen2025 != null && p.yerlesen2025 < p.kontenjan2025 ? (
|
{p.yerlesenSon != null && p.yerlesenSon < p.kontenjanSon ? (
|
||||||
<span className="rounded bg-amber-50 px-1 py-px text-[10px] font-medium leading-4 text-amber-600">
|
<span className="rounded bg-amber-50 px-1 py-px text-[10px] font-medium leading-4 text-amber-600">
|
||||||
boş kaldı
|
boş kaldı
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
{p.kontenjan2025.toLocaleString("tr-TR")} kişi
|
{p.kontenjanSon.toLocaleString("tr-TR")} kişi
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
"—"
|
"—"
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ export type BekleyenProgram = {
|
|||||||
| "il"
|
| "il"
|
||||||
| "unitur"
|
| "unitur"
|
||||||
| "tur"
|
| "tur"
|
||||||
| "sira2025"
|
| "efektifSira"
|
||||||
| "sira2024"
|
|
||||||
>;
|
>;
|
||||||
kaynak: ManuelKaynak;
|
kaynak: ManuelKaynak;
|
||||||
adaySira?: number;
|
adaySira?: number;
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import type {
|
|||||||
UniturGrubu,
|
UniturGrubu,
|
||||||
} from "@/types/yokatlas";
|
} from "@/types/yokatlas";
|
||||||
|
|
||||||
// Tabloda kullanılmayan alanlar (fakulte, puan2025) client'a serileşmesin.
|
// Tabloda kullanılmayan alanlar (fakulte, puanSon, siraSon) client'a
|
||||||
|
// serileşmesin.
|
||||||
// Alanlar tek tek seçilir: Program'a alan eklenirse ProgramListeSatiri
|
// Alanlar tek tek seçilir: Program'a alan eklenirse ProgramListeSatiri
|
||||||
// üzerinden derleyici burada da karar vermeye zorlar.
|
// üzerinden derleyici burada da karar vermeye zorlar.
|
||||||
export function programOzetle(p: Program): ProgramListeSatiri {
|
export function programOzetle(p: Program): ProgramListeSatiri {
|
||||||
@@ -20,13 +21,10 @@ export function programOzetle(p: Program): ProgramListeSatiri {
|
|||||||
il: p.il,
|
il: p.il,
|
||||||
tur: p.tur,
|
tur: p.tur,
|
||||||
sure: p.sure,
|
sure: p.sure,
|
||||||
sira2025: p.sira2025,
|
siraGecmisi: p.siraGecmisi,
|
||||||
sira2024: p.sira2024,
|
efektifSira: p.efektifSira,
|
||||||
sira2023: p.sira2023,
|
kontenjanSon: p.kontenjanSon,
|
||||||
sira2022: p.sira2022,
|
yerlesenSon: p.yerlesenSon,
|
||||||
sira2021: p.sira2021,
|
|
||||||
kontenjan2025: p.kontenjan2025,
|
|
||||||
yerlesen2025: p.yerlesen2025,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,10 +158,8 @@ export function sunucuylaBirlestir(sunucuItems: ManuelTercih[]): ManuelTercih[]
|
|||||||
return liste;
|
return liste;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function efektifSiraAl(
|
export function efektifSiraAl(p: Pick<Program, "efektifSira">): number | null {
|
||||||
p: Pick<Program, "sira2025" | "sira2024">,
|
return p.efektifSira ?? null;
|
||||||
): number | null {
|
|
||||||
return p.sira2025 ?? p.sira2024 ?? null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Program satırını manuel liste kaydına çevirir. */
|
/** Program satırını manuel liste kaydına çevirir. */
|
||||||
@@ -174,8 +172,7 @@ export function programdanManuelTercih(
|
|||||||
| "il"
|
| "il"
|
||||||
| "unitur"
|
| "unitur"
|
||||||
| "tur"
|
| "tur"
|
||||||
| "sira2025"
|
| "efektifSira"
|
||||||
| "sira2024"
|
|
||||||
>,
|
>,
|
||||||
adaySira?: number | null,
|
adaySira?: number | null,
|
||||||
kaynak: ManuelKaynak = "sonuc",
|
kaynak: ManuelKaynak = "sonuc",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// 24'lük listenin gövdesi. Görsel dil sonuç sayfasındaki ham program
|
// 24'lük listenin gövdesi. Görsel dil sonuç sayfasındaki ham program
|
||||||
// tablosuyla birebir aynıdır (bkz. program-tablosu.tsx): aynı beyaz kart,
|
// tablosuyla birebir aynıdır (bkz. program-tablosu.tsx): aynı beyaz kart,
|
||||||
// aynı satır düzeni, aynı "Son 5 yıl" / "Taban sıra" sütunları. Buradaki fark
|
// aynı satır düzeni, aynı "Son N yıl" / "Taban sıra" sütunları. Buradaki fark
|
||||||
// içerik: satır sırası tercih numarası, risk sütunu Yapay Zeka'dan gelir ve
|
// içerik: satır sırası tercih numarası, risk sütunu Yapay Zeka'dan gelir ve
|
||||||
// satır açıldığında gerekçe/risk notu görünür. Program adı ve tabanlar herkese
|
// satır açıldığında gerekçe/risk notu görünür. Program adı ve tabanlar herkese
|
||||||
// açık; Yapay Zeka katmanı kilitliBaslangic sonrasında ücretlidir (metin
|
// açık; Yapay Zeka katmanı kilitliBaslangic sonrasında ücretlidir (metin
|
||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug";
|
import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug";
|
||||||
|
import { VERI_YIL_SAYISI } from "@/lib/veri-yillari";
|
||||||
import type { RaporSonuc } from "@/features/rapor/types/rapor";
|
import type { RaporSonuc } from "@/features/rapor/types/rapor";
|
||||||
import {
|
import {
|
||||||
DILIM_ETIKET,
|
DILIM_ETIKET,
|
||||||
@@ -47,7 +48,7 @@ const RISK_STIL: Record<RiskSeviyesi, { nokta: string; icon: typeof Rocket }> =
|
|||||||
guvenli: { nokta: "bg-emerald-500", icon: ShieldCheck },
|
guvenli: { nokta: "bg-emerald-500", icon: ShieldCheck },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sütun sayısı: # · Program · Son 5 yıl · Risk · Taban sıra · detay oku
|
// Sütun sayısı: # · Program · Son N yıl · Risk · Taban sıra · detay oku
|
||||||
const SUTUN_SAYISI = 6;
|
const SUTUN_SAYISI = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,6 +162,19 @@ export function RaporListesi({
|
|||||||
return iller.size === 1 ? [...iller][0] : null;
|
return iller.size === 1 ? [...iller][0] : null;
|
||||||
}, [rapor]);
|
}, [rapor]);
|
||||||
|
|
||||||
|
// Sütun başlığı kaydın KENDİ penceresini söyler: eski raporların sıra
|
||||||
|
// geçmişi üretildiği günün yıl sayısını taşır (dizi doldurulmaz), başlık
|
||||||
|
// çizgide olmayan bir yılı vaat etmesin. Geçmişi hiç olmayan kayıtta
|
||||||
|
// bugünkü pencereye düşer.
|
||||||
|
const yilSayisi = useMemo(() => {
|
||||||
|
let enUzun = 0;
|
||||||
|
for (const t of rapor.tercihler) {
|
||||||
|
const n = rapor.programlar[t.programId]?.siraGecmisi?.length ?? 0;
|
||||||
|
if (n > enUzun) enUzun = n;
|
||||||
|
}
|
||||||
|
return enUzun > 0 ? enUzun : VERI_YIL_SAYISI;
|
||||||
|
}, [rapor]);
|
||||||
|
|
||||||
const satir = (t: RaporSonuc["tercihler"][number], index: number) => {
|
const satir = (t: RaporSonuc["tercihler"][number], index: number) => {
|
||||||
const p = rapor.programlar[t.programId];
|
const p = rapor.programlar[t.programId];
|
||||||
const analizKilitli = index >= kilitIdx;
|
const analizKilitli = index >= kilitIdx;
|
||||||
@@ -168,7 +182,7 @@ export function RaporListesi({
|
|||||||
(adaySira != null ? riskHesapla(p?.efektifSira, adaySira) : null) ??
|
(adaySira != null ? riskHesapla(p?.efektifSira, adaySira) : null) ??
|
||||||
dilimdenRisk(t.dilim);
|
dilimdenRisk(t.dilim);
|
||||||
const stil = RISK_STIL[risk];
|
const stil = RISK_STIL[risk];
|
||||||
const seri = raporSiraSerisi(p?.siraGecmisi);
|
const seri = raporSiraSerisi(p?.siraGecmisi, p?.ilkYil);
|
||||||
const taban = p?.efektifSira ?? programEtkinSira(seri);
|
const taban = p?.efektifSira ?? programEtkinSira(seri);
|
||||||
const acikMi = acik.has(t.sira);
|
const acikMi = acik.has(t.sira);
|
||||||
const devlet = p?.unitur === "DEVLET";
|
const devlet = p?.unitur === "DEVLET";
|
||||||
@@ -366,7 +380,7 @@ export function RaporListesi({
|
|||||||
<TableHead className="w-8 pl-4 pr-0">#</TableHead>
|
<TableHead className="w-8 pl-4 pr-0">#</TableHead>
|
||||||
<TableHead>Program</TableHead>
|
<TableHead>Program</TableHead>
|
||||||
<TableHead className="hidden text-right md:table-cell">
|
<TableHead className="hidden text-right md:table-cell">
|
||||||
Son 5 yıl
|
Son {yilSayisi} yıl
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<span className="hidden sm:inline">Yapay Zeka </span>Risk
|
<span className="hidden sm:inline">Yapay Zeka </span>Risk
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export function TadimlikSatiri({
|
|||||||
|
|
||||||
const p = satir.program;
|
const p = satir.program;
|
||||||
const risk = riskHesapla(p.efektifSira, sira) ?? dilimdenRisk(satir.dilim);
|
const risk = riskHesapla(p.efektifSira, sira) ?? dilimdenRisk(satir.dilim);
|
||||||
const seri = raporSiraSerisi(p.siraGecmisi);
|
const seri = raporSiraSerisi(p.siraGecmisi, p.ilkYil);
|
||||||
const taban = p.efektifSira ?? programEtkinSira(seri);
|
const taban = p.efektifSira ?? programEtkinSira(seri);
|
||||||
const devlet = p.unitur === "DEVLET";
|
const devlet = p.unitur === "DEVLET";
|
||||||
const girisUrl = `/giris?callback=${encodeURIComponent(sonucHref())}`;
|
const girisUrl = `/giris?callback=${encodeURIComponent(sonucHref())}`;
|
||||||
|
|||||||
@@ -34,8 +34,13 @@ export type RaporSonuc = {
|
|||||||
// ayıklamak için (bkz. features/rapor/rapor-queries.ts)
|
// ayıklamak için (bkz. features/rapor/rapor-queries.ts)
|
||||||
devMock?: boolean;
|
devMock?: boolean;
|
||||||
// Render için havuzdan zenginleştirilmiş program bilgisi
|
// Render için havuzdan zenginleştirilmiş program bilgisi
|
||||||
// efektifSira: COALESCE(sira2025, sira2024) — eski kayıtlı raporlarda yok
|
// efektifSira: üretim anındaki son yılın sırası, yoksa bir önceki yılınki —
|
||||||
// siraGecmisi: [2021..2025] taban sıralamaları — deterministik trend oku
|
// eski kayıtlı raporlarda yok
|
||||||
|
// siraGecmisi: [ilkYil..] taban sıralamaları — deterministik trend oku.
|
||||||
|
// Uzunluk üretim anındaki veri penceresidir (eski kayıtlarda 5 eleman);
|
||||||
|
// okuyan taraf diziyi doldurmaz, yılı `ilkYil + indeks` ile bulur.
|
||||||
|
// ilkYil: dizinin ilk elemanının yılı — eski kayıtlarda yok (okurken
|
||||||
|
// lib/veri-yillari ILK_YIL)
|
||||||
programlar: Record<
|
programlar: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -45,6 +50,7 @@ export type RaporSonuc = {
|
|||||||
unitur: string | null;
|
unitur: string | null;
|
||||||
efektifSira?: number | null;
|
efektifSira?: number | null;
|
||||||
siraGecmisi?: (number | null)[];
|
siraGecmisi?: (number | null)[];
|
||||||
|
ilkYil?: number;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
riskNotuMetni,
|
riskNotuMetni,
|
||||||
} from "../risk";
|
} from "../risk";
|
||||||
import { trendOzetiMetni } from "../trend";
|
import { trendOzetiMetni } from "../trend";
|
||||||
|
import { ILK_YIL } from "../veri-yillari";
|
||||||
|
|
||||||
// Yapay zekâdan istenen küçük şema (kullanıcı kararı, 6 Ağu 2026): riskNotu
|
// Yapay zekâdan istenen küçük şema (kullanıcı kararı, 6 Ağu 2026): riskNotu
|
||||||
// ve trendOzeti gerçek sıra geçmişinden deterministik üretilir; dilim iskeleti
|
// ve trendOzeti gerçek sıra geçmişinden deterministik üretilir; dilim iskeleti
|
||||||
@@ -141,29 +142,27 @@ function normalizeEt(
|
|||||||
|
|
||||||
function trendOzetiUret(p: AdayProgram): string {
|
function trendOzetiUret(p: AdayProgram): string {
|
||||||
// Cümle tadımlıkla ortak saf fonksiyondan gelir (lib/trend.ts)
|
// Cümle tadımlıkla ortak saf fonksiyondan gelir (lib/trend.ts)
|
||||||
return trendOzetiMetni([
|
return trendOzetiMetni(p.siraGecmisi);
|
||||||
p.sira2021,
|
|
||||||
p.sira2022,
|
|
||||||
p.sira2023,
|
|
||||||
p.sira2024,
|
|
||||||
p.sira2025,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function riskNotuUret(p: AdayProgram, adaySira: number): string {
|
function riskNotuUret(p: AdayProgram, adaySira: number): string {
|
||||||
return riskNotuMetni(riskHesapla(p.sira2025 ?? p.sira2024, adaySira));
|
return riskNotuMetni(riskHesapla(p.efektifSira, adaySira));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Havuz programını render için gereken bilgiye indirger. */
|
/** Havuz programını render için gereken bilgiye indirger. */
|
||||||
function programBilgisi(p: AdayProgram): RaporSonuc["programlar"][string] {
|
function programBilgisi(p: AdayProgram): RaporSonuc["programlar"][string] {
|
||||||
const gecmis = [p.sira2021, p.sira2022, p.sira2023, p.sira2024, p.sira2025];
|
const gecmis = p.siraGecmisi;
|
||||||
|
const gecmisVar = gecmis.some((s) => s != null);
|
||||||
return {
|
return {
|
||||||
isim: p.isim,
|
isim: p.isim,
|
||||||
universite: p.universite,
|
universite: p.universite,
|
||||||
il: p.il,
|
il: p.il,
|
||||||
unitur: p.unitur,
|
unitur: p.unitur,
|
||||||
efektifSira: p.sira2025 ?? p.sira2024,
|
efektifSira: p.efektifSira,
|
||||||
siraGecmisi: gecmis.some((s) => s != null) ? gecmis : undefined,
|
siraGecmisi: gecmisVar ? gecmis : undefined,
|
||||||
|
// Dizinin çapa yılı snapshot'a yazılır: kayıt, veri penceresi sonradan
|
||||||
|
// uzasa da kendi yıllarını bilir (eski kayıtlarda yok → okurken ILK_YIL).
|
||||||
|
ilkYil: gecmisVar ? ILK_YIL : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +182,7 @@ function mockRapor(
|
|||||||
// Prod ile aynı hedef hesabı: mock, uyarlanmış dağılımı da birebir taklit eder
|
// Prod ile aynı hedef hesabı: mock, uyarlanmış dağılımı da birebir taklit eder
|
||||||
const hedef = dilimHedefiHesapla(havuz);
|
const hedef = dilimHedefiHesapla(havuz);
|
||||||
const toplamHedef = hedef.hayal + hedef.dengeli + hedef.garanti;
|
const toplamHedef = hedef.hayal + hedef.dengeli + hedef.garanti;
|
||||||
const efektif = (p: AdayProgram) => p.sira2025 ?? p.sira2024 ?? 0;
|
const efektif = (p: AdayProgram) => p.efektifSira ?? 0;
|
||||||
const dilimden = (dilim: AdayProgram["dilim"], adet: number) =>
|
const dilimden = (dilim: AdayProgram["dilim"], adet: number) =>
|
||||||
havuz
|
havuz
|
||||||
.filter((p) => p.dilim === dilim)
|
.filter((p) => p.dilim === dilim)
|
||||||
@@ -293,7 +292,7 @@ export async function raporUret(
|
|||||||
const havuzMap = new Map(havuz.map((p) => [p.id, p]));
|
const havuzMap = new Map(havuz.map((p) => [p.id, p]));
|
||||||
const efektif = (id: string) => {
|
const efektif = (id: string) => {
|
||||||
const p = havuzMap.get(id)!;
|
const p = havuzMap.get(id)!;
|
||||||
return p.sira2025 ?? p.sira2024 ?? Number.MAX_SAFE_INTEGER;
|
return p.efektifSira ?? Number.MAX_SAFE_INTEGER;
|
||||||
};
|
};
|
||||||
|
|
||||||
let secim: Record<Dilim, AiSecim[]> | null = null;
|
let secim: Record<Dilim, AiSecim[]> | null = null;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
import { KATEGORILER, kategoriBul } from "./kategoriler";
|
import { KATEGORILER, kategoriBul } from "./kategoriler";
|
||||||
|
import { EFEKTIF_SIRA_SQL, SON_YIL, YILLAR } from "./veri-yillari";
|
||||||
import {
|
import {
|
||||||
PUAN_TURLERI,
|
PUAN_TURLERI,
|
||||||
type DilimKey,
|
type DilimKey,
|
||||||
@@ -27,12 +28,46 @@ export function getDb(): Database.Database {
|
|||||||
return globalForDb.yokatlasDb;
|
return globalForDb.yokatlasDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Yıl ekli kolonlar yalnız burada, `YILLAR`'dan üretilerek adlandırılır; DB
|
||||||
|
// dışına yılsız `Program` çıkar. SELECT_COLS ile okunan her satır
|
||||||
|
// `programSatiri` eşleyicisinden geçmelidir (ham satır `Program` değildir).
|
||||||
export const SELECT_COLS = `id, isim, universite, unitur, il, fakulte, tur, sure,
|
export const SELECT_COLS = `id, isim, universite, unitur, il, fakulte, tur, sure,
|
||||||
sira2025, sira2024, sira2023, sira2022, sira2021,
|
${YILLAR.map((yil) => `sira${yil} AS s${yil}`).join(", ")},
|
||||||
puan2025, kontenjan2025, yerlesen2025`;
|
puan${SON_YIL} AS puanSon, kontenjan${SON_YIL} AS kontenjanSon, yerlesen${SON_YIL} AS yerlesenSon`;
|
||||||
|
|
||||||
// 2025 sıralaması yoksa (az yerleşen/yeni program) 2024'e düşer
|
// Son yılın sıralaması yoksa (az yerleşen/yeni program) bir önceki yıla düşer.
|
||||||
export const EFEKTIF_SIRA = "COALESCE(sira2025, sira2024)";
|
// İfade indeks sözleşmesidir; tek kaynağı lib/veri-yillari.ts.
|
||||||
|
export const EFEKTIF_SIRA = EFEKTIF_SIRA_SQL;
|
||||||
|
|
||||||
|
/** SELECT_COLS ile okunan ham satır: sıra kolonları `s<yıl>` takma adıyla gelir. */
|
||||||
|
export type ProgramHamSatiri = Omit<
|
||||||
|
Program,
|
||||||
|
"siraGecmisi" | "efektifSira" | "siraSon"
|
||||||
|
> &
|
||||||
|
Record<`s${number}`, number | null>;
|
||||||
|
|
||||||
|
/** Ham DB satırını yılsız `Program`'a çevirir. */
|
||||||
|
export function programSatiri(row: ProgramHamSatiri): Program {
|
||||||
|
const siraGecmisi = YILLAR.map((yil) => row[`s${yil}`] ?? null);
|
||||||
|
const siraSon = siraGecmisi[siraGecmisi.length - 1] ?? null;
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
isim: row.isim,
|
||||||
|
universite: row.universite,
|
||||||
|
unitur: row.unitur,
|
||||||
|
il: row.il,
|
||||||
|
fakulte: row.fakulte,
|
||||||
|
tur: row.tur,
|
||||||
|
sure: row.sure,
|
||||||
|
siraGecmisi,
|
||||||
|
// EFEKTIF_SIRA ile aynı kural: son yıl, yoksa bir önceki yıl
|
||||||
|
efektifSira: siraSon ?? siraGecmisi[siraGecmisi.length - 2] ?? null,
|
||||||
|
siraSon,
|
||||||
|
puanSon: row.puanSon,
|
||||||
|
kontenjanSon: row.kontenjanSon,
|
||||||
|
yerlesenSon: row.yerlesenSon,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kullanıcının başarı sıralamasına göre programları üç dilime ayırır.
|
* Kullanıcının başarı sıralamasına göre programları üç dilime ayırır.
|
||||||
@@ -112,18 +147,18 @@ export function dilimAra(
|
|||||||
// 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
|
||||||
const yon = dilim === "hayal" ? "DESC" : "ASC";
|
const yon = dilim === "hayal" ? "DESC" : "ASC";
|
||||||
|
|
||||||
const programlar = db
|
const satirlar = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT ${SELECT_COLS} ${base} ORDER BY ${EFEKTIF_SIRA} ${yon} LIMIT ? OFFSET ?`
|
`SELECT ${SELECT_COLS} ${base} ORDER BY ${EFEKTIF_SIRA} ${yon} LIMIT ? OFFSET ?`
|
||||||
)
|
)
|
||||||
.all(...args, limit, offset) as Program[];
|
.all(...args, limit, offset) as ProgramHamSatiri[];
|
||||||
const toplam = (
|
const toplam = (
|
||||||
db.prepare(`SELECT COUNT(*) AS adet ${base}`).get(...args) as {
|
db.prepare(`SELECT COUNT(*) AS adet ${base}`).get(...args) as {
|
||||||
adet: number;
|
adet: number;
|
||||||
}
|
}
|
||||||
).adet;
|
).adet;
|
||||||
|
|
||||||
return { programlar, toplam };
|
return { programlar: satirlar.map(programSatiri), toplam };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function searchByRank(
|
export function searchByRank(
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import "server-only";
|
import "server-only";
|
||||||
import { getDb, SELECT_COLS, EFEKTIF_SIRA } from "./db";
|
import {
|
||||||
|
getDb,
|
||||||
|
programSatiri,
|
||||||
|
SELECT_COLS,
|
||||||
|
EFEKTIF_SIRA,
|
||||||
|
type ProgramHamSatiri,
|
||||||
|
} from "./db";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
import { kategoriBul, type Kategori } from "./kategoriler";
|
import { kategoriBul, type Kategori } from "./kategoriler";
|
||||||
import { uniAdiNormalize } from "./harita";
|
import { uniAdiNormalize } from "./harita";
|
||||||
@@ -24,7 +30,7 @@ export type BolumOzet = {
|
|||||||
kategoriler: Kategori[];
|
kategoriler: Kategori[];
|
||||||
lisans: boolean;
|
lisans: boolean;
|
||||||
onlisans: boolean;
|
onlisans: boolean;
|
||||||
/** MIN(COALESCE(sira2025, sira2024)) — en iyi (en küçük) taban sıralaması */
|
/** MIN(efektif sıra) — en iyi (en küçük) taban sıralaması */
|
||||||
enIyiSira: number | null;
|
enIyiSira: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,13 +137,15 @@ export function getBolumBySlug(slug: string): BolumDetay | null {
|
|||||||
if (!kayit) return null;
|
if (!kayit) return null;
|
||||||
|
|
||||||
const placeholders = kayit.hamIsimler.map(() => "?").join(",");
|
const placeholders = kayit.hamIsimler.map(() => "?").join(",");
|
||||||
const programlar = getDb()
|
const programlar = (
|
||||||
|
getDb()
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT ${SELECT_COLS} FROM programs
|
`SELECT ${SELECT_COLS} FROM programs
|
||||||
WHERE isim IN (${placeholders})
|
WHERE isim IN (${placeholders})
|
||||||
ORDER BY (${EFEKTIF_SIRA} IS NULL), ${EFEKTIF_SIRA} ASC`,
|
ORDER BY (${EFEKTIF_SIRA} IS NULL), ${EFEKTIF_SIRA} ASC`,
|
||||||
)
|
)
|
||||||
.all(...kayit.hamIsimler) as (Program & { onlisans?: number })[];
|
.all(...kayit.hamIsimler) as ProgramHamSatiri[]
|
||||||
|
).map(programSatiri);
|
||||||
|
|
||||||
// onlisans SELECT_COLS içinde yok; tur üzerinden ayır (TYT = önlisans)
|
// onlisans SELECT_COLS içinde yok; tur üzerinden ayır (TYT = önlisans)
|
||||||
const lisansProgramlari = programlar.filter((p) => p.tur !== "TYT");
|
const lisansProgramlari = programlar.filter((p) => p.tur !== "TYT");
|
||||||
@@ -156,14 +164,14 @@ export function getBolumBySlug(slug: string): BolumDetay | null {
|
|||||||
if (p.unitur === "DEVLET") devlet += 1;
|
if (p.unitur === "DEVLET") devlet += 1;
|
||||||
else if (p.unitur) vakif += 1;
|
else if (p.unitur) vakif += 1;
|
||||||
else bilinmeyenTur += 1;
|
else bilinmeyenTur += 1;
|
||||||
const efektif = p.sira2025 ?? p.sira2024;
|
const efektif = p.efektifSira;
|
||||||
if (efektif != null) {
|
if (efektif != null) {
|
||||||
if (minSira == null || efektif < minSira) minSira = efektif;
|
if (minSira == null || efektif < minSira) minSira = efektif;
|
||||||
if (maxSira == null || efektif > maxSira) maxSira = efektif;
|
if (maxSira == null || efektif > maxSira) maxSira = efektif;
|
||||||
}
|
}
|
||||||
if (p.puan2025 != null) {
|
if (p.puanSon != null) {
|
||||||
if (minPuan == null || p.puan2025 < minPuan) minPuan = p.puan2025;
|
if (minPuan == null || p.puanSon < minPuan) minPuan = p.puanSon;
|
||||||
if (maxPuan == null || p.puan2025 > maxPuan) maxPuan = p.puan2025;
|
if (maxPuan == null || p.puanSon > maxPuan) maxPuan = p.puanSon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,13 +365,15 @@ export function getUniversiteBySlug(slug: string): UniDetay | null {
|
|||||||
if (!kayit) return null;
|
if (!kayit) return null;
|
||||||
|
|
||||||
const placeholders = kayit.hamAdlar.map(() => "?").join(",");
|
const placeholders = kayit.hamAdlar.map(() => "?").join(",");
|
||||||
const programlar = getDb()
|
const programlar = (
|
||||||
|
getDb()
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT ${SELECT_COLS} FROM programs
|
`SELECT ${SELECT_COLS} FROM programs
|
||||||
WHERE universite IN (${placeholders})
|
WHERE universite IN (${placeholders})
|
||||||
ORDER BY fakulte, (${EFEKTIF_SIRA} IS NULL), ${EFEKTIF_SIRA} ASC`,
|
ORDER BY fakulte, (${EFEKTIF_SIRA} IS NULL), ${EFEKTIF_SIRA} ASC`,
|
||||||
)
|
)
|
||||||
.all(...kayit.hamAdlar) as Program[];
|
.all(...kayit.hamAdlar) as ProgramHamSatiri[]
|
||||||
|
).map(programSatiri);
|
||||||
|
|
||||||
const gruplar = new Map<string, Program[]>();
|
const gruplar = new Map<string, Program[]>();
|
||||||
let minSira: number | null = null;
|
let minSira: number | null = null;
|
||||||
@@ -373,11 +383,11 @@ export function getUniversiteBySlug(slug: string): UniDetay | null {
|
|||||||
const liste = gruplar.get(anahtar);
|
const liste = gruplar.get(anahtar);
|
||||||
if (liste) liste.push(p);
|
if (liste) liste.push(p);
|
||||||
else gruplar.set(anahtar, [p]);
|
else gruplar.set(anahtar, [p]);
|
||||||
const efektif = p.sira2025 ?? p.sira2024;
|
const efektif = p.efektifSira;
|
||||||
if (efektif != null && (minSira == null || efektif < minSira)) {
|
if (efektif != null && (minSira == null || efektif < minSira)) {
|
||||||
minSira = efektif;
|
minSira = efektif;
|
||||||
}
|
}
|
||||||
toplamKontenjan += p.kontenjan2025 ?? 0;
|
toplamKontenjan += p.kontenjanSon ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ozet = getAllUniversiteler().find((u) => u.slug === slug)!;
|
const ozet = getAllUniversiteler().find((u) => u.slug === slug)!;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from "@/types/yokatlas";
|
} from "@/types/yokatlas";
|
||||||
import type { RaporParams } from "@/features/rapor/types/rapor";
|
import type { RaporParams } from "@/features/rapor/types/rapor";
|
||||||
import { kategoriEslesir } from "./kategoriler";
|
import { kategoriEslesir } from "./kategoriler";
|
||||||
|
import { SON_YIL, YILLAR } from "./veri-yillari";
|
||||||
|
|
||||||
export type Dilim = "hayal" | "dengeli" | "garanti";
|
export type Dilim = "hayal" | "dengeli" | "garanti";
|
||||||
|
|
||||||
@@ -155,6 +156,13 @@ export function havuzOlustur(params: RaporParams): HavuzSonuc {
|
|||||||
return { havuz: dilimBasinaKirp(ilsizHam, PROMPT_LIMIT_DILIM), gevsetildi };
|
return { havuz: dilimBasinaKirp(ilsizHam, PROMPT_LIMIT_DILIM), gevsetildi };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prompt'a veri penceresinin tamamı değil son 4 yılı gider (yeniden eskiye):
|
||||||
|
// pencere uzadıkça satır başına token sayısı sabit kalır.
|
||||||
|
const PROMPT_YIL_SAYISI = 4;
|
||||||
|
const PROMPT_YIL_INDEKSLERI = YILLAR.map((_, i) => i)
|
||||||
|
.slice(-PROMPT_YIL_SAYISI)
|
||||||
|
.reverse();
|
||||||
|
|
||||||
/** Prompt'a enjekte edilecek kompakt satır (token tasarrufu). */
|
/** Prompt'a enjekte edilecek kompakt satır (token tasarrufu). */
|
||||||
export function havuzuKompaktJson(havuz: AdayProgram[]): string {
|
export function havuzuKompaktJson(havuz: AdayProgram[]): string {
|
||||||
return JSON.stringify(
|
return JSON.stringify(
|
||||||
@@ -165,14 +173,16 @@ export function havuzuKompaktJson(havuz: AdayProgram[]): string {
|
|||||||
unitur: p.unitur,
|
unitur: p.unitur,
|
||||||
il: p.il,
|
il: p.il,
|
||||||
dilim: p.dilim,
|
dilim: p.dilim,
|
||||||
sira: {
|
// Anahtarlar yıl taşır (y<yıl>, kontenjan<yıl>): model rakamın hangi
|
||||||
y2025: p.sira2025,
|
// yıla ait olduğunu anahtardan okur.
|
||||||
y2024: p.sira2024,
|
sira: Object.fromEntries(
|
||||||
y2023: p.sira2023,
|
PROMPT_YIL_INDEKSLERI.map((i) => [
|
||||||
y2022: p.sira2022,
|
`y${YILLAR[i]}`,
|
||||||
},
|
p.siraGecmisi[i] ?? null,
|
||||||
kontenjan2025: p.kontenjan2025,
|
]),
|
||||||
yerlesen2025: p.yerlesen2025,
|
),
|
||||||
|
[`kontenjan${SON_YIL}`]: p.kontenjanSon,
|
||||||
|
[`yerlesen${SON_YIL}`]: p.yerlesenSon,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function raporMaskele(rapor: RaporSonuc): MaskeliRapor {
|
|||||||
// kilitli metinler istemciye hiç ulaşmaz.
|
// kilitli metinler istemciye hiç ulaşmaz.
|
||||||
uyarilar: rapor.uyarilar?.slice(0, 1) ?? [],
|
uyarilar: rapor.uyarilar?.slice(0, 1) ?? [],
|
||||||
kilitliUyariSayisi: Math.max(0, (rapor.uyarilar?.length ?? 0) - 1),
|
kilitliUyariSayisi: Math.max(0, (rapor.uyarilar?.length ?? 0) - 1),
|
||||||
// Program adı, üniversite ve 2021–2025 taban sıralamaları ham veridir.
|
// Program adı, üniversite ve geçmiş yılların taban sıralamaları ham veridir.
|
||||||
programlar: rapor.programlar,
|
programlar: rapor.programlar,
|
||||||
// Kapsam (kaç program bulundu / dağılım uyarlandı mı) dürüstlük bilgisi:
|
// Kapsam (kaç program bulundu / dağılım uyarlandı mı) dürüstlük bilgisi:
|
||||||
// paketsiz kullanıcı da görür, maskelenmez.
|
// paketsiz kullanıcı da görür, maskelenmez.
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ export const RISK_PIN_RENK: Record<RiskSeviyesi, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deterministik risk: programın efektif taban sıralaması (COALESCE 2025→2024)
|
* Deterministik risk: programın efektif taban sıralaması (son yılın sırası,
|
||||||
* adayın sıralamasıyla kıyaslanır. Eşikler db.ts'teki dilim aralıklarıyla
|
* yoksa bir önceki yılınki) adayın sıralamasıyla kıyaslanır. Eşikler
|
||||||
* birebir aynı (dengeli: [sira, 1.4×sira], garanti: (1.4×sira, ∞)).
|
* db.ts'teki dilim aralıklarıyla birebir aynı (dengeli: [sira, 1.4×sira],
|
||||||
|
* garanti: (1.4×sira, ∞)).
|
||||||
*/
|
*/
|
||||||
export function riskHesapla(
|
export function riskHesapla(
|
||||||
efektifSira: number | null | undefined,
|
efektifSira: number | null | undefined,
|
||||||
@@ -72,7 +73,8 @@ export function dilimdenFarkliRiskEtiketi(
|
|||||||
* 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
|
||||||
* sırasıyla kıyaslar. Bu yüzden metin olasılık/ihtimal/"yerleşirsin" demez.
|
* sırasıyla kıyaslar. Bu yüzden metin olasılık/ihtimal/"yerleşirsin" demez.
|
||||||
* "Geçen yıl" da denmez: efektif sıra 2025 yoksa 2024'e düşüyor.
|
* "Geçen yıl" da denmez: efektif sıra, son yılın verisi yoksa bir önceki
|
||||||
|
* yılınkine düşüyor.
|
||||||
*/
|
*/
|
||||||
export function riskNotuMetni(risk: RiskSeviyesi | null): string {
|
export function riskNotuMetni(risk: RiskSeviyesi | null): string {
|
||||||
switch (risk) {
|
switch (risk) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { Dilim } from "./rapor-havuzu";
|
|||||||
import type { RaporSonuc } from "@/features/rapor/types/rapor";
|
import type { RaporSonuc } from "@/features/rapor/types/rapor";
|
||||||
import { dilimdenRisk, riskHesapla, riskNotuMetni } from "./risk";
|
import { dilimdenRisk, riskHesapla, riskNotuMetni } from "./risk";
|
||||||
import { trendOzetiMetni } from "./trend";
|
import { trendOzetiMetni } from "./trend";
|
||||||
|
import { VERI_YIL_SAYISI } from "./veri-yillari";
|
||||||
|
|
||||||
/** Sihirbaz kategorisi seçilmemiş kullanıcı için kova×tür başına fallback satır. */
|
/** Sihirbaz kategorisi seçilmemiş kullanıcı için kova×tür başına fallback satır. */
|
||||||
export const GENEL_KATEGORI = "genel";
|
export const GENEL_KATEGORI = "genel";
|
||||||
@@ -54,16 +55,41 @@ export type TadimlikTip = "genel" | "devlet" | "vakif";
|
|||||||
* gerekce, satırın servis edilen TEK LLM metnidir (batch'te temsili sıraya
|
* gerekce, satırın servis edilen TEK LLM metnidir (batch'te temsili sıraya
|
||||||
* göre yazıldı). Ürün yerleşme olasılığı hesaplamaz; bu yüzden olasılık /
|
* göre yazıldı). Ürün yerleşme olasılığı hesaplamaz; bu yüzden olasılık /
|
||||||
* ihtimal / şans / garanti / kesin / yüzde / "yerleşirsin" diyen gerekçe
|
* ihtimal / şans / garanti / kesin / yüzde / "yerleşirsin" diyen gerekçe
|
||||||
* servis edilmez. Eski batch'in "son 4 yıl" kalıbı da takılır (veri 5 yılı
|
* servis edilmez. Kalıp küçük harfledir: metin önce Türkçe kurallarıyla
|
||||||
* kapsıyor, bkz. veri-kapsami.ts). Kalıp küçük harfledir: metin önce Türkçe
|
* küçültülür ("İhtimal" → "ihtimal"; düz /i bayrağı İ/ı'yı eşlemez).
|
||||||
* kurallarıyla küçültülür ("İhtimal" → "ihtimal"; düz /i bayrağı İ/ı'yı
|
|
||||||
* eşlemez).
|
|
||||||
*/
|
*/
|
||||||
const YASAK_GEREKCE_DILI =
|
const YASAK_GEREKCE_DILI =
|
||||||
/olasılı|ihtimal|şans|garanti|kesin|yüzde|yerleşirsin|yerleşebil|son (4|dört) yıl/;
|
/olasılı|ihtimal|şans|garanti|kesin|yüzde|yerleşirsin|yerleşebil/;
|
||||||
|
|
||||||
|
// Bayat batch kalıbı: gerekçe "son N yıl" diyorsa N, verinin bugün kapsadığı
|
||||||
|
// yıl sayısı (VERI_YIL_SAYISI) olmalı. Veri penceresi uzayınca eski batch'in
|
||||||
|
// "son 5 yıl"ı da, daha eskisinin "son 4 yıl"ı da kendiliğinden takılır.
|
||||||
|
const SON_N_YIL = /son (\d+|[a-zçğıöşü]+) yıl/g;
|
||||||
|
const SAYI_SOZCUKLERI: Record<string, number> = {
|
||||||
|
bir: 1,
|
||||||
|
iki: 2,
|
||||||
|
üç: 3,
|
||||||
|
dört: 4,
|
||||||
|
beş: 5,
|
||||||
|
altı: 6,
|
||||||
|
yedi: 7,
|
||||||
|
sekiz: 8,
|
||||||
|
dokuz: 9,
|
||||||
|
on: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
function bayatYilSayisiVar(kucukMetin: string): boolean {
|
||||||
|
for (const [, ham] of kucukMetin.matchAll(SON_N_YIL)) {
|
||||||
|
const n = /^\d+$/.test(ham) ? Number(ham) : SAYI_SOZCUKLERI[ham];
|
||||||
|
// Sayı olmayan sözcük ("son birkaç yıl") yıl sayısı iddiası değildir
|
||||||
|
if (n != null && n !== VERI_YIL_SAYISI) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function gerekceServisEdilebilir(gerekce: string): boolean {
|
export function gerekceServisEdilebilir(gerekce: string): boolean {
|
||||||
return !YASAK_GEREKCE_DILI.test(gerekce.toLocaleLowerCase("tr-TR"));
|
const kucuk = gerekce.toLocaleLowerCase("tr-TR");
|
||||||
|
return !YASAK_GEREKCE_DILI.test(kucuk) && !bayatYilSayisiVar(kucuk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,10 +134,12 @@ export async function tadimlikSec(
|
|||||||
// aynı formülden adayın gerçek sırasına göre; trend, rapordakiyle aynı
|
// aynı formülden adayın gerçek sırasına göre; trend, rapordakiyle aynı
|
||||||
// saf fonksiyondan programın sıra geçmişinden okuma anında üretilir —
|
// saf fonksiyondan programın sıra geçmişinden okuma anında üretilir —
|
||||||
// metin rozetle çelişemez, "son 4 yıl" gibi bayat kalıp taşınmaz.
|
// metin rozetle çelişemez, "son 4 yıl" gibi bayat kalıp taşınmaz.
|
||||||
|
// Snapshot'ın sıra dizisi üretildiği günün penceresidir (eski
|
||||||
|
// satırlarda 5 eleman); yıllar dizinin kendi çapasından okunur.
|
||||||
riskNotu: riskNotuMetni(
|
riskNotu: riskNotuMetni(
|
||||||
riskHesapla(program.efektifSira, sira) ?? dilimdenRisk(satir.dilim),
|
riskHesapla(program.efektifSira, sira) ?? dilimdenRisk(satir.dilim),
|
||||||
),
|
),
|
||||||
trendOzeti: trendOzetiMetni(program.siraGecmisi),
|
trendOzeti: trendOzetiMetni(program.siraGecmisi, program.ilkYil),
|
||||||
program,
|
program,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,7 @@
|
|||||||
// düzeyine indi"); yıl aralığı sabit bir "son N yıl" kalıbından değil,
|
// düzeyine indi"); yıl aralığı sabit bir "son N yıl" kalıbından değil,
|
||||||
// serideki dolu yıllardan gelir.
|
// serideki dolu yıllardan gelir.
|
||||||
|
|
||||||
/**
|
import { ILK_YIL } from "./veri-yillari";
|
||||||
* Sıra geçmişi dizisinin ilk elemanının yılı. Dizi her yerde 2021'den başlar
|
|
||||||
* (bkz. programBilgisi, raporSiraSerisi); yeni veri yılı sona eklenir, bu
|
|
||||||
* çapa değişmez.
|
|
||||||
*/
|
|
||||||
const ILK_VERI_YILI = 2021;
|
|
||||||
|
|
||||||
/** 45000 -> "45B", 1_200_000 -> "1,2M" (şemadaki öğrenci dili). */
|
/** 45000 -> "45B", 1_200_000 -> "1,2M" (şemadaki öğrenci dili). */
|
||||||
function siraKisa(n: number): string {
|
function siraKisa(n: number): string {
|
||||||
@@ -21,7 +16,10 @@ function siraKisa(n: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [2021..] taban sıralaması dizisinden tek cümlelik trend özeti.
|
* [ilkYil..] taban sıralaması dizisinden tek cümlelik trend özeti. Dizi
|
||||||
|
* `ilkYil`'a demirlidir (varsayılan: veri penceresinin ilk yılı); yeni veri
|
||||||
|
* yılı sona eklenir, bu yüzden eski kayıtların daha kısa dizileri de doğru
|
||||||
|
* yılları verir. `ilkYil`, snapshot kendi çapasını taşıyorsa ondan gelir.
|
||||||
*
|
*
|
||||||
* Cümle yalnızca OLANI betimler: ilk ve son dolu yılın tabanını kıyaslar
|
* Cümle yalnızca OLANI betimler: ilk ve son dolu yılın tabanını kıyaslar
|
||||||
* (±%10 eşiği). "Zorlaşıyor / kolaylaşıyor" gibi geleceğe dönük yargı
|
* (±%10 eşiği). "Zorlaşıyor / kolaylaşıyor" gibi geleceğe dönük yargı
|
||||||
@@ -30,22 +28,23 @@ function siraKisa(n: number): string {
|
|||||||
*/
|
*/
|
||||||
export function trendOzetiMetni(
|
export function trendOzetiMetni(
|
||||||
gecmis: readonly (number | null | undefined)[] | undefined,
|
gecmis: readonly (number | null | undefined)[] | undefined,
|
||||||
|
ilkYil: number = ILK_YIL,
|
||||||
): string {
|
): string {
|
||||||
const seri: Array<[number, number]> = [];
|
const seri: Array<[number, number]> = [];
|
||||||
(gecmis ?? []).forEach((sira, i) => {
|
(gecmis ?? []).forEach((sira, i) => {
|
||||||
if (sira != null) seri.push([ILK_VERI_YILI + i, sira]);
|
if (sira != null) seri.push([ilkYil + i, sira]);
|
||||||
});
|
});
|
||||||
if (seri.length < 2) {
|
if (seri.length < 2) {
|
||||||
return "Karşılaştırma için yeterli geçmiş yıl verisi yok; trendi temkinli oku.";
|
return "Karşılaştırma için yeterli geçmiş yıl verisi yok; trendi temkinli oku.";
|
||||||
}
|
}
|
||||||
const [ilkYil, ilkSira] = seri[0];
|
const [basYil, ilkSira] = seri[0];
|
||||||
const [sonYil, sonSira] = seri[seri.length - 1];
|
const [sonYil, sonSira] = seri[seri.length - 1];
|
||||||
const oran = (sonSira - ilkSira) / ilkSira;
|
const oran = (sonSira - ilkSira) / ilkSira;
|
||||||
if (oran <= -0.1) {
|
if (oran <= -0.1) {
|
||||||
return `${ilkYil}-${sonYil} arasında taban ${siraKisa(ilkSira)} düzeyinden ${siraKisa(sonSira)} düzeyine indi; taban sıkılaştı.`;
|
return `${basYil}-${sonYil} arasında taban ${siraKisa(ilkSira)} düzeyinden ${siraKisa(sonSira)} düzeyine indi; taban sıkılaştı.`;
|
||||||
}
|
}
|
||||||
if (oran >= 0.1) {
|
if (oran >= 0.1) {
|
||||||
return `${ilkYil}-${sonYil} arasında taban ${siraKisa(ilkSira)} düzeyinden ${siraKisa(sonSira)} düzeyine çıktı; taban gevşedi.`;
|
return `${basYil}-${sonYil} arasında taban ${siraKisa(ilkSira)} düzeyinden ${siraKisa(sonSira)} düzeyine çıktı; taban gevşedi.`;
|
||||||
}
|
}
|
||||||
return `${ilkYil}-${sonYil} arasında taban sıralaması ${siraKisa(sonSira)} düzeyinde stabil seyretti.`;
|
return `${basYil}-${sonYil} arasında taban sıralaması ${siraKisa(sonSira)} düzeyinde stabil seyretti.`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Taban sıralaması geçmişinin kapsadığı yıl sayısı. Veritabanında
|
* Taban sıralaması geçmişinin kapsadığı yıl sayısı; "son N yıl" diyen tüm
|
||||||
* sira2021–sira2025 kolonları var (5 yıl); "son N yıl" diyen tüm metinler bu
|
* metinler bu sabitten okur. Değer yıl kapsamının tek kaynağından
|
||||||
* sabitten okur. Yeni veri yılı eklenince (ör. sira2026) burayı güncelle.
|
* (lib/veri-yillari.ts) türetilir — yeni veri yılı eklenince burası değişmez.
|
||||||
*/
|
*/
|
||||||
export const VERI_YIL_SAYISI = 5;
|
export { VERI_YIL_SAYISI } from "./veri-yillari";
|
||||||
|
|||||||
@@ -11,14 +11,16 @@ export type Program = {
|
|||||||
fakulte: string | null;
|
fakulte: string | null;
|
||||||
tur: string;
|
tur: string;
|
||||||
sure: number | null;
|
sure: number | null;
|
||||||
sira2025: number | null;
|
/** Taban sıralamaları; `YILLAR` (lib/veri-yillari) ile indeks hizalı. */
|
||||||
sira2024: number | null;
|
siraGecmisi: (number | null)[];
|
||||||
sira2023: number | null;
|
/** Son yılın sırası; o yıl verisi yoksa bir önceki yılınki. */
|
||||||
sira2022: number | null;
|
efektifSira: number | null;
|
||||||
sira2021: number | null;
|
// "Son" = en son yerleştirme yılı (SON_YIL). Alan adları bilinçli yılsız:
|
||||||
puan2025: number | null;
|
// yeni veri yılı yüklenince tip ve tüketiciler değişmez.
|
||||||
kontenjan2025: number | null;
|
siraSon: number | null;
|
||||||
yerlesen2025: number | null;
|
puanSon: number | null;
|
||||||
|
kontenjanSon: number | null;
|
||||||
|
yerlesenSon: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PUAN_TURLERI = {
|
export const PUAN_TURLERI = {
|
||||||
@@ -42,11 +44,15 @@ export type RankResults = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* /sonuc ham tablosunun satırı: tablo `fakulte` ve `puan2025`'i hiç
|
* /sonuc ham tablosunun satırı: tablo `fakulte`, `puanSon` ve `siraSon`'u
|
||||||
|
* (dizinin son elemanı; tablo `efektifSira` + `siraGecmisi` ile çalışır) hiç
|
||||||
* kullanmadığı için bu alanlar client'a serileştirilmez (satır başına en
|
* kullanmadığı için bu alanlar client'a serileştirilmez (satır başına en
|
||||||
* uzun string alanlardan biri fakulte). Katalog sayfaları tam Program kullanır.
|
* uzun string alanlardan biri fakulte). Katalog sayfaları tam Program kullanır.
|
||||||
*/
|
*/
|
||||||
export type ProgramListeSatiri = Omit<Program, "fakulte" | "puan2025">;
|
export type ProgramListeSatiri = Omit<
|
||||||
|
Program,
|
||||||
|
"fakulte" | "puanSon" | "siraSon"
|
||||||
|
>;
|
||||||
|
|
||||||
export type RankResultsListe = {
|
export type RankResultsListe = {
|
||||||
hayal: ProgramListeSatiri[];
|
hayal: ProgramListeSatiri[];
|
||||||
|
|||||||
Reference in New Issue
Block a user