Files
kolaytercih/scripts/unitur-onar.ts
bilalgursen b66c720b32 fix(veri): yanlış DEVLET etiketi kalıcı onarım + baz ad çelişki denetimi
3 kurum (Kıbrıs Amerikan, Uluslararası Balkan, Uluslararası Saraybosna)
kaynak CSV'den `unitur='DEVLET'` geliyordu. `db.ts:79-82` filtreleri
yüzünden "devlet üniversitesi istiyorum" diyen öğrencinin aday havuzuna
27 KKTC/yurt dışı programı sızıyor, "vakıf" diyende aynı 27'si eksik
kalıyordu. Kullanıcının fark edemeyeceği bir doğruluk hatasıydı.

unitur-onar.ts — Aşama D (KURUM_DUZELT):
  A/B/C yalnız `unitur IS NULL` satırlara dokunuyordu; bu satırlar NULL
  değil, DOLU ve YANLIŞ. D koşulsuz çalışır ve Aşama A'dan ÖNCE koşar ki
  yanlış değer başka satırlara kopyalanmasın. İdempotent (2. koşu: 0/0).
  Her düzeltmenin kanıtı kurumun YÖK Atlas'taki kendi parantezli ikinci
  yazımı: "(ÜSKÜP-MAKEDONYA)" → YURTDISI KAMU, "(KKTC-LEFKOŞA)" → KKTC.
  refresh.ts:519 `uniturSec` DB'deki değeri kopyaladığı için düzeltme
  buraya yazılınca her tazelemede kalıcı olur.

veri-kalite.ts — iki yeni ZORUNLU kontrol (dosyanın sonuna):
  13. "baz ad başına tek unitur" — kök neden buydu: unitur-onar.ts'in
      eski çelişki koruması GROUP BY universite (TAM ad) ile çalıştığı
      için aynı kurumun iki yazımı arasındaki DEVLET↔YURTDISI KAMU
      çelişkisini hiç görmüyordu.
  14. "DEVLET etiketi Türkiye ili ile destekli" — 13'ün kör noktası:
      Kıbrıs Amerikan'ın İKİ yazımı da yanlış etiketli olduğu için
      ortada çelişki yok. 13 tek başına 100 satırın yalnız 58'ini
      koruyordu. Şartnamenin ötesinde, gerekçesi raporun §4'ünde.

Doğrulama: düzeltme öncesi DB'de iki kontrol de KALDI (çıkış kodu 1);
sonrasında 0 ZORUNLU kaldı, 3 uyarı (üçü de önceden vardı). Tek satır
bilerek bozulduğunda ikisi de yakaladı. tsc + eslint temiz.

Veri (126 satır) ayrı commit'te. Rapor: docs/gece-vardiyasi/2026-09-22/
09-p2-unitur.md

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

222 lines
8.6 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 dört aşamalı:
*
* D) Kaynak veride DOLU ama YANLIŞ olan kurum alanları (KURUM_DUZELT)
* 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 NULL satırlara dokunur; D (KURUM_DUZELT)
* NULL koşulundan bağımsızdır — düzelttiği alanlar dolu ama yanlış.
*
* 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();
}
/** 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.
*/
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.
const dUnitur = db.prepare(
"UPDATE programs SET unitur = ? WHERE universite = ? AND unitur IS NOT ?"
);
const dIl = db.prepare(
"UPDATE programs 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ı): 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)");
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();
}