Merge branch 'worktree-agent-a5bcd7331b2f3c5b2' into v2
This commit is contained in:
79
scripts/eval/varyant-denetim.ts
Normal file
79
scripts/eval/varyant-denetim.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Program varyant etiketinin MEKANİK testi — parasız, anahtarsız, salt okur.
|
||||||
|
*
|
||||||
|
* pnpm tsx scripts/eval/varyant-denetim.ts
|
||||||
|
*
|
||||||
|
* react-server koşulu gerekmez: db.ts ve program-varyant.ts saf/sqlite.
|
||||||
|
* Doğrulananlar: (1) varyantEtiketi birim kuralları, (2) Başkent Tıp'ın 6
|
||||||
|
* satırının etiketleri birbirinden farklı ve hiçbiri null, (3) dilimAra'nın
|
||||||
|
* döndürdüğü her satırda `varyant` tanımlı (SELECT_COLS tüm yolu kapsıyor),
|
||||||
|
* (4) devlet/Türkçe/örgün programda null. data/yokatlas.db'ye yazılmaz.
|
||||||
|
* Beklenti tutmazsa çıkış kodu 1.
|
||||||
|
*/
|
||||||
|
import { dilimAra, programGetir } from "../../src/lib/db";
|
||||||
|
import { varyantEtiketi, type VaryantGirdisi } from "../../src/lib/program-varyant";
|
||||||
|
|
||||||
|
let hata = 0;
|
||||||
|
function bekle(ad: string, kosul: boolean, detay = ""): void {
|
||||||
|
console.log(`${kosul ? "✔" : "✘"} ${ad}${detay ? ` — ${detay}` : ""}`);
|
||||||
|
if (!kosul) hata++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Birim: etiket kuralları
|
||||||
|
const birim: [VaryantGirdisi, string | null][] = [
|
||||||
|
[{ bursOrani: "Burslu", ogrenimDili: "İngilizce", ogrenimTuru: "Örgün Öğretim" }, "Burslu · İngilizce"],
|
||||||
|
[{ bursOrani: null, ogrenimDili: "Türkçe", ogrenimTuru: "Örgün Öğretim" }, null],
|
||||||
|
[{ bursOrani: "Ücretli", ogrenimDili: "Türkçe", ogrenimTuru: "İkinci Öğretim" }, "Ücretli · İkinci Öğretim"],
|
||||||
|
[{ bursOrani: "%50 İndirimli", ogrenimDili: null, ogrenimTuru: null }, "%50 İndirimli"],
|
||||||
|
// DB'de burs_orani boş string de geliyor (devlet); null gibi davranır
|
||||||
|
[{ bursOrani: "", ogrenimDili: "Türkçe", ogrenimTuru: "Uzaktan Öğretim" }, "Uzaktan Öğretim"],
|
||||||
|
];
|
||||||
|
for (const [girdi, beklenen] of birim) {
|
||||||
|
const sonuc = varyantEtiketi(girdi);
|
||||||
|
bekle(
|
||||||
|
`birim ${JSON.stringify(girdi)}`,
|
||||||
|
sonuc === beklenen,
|
||||||
|
`beklenen ${JSON.stringify(beklenen)}, gelen ${JSON.stringify(sonuc)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Başkent Tıp ×6: isim hepsinde "Tıp", varyant hepsinde farklı
|
||||||
|
const BASKENT_TIP = ["200610657", "200610666", "200611488", "200690357", "200690358", "200690359"];
|
||||||
|
const baskent = BASKENT_TIP.map((id) => ({ id, p: programGetir(id) }));
|
||||||
|
for (const { id, p } of baskent) {
|
||||||
|
bekle(
|
||||||
|
`programGetir(${id}) varyant dolu`,
|
||||||
|
p != null && p.varyant != null,
|
||||||
|
p ? `${p.universite} / ${p.isim} → ${p.varyant}` : "bulunamadı",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const etiketler = baskent.map(({ p }) => p?.varyant ?? null);
|
||||||
|
bekle(
|
||||||
|
"Başkent Tıp 6 varyant birbirinden farklı",
|
||||||
|
new Set(etiketler).size === BASKENT_TIP.length,
|
||||||
|
etiketler.join(" | "),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. dilimAra yolu: her satırda varyant tanımlı (string ya da null)
|
||||||
|
const { programlar } = dilimAra(100000, "say", "dengeli", { limit: 50 });
|
||||||
|
const tanimsiz = programlar.filter(
|
||||||
|
(p) => !("varyant" in p) || (p.varyant !== null && typeof p.varyant !== "string"),
|
||||||
|
);
|
||||||
|
bekle(
|
||||||
|
`dilimAra 50 satırda varyant tanımlı (${programlar.length} satır)`,
|
||||||
|
programlar.length > 0 && tanimsiz.length === 0,
|
||||||
|
tanimsiz.map((p) => p.id).join(","),
|
||||||
|
);
|
||||||
|
const etiketli = programlar.filter((p) => p.varyant).length;
|
||||||
|
console.log(` (bilgi) dilimAra örneğinde etiketli satır: ${etiketli}/${programlar.length}`);
|
||||||
|
|
||||||
|
// 4. Devlet / Türkçe / örgün → null (Marmara Hukuk)
|
||||||
|
const marmara = programGetir("107210332");
|
||||||
|
bekle(
|
||||||
|
"107210332 Marmara Hukuk varyant null",
|
||||||
|
marmara != null && marmara.varyant === null,
|
||||||
|
marmara ? `${marmara.universite} / ${marmara.isim} → ${JSON.stringify(marmara.varyant)}` : "bulunamadı",
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(hata === 0 ? "\nVaryant denetimi: TEMİZ" : `\nVaryant denetimi: ${hata} hata`);
|
||||||
|
process.exit(hata === 0 ? 0 : 1);
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
SATIR_BUTON_BASLIK,
|
SATIR_BUTON_BASLIK,
|
||||||
SATIR_BUTON_HUCRE,
|
SATIR_BUTON_HUCRE,
|
||||||
SATIR_BUTON_KABI,
|
SATIR_BUTON_KABI,
|
||||||
|
SATIR_CIP,
|
||||||
SATIR_GRUP,
|
SATIR_GRUP,
|
||||||
SATIR_IKINCIL_LINK,
|
SATIR_IKINCIL_LINK,
|
||||||
SATIR_META,
|
SATIR_META,
|
||||||
@@ -242,6 +243,10 @@ export function BolumProgramListesi({
|
|||||||
)}
|
)}
|
||||||
<span className={SATIR_META}>
|
<span className={SATIR_META}>
|
||||||
<TurCipi unitur={p.unitur} />
|
<TurCipi unitur={p.unitur} />
|
||||||
|
{/* Burs/dil/öğretim türü: aynı adlı satırları ayırt eder (§4.15 çip) */}
|
||||||
|
{p.varyant ? (
|
||||||
|
<span className={SATIR_CIP}>{p.varyant}</span>
|
||||||
|
) : null}
|
||||||
{p.il ? (
|
{p.il ? (
|
||||||
<Link
|
<Link
|
||||||
href={ilSayfaHref(p.il)}
|
href={ilSayfaHref(p.il)}
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ export function UniversiteProgramTablosu({
|
|||||||
<span className={SATIR_CIP}>
|
<span className={SATIR_CIP}>
|
||||||
{TUR_ETIKET[p.tur] ?? p.tur}
|
{TUR_ETIKET[p.tur] ?? p.tur}
|
||||||
</span>
|
</span>
|
||||||
|
{/* Burs/dil/öğretim türü: aynı adlı satırları ayırt eder (§4.15 çip) */}
|
||||||
|
{p.varyant ? (
|
||||||
|
<span className={SATIR_CIP}>{p.varyant}</span>
|
||||||
|
) : null}
|
||||||
{p.sure ? (
|
{p.sure ? (
|
||||||
<span className="shrink-0">{p.sure} yıl</span>
|
<span className="shrink-0">{p.sure} yıl</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -452,6 +452,10 @@ function CekmeceSatiri({
|
|||||||
>
|
>
|
||||||
{trBaslikDuzeni(tercih.universite)}
|
{trBaslikDuzeni(tercih.universite)}
|
||||||
</Link>
|
</Link>
|
||||||
|
{/* Burs/dil/öğretim türü: aynı adlı satırları ayırt eder */}
|
||||||
|
{tercih.varyant ? (
|
||||||
|
<span className="shrink-0 text-slate-500">· {tercih.varyant}</span>
|
||||||
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
{tercih.risk || tercih.efektifSira != null ? (
|
{tercih.risk || tercih.efektifSira != null ? (
|
||||||
<span className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-slate-500">
|
<span className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-slate-500">
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type ProgramOzet = Pick<
|
|||||||
| "unitur"
|
| "unitur"
|
||||||
| "tur"
|
| "tur"
|
||||||
| "efektifSira"
|
| "efektifSira"
|
||||||
|
| "varyant"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const PROFILSIZ_EKLEME_TOAST_ID = "profilsiz-ekleme";
|
const PROFILSIZ_EKLEME_TOAST_ID = "profilsiz-ekleme";
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import {
|
|||||||
SATIR_BUTON_HUCRE,
|
SATIR_BUTON_HUCRE,
|
||||||
SATIR_BUTON_KABI,
|
SATIR_BUTON_KABI,
|
||||||
SATIR_ALT_GRUP,
|
SATIR_ALT_GRUP,
|
||||||
|
SATIR_CIP,
|
||||||
SATIR_IKINCIL_LINK,
|
SATIR_IKINCIL_LINK,
|
||||||
SATIR_META,
|
SATIR_META,
|
||||||
SATIR_MOBIL_SAYI,
|
SATIR_MOBIL_SAYI,
|
||||||
@@ -373,6 +374,10 @@ function ProgramSatiri({
|
|||||||
unitur={p.unitur}
|
unitur={p.unitur}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
|
{/* Burs/dil/öğretim türü: aynı adlı satırları ayırt eder (§4.15 çip) */}
|
||||||
|
{p.varyant ? (
|
||||||
|
<span className={SATIR_CIP}>{p.varyant}</span>
|
||||||
|
) : null}
|
||||||
{p.il ? (
|
{p.il ? (
|
||||||
<Link
|
<Link
|
||||||
href={ilSayfaHref(p.il)}
|
href={ilSayfaHref(p.il)}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export type BekleyenProgram = {
|
|||||||
| "unitur"
|
| "unitur"
|
||||||
| "tur"
|
| "tur"
|
||||||
| "efektifSira"
|
| "efektifSira"
|
||||||
|
| "varyant"
|
||||||
>;
|
>;
|
||||||
kaynak: ManuelKaynak;
|
kaynak: ManuelKaynak;
|
||||||
adaySira?: number;
|
adaySira?: number;
|
||||||
@@ -271,11 +272,16 @@ export function programEkleIste(opts: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const profil = profilsiz ? null : (durum.profil ?? tercihProfiliOku());
|
const profil = profilsiz ? null : (durum.profil ?? tercihProfiliOku());
|
||||||
const tercih = programdanManuelTercih(
|
// programdanManuelTercih (liste-store) varyantı bilmez; etiket burada
|
||||||
|
// kopyalanır ki çekmece aynı adlı satırları ayırt edebilsin.
|
||||||
|
const tercih = {
|
||||||
|
...programdanManuelTercih(
|
||||||
program,
|
program,
|
||||||
adaySira ?? profil?.sira ?? null,
|
adaySira ?? profil?.sira ?? null,
|
||||||
kaynak,
|
kaynak,
|
||||||
);
|
),
|
||||||
|
varyant: program.varyant ?? null,
|
||||||
|
};
|
||||||
const ok = ekle(tercih);
|
const ok = ekle(tercih);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export type ManuelTercih = {
|
|||||||
/** Ekleme / profil anındaki aday sıralamasına göre risk */
|
/** Ekleme / profil anındaki aday sıralamasına göre risk */
|
||||||
risk: RiskSeviyesi | null;
|
risk: RiskSeviyesi | null;
|
||||||
kaynak?: ManuelKaynak;
|
kaynak?: ManuelKaynak;
|
||||||
|
/** Burs/dil/öğretim türü etiketi (Program.varyant); eski kayıtlarda yok */
|
||||||
|
varyant?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Güvenilmeyen JSON'u (localStorage veya sunucu kaydı) listeye süzer. */
|
/** Güvenilmeyen JSON'u (localStorage veya sunucu kaydı) listeye süzer. */
|
||||||
@@ -66,6 +68,7 @@ export function dogrula(ham: unknown): ManuelTercih[] {
|
|||||||
: null,
|
: null,
|
||||||
risk,
|
risk,
|
||||||
kaynak,
|
kaynak,
|
||||||
|
varyant: typeof kayit.varyant === "string" ? kayit.varyant : null,
|
||||||
});
|
});
|
||||||
if (sonuc.length >= MANUEL_LISTE_MAX) break;
|
if (sonuc.length >= MANUEL_LISTE_MAX) break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export function programOzetle(p: Program): ProgramListeSatiri {
|
|||||||
efektifSira: p.efektifSira,
|
efektifSira: p.efektifSira,
|
||||||
kontenjanSon: p.kontenjanSon,
|
kontenjanSon: p.kontenjanSon,
|
||||||
yerlesenSon: p.yerlesenSon,
|
yerlesenSon: p.yerlesenSon,
|
||||||
|
varyant: p.varyant,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
SATIR_BUTON_HUCRE,
|
SATIR_BUTON_HUCRE,
|
||||||
SATIR_DUGME_BOYU,
|
SATIR_DUGME_BOYU,
|
||||||
SATIR_ALT_GRUP,
|
SATIR_ALT_GRUP,
|
||||||
|
SATIR_CIP,
|
||||||
SATIR_IKINCIL_LINK,
|
SATIR_IKINCIL_LINK,
|
||||||
SATIR_META,
|
SATIR_META,
|
||||||
SATIR_MOBIL_SAYI,
|
SATIR_MOBIL_SAYI,
|
||||||
@@ -267,6 +268,10 @@ export function RaporListesi({
|
|||||||
unitur={p?.unitur}
|
unitur={p?.unitur}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
|
{/* Burs/dil/öğretim türü: aynı adlı satırları ayırt eder (§4.15 çip) */}
|
||||||
|
{p?.varyant ? (
|
||||||
|
<span className={SATIR_CIP}>{p.varyant}</span>
|
||||||
|
) : null}
|
||||||
{p?.il ? (
|
{p?.il ? (
|
||||||
<Link
|
<Link
|
||||||
href={ilSayfaHref(p.il)}
|
href={ilSayfaHref(p.il)}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import "server-only";
|
|||||||
import { desc, eq, sql } from "drizzle-orm";
|
import { desc, eq, sql } from "drizzle-orm";
|
||||||
import { appDb, schema } from "@/lib/appdb";
|
import { appDb, schema } from "@/lib/appdb";
|
||||||
import { anahtarVar } from "@/lib/ai/client";
|
import { anahtarVar } from "@/lib/ai/client";
|
||||||
|
import { programOzellikleriGetir } from "@/lib/program-ozellik";
|
||||||
|
import { varyantEtiketi } from "@/lib/program-varyant";
|
||||||
import type { RaporSonuc } from "./types/rapor";
|
import type { RaporSonuc } from "./types/rapor";
|
||||||
|
|
||||||
// Rapor feature'ının sorgu kapısı: kayıtlı rapor, sohbet geçmişi ve girişsiz
|
// Rapor feature'ının sorgu kapısı: kayıtlı rapor, sohbet geçmişi ve girişsiz
|
||||||
@@ -37,9 +39,23 @@ export async function kullanicininRaporu(userId: string) {
|
|||||||
where: eq(schema.reports.userId, userId),
|
where: eq(schema.reports.userId, userId),
|
||||||
});
|
});
|
||||||
if (satir?.result && bayatMockMu(satir.result as RaporSonuc)) return null;
|
if (satir?.result && bayatMockMu(satir.result as RaporSonuc)) return null;
|
||||||
|
if (satir?.result) varyantlariEkle(satir.result as RaporSonuc);
|
||||||
return satir ?? null;
|
return satir ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kayıtlı raporun program satırlarına burs/dil/öğretim türü etiketi (okuma
|
||||||
|
// anında, tek sorgu): rapor JSON'u üretim anında bunu taşımıyor, eski
|
||||||
|
// kayıtlar da bu yoldan kazanır. yokatlas.db salt okunur; app.db'ye yazılmaz.
|
||||||
|
function varyantlariEkle(sonuc: RaporSonuc): void {
|
||||||
|
const idler = Object.keys(sonuc.programlar ?? {});
|
||||||
|
if (idler.length === 0) return;
|
||||||
|
const ozellikler = programOzellikleriGetir(idler);
|
||||||
|
for (const id of idler) {
|
||||||
|
const o = ozellikler[id];
|
||||||
|
sonuc.programlar[id].varyant = o ? varyantEtiketi(o) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kullanıcının danışman sohbet geçmişi: en YENİ 100 mesaj, eskiden yeniye.
|
* Kullanıcının danışman sohbet geçmişi: en YENİ 100 mesaj, eskiden yeniye.
|
||||||
* created_at saniye çözünürlüklü; aynı saniyedeki soru-cevap sırası rowid ile
|
* created_at saniye çözünürlüklü; aynı saniyedeki soru-cevap sırası rowid ile
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ export type RaporSonuc = {
|
|||||||
efektifSira?: number | null;
|
efektifSira?: number | null;
|
||||||
siraGecmisi?: (number | null)[];
|
siraGecmisi?: (number | null)[];
|
||||||
ilkYil?: number;
|
ilkYil?: number;
|
||||||
|
/** Burs/dil/öğretim türü etiketi; DB'ye yazılmaz, okuma anında
|
||||||
|
* program_ozellik'ten eklenir (rapor-queries kullanicininRaporu) */
|
||||||
|
varyant?: string | null;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Database from "better-sqlite3";
|
|||||||
import { KATEGORILER, kategoriBul } from "./kategoriler";
|
import { KATEGORILER, kategoriBul } from "./kategoriler";
|
||||||
import { EFEKTIF_SIRA_SQL, ILK_YIL, SON_YIL, YILLAR } from "./veri-yillari";
|
import { EFEKTIF_SIRA_SQL, ILK_YIL, SON_YIL, YILLAR } from "./veri-yillari";
|
||||||
import { turkishSlugify } from "./slug";
|
import { turkishSlugify } from "./slug";
|
||||||
|
import { varyantEtiketi } from "./program-varyant";
|
||||||
import {
|
import {
|
||||||
PUAN_TURLERI,
|
PUAN_TURLERI,
|
||||||
type ArsivProgram,
|
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
|
// 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
|
// 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).
|
// `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,
|
export const SELECT_COLS = `id, isim, universite, unitur, il, fakulte, tur, sure,
|
||||||
${YILLAR.map((yil) => `sira${yil} AS s${yil}`).join(", ")},
|
${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.
|
// 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.
|
// İfade indeks sözleşmesidir; tek kaynağı lib/veri-yillari.ts.
|
||||||
export const EFEKTIF_SIRA = EFEKTIF_SIRA_SQL;
|
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<
|
export type ProgramHamSatiri = Omit<
|
||||||
Program,
|
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. */
|
/** Ham DB satırını yılsız `Program`'a çevirir. */
|
||||||
export function programSatiri(row: ProgramHamSatiri): Program {
|
export function programSatiri(row: ProgramHamSatiri): Program {
|
||||||
@@ -72,6 +86,7 @@ export function programSatiri(row: ProgramHamSatiri): Program {
|
|||||||
puanSon: row.puanSon,
|
puanSon: row.puanSon,
|
||||||
kontenjanSon: row.kontenjanSon,
|
kontenjanSon: row.kontenjanSon,
|
||||||
yerlesenSon: row.yerlesenSon,
|
yerlesenSon: row.yerlesenSon,
|
||||||
|
varyant: varyantEtiketi(row),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
40
src/lib/program-varyant.ts
Normal file
40
src/lib/program-varyant.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Program varyant etiketi — saf, DB'siz. `programs.isim` burs/dil/öğretim
|
||||||
|
// türünü taşımaz (Başkent Tıp 6 satırda da "Tıp"); ayırt edici alanlar
|
||||||
|
// `program_ozellik.burs_orani / ogrenim_dili / ogrenim_turu`. Bu modül o üç
|
||||||
|
// alanı satır altında gösterilecek tek etikete çevirir; db.ts `programSatiri`
|
||||||
|
// ve rapor-queries (kayıtlı rapor) aynı fonksiyonu kullanır.
|
||||||
|
|
||||||
|
export type VaryantGirdisi = {
|
||||||
|
/** "Burslu" | "%50 İndirimli" | "%25 İndirimli" | "Ücretli"; devlette null/boş */
|
||||||
|
bursOrani: string | null;
|
||||||
|
/** "Türkçe" | "İngilizce" | "İngilizce (%30)" | "Almanca" … */
|
||||||
|
ogrenimDili: string | null;
|
||||||
|
/** "Örgün Öğretim" | "Uzaktan Öğretim" | "Açık Öğretim" | "UOLP" … */
|
||||||
|
ogrenimTuru: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VARSAYILAN_DIL = "Türkçe";
|
||||||
|
const VARSAYILAN_OGRETIM = "Örgün Öğretim";
|
||||||
|
const AYRAC = " · ";
|
||||||
|
|
||||||
|
function dolu(deger: string | null | undefined): string | null {
|
||||||
|
const metin = deger?.trim();
|
||||||
|
return metin ? metin : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Satır altı etiketi: "Burslu · İngilizce", "%50 İndirimli", "İkinci Öğretim",
|
||||||
|
* "Ücretli · Uzaktan Öğretim". Sıra: burs (varsa, DB'deki hâliyle), dil
|
||||||
|
* (yalnız Türkçe değilse), öğretim türü (yalnız örgün değilse). Üçü de
|
||||||
|
* varsayılansa (devlet / Türkçe / örgün) null — satırda hiçbir şey çizilmez.
|
||||||
|
*/
|
||||||
|
export function varyantEtiketi(g: VaryantGirdisi): string | null {
|
||||||
|
const parcalar: string[] = [];
|
||||||
|
const burs = dolu(g.bursOrani);
|
||||||
|
if (burs) parcalar.push(burs);
|
||||||
|
const dil = dolu(g.ogrenimDili);
|
||||||
|
if (dil && dil !== VARSAYILAN_DIL) parcalar.push(dil);
|
||||||
|
const ogretim = dolu(g.ogrenimTuru);
|
||||||
|
if (ogretim && ogretim !== VARSAYILAN_OGRETIM) parcalar.push(ogretim);
|
||||||
|
return parcalar.length > 0 ? parcalar.join(AYRAC) : null;
|
||||||
|
}
|
||||||
@@ -433,6 +433,9 @@ export function havuzuKompaktJson(havuz: AdayProgram[]): string {
|
|||||||
unitur: p.unitur,
|
unitur: p.unitur,
|
||||||
il: p.il,
|
il: p.il,
|
||||||
dilim: p.dilim,
|
dilim: p.dilim,
|
||||||
|
// Burs/dil/öğretim türü: model aynı adlı satırları (Başkent Tıp
|
||||||
|
// burslu / %50 / ücretli) ayırt etsin, gerekçede "burslu" diyebilsin
|
||||||
|
...(p.varyant ? { varyant: p.varyant } : {}),
|
||||||
// Anahtarlar yıl taşır (y<yıl>, kontenjan<yıl>): model rakamın hangi
|
// Anahtarlar yıl taşır (y<yıl>, kontenjan<yıl>): model rakamın hangi
|
||||||
// yıla ait olduğunu anahtardan okur.
|
// yıla ait olduğunu anahtardan okur.
|
||||||
sira: Object.fromEntries(
|
sira: Object.fromEntries(
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ export type Program = {
|
|||||||
puanSon: number | null;
|
puanSon: number | null;
|
||||||
kontenjanSon: number | null;
|
kontenjanSon: number | null;
|
||||||
yerlesenSon: number | null;
|
yerlesenSon: number | null;
|
||||||
|
/**
|
||||||
|
* Burs / dil / öğretim türü etiketi ("Burslu · İngilizce", "%50 İndirimli",
|
||||||
|
* "Uzaktan Öğretim"); `program_ozellik`'ten `varyantEtiketi` ile üretilir
|
||||||
|
* (lib/program-varyant). Devlet/Türkçe/örgün programda null. `isim` aynı
|
||||||
|
* olan satırları (Başkent Tıp ×6) listede ayırt eden tek alan.
|
||||||
|
*/
|
||||||
|
varyant: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PUAN_TURLERI = {
|
export const PUAN_TURLERI = {
|
||||||
|
|||||||
Reference in New Issue
Block a user