Files
kolaytercih/scripts/unitur-onar.ts
bilalgursen 1e810e1782 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>
2026-08-09 00:11:53 +03:00

159 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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();
}