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

@@ -10,6 +10,7 @@ import type { Dilim } from "./rapor-havuzu";
import type { RaporSonuc } from "@/features/rapor/types/rapor";
import { dilimdenRisk, riskHesapla, riskNotuMetni } from "./risk";
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. */
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
* 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).
* servis edilmez. 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/;
/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 {
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ı
// 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.
// 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(
riskHesapla(program.efektifSira, sira) ?? dilimdenRisk(satir.dilim),
),
trendOzeti: trendOzetiMetni(program.siraGecmisi),
trendOzeti: trendOzetiMetni(program.siraGecmisi, program.ilkYil),
program,
};
}