Files
kolaytercih/docs/gece-vardiyasi/2026-09-22/26-veri-kalitesi.md
bilalgursen 2b350d0c5d content(rehber): gece vardiyasının 14 yeni yazısı + 2 düzeltme, ekip raporları ve kurallar
14 yeni rehber yazısı (hepsi editör denetiminden geçti, 46 yazıda 829 iç link
tarandı, 0 kırık hedef; 180+ DB rakamı yazıların kendi filtreleriyle yeniden
koşuldu):

  taban-siralamalari-alti-yilda-nasil-degisti · kac-net-ile-hangi-bolum ·
  siralama-bandlari-hangi-kapilar-acilir · ayni-bolum-farkli-universite-siralama-farki ·
  hangi-bolumlerin-kontenjani-azaldi · yeni-acilan-bolumler-nasil-degerlendirilir ·
  bilgisayar-mi-yazilim-muhendisligi-mi · kktc-universiteleri-okunur-mu ·
  yapay-zekaya-tercih-sordum-guvenilir-mi · ek-madde-1-puanim-yetiyor-mu ·
  universitede-ilk-hafta-ders-kaydi-muafiyet-kayit-dondurma · yks-2027-takvimi ·
  rehber-ogretmenler-icin-veri-kaynaklari · bolumumu-sevmedim-hangi-kapilar-var

İki canlı yazı düzeltildi:
- veliler-icin-tercih-rehberi: veli diliyle kapanış; altındaki "sıralamanı gir"
  kutusu artık veliye ölü uç değil
- bos-kontenjanlar-ne-anlatiyor: "birkaç yüz kişilik fark" aslında ~9 bin
  (ÖSYM 265.356 ↔ bizim 256.532); payda açıklaması tersti — ek kontenjan
  paydaya girseydi oran düşerdi. guncelleme alanı eklendi (sitemap lastmod).

Kurallar (AGENTS.md):
- Yayın sonrası arama motoru bildirimi: URL denetimi yalnız canlıda 200 dönen
  adrese; kota sayacı; mülk/hesap notu; sitemap grep kuralı
