Compare commits
2 Commits
cae2aaab4d
...
004473a078
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
004473a078 | ||
|
|
79bc7fa8be |
@@ -52,7 +52,7 @@ export const metadata: Metadata = {
|
|||||||
template: "%s — KolayTercih",
|
template: "%s — KolayTercih",
|
||||||
},
|
},
|
||||||
description:
|
description:
|
||||||
"YKS sıralamana göre gerçek YÖK Atlas verisiyle dengeli 24 tercihlik liste. İnsan danışmanın onda bir fiyatına, yapay zekâ destekli tercih danışmanlığı.",
|
"YKS sıralamana göre gerçek YÖK Atlas verisiyle dengeli 24 tercihlik liste. İnsan danışmandan çok daha düşük fiyata, yapay zekâ destekli tercih danışmanlığı.",
|
||||||
applicationName: "KolayTercih",
|
applicationName: "KolayTercih",
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: "website",
|
type: "website",
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const problems = [
|
|||||||
icon: Wallet,
|
icon: Wallet,
|
||||||
title: "Danışman lüks haline geldi",
|
title: "Danışman lüks haline geldi",
|
||||||
description:
|
description:
|
||||||
"İyi bir tercih danışmanı 2.000–10.000 TL istiyor. Ücretsiz robotlar ise sadece liste filtreliyor, akıl vermiyor.",
|
"İyi bir tercih danışmanının ücreti birkaç bin lirayı bulabiliyor. Ücretsiz robotlar ise sadece liste filtreliyor, akıl vermiyor.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ const comparison = [
|
|||||||
{
|
{
|
||||||
feature: "Fiyat",
|
feature: "Fiyat",
|
||||||
robot: "Ücretsiz",
|
robot: "Ücretsiz",
|
||||||
danisman: "2.000–10.000 TL",
|
danisman: "Danışmana göre değişir",
|
||||||
tercihai: `${PAKET_FIYATI} TL · tek seferlik`,
|
tercihai: `${PAKET_FIYATI} TL · tek seferlik`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -480,7 +480,7 @@ export default function Home() {
|
|||||||
Ne kadar? Ne dahil?
|
Ne kadar? Ne dahil?
|
||||||
</h2>
|
</h2>
|
||||||
<p className="max-w-xl text-lg leading-relaxed text-slate-600">
|
<p className="max-w-xl text-lg leading-relaxed text-slate-600">
|
||||||
İnsan danışmanın 2.000–10.000 TL aldığı işi tek seferlik{" "}
|
İnsan danışmanın birkaç bin liraya yaptığı işi tek seferlik{" "}
|
||||||
{PAKET_FIYATI} TL'ye alırsın. Abonelik yok, gizli ücret
|
{PAKET_FIYATI} TL'ye alırsın. Abonelik yok, gizli ücret
|
||||||
yok.
|
yok.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -46,11 +46,12 @@ export default function PaketPage({
|
|||||||
<SectionEyebrow>Tek seferlik ödeme · Abonelik yok</SectionEyebrow>
|
<SectionEyebrow>Tek seferlik ödeme · Abonelik yok</SectionEyebrow>
|
||||||
<h1 className="mt-4 font-heading text-3xl font-bold sm:text-4xl">
|
<h1 className="mt-4 font-heading text-3xl font-bold sm:text-4xl">
|
||||||
İnsan danışmanın işini,{" "}
|
İnsan danışmanın işini,{" "}
|
||||||
<span className="text-primary">onda bir fiyatına</span>
|
<span className="text-primary">çok daha düşük fiyata</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mx-auto mt-3 max-w-xl text-slate-600">
|
<p className="mx-auto mt-3 max-w-xl text-slate-600">
|
||||||
Tercih danışmanları bu dönemde 2.000–10.000 TL alıyor. KolayTercih
|
Tercih danışmanlarının ücreti bu dönemde birkaç bin lirayı
|
||||||
aynı işi gerçek YÖK Atlas verisiyle, dakikalar içinde yapar.
|
bulabiliyor. KolayTercih aynı işi gerçek YÖK Atlas verisiyle,
|
||||||
|
dakikalar içinde yapar.
|
||||||
</p>
|
</p>
|
||||||
</Parallax>
|
</Parallax>
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
riskHesapla,
|
riskHesapla,
|
||||||
riskNotuMetni,
|
riskNotuMetni,
|
||||||
} from "../risk";
|
} from "../risk";
|
||||||
|
import { trendOzetiMetni } from "../trend";
|
||||||
|
|
||||||
// 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
|
||||||
@@ -132,37 +133,15 @@ function normalizeEt(
|
|||||||
// geçmişinden hesaplanır; model bu alanlarda jenerik dolgu yazıyordu, kod
|
// geçmişinden hesaplanır; model bu alanlarda jenerik dolgu yazıyordu, kod
|
||||||
// kesin rakam verir ("45B düzeyinden 38B düzeyine indi").
|
// kesin rakam verir ("45B düzeyinden 38B düzeyine indi").
|
||||||
|
|
||||||
/** 45000 -> "45B", 1_200_000 -> "1,2M" (şemadaki öğrenci dili). */
|
|
||||||
function siraKisa(n: number): string {
|
|
||||||
if (n >= 1_000_000) {
|
|
||||||
return `${(n / 1_000_000).toLocaleString("tr-TR", { maximumFractionDigits: 1 })}M`;
|
|
||||||
}
|
|
||||||
if (n >= 1000) return `${Math.round(n / 1000)}B`;
|
|
||||||
return String(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
function trendOzetiUret(p: AdayProgram): string {
|
function trendOzetiUret(p: AdayProgram): string {
|
||||||
const ham: Array<[number, number | null | undefined]> = [
|
// Cümle tadımlıkla ortak saf fonksiyondan gelir (lib/trend.ts)
|
||||||
[2021, p.sira2021],
|
return trendOzetiMetni([
|
||||||
[2022, p.sira2022],
|
p.sira2021,
|
||||||
[2023, p.sira2023],
|
p.sira2022,
|
||||||
[2024, p.sira2024],
|
p.sira2023,
|
||||||
[2025, p.sira2025],
|
p.sira2024,
|
||||||
];
|
p.sira2025,
|
||||||
const seri = ham.filter((cift): cift is [number, number] => cift[1] != null);
|
]);
|
||||||
if (seri.length < 2) {
|
|
||||||
return "Karşılaştırma için yeterli geçmiş yıl verisi yok; trendi temkinli oku.";
|
|
||||||
}
|
|
||||||
const [ilkYil, ilkSira] = seri[0];
|
|
||||||
const [sonYil, sonSira] = seri[seri.length - 1];
|
|
||||||
const oran = (sonSira - ilkSira) / ilkSira;
|
|
||||||
if (oran <= -0.1) {
|
|
||||||
return `${ilkYil}-${sonYil} arasında taban ${siraKisa(ilkSira)} düzeyinden ${siraKisa(sonSira)} düzeyine indi; bölüm zorlaşıyor.`;
|
|
||||||
}
|
|
||||||
if (oran >= 0.1) {
|
|
||||||
return `${ilkYil}-${sonYil} arasında taban ${siraKisa(ilkSira)} düzeyinden ${siraKisa(sonSira)} düzeyine çıktı; yerleşmek görece kolaylaşıyor.`;
|
|
||||||
}
|
|
||||||
return `${ilkYil}-${sonYil} arasında taban sıralaması ${siraKisa(sonSira)} düzeyinde stabil seyretti.`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function riskNotuUret(p: AdayProgram, adaySira: number): string {
|
function riskNotuUret(p: AdayProgram, adaySira: number): string {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type { PuanTuruKey } from "@/types/yokatlas";
|
|||||||
import type { Dilim } from "./rapor-havuzu";
|
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";
|
||||||
|
|
||||||
/** 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";
|
||||||
@@ -49,6 +50,22 @@ export interface TadimlikSatir {
|
|||||||
|
|
||||||
export type TadimlikTip = "genel" | "devlet" | "vakif";
|
export type TadimlikTip = "genel" | "devlet" | "vakif";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 /
|
||||||
|
* 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ı
|
||||||
|
* kapsıyor, bkz. veri-kapsami.ts). Kalıp küçük harfledir: metin önce Türkçe
|
||||||
|
* kurallarıyla küçültülür ("İhtimal" → "ihtimal"; düz /i bayrağı İ/ı'yı
|
||||||
|
* eşlemez).
|
||||||
|
*/
|
||||||
|
const YASAK_GEREKCE_DILI =
|
||||||
|
/olasılı|ihtimal|şans|garanti|kesin|yüzde|yerleşirsin|yerleşebil|son (4|dört) yıl/;
|
||||||
|
|
||||||
|
export function gerekceServisEdilebilir(gerekce: string): boolean {
|
||||||
|
return !YASAK_GEREKCE_DILI.test(gerekce.toLocaleLowerCase("tr-TR"));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kova × tür × kategori × tip anahtarıyla tadımlık satırı seçer. Kategoriye
|
* Kova × tür × kategori × tip anahtarıyla tadımlık satırı seçer. Kategoriye
|
||||||
* özel satır yoksa "genel" kategoriye düşer; o da yoksa null — çağıran taraf
|
* özel satır yoksa "genel" kategoriye düşer; o da yoksa null — çağıran taraf
|
||||||
@@ -56,6 +73,9 @@ export type TadimlikTip = "genel" | "devlet" | "vakif";
|
|||||||
*
|
*
|
||||||
* Tip fallback'i YOKTUR: kullanıcı devlet/vakıf seçtiyse yanlış tipte örnek
|
* Tip fallback'i YOKTUR: kullanıcı devlet/vakıf seçtiyse yanlış tipte örnek
|
||||||
* asla gösterilmez — uygun satır yoksa tadımlık gizlenir.
|
* asla gösterilmez — uygun satır yoksa tadımlık gizlenir.
|
||||||
|
*
|
||||||
|
* Gerekçesi dil filtresine takılan satır YOK sayılır: aynı sırayla "genel"
|
||||||
|
* kategoriye düşülür, o da takılırsa null (tadımlık gizlenir).
|
||||||
*/
|
*/
|
||||||
export async function tadimlikSec(
|
export async function tadimlikSec(
|
||||||
sira: number,
|
sira: number,
|
||||||
@@ -76,20 +96,22 @@ export async function tadimlikSec(
|
|||||||
eq(schema.tadimlikHavuzu.tip, tip),
|
eq(schema.tadimlikHavuzu.tip, tip),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
if (satir) {
|
if (satir && gerekceServisEdilebilir(satir.gerekce)) {
|
||||||
const program = satir.program as RaporSonuc["programlar"][string];
|
const program = satir.program as RaporSonuc["programlar"][string];
|
||||||
return {
|
return {
|
||||||
programId: satir.programId,
|
programId: satir.programId,
|
||||||
dilim: satir.dilim,
|
dilim: satir.dilim,
|
||||||
|
// Servis edilen tek LLM metni; yukarıdaki dil filtresinden geçti.
|
||||||
gerekce: satir.gerekce,
|
gerekce: satir.gerekce,
|
||||||
// DB'deki riskNotu batch'te LLM'in temsili sıraya göre yazdığı
|
// DB'deki riskNotu ve trendOzeti batch'te LLM'in yazdığı cümlelerdir;
|
||||||
// cümledir; servis EDİLMEZ. Not, rozetle (tadimlik-satiri.tsx) aynı
|
// ikisi de servis EDİLMEZ. Risk notu, rozetle (tadimlik-satiri.tsx)
|
||||||
// formülden adayın gerçek sırasına göre okuma anında üretilir — metin
|
// aynı formülden adayın gerçek sırasına göre; trend, rapordakiyle aynı
|
||||||
// ile rozet çelişemez, olasılık dili sızamaz.
|
// 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.
|
||||||
riskNotu: riskNotuMetni(
|
riskNotu: riskNotuMetni(
|
||||||
riskHesapla(program.efektifSira, sira) ?? dilimdenRisk(satir.dilim),
|
riskHesapla(program.efektifSira, sira) ?? dilimdenRisk(satir.dilim),
|
||||||
),
|
),
|
||||||
trendOzeti: satir.trendOzeti,
|
trendOzeti: trendOzetiMetni(program.siraGecmisi),
|
||||||
program,
|
program,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/lib/trend.ts
Normal file
51
src/lib/trend.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Deterministik trend cümlesi: rapor satırları (lib/ai/rapor.ts) ve girişsiz
|
||||||
|
// tadımlık (lib/tadimlik-havuzu.ts) AYNI saf fonksiyondan okur. Cümle gerçek
|
||||||
|
// YÖK Atlas sıra geçmişinden kesin rakamla kurulur ("45B düzeyinden 38B
|
||||||
|
// düzeyine indi"); yıl aralığı sabit bir "son N yıl" kalıbından değil,
|
||||||
|
// serideki dolu yıllardan gelir.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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). */
|
||||||
|
function siraKisa(n: number): string {
|
||||||
|
if (n >= 1_000_000) {
|
||||||
|
return `${(n / 1_000_000).toLocaleString("tr-TR", { maximumFractionDigits: 1 })}M`;
|
||||||
|
}
|
||||||
|
if (n >= 1000) return `${Math.round(n / 1000)}B`;
|
||||||
|
return String(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [2021..] taban sıralaması dizisinden tek cümlelik trend özeti.
|
||||||
|
*
|
||||||
|
* 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ı
|
||||||
|
* kurmaz — iki uç yılın kıyası aradaki seyri ve gelecek yılı söylemez.
|
||||||
|
* Küçülen sıra = taban sıkılaştı, büyüyen sıra = taban gevşedi.
|
||||||
|
*/
|
||||||
|
export function trendOzetiMetni(
|
||||||
|
gecmis: readonly (number | null | undefined)[] | undefined,
|
||||||
|
): string {
|
||||||
|
const seri: Array<[number, number]> = [];
|
||||||
|
(gecmis ?? []).forEach((sira, i) => {
|
||||||
|
if (sira != null) seri.push([ILK_VERI_YILI + i, sira]);
|
||||||
|
});
|
||||||
|
if (seri.length < 2) {
|
||||||
|
return "Karşılaştırma için yeterli geçmiş yıl verisi yok; trendi temkinli oku.";
|
||||||
|
}
|
||||||
|
const [ilkYil, ilkSira] = seri[0];
|
||||||
|
const [sonYil, sonSira] = seri[seri.length - 1];
|
||||||
|
const oran = (sonSira - ilkSira) / ilkSira;
|
||||||
|
if (oran <= -0.1) {
|
||||||
|
return `${ilkYil}-${sonYil} arasında taban ${siraKisa(ilkSira)} düzeyinden ${siraKisa(sonSira)} düzeyine indi; taban sıkılaştı.`;
|
||||||
|
}
|
||||||
|
if (oran >= 0.1) {
|
||||||
|
return `${ilkYil}-${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.`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user