feat: unitur NULL backfill scripti + refresh ingest onarımı
YÖK Atlas API'sinin universiteTuru döndürmediği ~2.081 satır (Nişantaşı, Beykent gibi vakıflar + KKTC kampüsleri) devlet/vakıf filtrelerinden ve sihirbaz sayaçlarından sessizce düşüyordu. unitur-onar üç aşamada doldurur: aynı üniversitenin dolu satırından kopya → KKTC ili kuralı → manuel eşleme (Cerrahpaşa, MSB/İçişleri SBÜ, Alanya, Manas, Yesevi, Az. Pedagoji). "ÜNİVERSİTESİ- CERRAHPAŞA" tarzı tire etrafı boşluklu duplikat adlar da normalize edilir; refresh.ts hem insert yolunda adı normalize eder hem her tazeleme sonunda onarımı idempotent olarak koşar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
|
import { uniAdiNormalize, uniturOnar } from "./unitur-onar";
|
||||||
|
|
||||||
const API = "https://yokatlas.yok.gov.tr/api/tercih-kilavuz/search";
|
const API = "https://yokatlas.yok.gov.tr/api/tercih-kilavuz/search";
|
||||||
const PAGE_SIZE = 500;
|
const PAGE_SIZE = 500;
|
||||||
@@ -169,7 +170,9 @@ async function main() {
|
|||||||
insert.run(
|
insert.run(
|
||||||
id,
|
id,
|
||||||
r.birimAdi,
|
r.birimAdi,
|
||||||
r.universiteAdi,
|
// "ÜNİVERSİTESİ- CERRAHPAŞA" gibi tire etrafı boşluklu varyantlar
|
||||||
|
// duplikat kurum adı üretmesin
|
||||||
|
uniAdiNormalize(r.universiteAdi),
|
||||||
r.universiteTuru ?? null,
|
r.universiteTuru ?? null,
|
||||||
r.ilAdi ?? null,
|
r.ilAdi ?? null,
|
||||||
r.fymkAdi ?? null,
|
r.fymkAdi ?? null,
|
||||||
@@ -205,6 +208,11 @@ async function main() {
|
|||||||
console.log(
|
console.log(
|
||||||
`DB toplam: ${stats.toplam} program, 2025 sıralaması olan: ${stats.sirali}`
|
`DB toplam: ${stats.toplam} program, 2025 sıralaması olan: ${stats.sirali}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// API'nin universiteTuru döndürmediği kayıtlar devlet/vakıf filtrelerinden
|
||||||
|
// düşmesin: her tazelemeden sonra unitur onarımı koşulur (idempotent).
|
||||||
|
db.transaction(() => uniturOnar(db))();
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
158
scripts/unitur-onar.ts
Normal file
158
scripts/unitur-onar.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* unitur (üniversite tipi) NULL onarımı + üniversite adı normalizasyonu.
|
||||||
|
*
|
||||||
|
* YÖK Atlas API'si bazı kayıtlarda universiteTuru döndürmüyor; DB'de ~2.081
|
||||||
|
* satır unitur=NULL kalıyor ve bunlar hem devlet/vakıf filtrelerinden hem
|
||||||
|
* sihirbaz sayaçlarından sessizce düşüyor. Onarım üç aşamalı:
|
||||||
|
*
|
||||||
|
* A) Aynı üniversitenin dolu satırından kopyala (Nişantaşı, Beykent…)
|
||||||
|
* B) Kalanlarda il KKTC yerleşimiyse unitur='KKTC'
|
||||||
|
* C) Küçük manuel eşleme (Cerrahpaşa, MSB/İçişleri, Alanya, Manas…)
|
||||||
|
*
|
||||||
|
* Ayrıca "İSTANBUL ÜNİVERSİTESİ- CERRAHPAŞA" gibi tire etrafı boşluklu
|
||||||
|
* duplikat adlar normalize edilir; refresh.ts insert yolunda da aynı
|
||||||
|
* normalizasyon uygulanır ki duplikat bir daha oluşmasın.
|
||||||
|
*
|
||||||
|
* İdempotenttir; yalnızca NULL satırlara dokunur.
|
||||||
|
*
|
||||||
|
* Kullanım: npx tsx scripts/unitur-onar.ts (dev sunucu KAPALIYKEN —
|
||||||
|
* uygulama DB'yi readonly açar ama WAL checkpoint için tek yazar olmalı)
|
||||||
|
*/
|
||||||
|
import path from "node:path";
|
||||||
|
import Database from "better-sqlite3";
|
||||||
|
|
||||||
|
/** Tire etrafı boşluk + çift boşluk temizliği (üniversite adı için). */
|
||||||
|
export function uniAdiNormalize(ad: string): string {
|
||||||
|
return ad.replace(/\s*-\s*/g, "-").replace(/\s{2,}/g, " ").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const KKTC_ILLERI = [
|
||||||
|
"GİRNE",
|
||||||
|
"LEFKOŞA",
|
||||||
|
"GAZİMAĞUSA",
|
||||||
|
"LEFKE",
|
||||||
|
"GÜZELYURT",
|
||||||
|
"KIBRIS",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Hiçbir satırında unitur olmayan ve il'den de çıkarılamayan kurumlar.
|
||||||
|
const MANUEL_UNITUR: Record<string, string> = {
|
||||||
|
"İSTANBUL ÜNİVERSİTESİ-CERRAHPAŞA": "DEVLET",
|
||||||
|
"İÇİŞLERİ BAKANLIĞI VE MİLLİ SAVUNMA BAKANLIĞI ADINA SAĞLIK BİLİMLERİ ÜNİVERSİTESİNDE EĞİTİM ALACAKLAR":
|
||||||
|
"DEVLET",
|
||||||
|
"ALANYA ÜNİVERSİTESİ": "VAKIF",
|
||||||
|
"KIRGIZİSTAN-TÜRKİYE MANAS ÜNİVERSİTESİ": "YURTDISI KAMU",
|
||||||
|
"HOCA AHMET YESEVİ ULUSLARARASI TÜRK-KAZAK ÜNİVERSİTESİ": "YURTDISI KAMU",
|
||||||
|
"AZERBAYCAN DEVLET PEDAGOJİ ÜNİVERSİTESİ": "YURTDISI KAMU",
|
||||||
|
};
|
||||||
|
|
||||||
|
// İl'i hiç olmayan kampüsler için ad → il eşlemesi (yalnızca kesin olanlar).
|
||||||
|
const MANUEL_IL: Record<string, string> = {
|
||||||
|
"İSTANBUL ÜNİVERSİTESİ-CERRAHPAŞA": "İSTANBUL",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function uniturOnar(db: Database.Database): void {
|
||||||
|
// 0) Üniversite adı normalizasyonu (Cerrahpaşa duplikatını birleştirir)
|
||||||
|
const adlar = db
|
||||||
|
.prepare("SELECT DISTINCT universite FROM programs")
|
||||||
|
.all() as { universite: string }[];
|
||||||
|
const adDuzelt = db.prepare(
|
||||||
|
"UPDATE programs SET universite = ? WHERE universite = ?"
|
||||||
|
);
|
||||||
|
let duzeltilen = 0;
|
||||||
|
for (const { universite } of adlar) {
|
||||||
|
const temiz = uniAdiNormalize(universite);
|
||||||
|
if (temiz !== universite) {
|
||||||
|
adDuzelt.run(temiz, universite);
|
||||||
|
duzeltilen++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (duzeltilen > 0) console.log(`Ad normalizasyonu: ${duzeltilen} kurum`);
|
||||||
|
|
||||||
|
// A) Aynı üniversitenin dolu satırından kopyala. Birden fazla FARKLI dolu
|
||||||
|
// değer varsa sessizce seçim yapma — kaynak veride tutarsızlık demektir.
|
||||||
|
const celiskiler = db
|
||||||
|
.prepare(
|
||||||
|
`SELECT universite, COUNT(DISTINCT unitur) AS n FROM programs
|
||||||
|
WHERE unitur IS NOT NULL GROUP BY universite HAVING n > 1`
|
||||||
|
)
|
||||||
|
.all() as { universite: string; n: number }[];
|
||||||
|
if (celiskiler.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Aynı üniversitede birden fazla unitur: ${celiskiler
|
||||||
|
.map((c) => c.universite)
|
||||||
|
.join(", ")} — önce kaynak veriyi kontrol et.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const aKopya = db
|
||||||
|
.prepare(
|
||||||
|
`UPDATE programs SET unitur = (
|
||||||
|
SELECT p2.unitur FROM programs p2
|
||||||
|
WHERE p2.universite = programs.universite AND p2.unitur IS NOT NULL
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
WHERE unitur IS NULL AND EXISTS (
|
||||||
|
SELECT 1 FROM programs p3
|
||||||
|
WHERE p3.universite = programs.universite AND p3.unitur IS NOT NULL
|
||||||
|
)`
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
console.log(`Aşama A (üniversiteden kopya): ${aKopya.changes} satır`);
|
||||||
|
|
||||||
|
// B) Kalan NULL'larda il KKTC yerleşimiyse
|
||||||
|
const bKktc = db
|
||||||
|
.prepare(
|
||||||
|
`UPDATE programs SET unitur = 'KKTC'
|
||||||
|
WHERE unitur IS NULL AND il IN (${KKTC_ILLERI.map(() => "?").join(",")})`
|
||||||
|
)
|
||||||
|
.run(...KKTC_ILLERI);
|
||||||
|
console.log(`Aşama B (KKTC ili): ${bKktc.changes} satır`);
|
||||||
|
|
||||||
|
// C) Manuel eşlemeler
|
||||||
|
const cUnitur = db.prepare(
|
||||||
|
"UPDATE programs SET unitur = ? WHERE unitur IS NULL AND universite = ?"
|
||||||
|
);
|
||||||
|
let cToplam = 0;
|
||||||
|
for (const [ad, tur] of Object.entries(MANUEL_UNITUR)) {
|
||||||
|
cToplam += cUnitur.run(tur, ad).changes;
|
||||||
|
}
|
||||||
|
console.log(`Aşama C (manuel): ${cToplam} satır`);
|
||||||
|
|
||||||
|
const ilDuzelt = db.prepare(
|
||||||
|
"UPDATE programs SET il = ? WHERE il IS NULL AND universite = ?"
|
||||||
|
);
|
||||||
|
let ilToplam = 0;
|
||||||
|
for (const [ad, il] of Object.entries(MANUEL_IL)) {
|
||||||
|
ilToplam += ilDuzelt.run(il, ad).changes;
|
||||||
|
}
|
||||||
|
if (ilToplam > 0) console.log(`İl doldurma (manuel): ${ilToplam} satır`);
|
||||||
|
|
||||||
|
// Rapor: kalan NULL'lar
|
||||||
|
const kalan = db
|
||||||
|
.prepare(
|
||||||
|
`SELECT universite, COUNT(*) AS adet FROM programs
|
||||||
|
WHERE unitur IS NULL GROUP BY universite ORDER BY adet DESC`
|
||||||
|
)
|
||||||
|
.all() as { universite: string; adet: number }[];
|
||||||
|
if (kalan.length === 0) {
|
||||||
|
console.log("Kalan NULL unitur: 0 ✓");
|
||||||
|
} else {
|
||||||
|
console.log(`Kalan NULL unitur (${kalan.length} kurum):`);
|
||||||
|
for (const k of kalan) console.log(` ${k.universite}: ${k.adet}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doğrudan koşulduğunda (npx tsx scripts/unitur-onar.ts)
|
||||||
|
if (process.argv[1]?.endsWith("unitur-onar.ts")) {
|
||||||
|
const db = new Database(path.join(process.cwd(), "data", "yokatlas.db"));
|
||||||
|
db.pragma("journal_mode = WAL");
|
||||||
|
db.transaction(() => uniturOnar(db))();
|
||||||
|
db.pragma("wal_checkpoint(TRUNCATE)");
|
||||||
|
const dagilim = db
|
||||||
|
.prepare(
|
||||||
|
"SELECT COALESCE(unitur,'(NULL)') AS t, COUNT(*) AS n FROM programs GROUP BY unitur ORDER BY n DESC"
|
||||||
|
)
|
||||||
|
.all() as { t: string; n: number }[];
|
||||||
|
console.log("Dağılım:", dagilim.map((d) => `${d.t}=${d.n}`).join(" "));
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user