/** * 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 = { "İ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 = { "İ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(); }