feat: update app.db and BACKLOG.md with new decisions and adjustments
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:
bilalgursen
2026-10-06 17:17:49 +03:00
parent c0696e2cbc
commit a3cb058a3b
74 changed files with 10334 additions and 445 deletions

View File

@@ -10,12 +10,19 @@ import {
} from "@/types/yokatlas";
import type { RaporParams } from "@/features/rapor/types/rapor";
import { kategoriEslesir } from "./kategoriler";
import { uyumBaglami, uyumBaglamiVar, uyumPuanla, type UyumBaglami } from "./uyum-puani";
import { SON_YIL, YILLAR } from "./veri-yillari";
export type Dilim = "hayal" | "dengeli" | "garanti";
export interface AdayProgram extends Program {
dilim: Dilim;
/** Uyum puanı 0-1 (lib/uyum-puani.ts); ölçüt yoksa yazılmaz. */
uyum?: number;
/** Koddan üretilen "öne çıkan" veri cümleleri (≤3); gerekçede meşru sayı kaynağı. */
oneCikan?: string[];
/** Gevşetme katmanı (0 = adayın seçimine tam uyan); sıralamada birincil anahtar. */
katman?: number;
}
export type Gevsetilen = "profil" | "kategori" | "il";
@@ -75,6 +82,15 @@ export type DilimHedef = Record<Dilim, number>;
// Listenin ideal iskeleti: 5 hayal / 13 dengeli / 6 garanti = 24 tercih.
export const IDEAL_HEDEF: DilimHedef = { hayal: 5, dengeli: 13, garanti: 6 };
// "Kolay yerleşme" önceliği seçiliyse güvenli dilim payı artar (6 Eki 2026):
// 4 hayal / 12 dengeli / 8 güvenli. /meraklisina şeması bu iki iskeleti anlatır.
export const IDEAL_HEDEF_KOLAY_YERLESME: DilimHedef = { hayal: 4, dengeli: 12, garanti: 8 };
export const KOLAY_YERLESME_ONCELIGI = "Kolay yerleşme";
/** Adayın önceliklerine göre liste iskeleti (tek kaynak; rapor, mock ve eval aynı yoldan). */
export function idealHedef(oncelikler?: readonly string[]): DilimHedef {
return oncelikler?.includes(KOLAY_YERLESME_ONCELIGI) ? IDEAL_HEDEF_KOLAY_YERLESME : IDEAL_HEDEF;
}
const DILIM_ONCELIGI: Dilim[] = ["dengeli", "garanti", "hayal"];
@@ -112,12 +128,15 @@ function eksigiDagit(pay: DilimHedef, mevcut: DilimHedef, toplam: number) {
* en riskli dilim en son). Havuz 24'ün altındaysa hedef havuzun tamamıdır.
* Deterministiktir: aynı havuz her zaman aynı hedefi üretir.
*/
export function dilimHedefiHesapla(havuz: AdayProgram[]): DilimHedef {
export function dilimHedefiHesapla(
havuz: AdayProgram[],
ideal: DilimHedef = IDEAL_HEDEF,
): DilimHedef {
const mevcut = dilimSay(havuz);
const hedef: DilimHedef = {
hayal: Math.min(IDEAL_HEDEF.hayal, mevcut.hayal),
dengeli: Math.min(IDEAL_HEDEF.dengeli, mevcut.dengeli),
garanti: Math.min(IDEAL_HEDEF.garanti, mevcut.garanti),
hayal: Math.min(ideal.hayal, mevcut.hayal),
dengeli: Math.min(ideal.dengeli, mevcut.dengeli),
garanti: Math.min(ideal.garanti, mevcut.garanti),
};
eksigiDagit(hedef, mevcut, Math.min(HEDEF, havuz.length));
return hedef;
@@ -166,16 +185,49 @@ function dilimBasinaKirp(havuz: AdayProgram[], n: number): AdayProgram[] {
function oncelikliBirlestir(...katmanlar: AdayProgram[][]): AdayProgram[] {
const gorulen = new Set<string>();
const sonuc: AdayProgram[] = [];
for (const katman of katmanlar) {
katmanlar.forEach((katman, i) => {
for (const p of katman) {
if (gorulen.has(p.id)) continue;
gorulen.add(p.id);
sonuc.push(p);
sonuc.push({ ...p, katman: i });
}
}
});
return sonuc;
}
// Uyum puanının sıradaki ağırlığı; kalan sıraya yakınlık. İkisi de 0-1.
const UYUM_AGIRLIGI = 0.6;
/**
* Havuzu kırpmadan önce dilim içinde sıralar: önce gevşetme katmanı (adayın
* seçimine uyanlar öne — bu kural uyum puanından güçlü), sonra
* 0,6 × uyum + 0,4 × sıraya yakınlık (yakınlık dilim içinde 0-1'e açılır).
* Puanı ve "öne çıkan" cümleleri satıra yazar. Ölçüt yoksa havuza dokunmaz:
* sıra ve eval `beklenen` değerleri değişmez. Deterministik (kararlı sort).
*/
function uyumlaSirala(havuz: AdayProgram[], baglam: UyumBaglami): AdayProgram[] {
if (!uyumBaglamiVar(baglam) || havuz.length === 0) return havuz;
const puanlar = uyumPuanla(havuz, baglam);
const sira = baglam.sira;
const enUzak: Record<Dilim, number> = { hayal: 0, dengeli: 0, garanti: 0 };
for (const p of havuz) {
const d = Math.abs((p.efektifSira ?? sira) - sira);
if (d > enUzak[p.dilim]) enUzak[p.dilim] = d;
}
const anahtar = (p: AdayProgram) => {
const uyum = puanlar.get(p.id)?.puan ?? 0.5;
const d = Math.abs((p.efektifSira ?? sira) - sira);
const yakinlik = enUzak[p.dilim] > 0 ? 1 - d / enUzak[p.dilim] : 1;
return UYUM_AGIRLIGI * uyum + (1 - UYUM_AGIRLIGI) * yakinlik;
};
return havuz
.map((p) => {
const u = puanlar.get(p.id);
return u ? { ...p, uyum: u.puan, oneCikan: u.oneCikan } : p;
})
.sort((a, b) => (a.katman ?? 0) - (b.katman ?? 0) || anahtar(b) - anahtar(a));
}
// Program adının sonundaki kontenjan eki; YÖK Atlas bu kontenjanları ayrı
// program satırı olarak verir.
const OZEL_KONTENJAN = /\((KKTC Uyruklu|M\.T\.O\.K\.)\)/;
@@ -242,6 +294,10 @@ export function havuzOlustur(
? havuz.filter((p) => p.il != null && liste.includes(p.il))
: havuz;
// Uyum puanı bağlamı (öncelikler, özel durum, kampüs, ev ili) — havuzu
// daraltmaz, kırpmadan önce sıralar (uyumlaSirala)
const baglam = uyumBaglami(params, profil);
// 0. Profil süzgeci (danışmana söylenenler) — yalnız yeterli havuz bırakırsa.
// Bırakmazsa profil gevşetilir ve aşağıdaki sihirbaz akışı aynen sürer;
// liste yine kurulur, gevşetme yapay zekâya ve adaya söylenir.
@@ -264,7 +320,7 @@ export function havuzOlustur(
profilliFiltreli = kategoriFiltre(profilliHam);
if (profilliFiltreli.length >= HEDEF) {
return {
havuz: dilimBasinaKirp(profilliFiltreli, PROMPT_LIMIT_DILIM),
havuz: dilimBasinaKirp(uyumlaSirala(profilliFiltreli, baglam), PROMPT_LIMIT_DILIM),
gevsetildi: [],
};
}
@@ -277,7 +333,7 @@ export function havuzOlustur(
const tamFiltreli = kategoriFiltre(tamHam);
const kirp = (...katmanlar: AdayProgram[][]) =>
dilimBasinaKirp(
oncelikliBirlestir(profilliFiltreli, ...katmanlar),
uyumlaSirala(oncelikliBirlestir(profilliFiltreli, ...katmanlar), baglam),
PROMPT_LIMIT_DILIM,
);
if (tamFiltreli.length >= HEDEF) {
@@ -338,6 +394,9 @@ export function havuzuKompaktJson(havuz: AdayProgram[]): string {
),
[`kontenjan${SON_YIL}`]: p.kontenjanSon,
[`yerlesen${SON_YIL}`]: p.yerlesenSon,
// Koddan hesaplanan "öne çıkan" veriler (uyum-puani.ts); model gerekçede
// bunlardan birini yazabilir, başka sayı yazamaz (ai/rapor.ts denetimi)
...(p.oneCikan && p.oneCikan.length > 0 ? { one_cikan: p.oneCikan } : {}),
})),
);
}