- Yıkıcı git komutları yasak: reset/checkout --/restore/clean/stash
- Tarayıcı açma: tasarımcı ve UX gerekli gördüğünde açabilir (yetenek için
  .claude/agents/tasarimci.md'ye tarayıcı araçları eklendi)

docs/gece-vardiyasi/2026-09-22/ — 34 rapor: CEO kararları ve kapanış turu,
marka/ödeme analizi, sıfır ödeme teşhisi, SEO konu planı, veri kalitesi,
editör denetimi, CTO şartnamesi, yazılımcı ve güvenlik raporları, SABAH-OZETI.

docs/ekip/YAYIN-KUYRUGU.md — Search Console bildirim kuyruğu.

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

392 lines
33 KiB
Markdown
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.
# 26 — Veri mühendisi: `programs.il` temizliği (A4/T34) + vakıf burs etiketi (A5)
**Tarih:** 22 Eylül 2026, gece vardiyası · **Rol:** `veri-muhendisi` · **Mod:** yalnız teşhis.
**Yazılan tek dosya:** bu rapor. `data/` altına, veritabanına, `scripts/` altına **hiç yazılmadı**; tüm bağlantılar `sqlite3 -readonly data/yokatlas.db`. Kopya alınmadı (disk), `pnpm install`/`build` koşulmadı.
Kanıt işaretleri: **[D]** bu gece koşulan sorgu · **[K]** koddan çıkarım (dosya:satır) · **[doğrulanmalı]** birincil kaynakla teyit şart.
---
## 1. Beş maddelik özet
1. **Kök neden tek cümle: `il`, `fakulte` ve `isim` alanları yalnız INSERT yolunda yazılıyor, UPDATE yolunda hiç tazelenmiyor.** `scripts/refresh.ts:665-690` — mevcut satıra yalnız `sira/puan/kontenjan/yerlesen` yazılır. Bu yüzden 2021'de üçüncü parti bir CSV'den (`scripts/ingest.ts:4` → `yokatlas-dataset-2025`) gelen konum ve ad değerleri **kalıcı**: API doğrusunu söylese bile DB'ye giremiyor. `il` kirliliği bir "YÖK Atlas hatası" değil, **bizim tazeleme hattımızın kör noktası**.
2. **A5'in cevabı: kırılım YAPILABİLİR, veri elimizde.** Burs/dil etiketi YÖK Atlas API'sinde ayrı bir kolon değil, `birimAdi` alanının **içinde** geliyor ("Tıp (İngilizce) (Burslu)") — `scripts/refresh.ts:37-39` bunu açıkça yazıyor. Kanıt: 2026'da **yeni açılan** vakıf lisans satırlarının %93,7'si (326/348) etiketli, **eskiden beri var olan** satırların yalnız %14,2'si (628/4.427) etiketli [D]. Fark, `--isim-esitle` bayrağının varsayılan KAPALI olmasından geliyor. Tek koşuyla geri alınabilir. → **T11 "kırılım var" tarafına düşer.**
3. **A4'ün rakamları: 21.493 satırın 20.092'si tam ve temiz 81 Türkiye ili, 1.291'i KKTC/yurt dışı, 110'u boş** [D]. Türkiye tarafında eksik il yok, uydurma il yok — yani `/il/[slug]` (T7) yalnız bir **filtreye** ihtiyaç duyuyor, şema değişikliğine değil.
4. **Yanlış `unitur` 100 satır** (Kıbrıs Amerikan 42 + Uluslararası Balkan 34 + Uluslararası Saraybosna 24) [D] — bunlar sihirbazın "Devlet" adımında devlet programı gibi görünüyor. Ters yönde daha büyük bir sızıntı var ve şimdiye dek raporlanmamış: sihirbazın **"Vakıf (burslu olursa)"** kovası `unitur <> 'DEVLET'` ile kurulduğu için (`src/lib/db.ts:77-83`) içine **1.256 KKTC/yurt dışı satırı** giriyor — vakıf havuzunun %13,4'ü.
5. **Kapsam dışı ama yayını etkileyen bulgu:** `kontenjan<yıl>` kolonu yalnız **genel kontenjanı** tutuyor (`kontenjan2025 = gk2025`, 19.857/19.857 satır), `yerlesen<yıl>` ise **tüm kota türlerinin toplamı** (`gkY+sgyY+dprmY+obkY`, `scripts/refresh.ts:212-217`). Bu yüzden devlet lisans doluluğu **%102,1** çıkıyor [D] — matematiksel olarak imkânsız. Bu gece yayımlanan `bilgisayar-mi-yazilim-muhendisligi-mi` yazısındaki "devlet YM %102,1" rakamı bu sapmadır. **KKTC ve vakıf rakamları etkilenmiyor** (KKTC'de aşan satır 0; devlet lisansta 5.618) — yani `kktc-universiteleri-okunur-mu` yazısı güvende.
---
## 2. Veri kalitesi tablosu
| # | Kontrol | Sorgu (hepsi `sqlite3 -readonly data/yokatlas.db`) | Sonuç | Önem |
|---|---|---|---|---|
| K1 | Farklı `il` değeri sayısı | `SELECT COUNT(DISTINCT il) FROM programs` | **91** (81 TR ili + 6 KKTC + 4 yurt dışı) | bilgi |
| K2 | Boş `il` | `SELECT COUNT(*) FROM programs WHERE il IS NULL` | **110** satır / **9** kurum; hepsi `son_kilavuz_yili=2026` | **P1** |
| K3 | TR dışı `il` satırı | `WHERE il IN ('LEFKOŞA','GİRNE','GAZİMAĞUSA','LEFKE','GÜZELYURT','KIBRIS','SARAYBOSNA - BOSNA - HERSEK','BİŞKEK-KIRGIZİSTAN','TÜRKİSTAN-KAZAKİSTAN','BAKÜ-AZERBAYCAN')` | **1.291** | **P1** |
| K4 | TR ili tam mı | aynı listenin `NOT IN`'i + `COUNT(DISTINCT il)` | **81/81** — eksik il yok, fazla il yok | ✓ temiz |
| K5 | Yanlış `unitur='DEVLET'` | `WHERE unitur='DEVLET' AND (universite LIKE 'KIBRIS AMERİKAN%' OR LIKE '%BALKAN%' OR LIKE '%SARAYBOSNA%')` | **100** satır | **P1** |
| K6 | "Vakıf" kovasının saflığı | `WHERE unitur IS NOT NULL AND unitur<>'DEVLET' GROUP BY unitur` | VAKIF 7.974 · **KKTC 1.145** · VAKIF MYO 121 · **YURTDISI KAMU 99** · **YURTDISI VAKIF 12** | **P1** |
| K7 | `unitur` boş | `WHERE unitur IS NULL` | **0** ✓ (`unitur-onar.ts` işini yapmış) | ✓ |
| K8 | Aynı kurumun iki `universite` yazımı | `bazAd` çakışma sorgusu (§3.4) | **95 kurum / 1.449 satır** — bildirilen "10 KKTC üniversitesi" değil, **katalogun tamamı** | P2 |
| K9 | Aynı kurumda iki farklı `unitur` | K8 sorgusunun `unitur` kırılımı | **4 kurum**: Balkan, Saraybosna, Manas, Yesevi (DEVLET↔YURTDISI KAMU / KAMU↔VAKIF) | **P1** |
| K10 | Ayırt edilemeyen program adı (2026 aktif) | `GROUP BY universite,isim,tur,onlisans HAVING COUNT(*)>1` | **4.206 grup / 10.164 satır** (Vakıf 6.079 · Devlet 3.209 · KKTC 985) | **P1** |
| K11 | Vakıf lisansta burs etiketi | §4 sorgusu | **3.821/4.775 etiketsiz** (KKTC 747/849) — bildirilen rakam **doğrulandı** | **P1** |
| K12 | Etiketin satır yaşıyla ilişkisi | §4 sorgusu | 2026'da yeni: **326/348 etiketli**; eskiden var olan: **628/4.427** | **kanıt** |
| K13 | `yerlesen > kontenjan` | `WHERE yerlesen2026 > kontenjan2026` | **11.964/21.493** satır; devlet lisansta 5.618, KKTC lisansta **0** | **P1** |
| K14 | `kontenjan` = genel kontenjan mı | `kontenjan2025 <> gk2025` | **0** satır → evet, yalnız genel kontenjan | **kanıt** |
| K15 | `netler` yetim satırı | `LEFT JOIN programs ... WHERE p.id IS NULL` | **8.215** satır (silinmiş/kimliği devredilmiş kodlara ait) | P2 |
| K16 | `netler` yıl kapsamı | `SELECT MAX(yil) FROM netler` | **2025** — T36 doğrulandı, 2026 net verisi yok | **P1** |
| K17 | Aykırı sıralama | `WHERE sira2026<=0` | **0** ✓ | ✓ |
| K18 | `fakulte` boş (2026 aktif) | `WHERE fakulte IS NULL AND kontenjan2026 IS NOT NULL` | **0** ✓ | ✓ |
---
## 3. Görev 1 — `programs.il` (A4 / T34)
### 3.1 Bildirilen bulguların tek tek doğrulanması
| Bildirilen | Durum | Kanıt |
|---|---|---|
| 91 farklı `il` değeri | **DOĞRU** | K1 |
| `il` boş 110 satır / 9 üniversite | **DOĞRU** | K2; kurum listesi §3.2 |
| Uluslararası Balkan Ü. 26 satır `il='SARAYBOSNA - BOSNA - HERSEK'`, kurum Üsküp'te | **DOĞRU** (26 satır) — ayrıca aynı kurumun 8 satırı `il` boş, 14 satırı ayrı adla `YURTDISI KAMU` | §3.2 |
| Kıbrıs Amerikan Ü. 42 satır `unitur='DEVLET'` | **DOĞRU** (36 + 6, iki ad hattı) | K5 |
| `src/lib/db.ts:79` devlet filtresi bunları devlet sayıyor | **DOĞRU** | `uniturFiltreSql`, `src/lib/db.ts:77-83` [K] |
| 10 KKTC üniversitesi iki ayrı `universite` adıyla duruyor | **DOĞRU ama eksik tarif** — aynı desen **95 kurumda** var (1.449 satır). KKTC'ye özgü değil, API'nin 2026 ad biçiminden geliyor | K8 |
| ODTÜ KKK programları ana kampüsle **aynı `universite` ve `il`** altında | **YARI ÇÜRÜTÜLDÜ.** 8 KKK satırı `il='KIBRIS'` ve `fakulte='ODTÜ Kuzey Kıbrıs Kampusu (KKTC-Güzelyurt)'` taşıyor — yani ayrılabilir. Ortak olan yalnız `universite`. **Ama** ODTÜ *Yazılım Mühendisliği*'nin 2 satırı gerçekten `il='ANKARA'`, `fakulte='Mühendislik Fakültesi'` ile duruyor → yazarın asıl iddiası doğru, sebebi farklı | §3.3 |
### 3.2 Boş `il`: 110 satır, 9 kurum, hepsi 2026 kılavuzundan
```sql
SELECT universite, unitur, COUNT(*) FROM programs WHERE il IS NULL GROUP BY 1,2 ORDER BY 3 DESC;
```
| Kurum | `unitur` | Satır | `il` nereden türetilebilir |
|---|---|---|---|
| TİRAN NEW YORK ÜNİVERSİTESİ (TİRAN-ARNAVUTLUK) | YURTDISI KAMU | 36 | ad parantezi |
| İÇİŞLERİ BAKANLIĞI VE MİLLİ SAVUNMA BAKANLIĞI ADINA SAĞLIK BİLİMLERİ ÜNİVERSİTESİNDE EĞİTİM ALACAKLAR | DEVLET | 15 | **türetilemez** (sahte kurum adı; bakanlık kontenjanı) |
| ULUSLARARASI BALKAN ÜNİVERSİTESİ (ÜSKÜP-MAKEDONYA) | YURTDISI KAMU | 14 | ad parantezi |
| ULUSLARARASI SARAYBOSNA ÜNİVERSİTESİ (SARAYBOSNA-BOSNA-HERSEK) | YURTDISI KAMU | 14 | ad parantezi |
| ULUSLARARASI SARAYBOSNA ÜNİVERSİTESİ | DEVLET | 9 | kardeş satırdan |
| ULUSLARARASI BALKAN ÜNİVERSİTESİ | DEVLET | 8 | kardeş satırdan (**Üsküp**, Saraybosna değil) |
| KIRGIZİSTAN-TÜRKİYE MANAS ÜNİVERSİTESİ (BİŞKEK-KIRGIZİSTAN) | YURTDISI VAKIF | 8 | ad parantezi |
| HOCA AHMET YESEVİ … (TÜRKİSTAN-KAZAKİSTAN) | YURTDISI VAKIF | 4 | ad parantezi |
| KIBRIS AMERİKAN ÜNİVERSİTESİ | DEVLET | 2 | kardeş satırdan (LEFKOŞA) |
**95 satır ad/kardeş satırdan doldurulabilir; 15 satır (MSB/İçişleri) doldurulamaz** — ÖSYM bunları tek bir yerleşkeye bağlamıyor. Tahminle doldurmuyoruz; eksik, eksik kalır ve `veri-kalite`'de **beyaz listeye** alınır.
### 3.3 Kök neden — hangi betik hangi alanı nereden yazıyor
| Alan | Yazan | Kaynak | UPDATE yolunda tazeleniyor mu |
|---|---|---|---|
| `il` | `ingest.ts` (2021 CSV) → sonra yalnız `refresh.ts` INSERT (`r.ilAdi`, satır 679) | üçüncü parti CSV, sonra API | **HAYIR** |
| `unitur` | `ingest.ts` CSV → `refresh.ts` INSERT `uniturSec()` (518-524) → `unitur-onar.ts` yalnız NULL'lar | CSV, sonra API (koşullu) | **HAYIR** |
| `fakulte` | `ingest.ts` CSV → `refresh.ts` INSERT `fakulteSec()` | CSV, sonra API | **HAYIR** |
| `isim` | `ingest.ts` CSV → `refresh.ts` INSERT `r.birimAdi`; UPDATE yalnız `--isim-esitle` ile (643-648) | CSV, sonra API | **bayrak kapalıyken HAYIR** |
| `sira/puan/kontenjan/yerlesen` | `refresh.ts` UPDATE (474-480) | API | evet |
Üç mekanizma üst üste biniyor:
**(a) UPDATE körlüğü.** `refresh.ts:665-680` — `if (exists.get(id)) update.run(...)` yalnız dört yıl kolonunu yazar. 2021 CSV'sinden gelen `il`/`fakulte`/`isim` beş yıldır hiç tazelenmedi. Kıbrıs Amerikan'ın 36 satırı `il='LEFKOŞA'`, Balkan'ın 26 satırı `il='SARAYBOSNA - BOSNA - HERSEK'` — ikisi de **2021 verisi taşıyor** (`kontenjan2021 IS NOT NULL`), yani CSV kökenli [D].
**(b) `uniturSec` yanlışı kalıcılaştırıyor.** `refresh.ts:519-525`:
```ts
const uniturSec = (universite: string, api: string | null): string | null => {
for (const k of [universite, bazAd(universite)]) {
const set = uniturlar.get(k);
if (set?.size === 1) return [...set][0]; // ← DB'deki değer API'yi EZİYOR
}
return api;
};
```
Niyet doğru (sözlük tutarlılığı, `uniturOnar`'ın çelişki koruması patlamasın diye), sonuç ters: API "KKTC" dese bile DB'de tek bir `DEVLET` varsa `DEVLET` yazılır. Kıbrıs Amerikan'ın 2026'da eklenen 6 yeni satırı bu yüzden de `DEVLET` oldu.
**(c) İki ayrı ad hattı = iki ayrı kaynak.** API 2026'da üniversite adını **`AD (İL)`** biçiminde veriyor (`ORTA DOĞU TEKNİK ÜNİVERSİTESİ (ANKARA)`, `DOĞU AKDENİZ ÜNİVERSİTESİ (KKTC-GAZİMAĞUSA)`). CSV ise sade ad + ayrı `il` veriyordu. Böylece 95 kurumda iki paralel ad hattı oluştu:
```sql
-- K8
WITH b AS (SELECT universite, unitur, il,
TRIM(CASE WHEN universite LIKE '%)' THEN substr(universite,1,instr(universite,' (')-1)
ELSE universite END) baz, COUNT(*) n
FROM programs GROUP BY 1,2,3)
SELECT baz, universite, unitur, COALESCE(il,'(NULL)'), n FROM b
WHERE baz IN (SELECT baz FROM b GROUP BY baz HAVING COUNT(DISTINCT universite)>1);
-- 95 kurum / 1.449 satır
```
Yıl profili: `AD (TR ili)` eki 1.224 satır/80 kurum · `AD (KKTC-şehir)` 149/10 · `AD (şehir-ülke)` 76/5 [D].
**ODTÜ örneği — sorunun tam resmi.** `universite='ORTA DOĞU TEKNİK ÜNİVERSİTESİ (ANKARA)'` altında 11 satır var: 3'ü `il='ANKARA'` (KKTC uyruklu kontenjanı, doğru), 8'i `il='KIBRIS'` + `fakulte='ODTÜ Kuzey Kıbrıs Kampusu (KKTC-Güzelyurt)'` (doğru). **Ama** `universite='ORTA DOĞU TEKNİK ÜNİVERSİTESİ'` altındaki 72 eski CSV satırının içinde *Yazılım Mühendisliği* de var:
```sql
SELECT universite, il, fakulte, isim, kontenjan2026, sira2026
FROM programs WHERE isim LIKE 'Yazılım Müh%' AND universite LIKE 'ORTA DOĞU%';
-- ORTA DOĞU TEKNİK ÜNİVERSİTESİ | ANKARA | Mühendislik Fakültesi | Yazılım Mühendisliği | 45 | 299241
-- ORTA DOĞU TEKNİK ÜNİVERSİTESİ | ANKARA | Mühendislik Fakültesi | Yazılım Mühendisliği | 5 | 293024
```
ODTÜ Ankara'da Yazılım Mühendisliği **yoktur**; program Kuzey Kıbrıs Kampusu'ndadır (`ncc.metu.edu.tr/tr/sng`, yazar 13 teyit etmiş) [doğrulanmalı: 2026 ÖSYM kılavuzu]. 45/5 kontenjan ikilisi de KKK'nin klasik **Burslu / %50 İndirimli** desenidir — ama iki satırın adı birebir aynı, çünkü A5'teki etiket kaybı tam burada vuruyor. Yani üç hata aynı satırda: yanlış `il`, yanlış `fakulte`, kayıp burs etiketi. Aynı desen ODTÜ'de 16 ad grubunda daha var (Bilgisayar Müh. ×3 → 105/65/65, Elektrik-Elektronik ×3, İşletme ×6 …) [D].
### 3.4 Etki haritası — hangi yüzey ne yanlış gösteriyor
| Yüzey | Dosya | Ne oluyor | Etkilenen |
|---|---|---|---|
| **Sihirbaz "Devlet" adımı** | `src/lib/db.ts:78` `unitur = 'DEVLET'` | KKTC ve Balkan/Saraybosna programları devlet üniversitesi gibi havuza giriyor | **100 satır** |
| **Sihirbaz "Vakıf (burslu olursa)" adımı** | `src/lib/db.ts:79-80` `unitur <> 'DEVLET'` | KKTC (1.145) + yurt dışı (111) satırı vakıf havuzuna giriyor; etiket kaybı yüzünden öğrenci hangisinin burslu olduğunu da göremiyor | **1.256 satır / havuzun %13,4'ü** |
| **Sihirbaz il adımı (facet)** | `src/lib/db.ts:325-327` — kodda not düşülmüş: "81 il + KKTC/yurtdışı" | DAÜ'nün 276 satırı **GAZİMAĞUSA** ve **KIBRIS** çiplerine bölünmüş; aynı bölünme 9 KKTC kurumunda var | **1.080 satır** |
| **`/universiteler` hub'ı** | `src/lib/katalog.ts:270-337` | `MAX(unitur)` + alfabetik ilk ad hattı kazanıyor → Uluslararası Balkan Ü. **"Devlet / Saraybosna"** görünüyor (doğrusu: yurt dışı kamu / Üsküp) | 3 kurum kartı |
| **`/universite/<slug>`** | `src/lib/katalog.ts:402` `stats.devletMi` | Kıbrıs Amerikan Ü. sayfasında "devlet üniversitesi" rozeti | 1 sayfa |
| **il faceti / il listesi** | aynı | `il` boş 110 satır **hiçbir il filtresine düşmüyor** — sessizce kayboluyor | 110 satır |
| **`/il/[slug]` planı (T7, #18 yazısı)** | henüz yok | Bugünkü `SELECT DISTINCT il` 91 değer döner → `/il/baku-azerbaycan`, `/il/kibris` gibi sayfalar üretilir | **önkoşul** |
| **Bu gecenin yazıları** | `content/rehber/bilgisayar-mi-yazilim-muhendisligi-mi.md` | ODTÜ head-to-head tablodan çıkarılmış ve **neden çıkarıldığı yazıda yazılmış** → doğru davranış, düzeltme gerekmiyor. Ama aynı yazının **devlet doluluk** rakamları §5'teki kota asimetrisinden sapmalı | 1 yazı, 2 rakam |
| | `content/rehber/kktc-universiteleri-okunur-mu.md` | KKTC evreni `unitur='KKTC' OR universite LIKE 'KIBRIS AMERİKAN%'` ile elle kurulmuş → yazı **doğru**; düzeltme sonrası sorgu sadeleşir, rakam değişmez | — |
### 3.5 Önerilen şema — **yeni kolon yok**
Soru "il yanında `ulke`/`bolge` mi, yoksa ayrı `kampus` alanı mı" şeklinde soruldu. **Cevabım: ikisi de değil.** Her iki bilgi de zaten DB'de:
- **Ülke/statü ayrımı `unitur`'de yaşıyor:** `DEVLET · VAKIF · VAKIF MYO · KKTC · YURTDISI KAMU · YURTDISI VAKIF`. Bir `ulke` kolonu bu sözlüğün kopyasını çıkarır ve iki alanın çelişme ihtimalini yaratır.
- **Kampüs bilgisi `fakulte`'de yaşıyor:** `ODTÜ Kuzey Kıbrıs Kampusu (KKTC-Güzelyurt)`, `ASBÜ-Kuzey Kıbrıs Yerleşkesi (KKTC-Lefkoşa)`, `İTÜ-KKTC EĞİTİM ARAŞTIRMA YERLEŞKESİ (GAZİMAĞUSA)` [D].
| | A) `ulke`/`bolge` kolonu | B) `il` normalize + `kampus` kolonu | **C) Kolon yok: `il`'i normalize et, `unitur`'ü düzelt** |
|---|---|---|---|
| Göç | `yokatlas-goc.ts` + `ingest.ts` + `refresh.ts` INSERT + `veri-kalite.ts` | aynısı | **yok** |
| Dolduran kaynak | API'de **böyle bir alan yok** — ad parantezinden türetmek zorundayız | aynı sorun | ad parantezi (aynı iş, kolonsuz) |
| Yıllık refresh maliyeti | her yıl yeni kolonun da senkronu | aynı + `kampus` eşlemesi | `il` senkronu (zaten yapılmalı) |
| Okuyan taraf değişikliği | `katalog.ts`, `db.ts`, facet, tipler | aynısı + tipler | **tek satır filtre**: `AND unitur IN ('DEVLET','VAKIF','VAKIF MYO')` |
| T7 (`/il/[slug]`) çözülür mü | evet | evet | **evet** |
| Çift kaynak riski | `unitur` ile çelişebilir | `fakulte` ile çelişebilir | yok |
**Öneri C.** İki `UPDATE` + bir satır kod. Kurucunun "over-engineering yapma" talimatına uyan en küçük çözüm.
C'nin tek gerçek açığı, şema değil **hat** açığı: `il`/`fakulte`/`isim` UPDATE yolunda tazelenmediği sürece aynı kir her yıl birikir. Bunun kolonla ilgisi yok — çözümü `refresh.ts`'e `--kimlik-onar`/`--isim-esitle` ile aynı güvenlik desenini taşıyan bir **`--yer-esitle`** bayrağı: API ile `il`/`fakulte` farkı olan satırları önce **listeler**, gözle onaylanınca yazar. Ayrı bir CTO şartnamesine değer; bu gece yazılmaz.
### 3.6 Düzeltme betiği taslağı — **KOŞULMADI, yalnız yazıldı**
Ön koşullar: dev sunucu **kapalı** (WAL tek yazar), önce yedek, sonra `pnpm veri-kalite --yil 2026`.
```sh
# 0) Yedek — geri alma yolu budur. WAL'i önce kapat.
sqlite3 data/yokatlas.db "PRAGMA wal_checkpoint(TRUNCATE);"
cp data/yokatlas.db data/yokatlas.db.yedek-2026-09-22
```
```sql
BEGIN;
-- ── 1) unitur: yanlış DEVLET etiketleri (K5 = 100 satır) ────────────────
-- Kıbrıs Amerikan Ü. YÖK'ün KKTC listesinde (yok.gov.tr/tr/university?type=6)
UPDATE programs SET unitur='KKTC'
WHERE unitur='DEVLET' AND universite LIKE 'KIBRIS AMERİKAN%'; -- 42
-- Balkan + Saraybosna: aynı kurumun diğer ad hattı zaten YURTDISI KAMU
UPDATE programs SET unitur='YURTDISI KAMU'
WHERE unitur='DEVLET'
AND (universite LIKE 'ULUSLARARASI BALKAN%'
OR universite LIKE 'ULUSLARARASI SARAYBOSNA%'); -- 58
-- ── 2) il: KKTC'de 'KIBRIS' toplayıcısını şehre indir (179 satır) ───────
-- Şehir zaten ad ya da fakülte parantezinde yazılı; tahmin YOK.
UPDATE programs SET il = CASE
WHEN universite LIKE '%(KKTC-LEFKOŞA)%' THEN 'LEFKOŞA'
WHEN universite LIKE '%(KKTC-GİRNE)%' THEN 'GİRNE'
WHEN universite LIKE '%(KKTC-GAZİMAĞUSA)%' THEN 'GAZİMAĞUSA'
WHEN universite LIKE '%(KKTC-LEFKE)%' THEN 'LEFKE'
WHEN fakulte LIKE '%(KKTC-Lefkoşa)%' THEN 'LEFKOŞA'
WHEN fakulte LIKE '%(KKTC-Güzelyurt)%' THEN 'GÜZELYURT'
WHEN fakulte LIKE '%(GAZİMAĞUSA)%' THEN 'GAZİMAĞUSA'
END
WHERE il='KIBRIS'
AND (universite LIKE '%(KKTC-%' OR fakulte LIKE '%(KKTC-%' OR fakulte LIKE '%(GAZİMAĞUSA)%');
-- beklenen: 179; sonrasında SELECT COUNT(*) FROM programs WHERE il='KIBRIS' = 0
-- ── 3) il: yurt dışı değerini mevcut sözleşmeye uydur (26 satır) ────────
-- Mevcut sözleşme: 'ŞEHİR-ÜLKE' (BİŞKEK-KIRGIZİSTAN, TÜRKİSTAN-KAZAKİSTAN)
UPDATE programs SET il='ÜSKÜP-KUZEY MAKEDONYA'
WHERE universite='ULUSLARARASI BALKAN ÜNİVERSİTESİ' AND il='SARAYBOSNA - BOSNA - HERSEK'; -- 26
UPDATE programs SET il='SARAYBOSNA-BOSNA HERSEK'
WHERE il='SARAYBOSNA - BOSNA - HERSEK'; -- 15
-- ── 4) il: boş 110 satırın 95'i (kalan 15 bilerek NULL) ─────────────────
UPDATE programs SET il = CASE
WHEN universite LIKE '%(TİRAN-ARNAVUTLUK)%' THEN 'TİRAN-ARNAVUTLUK'
WHEN universite LIKE '%(ÜSKÜP-MAKEDONYA)%' THEN 'ÜSKÜP-KUZEY MAKEDONYA'
WHEN universite LIKE '%(SARAYBOSNA-BOSNA-HERSEK)%' THEN 'SARAYBOSNA-BOSNA HERSEK'
WHEN universite LIKE '%(BİŞKEK-KIRGIZİSTAN)%' THEN 'BİŞKEK-KIRGIZİSTAN'
WHEN universite LIKE '%(TÜRKİSTAN-KAZAKİSTAN)%' THEN 'TÜRKİSTAN-KAZAKİSTAN'
WHEN universite='ULUSLARARASI BALKAN ÜNİVERSİTESİ' THEN 'ÜSKÜP-KUZEY MAKEDONYA'
WHEN universite='ULUSLARARASI SARAYBOSNA ÜNİVERSİTESİ' THEN 'SARAYBOSNA-BOSNA HERSEK'
WHEN universite='KIBRIS AMERİKAN ÜNİVERSİTESİ' THEN 'LEFKOŞA'
END
WHERE il IS NULL
AND universite <> 'İÇİŞLERİ BAKANLIĞI VE MİLLİ SAVUNMA BAKANLIĞI ADINA SAĞLIK BİLİMLERİ ÜNİVERSİTESİNDE EĞİTİM ALACAKLAR';
-- beklenen: 95; kalan 15 MSB/İçişleri satırı NULL kalır (kaynak da boş veriyor)
COMMIT;
```
**Doğrulama sorguları (düzeltmeden sonra koşulur):**
```sql
SELECT COUNT(DISTINCT il) FROM programs; -- beklenen 88 (81 TR + 5 KKTC şehri + ... )
SELECT COUNT(*) FROM programs WHERE il IS NULL; -- beklenen 15
SELECT COUNT(*) FROM programs WHERE il='KIBRIS'; -- beklenen 0
SELECT COUNT(*) FROM programs WHERE unitur='DEVLET'
AND (universite LIKE 'KIBRIS AMERİKAN%' OR universite LIKE '%BALKAN%'
OR universite LIKE '%SARAYBOSNA%'); -- beklenen 0
SELECT COUNT(DISTINCT il) FROM programs
WHERE unitur IN ('DEVLET','VAKIF','VAKIF MYO'); -- beklenen 81 ← T7'nin kabul kriteri
SELECT SUM(kontenjan2026), SUM(yerlesen2026) FROM programs; -- düzeltmeden ÖNCEKİYLE AYNI olmalı
```
**Geri alma:** `data/yokatlas.db.yedek-2026-09-22` dosyasını geri kopyala. Betik yalnız 4 kolonsuz alana dokunuyor, sıralama/kontenjan verisine hiç dokunmuyor — son doğrulama sorgusu bunu kanıtlar.
**Birlikte gitmesi gereken kod değişikliği (tek satır):** `/il/[slug]` ve il faceti `AND unitur IN ('DEVLET','VAKIF','VAKIF MYO')` ile süzülmeli; yoksa `/il/uskup-kuzey-makedonya` üretilir. Sihirbazın "Vakıf" kovası da `unitur IN ('VAKIF','VAKIF MYO')` olmalı (`src/lib/db.ts:79`) — bu **ayrı bir ürün kararı**: KKTC programları havuzdan çıkarsa vakıf havuzu 9.351 → 8.095 satıra iner. **[BİLAL]** değil, `urun-yoneticisi`/`ceo` kararı.
**İdempotens uyarısı:** betik koşulduktan sonra `unitur-onar.ts`'in çelişki koruması (`aynı üniversitede birden fazla unitur`) hâlâ geçer — Balkan/Saraybosna'nın iki ad hattı farklı `universite` dizgileri olduğu için ayrı gruplar sayılıyor [K].
---
## 4. Görev 2 — vakıf burs etiketi (A5)
### Tek cümlelik cevap
**Etiket YÖK Atlas API'sinde ayrı bir alan değil, program adının (`birimAdi`) içinde geliyor; veri elimizde ve kırılım yapılabilir — bugün eksik olmasının sebebi `refresh.ts`'in `--isim-esitle` bayrağının varsayılan kapalı olması, kaynağın etiketi vermemesi değil.**
### Kanıt zinciri
**(1) Kodun kendi belgesi** — `scripts/refresh.ts:37-39`:
> `--isim-esitle`: kimliği AYNI olup yalnız adı farklı satırlarda (API burs/dil eki veriyor: "Tıp (İngilizce) (Burslu)") isim API'den yazılır. **Varsayılan KAPALI**: isim, sihirbaz kategorilerini ve bölüm sayfası başlığını besler.
Yani etiketin kaynağı biliniyor ve yazılmaması **bilinçli bir seçim**. Gerekçe de meşru: `isim` sihirbaz kategorilerini besliyor, "Tıp (Burslu)" ile "Tıp" farklı kategoriye düşebilir.
**(2) API isteği burs boyutunu tanıyor** — `refresh.ts:181` filtre nesnesinde `bursOraniId: null` var; yani uç nokta burs oranına göre süzmeyi destekliyor. Ama yanıt kaydından tutulan alanlar listesinde (`TUTULAN_ALANLAR`, satır 124-148) burs alanı **yok** — ham `.jsonl.gz` dosyası kaydın tamamını yazıyor (`refresh.ts:378`), ince kayıt değil, ama `data/ham/` bu makinede **mevcut değil** (`.gitignore:48`), bu yüzden API yanıtında ayrı bir `bursOrani` alanı olup olmadığı **yerel olarak doğrulanamadı** [doğrulanmalı — tek `--ham` koşusuyla ya da 1 isteklik `size:1` sorgusuyla 2 dakikada kapanır].
**(3) Asıl kanıt — satır yaşına göre etiketlilik.** Yeni satırlar `INSERT`'te `r.birimAdi`'yi doğrudan yazıyor (`refresh.ts:677`), eski satırların adı hiç güncellenmiyor:
```sql
SELECT CASE WHEN sira2025 IS NOT NULL OR kontenjan2025 IS NOT NULL
THEN '2025te de vardi' ELSE '2026da yeni' END durum,
COUNT(*) n,
SUM(isim LIKE '%(Burslu)%' OR isim LIKE '%İndirimli)%' OR isim LIKE '%(Ücretli)%') etiketli
FROM programs
WHERE kontenjan2026 IS NOT NULL AND onlisans=0 AND unitur LIKE 'VAKIF%'
GROUP BY 1;
```
| Satır | n | Etiketli | Oran |
|---|---|---|---|
| 2026'da **yeni açılan** (isim API'den yazıldı) | 348 | **326** | **%93,7** |
| 2025'te de **var olan** (isim 2021 CSV'sinden) | 4.427 | 628 | %14,2 |
Aynı veri, aynı kurumlar, aynı yıl — tek fark satırın INSERT mi UPDATE mi yolundan geçtiği. **API etiketi veriyor; biz yazmıyoruz.**
**(4) `veri_meta` kaydı da aynı şeyi gösteriyor.** `kimlik_onar:*` kayıtlarının `yeni.isim` alanları API'den geldiği gibi duruyor: `"Ebelik (Burslu)"`, `"Ebelik (%50 İndirimli)"`, `"Siber Güvenlik (Burslu) (İngilizce)"`, `"Endüstri Mühendisliği (Ücretli)"`, `"Anestezi (%25 İndirimli)"` [D]. Etiket sözlüğü tam: **Burslu · %75/%50/%25 İndirimli · Ücretli**.
### Bugünkü kırılım (bildirilen rakamlar doğrulandı)
```sql
SELECT CASE WHEN unitur LIKE 'VAKIF%' THEN 'Vakıf' WHEN unitur='KKTC' THEN 'KKTC'
WHEN unitur='DEVLET' THEN 'Devlet' ELSE unitur END grup,
SUM(onlisans=0) lisans,
SUM(onlisans=0 AND isim LIKE '%(Burslu)%') burslu,
SUM(onlisans=0 AND isim LIKE '%İndirimli)%') indirimli,
SUM(onlisans=0 AND isim LIKE '%(Ücretli)%') ucretli,
SUM(onlisans=0 AND isim NOT LIKE '%(Burslu)%' AND isim NOT LIKE '%İndirimli)%'
AND isim NOT LIKE '%(Ücretli)%') etiketsiz
FROM programs WHERE kontenjan2026 IS NOT NULL GROUP BY 1;
```
| Grup | 2026 lisans | Burslu | İndirimli | Ücretli | **Etiketsiz** |
|---|---|---|---|---|---|
| Devlet | 6.505 | 9 | 39 | 13 | 6.444 (beklenen — devlette burs etiketi olmaz) |
| **Vakıf** | 4.775 | 339 | 416 | 199 | **3.821 (%80,0)** |
| **KKTC** | 849 | 39 | 51 | 12 | **747 (%88,0)** |
| Yurt dışı kamu | 98 | 14 | 31 | 19 | 34 |
Yazarların bildirdiği 3.821/4.775 ve (873 payda ile) 768/873 rakamları **doğrulandı**; payda farkı (849 vs 873) yalnız KKTC evreninin `unitur='KKTC'` mi yoksa `+ Kıbrıs Amerikan` mı tanımlandığına bağlı — A4 düzeltmesinden sonra iki tanım birleşiyor.
### `data/kaynak/` altındaki dosyalarda burs bilgisi var mı
**Yok.** Oradaki iki dosya kurum düzeyinde YÖK istatistiği:
- `yok-istatistik-ogrenci-sayilari-2025-2026.xls` — sütunları: `Okuyan Önlisans · Okuyan Lisans · Okuyan Yükseklisans · Okuyan Doktora · Genel Toplam`, satırları üniversite × il × (DEVLET/VAKIF/KKTC).
- `yok-istatistik-ogretim-elemani-sayilari-2025-2026.xls` — öğretim elemanı sayıları.
İkisi de **program düzeyinde değil**, burs/ücret alanı içermiyor (JasperReports dökümü; `file` + `strings` ile başlık satırları okundu, dosyalar değiştirilmedi). Bu dosyalar A5 için kullanılamaz; başka bir işe (kurum büyüklüğü/öğrenci-öğretim üyesi oranı içerikleri) yarayabilir.
### Yazarlara verilecek net cevap
> **Kırılım bugün yapılamaz, ama kalıcı olarak imkânsız değil.** Veri kaynakta var; DB'ye tek bir refresh koşusuyla girer. **Bu koşu yapılana kadar hiçbir yazıda burslu/%50/ücretli dağılımı verilmesin** — bugünkü sayımlar (%80'i etiketsiz) gerçek dağılım değil, tazelenmemiş adların sayımıdır ve yayımlanırsa **yanlış** olur.
### Bu koşunun bedeli ve riski (Bilal/CTO için)
```sh
# ÖNCE kopyada: ham dosya yoksa API'den tek geçiş (~22 sn/21.493 kayıt, 0,5 sn/sayfa)
cp data/yokatlas.db /tmp/deneme.db
pnpm refresh --yil 2026 --db /tmp/deneme.db --isim-esitle
pnpm veri-kalite --yil 2026 --db /tmp/deneme.db --onceki data/yokatlas.db
```
- **Etkilenecek satır:** kabaca 10.164 (K10'daki ayırt edilemeyen ad grupları); kesin sayıyı koşunun `"yalnız adı (burs/dil eki) farklı N satır"` satırı verir (`refresh.ts:609`).
- **Risk — küçümsenmemeli:** `isim` sihirbaz kategorilerini (`src/lib/kategoriler.ts`) ve `/bolum/<slug>` başlıklarını besliyor. "Bilgisayar Mühendisliği" → "Bilgisayar Mühendisliği (Burslu)" olursa kategori eşlemesi ve bölüm slug'ı kayabilir; **kaydedilmiş raporlardaki program adları da tutmayabilir.** Bu yüzden doğru sıra: (1) kopyada koş, (2) kategori eşleme farkını say, (3) gerekirse okuyan tarafa "etiketi kırp" normalizasyonu ekle, (4) ancak sonra gerçek DB. **Bu gece yapılmaz.**
- **Daha ucuz alternatif:** `isim`'e hiç dokunmadan ayrı bir `burs` kolonu türetmek (`Burslu/%75/%50/%25/Ücretli/—`). Ama bu §3.5'te reddettiğim "yeni kolon" yoluna girer ve kategori riskini de çözmez, sadece erteler. **Önerim: `--isim-esitle` + okuyan tarafta etiket kırpma.**
---
## 5. Kapsam dışı ama yayını ilgilendiren bulgu: doluluk oranları yukarı sapmalı
Görevde yok, ama bu gece yayımlanan rakamları etkilediği için rapora giriyor.
```sql
SELECT COUNT(*) FROM programs WHERE kontenjan2025 IS NOT NULL AND gk2025 IS NOT NULL
AND kontenjan2025 <> gk2025; -- 0 → kontenjan = YALNIZ genel kontenjan
SELECT COUNT(*) FROM programs WHERE yerlesen2025 IS NOT NULL AND gkY2025 IS NOT NULL
AND yerlesen2025 <> gkY2025; -- 7.989 → yerlesen = gkY+sgyY+dprmY+obkY
```
`scripts/refresh.ts:212-217` `yerlesen()` dört kota türünü toplarken, `kontenjan` kolonuna yalnız `r.kontenjan` (genel kontenjan) yazılıyor. Pay ile payda aynı evreni saymıyor:
| Grup (2026 lisans) | Kontenjan | Yerleşen | Doluluk | `yerlesen>kontenjan` satır |
|---|---|---|---|---|
| Devlet | 280.131 | 286.150 | **%102,1** | **5.618** |
| Vakıf | 99.638 | 78.475 | %78,8 | — |
| KKTC | 13.162 | 7.923 | %60,2 | **0** |
- **KKTC ve vakıf rakamları güvende** — KKTC'de okul birincisi/şehit-gazi/depremzade kontenjanı olmadığı için sapma sıfır; `kktc-universiteleri-okunur-mu` yazısının ÖSYM ile birebir uyuşması bunu bağımsız olarak doğruluyor (13.162/7.923 ÖSYM Tablo 1 ile aynı).
- **Devlet doluluk rakamları sapmalı.** `bilgisayar-mi-yazilim-muhendisligi-mi` yazısındaki "devlet YM **%102,1**" tam olarak bu sapmadır; %100'ü aşan her doluluk rakamı bu hatanın imzasıdır.
- **En küçük düzeltme yazı tarafında:** doluluk hesabında paydayı da tüm kota türlerini kapsayacak şekilde kurmak mümkün değil (toplam kontenjan kolonu DB'de yok) → bu sezon **devlet için doluluk rakamı yayımlanmasın**, ya da "genel kontenjana göre" kaydıyla ve %100 üstü değerler açıklanarak verilsin. Kalıcı düzeltme `kontenjanToplam<yıl>` kolonu gerektirir → yeni yılın refresh'ine bağlanmalı, bu gece değil.
- **Backlog önerisi:** `veri-kalite.ts`'e **ZORUNLU** kontrol olarak `yerlesen>kontenjan` oranı eklensin; bugün böyle bir kontrol yok (`scripts/veri-kalite.ts:145-150` yalnız `unitur boş` / `il boş` **bilgi** satırı basıyor, çıkış kodunu etkilemiyor).
---
## 6. Backlog önerileri (`urun-yoneticisi`ne)
| Öneri | Gerekçe | Boy |
|---|---|---|
| **BACKLOG #9'u genişlet:** "3 yurt dışı/KKTC üniversitesi DEVLET etiketli" → gerçek kapsam **100 satır + 179 `il='KIBRIS'` + 110 boş `il` + 26 yanlış şehir**; çözüm §3.6 betiği | bugünkü madde kapsamı gerçeğin üçte biri | S |
| **T34 kabul kriteri:** `SELECT COUNT(DISTINCT il) FROM programs WHERE unitur IN ('DEVLET','VAKIF','VAKIF MYO')` = **81** ve `COUNT(*) WHERE il IS NULL` = **15** | T7/#18'in önkoşulu, komutla doğrulanabilir | — |
| **Yeni:** `refresh.ts --yer-esitle` (il/fakulte senkronu, önce listeler sonra yazar) | yoksa aynı kir 2027'de yeniden birikir | M |
| **Yeni:** `veri-kalite.ts`'e 3 ZORUNLU kontrol — (a) aynı baz kurum adında iki `unitur`, (b) TR dışı `il` × TR `unitur` çelişkisi, (c) `yerlesen>kontenjan` | üçü de bu gece elle bulundu; otomatik olmalı | S |
| **A5 sonrası:** `--isim-esitle` koşusu + kategori eşleme farkının sayımı | T11'in önkoşulu | M |
---
## 7. Engeller
1. **`data/ham/2026-2026-09-21.jsonl.gz` bu makinede yok** (`.gitignore:48`, dizin hiç yok). API yanıtında ayrı bir `bursOrani`/`ogrenimTuru` alanı olup olmadığını yerel olarak doğrulayamadım — A5'in cevabı bundan bağımsız (etiket `birimAdi` içinde geliyor, §4/3 kanıtı), ama "ayrıca ayrı kolon da var mı" sorusu açık kaldı.
2. **Disk.** Kural gereği DB kopyası alınmadı, betik denemesi yapılmadı; §3.6 ve §4'teki komutlar **hiç koşulmadı**, yalnız yazıldı.
3. **ÖSYM 2026 kılavuzu açılamadı** (yazar 14 de aynı engele takılmış: PDF metin katmanı çıkarılamıyor, makinede `poppler` yok). "ODTÜ Yazılım Mühendisliği Ankara'da değil, Kuzey Kıbrıs'ta" iddiası bu yüzden kılavuz maddesine değil, (a) ODTÜ KKK'nin kendi sayfasına ve (b) 45/5 Burslu-%50 kontenjan desenine dayanıyor → **[doğrulanmalı]** işaretli kaldı.
---
## Bilal'den istenen (3 madde)
1. **§3.6 düzeltme betiğine onay** — 100 `unitur` + 320 `il` satırını düzeltir, sıralama/kontenjan verisine dokunmaz, geri alma tek `cp`. Onaylarsan betiği `veri/il-temizligi` dalında yazıp kopyada doğrularım; T7 (`/il/[slug]`) ve #18 yazısı bu onaya bağlı. *(karar: 3 dk)*
2. **Ürün kararı: sihirbazın "Vakıf (burslu olursa)" adımı KKTC ve yurt dışı programlarını kapsasın mı?** Bugün kapsıyor (1.256 satır, havuzun %13,4'ü) ve öğrenciye söylenmiyor. Kapsamasın dersen vakıf havuzu 9.351 → 8.095'e iner. *(karar: 2 dk)*
3. **Devlet doluluk rakamı bu sezon yayımlanmasın** onayı — `bilgisayar-mi-yazilim-muhendisligi-mi` yazısındaki "%102,1" ve "%99,8" kota asimetrisinden sapmalı (§5); yazı yayına girmeden `rehber-editoru` o iki rakamı çıkarsın ya da "genel kontenjana göre" kaydıyla versin. *(karar: 2 dk, uygulama: 10 dk)*