All checks were successful
Deploy / deploy (push) Successful in 16m29s
- Introduced a new "detay" script in package.json for additional processing. - Refactored ListemSekmeleri component to improve tab functionality and state management. - Updated UI elements for better user interaction and clarity, including renaming "Seçtiklerim" to "Kendi Listem" for consistency across the application. - Made adjustments to related components for improved integration and user experience.
268 lines
7.8 KiB
TypeScript
268 lines
7.8 KiB
TypeScript
/**
|
||
* Canlı YÖK Atlas API'sinden program detay verilerini çekip DB'ye işler:
|
||
*
|
||
* 1. Kontenjan türü kırılımı (son yerleştirme yılı, ör. 2025):
|
||
* Genel / Okul Birincisi / Şehit-Gazi / Deprem / 34 Yaş Üstü Kadın
|
||
* için kontenjan + yerleşen. Kaynak: POST /api/tercih-kilavuz/search
|
||
* (gk1/gkY1, obk1/obkY1, sgy1/sgyY1, dprm1/dprmY1, y34_1/y34Y1 —
|
||
* "1" son yerleştirme yılı demek; kayıttaki `yil` kılavuz yılıdır,
|
||
* veri yılı = yil - 1).
|
||
*
|
||
* 2. Yerleşen son kişinin netleri (yıl bazında, tüm puan türleri):
|
||
* Kaynak: POST /api/netler/search → `netler` tablosu.
|
||
*
|
||
* Not: YÖK Atlas 2026'da SPA'ya geçti; eski content/lisans-dynamic/*.php
|
||
* panelleri (ve onlarla birlikte kadın-erkek dağılımı) yayından kalktı.
|
||
* Cinsiyet verisi yeni API'lerin hiçbirinde yok.
|
||
*
|
||
* Kullanım: npx tsx scripts/detay.ts
|
||
*/
|
||
import path from "node:path";
|
||
import Database from "better-sqlite3";
|
||
|
||
const KILAVUZ_API = "https://yokatlas.yok.gov.tr/api/tercih-kilavuz/search";
|
||
const NETLER_API = "https://yokatlas.yok.gov.tr/api/netler/search";
|
||
const PAGE_SIZE = 500;
|
||
const DELAY_MS = 300;
|
||
|
||
type KilavuzRecord = {
|
||
kilavuzKodu: number;
|
||
eskiKilavuzKodu?: number | null;
|
||
yil: number;
|
||
gk1?: number | null;
|
||
gkY1?: number | null;
|
||
obk1?: number | null;
|
||
obkY1?: number | null;
|
||
sgy1?: number | null;
|
||
sgyY1?: number | null;
|
||
dprm1?: number | null;
|
||
dprmY1?: number | null;
|
||
y34_1?: number | null;
|
||
y34Y1?: number | null;
|
||
};
|
||
|
||
type NetlerRecord = {
|
||
kilavuzKodu: number;
|
||
yil: number;
|
||
puanTuru: string | null;
|
||
tabanPuan?: number | null;
|
||
obp?: number | null;
|
||
katsayi?: number | null;
|
||
tytTrkNet?: number | null;
|
||
tytSosNet?: number | null;
|
||
tytMatNet?: number | null;
|
||
tytFenNet?: number | null;
|
||
aytMatNet?: number | null;
|
||
aytFizNet?: number | null;
|
||
aytKimNet?: number | null;
|
||
aytBioNet?: number | null;
|
||
aytTdeNet?: number | null;
|
||
aytTrh1Net?: number | null;
|
||
aytCog1Net?: number | null;
|
||
aytTrh2Net?: number | null;
|
||
aytCog2Net?: number | null;
|
||
aytDinNet?: number | null;
|
||
aytFelNet?: number | null;
|
||
ydtYdilNet?: number | null;
|
||
};
|
||
|
||
type ApiPage<T> = { content: T[]; totalPages: number; totalElements: number };
|
||
|
||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||
|
||
async function fetchPage<T>(api: string, page: number): Promise<ApiPage<T>> {
|
||
const res = await fetch(api, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
filters: {},
|
||
page,
|
||
size: PAGE_SIZE,
|
||
sortBy: "kilavuzKodu",
|
||
direction: "ASC",
|
||
}),
|
||
});
|
||
if (!res.ok) throw new Error(`${api} ${res.status} (sayfa ${page})`);
|
||
return (await res.json()) as ApiPage<T>;
|
||
}
|
||
|
||
function num(v: unknown): number | null {
|
||
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
||
}
|
||
|
||
const NET_COLS = [
|
||
"puanTuru",
|
||
"tabanPuan",
|
||
"obp",
|
||
"katsayi",
|
||
"tytTrkNet",
|
||
"tytSosNet",
|
||
"tytMatNet",
|
||
"tytFenNet",
|
||
"aytMatNet",
|
||
"aytFizNet",
|
||
"aytKimNet",
|
||
"aytBioNet",
|
||
"aytTdeNet",
|
||
"aytTrh1Net",
|
||
"aytCog1Net",
|
||
"aytTrh2Net",
|
||
"aytCog2Net",
|
||
"aytDinNet",
|
||
"aytFelNet",
|
||
"ydtYdilNet",
|
||
] as const;
|
||
|
||
async function main() {
|
||
const db = new Database(path.join(process.cwd(), "data", "yokatlas.db"));
|
||
db.pragma("journal_mode = WAL");
|
||
|
||
// ── 1. Kontenjan türü kırılımı ─────────────────────────────────────────
|
||
const first = await fetchPage<KilavuzRecord>(KILAVUZ_API, 0);
|
||
// API kaydındaki yil kılavuz yılı; "1" ekli alanlar bir önceki
|
||
// yerleştirme dönemine ait (2026 kılavuzu → 2025 verisi)
|
||
const veriYili = first.content[0].yil - 1;
|
||
|
||
const kontCols = [
|
||
`gk${veriYili}`,
|
||
`gkY${veriYili}`,
|
||
`obk${veriYili}`,
|
||
`obkY${veriYili}`,
|
||
`sgy${veriYili}`,
|
||
`sgyY${veriYili}`,
|
||
`dprm${veriYili}`,
|
||
`dprmY${veriYili}`,
|
||
`y34k${veriYili}`,
|
||
`y34kY${veriYili}`,
|
||
];
|
||
const mevcut = new Set(
|
||
(db.prepare("PRAGMA table_info(programs)").all() as { name: string }[]).map(
|
||
(c) => c.name
|
||
)
|
||
);
|
||
for (const col of kontCols) {
|
||
if (!mevcut.has(col)) {
|
||
db.exec(`ALTER TABLE programs ADD COLUMN ${col} INTEGER`);
|
||
}
|
||
}
|
||
|
||
const updateKont = db.prepare(`
|
||
UPDATE programs SET ${kontCols.map((c) => `${c} = ?`).join(", ")}
|
||
WHERE id = ?
|
||
`);
|
||
const exists = db.prepare("SELECT 1 FROM programs WHERE id = ?");
|
||
|
||
// Kılavuz kodu yıllar arasında değişebiliyor (eskiKilavuzKodu); netler
|
||
// API'si yeni kodla döndüğü için eşleşen DB id'sini haritada tutuyoruz.
|
||
const dbId = new Map<number, string>();
|
||
let kontUpdated = 0;
|
||
let kontMissing = 0;
|
||
|
||
const processKilavuz = db.transaction((records: KilavuzRecord[]) => {
|
||
for (const r of records) {
|
||
if (!r.kilavuzKodu) continue;
|
||
let id = String(r.kilavuzKodu);
|
||
if (!exists.get(id)) {
|
||
const eski = r.eskiKilavuzKodu ? String(r.eskiKilavuzKodu) : null;
|
||
if (eski && exists.get(eski)) {
|
||
id = eski;
|
||
} else {
|
||
kontMissing++;
|
||
continue;
|
||
}
|
||
}
|
||
dbId.set(r.kilavuzKodu, id);
|
||
updateKont.run(
|
||
num(r.gk1),
|
||
num(r.gkY1),
|
||
num(r.obk1),
|
||
num(r.obkY1),
|
||
num(r.sgy1),
|
||
num(r.sgyY1),
|
||
num(r.dprm1),
|
||
num(r.dprmY1),
|
||
num(r.y34_1),
|
||
num(r.y34Y1),
|
||
id
|
||
);
|
||
kontUpdated++;
|
||
}
|
||
});
|
||
|
||
console.log(
|
||
`Kontenjan kırılımı (${veriYili}): ${first.totalElements} kayıt, ${first.totalPages} sayfa…`
|
||
);
|
||
processKilavuz(first.content);
|
||
for (let p = 1; p < first.totalPages; p++) {
|
||
await sleep(DELAY_MS);
|
||
processKilavuz((await fetchPage<KilavuzRecord>(KILAVUZ_API, p)).content);
|
||
process.stdout.write(`\rkontenjan sayfa ${p + 1}/${first.totalPages}`);
|
||
}
|
||
console.log(
|
||
`\nKontenjan kırılımı: ${kontUpdated} güncellendi, ${kontMissing} DB'de yok`
|
||
);
|
||
|
||
// ── 2. Yerleşen son kişinin netleri ────────────────────────────────────
|
||
db.exec(`
|
||
CREATE TABLE IF NOT EXISTS netler (
|
||
program_id TEXT NOT NULL,
|
||
yil INTEGER NOT NULL,
|
||
${NET_COLS.map((c) => `${c} ${c === "puanTuru" ? "TEXT" : "REAL"}`).join(
|
||
",\n "
|
||
)},
|
||
PRIMARY KEY (program_id, yil)
|
||
);
|
||
`);
|
||
|
||
const insertNet = db.prepare(`
|
||
INSERT OR REPLACE INTO netler (program_id, yil, ${NET_COLS.join(", ")})
|
||
VALUES (${["?", "?", ...NET_COLS.map(() => "?")].join(", ")})
|
||
`);
|
||
|
||
let netRows = 0;
|
||
const processNetler = db.transaction((records: NetlerRecord[]) => {
|
||
for (const r of records) {
|
||
if (!r.kilavuzKodu || !r.yil) continue;
|
||
const id = dbId.get(r.kilavuzKodu) ?? String(r.kilavuzKodu);
|
||
insertNet.run(
|
||
id,
|
||
r.yil,
|
||
r.puanTuru?.trim() ?? null,
|
||
...NET_COLS.slice(1).map((c) => num(r[c as keyof NetlerRecord]))
|
||
);
|
||
netRows++;
|
||
}
|
||
});
|
||
|
||
const netFirst = await fetchPage<NetlerRecord>(NETLER_API, 0);
|
||
console.log(
|
||
`Netler: ${netFirst.totalElements} kayıt, ${netFirst.totalPages} sayfa…`
|
||
);
|
||
processNetler(netFirst.content);
|
||
for (let p = 1; p < netFirst.totalPages; p++) {
|
||
await sleep(DELAY_MS);
|
||
processNetler((await fetchPage<NetlerRecord>(NETLER_API, p)).content);
|
||
process.stdout.write(`\rnetler sayfa ${p + 1}/${netFirst.totalPages}`);
|
||
}
|
||
console.log(`\nNetler: ${netRows} satır yazıldı`);
|
||
|
||
const ozet = db
|
||
.prepare(
|
||
`SELECT
|
||
(SELECT COUNT(*) FROM programs WHERE gkY${veriYili} IS NOT NULL) AS kontlu,
|
||
(SELECT COUNT(*) FROM netler) AS net,
|
||
(SELECT COUNT(DISTINCT program_id) FROM netler) AS netli_program`
|
||
)
|
||
.get() as { kontlu: number; net: number; netli_program: number };
|
||
console.log(
|
||
`Özet → kontenjan kırılımı olan program: ${ozet.kontlu}, ` +
|
||
`net satırı: ${ozet.net} (${ozet.netli_program} program)`
|
||
);
|
||
db.close();
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error(err);
|
||
process.exit(1);
|
||
});
|