feat(veri): Program tipi yılsız — db eşleyicisi, lib ve snapshot ilkYil (P-B a)

Program artık siraGecmisi (YILLAR ile hizalı) + efektifSira + siraSon/puanSon/
kontenjanSon/yerlesenSon taşır. db.ts kolonları YILLAR'dan üretip takma adla
seçer, programSatiri eşleyicisi ham satırı Program'a çevirir; SELECT_COLS'un
üç tüketicisi de eşleyiciden geçer. EFEKTIF_SIRA veri-yillari'dan gelir.

- trend: ILK_YIL tek kaynaktan, opsiyonel ilkYil parametresi
- rapor snapshot'ına opsiyonel ilkYil yazılır (eski kayıtlar: okurken ILK_YIL)
- rapor promptu son 4 yılı göndermeye devam eder, anahtarlar dinamik
- tadımlık "son N yıl" filtresi genel: N != VERI_YIL_SAYISI ise servis edilmez
- veri-kapsami VERI_YIL_SAYISI'nı veri-yillari'dan türetir

Bileşenler bir sonraki commit'te yeni tipe geçer (ikisi birlikte derlenir).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-21 17:00:39 +03:00
parent 38e02e0ed9
commit ce40b7c5b3
14 changed files with 195 additions and 106 deletions

View File

@@ -26,6 +26,7 @@ import {
riskNotuMetni,
} from "../risk";
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
// ve trendOzeti gerçek sıra geçmişinden deterministik üretilir; dilim iskeleti
@@ -141,29 +142,27 @@ function normalizeEt(
function trendOzetiUret(p: AdayProgram): string {
// Cümle tadımlıkla ortak saf fonksiyondan gelir (lib/trend.ts)
return trendOzetiMetni([
p.sira2021,
p.sira2022,
p.sira2023,
p.sira2024,
p.sira2025,
]);
return trendOzetiMetni(p.siraGecmisi);
}
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. */
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 {
isim: p.isim,
universite: p.universite,
il: p.il,
unitur: p.unitur,
efektifSira: p.sira2025 ?? p.sira2024,
siraGecmisi: gecmis.some((s) => s != null) ? gecmis : undefined,
efektifSira: p.efektifSira,
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
const hedef = dilimHedefiHesapla(havuz);
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) =>
havuz
.filter((p) => p.dilim === dilim)
@@ -293,7 +292,7 @@ export async function raporUret(
const havuzMap = new Map(havuz.map((p) => [p.id, p]));
const efektif = (id: string) => {
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;