feat: update app.db and BACKLOG.md with new decisions and adjustments
All checks were successful
Deploy / deploy (push) Successful in 7m45s
All checks were successful
Deploy / deploy (push) Successful in 7m45s
- Updated the app.db file to reflect recent changes in the application state. - Modified BACKLOG.md to include new entries regarding the removal of certain wizard steps, adjustments to priority chips, and the introduction of special quota conditions for candidates. - Added details on the decision to link priority chips to data sources and clarified the implications for user experience and data handling.
This commit is contained in:
277
src/lib/db.ts
277
src/lib/db.ts
@@ -3,11 +3,17 @@
|
||||
import path from "node:path";
|
||||
import Database from "better-sqlite3";
|
||||
import { KATEGORILER, kategoriBul } from "./kategoriler";
|
||||
import { EFEKTIF_SIRA_SQL, SON_YIL, YILLAR } from "./veri-yillari";
|
||||
import { EFEKTIF_SIRA_SQL, ILK_YIL, SON_YIL, YILLAR } from "./veri-yillari";
|
||||
import { turkishSlugify } from "./slug";
|
||||
import {
|
||||
PUAN_TURLERI,
|
||||
type ArsivProgram,
|
||||
type DilimKey,
|
||||
type KontenjanDilimi,
|
||||
type KontenjanTurleri,
|
||||
type KontenjanTuruAnahtari,
|
||||
type Program,
|
||||
type ProgramGecmisSatiri,
|
||||
type ProgramNetSatiri,
|
||||
type PuanTuruKey,
|
||||
type RankResults,
|
||||
@@ -300,7 +306,7 @@ export function havuzAdaylari(
|
||||
export function netlerGetir(programId: string): ProgramNetSatiri[] {
|
||||
return getDb()
|
||||
.prepare(
|
||||
`SELECT yil, puanTuru, tabanPuan, obp,
|
||||
`SELECT yil, puanTuru, tabanPuan, obp, katsayi,
|
||||
tytTrkNet, tytSosNet, tytMatNet, tytFenNet,
|
||||
aytMatNet, aytFizNet, aytKimNet, aytBioNet,
|
||||
aytTdeNet, aytTrh1Net, aytCog1Net, aytTrh2Net, aytCog2Net,
|
||||
@@ -318,6 +324,273 @@ export function programGetir(id: string): Program | null {
|
||||
return row ? programSatiri(row) : null;
|
||||
}
|
||||
|
||||
// ── Yıl bazlı geçmiş, kontenjan türleri, arşiv ───────────────────────────
|
||||
// Bu alanlar `Program` tipine girmez: liste satırları client'a serileşiyor,
|
||||
// her satırı 4×6 kolonla şişirmek istemiyoruz. Program ayrıntı çekmecesi
|
||||
// (/api/program-ayrinti), danışman araçları ve uyum puanı buradan okur.
|
||||
|
||||
let programKolonlari: Set<string> | null = null;
|
||||
|
||||
/** `programs` tablosunun kolon adları (bir kez okunur). */
|
||||
function programKolonlariGetir(): Set<string> {
|
||||
if (!programKolonlari) {
|
||||
programKolonlari = new Set(
|
||||
(getDb().prepare("PRAGMA table_info(programs)").all() as { name: string }[]).map(
|
||||
(c) => c.name
|
||||
)
|
||||
);
|
||||
}
|
||||
return programKolonlari;
|
||||
}
|
||||
|
||||
/**
|
||||
* Programın yıl bazlı geçmişi, YENİDEN ESKİYE. Veri penceresi `YILLAR`;
|
||||
* kolonu olmayan yıl atlanır. Bilinmeyen id'de boş dizi.
|
||||
*/
|
||||
export function programGecmisiGetir(id: string): ProgramGecmisSatiri[] {
|
||||
const kolonlar = programKolonlariGetir();
|
||||
const yillar = YILLAR.filter((y) => kolonlar.has(`sira${y}`));
|
||||
if (yillar.length === 0) return [];
|
||||
const secim = yillar
|
||||
.map((y) => `sira${y} AS s${y}, puan${y} AS p${y}, kontenjan${y} AS k${y}, yerlesen${y} AS y${y}`)
|
||||
.join(", ");
|
||||
const row = getDb()
|
||||
.prepare(`SELECT ${secim} FROM programs WHERE id = ?`)
|
||||
.get(id) as Record<string, number | null> | undefined;
|
||||
if (!row) return [];
|
||||
return [...yillar].reverse().map((yil) => ({
|
||||
yil,
|
||||
sira: row[`s${yil}`] ?? null,
|
||||
puan: row[`p${yil}`] ?? null,
|
||||
kontenjan: row[`k${yil}`] ?? null,
|
||||
yerlesen: row[`y${yil}`] ?? null,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Kontenjan türü kolonu öneki → KontenjanTurleri alanı. */
|
||||
const KONTENJAN_TURU_KOLON = {
|
||||
gk: "genel",
|
||||
obk: "okulBirincisi",
|
||||
sgy: "sehitGaziYakini",
|
||||
dprm: "depremzede",
|
||||
y34k: "kadin34",
|
||||
} as const;
|
||||
|
||||
/** KontenjanTurleri alanı → kolon öneki (SQL süzgeçleri `${onek}${kontenjanTuruYili()}`). */
|
||||
export const KONTENJAN_TURU_ONEK: Record<KontenjanTuruAnahtari, string> = {
|
||||
genel: "gk",
|
||||
okulBirincisi: "obk",
|
||||
sehitGaziYakini: "sgy",
|
||||
depremzede: "dprm",
|
||||
kadin34: "y34k",
|
||||
};
|
||||
|
||||
let kontenjanTuruYiliOnbellek: number | null | undefined;
|
||||
|
||||
/**
|
||||
* Kontenjan türü kırılımının bulunduğu en yeni yıl (`gk<yil>` kolonu olan).
|
||||
* Kırılım bir sonraki kılavuzla açıklandığı için SON_YIL'dan bir geride
|
||||
* olabilir; hiç yoksa null (eski DB).
|
||||
*/
|
||||
export function kontenjanTuruYili(): number | null {
|
||||
if (kontenjanTuruYiliOnbellek !== undefined) return kontenjanTuruYiliOnbellek;
|
||||
const kolonlar = programKolonlariGetir();
|
||||
let bulunan: number | null = null;
|
||||
for (let y = SON_YIL; y >= ILK_YIL; y--) {
|
||||
if (kolonlar.has(`gk${y}`) && kolonlar.has(`y34k${y}`)) {
|
||||
bulunan = y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
kontenjanTuruYiliOnbellek = bulunan;
|
||||
return bulunan;
|
||||
}
|
||||
|
||||
function kontenjanTurleriSatiri(
|
||||
yil: number,
|
||||
row: Record<string, number | null>
|
||||
): KontenjanTurleri {
|
||||
const dilim = (onek: string): KontenjanDilimi => ({
|
||||
kontenjan: row[`${onek}${yil}`] ?? null,
|
||||
yerlesen: row[`${onek}Y${yil}`] ?? null,
|
||||
});
|
||||
return {
|
||||
yil,
|
||||
genel: dilim("gk"),
|
||||
okulBirincisi: dilim("obk"),
|
||||
sehitGaziYakini: dilim("sgy"),
|
||||
depremzede: dilim("dprm"),
|
||||
kadin34: dilim("y34k"),
|
||||
};
|
||||
}
|
||||
|
||||
function kontenjanTuruSecimi(yil: number): string {
|
||||
return Object.keys(KONTENJAN_TURU_KOLON)
|
||||
.map((onek) => `${onek}${yil}, ${onek}Y${yil}`)
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
/** Tek programın kontenjan türleri; kırılım kolonu ya da program yoksa null. */
|
||||
export function kontenjanTurleriGetir(id: string): KontenjanTurleri | null {
|
||||
const yil = kontenjanTuruYili();
|
||||
if (yil == null) return null;
|
||||
const row = getDb()
|
||||
.prepare(`SELECT ${kontenjanTuruSecimi(yil)} FROM programs WHERE id = ?`)
|
||||
.get(id) as Record<string, number | null> | undefined;
|
||||
return row ? kontenjanTurleriSatiri(yil, row) : null;
|
||||
}
|
||||
|
||||
const IN_PARCA = 500;
|
||||
|
||||
/** Çok programın kontenjan türleri (uyum puanı, arama sonuçları); id → satır. */
|
||||
export function kontenjanTurleriGetirToplu(
|
||||
ids: readonly string[]
|
||||
): Record<string, KontenjanTurleri> {
|
||||
const sonuc: Record<string, KontenjanTurleri> = {};
|
||||
const yil = kontenjanTuruYili();
|
||||
if (yil == null || ids.length === 0) return sonuc;
|
||||
const db = getDb();
|
||||
for (let i = 0; i < ids.length; i += IN_PARCA) {
|
||||
const parca = ids.slice(i, i + IN_PARCA);
|
||||
const satirlar = db
|
||||
.prepare(
|
||||
`SELECT id, ${kontenjanTuruSecimi(yil)} FROM programs WHERE id IN (${parca.map(() => "?").join(",")})`
|
||||
)
|
||||
.all(...parca) as (Record<string, number | null> & { id: string })[];
|
||||
for (const r of satirlar) sonuc[r.id] = kontenjanTurleriSatiri(yil, r);
|
||||
}
|
||||
return sonuc;
|
||||
}
|
||||
|
||||
// Katlanmış (Türkçe harf duyarsız) ad eşleştirme: SQLite LIKE "isletme"yi
|
||||
// İşletme'yle, "bogazici"yi Boğaziçi'yle eşlemez. Kolonun ayrık değerleri
|
||||
// (birkaç bin) tablo başına bir kez okunur, eşleşme JS'te yapılır. Danışman
|
||||
// araçları (program_ara) ve arşiv araması aynı yardımcıyı kullanır.
|
||||
type AyrikDeger = { ham: string; katli: string };
|
||||
const ayrikDegerler = new Map<string, AyrikDeger[]>();
|
||||
|
||||
export function ayrikListe(
|
||||
kolon: "isim" | "universite",
|
||||
tablo: "programs" | "programs_arsiv" = "programs"
|
||||
): AyrikDeger[] {
|
||||
const anahtar = `${tablo}.${kolon}`;
|
||||
let liste = ayrikDegerler.get(anahtar);
|
||||
if (!liste) {
|
||||
liste = (getDb().prepare(`SELECT DISTINCT ${kolon} AS v FROM ${tablo}`).all() as { v: string | null }[])
|
||||
.filter((x): x is { v: string } => x.v != null)
|
||||
.map((x) => ({ ham: x.v, katli: turkishSlugify(x.v) }));
|
||||
ayrikDegerler.set(anahtar, liste);
|
||||
}
|
||||
return liste;
|
||||
}
|
||||
|
||||
/** Kolonun, katlanmış hâli `katliSorgu`yu içeren ham değerleri. */
|
||||
export function katliEslesenler(
|
||||
kolon: "isim" | "universite",
|
||||
katliSorgu: string,
|
||||
tablo: "programs" | "programs_arsiv" = "programs"
|
||||
): string[] {
|
||||
return ayrikListe(kolon, tablo)
|
||||
.filter((x) => x.katli.includes(katliSorgu))
|
||||
.map((x) => x.ham);
|
||||
}
|
||||
|
||||
let arsivTablosuVar: boolean | null = null;
|
||||
|
||||
function arsivTablosuVarMi(): boolean {
|
||||
if (arsivTablosuVar == null) {
|
||||
arsivTablosuVar =
|
||||
getDb()
|
||||
.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='programs_arsiv'")
|
||||
.get() !== undefined;
|
||||
}
|
||||
return arsivTablosuVar;
|
||||
}
|
||||
|
||||
const ARSIV_SELECT = `id, isim, universite, unitur, il, fakulte, tur, sure, son_kilavuz_yili AS sonKilavuzYili, eski_kod AS eskiKod,
|
||||
${YILLAR.map((y) => `sira${y} AS s${y}, puan${y} AS p${y}, kontenjan${y} AS k${y}, yerlesen${y} AS y${y}`).join(", ")}`;
|
||||
|
||||
function arsivSatiri(row: Record<string, unknown>): ArsivProgram {
|
||||
// Dolu son yerleştirme yılı: sırası ya da kontenjanı olan en yeni yıl
|
||||
let sonYil: number | null = null;
|
||||
for (const y of [...YILLAR].reverse()) {
|
||||
if (row[`s${y}`] != null || row[`k${y}`] != null) {
|
||||
sonYil = y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const deger = (onek: string) =>
|
||||
sonYil == null ? null : ((row[`${onek}${sonYil}`] as number | null) ?? null);
|
||||
return {
|
||||
id: String(row.id),
|
||||
isim: String(row.isim),
|
||||
universite: String(row.universite),
|
||||
unitur: (row.unitur as string | null) ?? null,
|
||||
il: (row.il as string | null) ?? null,
|
||||
fakulte: (row.fakulte as string | null) ?? null,
|
||||
tur: String(row.tur),
|
||||
sure: (row.sure as number | null) ?? null,
|
||||
sonKilavuzYili: (row.sonKilavuzYili as number | null) ?? null,
|
||||
sonYil,
|
||||
sonSira: deger("s"),
|
||||
sonPuan: deger("p"),
|
||||
sonKontenjan: deger("k"),
|
||||
sonYerlesen: deger("y"),
|
||||
eskiKod: (row.eskiKod as string | null) ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function inKosulu(kolon: string, degerler: string[]): string {
|
||||
return degerler.length ? `${kolon} IN (${degerler.map(() => "?").join(",")})` : "0 = 1";
|
||||
}
|
||||
|
||||
/**
|
||||
* Arşivdeki (güncel kılavuzda olmayan) programları arar: bölüm adı parçası,
|
||||
* üniversite adı parçası (katlanmış), il (DB biçimi). En yeni son kılavuz yılı
|
||||
* önce. Tablo yoksa boş.
|
||||
*/
|
||||
export function arsivProgramAra(
|
||||
a: { arama?: string; universite?: string; il?: string; limit?: number }
|
||||
): { toplam: number; programlar: ArsivProgram[] } {
|
||||
if (!arsivTablosuVarMi()) return { toplam: 0, programlar: [] };
|
||||
const kosullar: string[] = [];
|
||||
const args: (string | number)[] = [];
|
||||
if (a.arama) {
|
||||
const adlar = katliEslesenler("isim", turkishSlugify(a.arama), "programs_arsiv");
|
||||
kosullar.push(inKosulu("isim", adlar));
|
||||
args.push(...adlar);
|
||||
}
|
||||
if (a.universite) {
|
||||
const adlar = katliEslesenler("universite", turkishSlugify(a.universite), "programs_arsiv");
|
||||
kosullar.push(inKosulu("universite", adlar));
|
||||
args.push(...adlar);
|
||||
}
|
||||
if (a.il) {
|
||||
kosullar.push("il = ?");
|
||||
args.push(a.il);
|
||||
}
|
||||
if (kosullar.length === 0) return { toplam: 0, programlar: [] };
|
||||
const where = `WHERE ${kosullar.join(" AND ")}`;
|
||||
const db = getDb();
|
||||
const toplam = (db.prepare(`SELECT COUNT(*) AS n FROM programs_arsiv ${where}`).get(...args) as { n: number }).n;
|
||||
const satirlar = db
|
||||
.prepare(
|
||||
`SELECT ${ARSIV_SELECT} FROM programs_arsiv ${where}
|
||||
ORDER BY son_kilavuz_yili DESC, universite, isim LIMIT ?`
|
||||
)
|
||||
.all(...args, a.limit ?? 10) as Record<string, unknown>[];
|
||||
return { toplam, programlar: satirlar.map(arsivSatiri) };
|
||||
}
|
||||
|
||||
/** Arşivdeki tek program (id `kod` ya da `kod~yıl` biçiminde olabilir). */
|
||||
export function arsivProgramGetir(id: string): ArsivProgram | null {
|
||||
if (!arsivTablosuVarMi()) return null;
|
||||
const row = getDb()
|
||||
.prepare(`SELECT ${ARSIV_SELECT} FROM programs_arsiv WHERE id = ? OR id LIKE ? ORDER BY son_kilavuz_yili DESC LIMIT 1`)
|
||||
.get(id, `${id}~%`) as Record<string, unknown> | undefined;
|
||||
return row ? arsivSatiri(row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kayıtlı manuel liste satırlarının güncel efektif sırası (son yıl, yoksa bir
|
||||
* önceki). Kayıtlar efektif sırayı ekleme anının snapshot'ı olarak taşır; yeni
|
||||
|
||||
Reference in New Issue
Block a user