Files
kolaytercih/scripts/unitur-onar.ts
bilalgursen 972268995e fix(veri): KURUM_DUZELT arşiv tablosunu da tarar (BACKLOG #21)
Dün Aşama D yalnız `programs`'a uygulanmıştı; `programs_arsiv`'de aynı yanlış
etiket duruyordu (31 ULUSLARARASI BALKAN + 30 ULUSLARARASI SARAYBOSNA
`unitur='DEVLET'`, Balkan'ın 24 satırında il Saraybosna). Kapanan programlar
arşive `programs`'tan olduğu gibi taşındığı için etiket oraya da yazılmış.

- `unitur-onar.ts` Aşama D iki tabloyu da tarar (`programs_arsiv` varsa).
  Dokunulan kolon yalnız `unitur` ve `il`; A/B/C aşamaları `programs`'ta kalır.
- `veri-kalite.ts` kontrol 13 ve 14 arşivi de tarar. Genişletme olmadan iki
  tablo ayrışabilir ve denetim bunu görmez.
- Yanlış etiket duran DB'de iki kontrol de KALDI veriyor (çıkış kodu 1):
  `baz ad başına tek unitur — … BALKAN/YURTDISI KAMU,DEVLET/81 ; … SARAYBOSNA/68`
  `DEVLET etiketi Türkiye ili ile destekli — … /SARAYBOSNA - BOSNA - HERSEK/31 ; /30`

Kullanıcıya bugün yansımıyor: `src/` tarafında `programs_arsiv`'i okuyan SQL
yok. Düzeltme tablo ileride okunduğunda sızıntının geri gelmemesi içindir.

Dünkü veri commit'inin (1df86cf) gövdesindeki rakam düzeltmesi: "/universiteler
hub'ında Devlet listesi 170 → 166" YANLIŞTIR. 170/166 ham `programs.universite`
ad sayısıdır, hub satırı değil; `uniAdiNormalize` parantezli eki attığı için
Kıbrıs Amerikan'ın iki yazımı tek slug. Hub'daki doğru değişim 132 → 129 kurum
(3 kurum). Kaynak: `12-kod-denetimi.md` O1, dalın kendi build HTML'i.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 07:26:56 +03:00

244 lines
9.6 KiB
TypeScript
Raw Permalink 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 dört aşamalı:
*
* D) Kaynak veride DOLU ama YANLIŞ olan kurum alanları (KURUM_DUZELT) —
* `programs` VE `programs_arsiv` üzerinde
* 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. A/B/C yalnızca `programs` tablosunun NULL satırlarına dokunur;
* D (KURUM_DUZELT) NULL koşulundan bağımsızdır — düzelttiği alanlar dolu ama
* yanlış — ve `programs_arsiv`'i de tarar (varsa).
*
* 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";
import { tabloVar } from "./yokatlas-goc";
/** 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();
}
/** KKTC il değerleri — tek kaynak; veri-kalite.ts de bunu okur. */
export 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",
};
/**
* Aşama D — kaynak veride DOLU ama YANLIŞ olan kurum alanları.
*
* MANUEL_UNITUR'dan farkı: burada değer NULL değil, yanlış. Bu yüzden bu
* harita NULL koşulu OLMADAN çalışır. Her satırın kanıtı kurumun YÖK Atlas'ta
* geçen parantezli ikinci yazımıdır (aynı kurum, doğru etiketli):
*
* "ULUSLARARASI BALKAN ÜNİVERSİTESİ (ÜSKÜP-MAKEDONYA)" → YURTDISI KAMU
* "ULUSLARARASI SARAYBOSNA ÜNİVERSİTESİ (SARAYBOSNA-…)" → YURTDISI KAMU
* "KIBRIS AMERİKAN ÜNİVERSİTESİ (KKTC-LEFKOŞA)" → ili KKTC
*
* Yanlış etiket ilk CSV ingest'inden (scripts/ingest.ts) geliyor; refresh.ts
* `uniturSec` DB'deki değeri sadakatle kopyaladığı için her tazelemede
* yeniden üretiliyordu. Düzeltme buraya yazılınca kalıcı olur.
*
* Etki: `src/lib/db.ts` devlet filtresi (`unitur = 'DEVLET'`) bu kurumların
* programlarını sihirbaz havuzuna alıyordu; vakıf filtresi (`unitur <>
* 'DEVLET'`) ise dışarıda bırakıyordu.
*
* `il` düzeltmesi yalnız YANLIŞ yazılmış değeri hedefler (yanlis → dogru);
* boş il'ler kapsam dışı — eksik, eksik olarak görünmeli.
*
* Aynı yanlış etiket `programs_arsiv`'e de yazılmıştı (kapanan programlar oraya
* `programs`'tan olduğu gibi taşınıyor). Bugün `src/` tarafında arşivi okuyan
* SQL yok, ama tablo ileride okunursa sızıntı geri gelirdi; bu yüzden Aşama D
* iki tabloyu da tarar. Dokunulan kolon yalnız `unitur` ve `il`.
*/
const KURUM_DUZELT: Record<
string,
{ unitur?: string; il?: { yanlis: string; dogru: string } }
> = {
"KIBRIS AMERİKAN ÜNİVERSİTESİ": { unitur: "KKTC" },
"KIBRIS AMERİKAN ÜNİVERSİTESİ (KKTC-LEFKOŞA)": { unitur: "KKTC" },
"ULUSLARARASI BALKAN ÜNİVERSİTESİ": {
unitur: "YURTDISI KAMU",
// Kurum Üsküp'te; 26 satırda ili Saraybosna yazıyor. Doğru değerin yazımı
// DB'deki diğer yurt dışı illerin biçimini izler (BİŞKEK-KIRGIZİSTAN…)
// ve kurumun kendi parantezli adından alınmıştır.
il: { yanlis: "SARAYBOSNA - BOSNA - HERSEK", dogru: "ÜSKÜP-MAKEDONYA" },
},
"ULUSLARARASI SARAYBOSNA ÜNİVERSİTESİ": { unitur: "YURTDISI KAMU" },
};
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`);
// D) Dolu ama yanlış kurum alanları. A'dan ÖNCE koşar: yanlış değer başka
// satırlara kopyalanmadan düzelsin. NULL koşulu yok; değer zaten dolu.
// `programs` ve (varsa) `programs_arsiv` aynı haritadan geçer — iki tablo
// ayrışırsa arşiv ileride okunduğunda aynı sızıntı geri gelir.
const dTablolar = tabloVar(db, "programs_arsiv")
? ["programs", "programs_arsiv"]
: ["programs"];
for (const tablo of dTablolar) {
const dUnitur = db.prepare(
`UPDATE ${tablo} SET unitur = ? WHERE universite = ? AND unitur IS NOT ?`
);
const dIl = db.prepare(
`UPDATE ${tablo} SET il = ? WHERE universite = ? AND il = ?`
);
let dUniturToplam = 0;
let dIlToplam = 0;
for (const [ad, duzelt] of Object.entries(KURUM_DUZELT)) {
if (duzelt.unitur) {
dUniturToplam += dUnitur.run(duzelt.unitur, ad, duzelt.unitur).changes;
}
if (duzelt.il) {
dIlToplam += dIl.run(duzelt.il.dogru, ad, duzelt.il.yanlis).changes;
}
}
console.log(
`Aşama D (yanlış kurum alanı) ${tablo}: unitur ${dUniturToplam} satır, il ${dIlToplam} satır`
);
}
// 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)");
for (const tablo of tabloVar(db, "programs_arsiv")
? ["programs", "programs_arsiv"]
: ["programs"]) {
const dagilim = db
.prepare(
`SELECT COALESCE(unitur,'(NULL)') AS t, COUNT(*) AS n FROM ${tablo} GROUP BY unitur ORDER BY n DESC`
)
.all() as { t: string; n: number }[];
console.log(
`Dağılım (${tablo}):`,
dagilim.map((d) => `${d.t}=${d.n}`).join(" ")
);
}
db.close();
}