feat(veri): Program.varyant — burs/dil/öğretim türü etiketi tek kaynaktan (program_ozellik)

programs.isim burs/dil varyantını taşımıyor (Başkent Tıp 6 satırda da
"Tıp"); ayırt edici alanlar program_ozellik.burs_orani / ogrenim_dili /
ogrenim_turu. SELECT_COLS üç ilişkili alt sorgu kazanır, programSatiri
varyantEtiketi ile "Burslu · İngilizce" gibi tek etiket üretir; devlet /
Türkçe / örgün programda null. Havuz JSON'una da girer: model aynı adlı
satırları ayırt edebilsin.

Tablo yokluğu koruması bilinçli yok: data/yokatlas.db depoda ve Docker
seed'inde (Dockerfile) program_ozellik tablosuyla geliyor. programs_arsiv
sorguları dokunulmadı (arşivde özellik yok).

Test: pnpm tsx scripts/eval/varyant-denetim.ts (salt okur, çıkış 0).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-07 23:12:03 +03:00
parent 4d30dd1305
commit 5c583ac8b4
5 changed files with 148 additions and 4 deletions

View File

@@ -5,6 +5,7 @@ import Database from "better-sqlite3";
import { KATEGORILER, kategoriBul } from "./kategoriler";
import { EFEKTIF_SIRA_SQL, ILK_YIL, SON_YIL, YILLAR } from "./veri-yillari";
import { turkishSlugify } from "./slug";
import { varyantEtiketi } from "./program-varyant";
import {
PUAN_TURLERI,
type ArsivProgram,
@@ -37,20 +38,33 @@ export function getDb(): Database.Database {
// 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).
// Burs/dil/öğretim türü `program_ozellik`'ten ilişkili alt sorguyla gelir
// (id PRIMARY KEY). Bütün SELECT_COLS kullanımları takma adsız `FROM programs`
// olduğu için `programs.id` çözümlenir; `programs_arsiv` (ARSIV_SELECT) bu
// kolonları almaz (arşivde özellik yok). Tablo yokluğu koruması gerekmez:
// data/yokatlas.db depoda ve Docker seed'inde tabloyla gelir.
export const SELECT_COLS = `id, isim, universite, unitur, il, fakulte, tur, sure,
${YILLAR.map((yil) => `sira${yil} AS s${yil}`).join(", ")},
puan${SON_YIL} AS puanSon, kontenjan${SON_YIL} AS kontenjanSon, yerlesen${SON_YIL} AS yerlesenSon`;
puan${SON_YIL} AS puanSon, kontenjan${SON_YIL} AS kontenjanSon, yerlesen${SON_YIL} AS yerlesenSon,
(SELECT burs_orani FROM program_ozellik o WHERE o.id = programs.id) AS bursOrani,
(SELECT ogrenim_dili FROM program_ozellik o WHERE o.id = programs.id) AS ogrenimDili,
(SELECT ogrenim_turu FROM program_ozellik o WHERE o.id = programs.id) AS ogrenimTuru`;
// Son yılın sıralaması yoksa (az yerleşen/yeni program) bir önceki yıla düşer.
// İ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. */
/** SELECT_COLS ile okunan ham satır: sıra kolonları `s<yıl>` takma adıyla,
* varyant ise üç ham özellik alanı olarak gelir. */
export type ProgramHamSatiri = Omit<
Program,
"siraGecmisi" | "efektifSira" | "siraSon"
"siraGecmisi" | "efektifSira" | "siraSon" | "varyant"
> &
Record<`s${number}`, number | null>;
Record<`s${number}`, number | null> & {
bursOrani: string | null;
ogrenimDili: string | null;
ogrenimTuru: string | null;
};
/** Ham DB satırını yılsız `Program`'a çevirir. */
export function programSatiri(row: ProgramHamSatiri): Program {
@@ -72,6 +86,7 @@ export function programSatiri(row: ProgramHamSatiri): Program {
puanSon: row.puanSon,
kontenjanSon: row.kontenjanSon,
yerlesenSon: row.yerlesenSon,
varyant: varyantEtiketi(row),
};
}