/** * 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 = { content: T[]; totalPages: number; totalElements: number }; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); async function fetchPage(api: string, page: number): Promise> { 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; } 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(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(); 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(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(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(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); });