fix(veri): kimlik kilidi — kodu başka programa devredilmiş satırlar yakalanır ve onarılır
Kılavuz kodu başka programa devredildiğinde (ör. 209210292: Diş Hekimliği SAY lisans → İlk ve Acil Yardım TYT önlisans) refresh yalnız koda bakıp yıl kolonlarını yazıyordu; satır eski adıyla yeni programın sırasını gösteriyordu. - scripts/kimlik.ts (yeni): kimlik = puan türü + önlisans + baz ad slug'ı. Devir yılı kanıt zinciriyle bulunur (API …1/…2/…3 sıra/puanı DB ile eşleşen ardışık yıllar + netler.puanTuru); API geçmişi kod bazlı ve kirli olduğu için (gk2, bazı basariSirasi3) zincirsiz güvenilmez. Yalnız adı değişen ve API penceresinin tamamında sürekliliği olan satır "ad değişimi"dir, geçmişi korunur. - refresh: uyumsuz satır varsa listeler ve HİÇBİR ŞEY yazmadan durur; --kimlik-onar ile isim/tur/onlisans/fakulte/sure API'den yazılır, devir öncesi tüm yıl kolonları NULL'lanır, eski kimlik + geçmişi programs_arsiv'e <kod>~<yıl> id'siyle kopyalanır, eski yılların netleri oraya taşınır, veri_meta'ya kimlik_onar:<kod> kaydı düşer. Önceki-yıl sağlaması onarılanlar dışındaki satırlar için aynen geçerli. --isim-esitle (varsayılan kapalı): kimliği aynı, yalnız burs/dil eki farklı satırlarda isim API'den. - gecmis-doldur: onarılmış satırda devir öncesi yılları hiçbir kaynaktan doldurmaz (yoksa API'nin kod bazlı …N alanları eski programı geri yazar). - veri-kalite: --ham ile ZORUNLU kimlik kontrolü (0 uyumsuz beklenir); --onceki karşılaştırmasında onarılanlar ayrı doğrulanır. - docs/veri/yillik-guncelleme.md: kimlik kilidi adımı. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -4,8 +4,13 @@
|
||||
* UYARI'lar çıkış kodunu etkilemez; insan gözüyle bakılır.
|
||||
*
|
||||
* Kullanım:
|
||||
* pnpm veri-kalite --yil 2026 --db /tmp/deneme.db [--onceki data/yokatlas.db] [--arsivli]
|
||||
* pnpm veri-kalite --yil 2026 --db /tmp/deneme.db [--onceki data/yokatlas.db] [--arsivli] [--ham <dosya>]
|
||||
*
|
||||
* --ham <dosya> Ham API çekimi (yoksa data/ham altındaki en yenisi). Verilirse
|
||||
* KİMLİK kontrolü koşar: her DB satırının puan türü / önlisans /
|
||||
* baz ad slug'ı aynı koddaki API kaydıyla uyuşmalı (0 beklenir;
|
||||
* uyuşmayan = kılavuz kodu başka programa devredilmiş, satır eski
|
||||
* adıyla yeni programın sırasını gösteriyor → refresh --kimlik-onar).
|
||||
* --onceki <db> Güncelleme ÖNCESİ DB. Verilirse: iki DB'de de bulunan her
|
||||
* programın önceki yıl kolonları (sira/puan/kontenjan/yerlesen)
|
||||
* birebir aynı mı (dolu değer değişmiş/silinmiş olamaz).
|
||||
@@ -14,6 +19,8 @@
|
||||
import path from "node:path";
|
||||
import { EFEKTIF_SIRA_SQL, ILK_YIL } from "../src/lib/veri-yillari";
|
||||
import { EFEKTIF_INDEKS, efektifSorguPlani } from "./db-index";
|
||||
import { kimlikFarklari, onarimKayitlari, yilKolonlari, type DbKimlik } from "./kimlik";
|
||||
import { hamOku, sonHamDosya, TUR_MAP } from "./refresh";
|
||||
import {
|
||||
argOku,
|
||||
bayrakVar,
|
||||
@@ -164,6 +171,52 @@ async function main() {
|
||||
);
|
||||
bilgi("eski_kod paylaşımı (bölünme)", eskiKodCakisma > 0, `birden çok güncel programın işaret ettiği eski kod: ${eskiKodCakisma}`);
|
||||
|
||||
// 9b. Kimlik: kod aynı, program aynı mı? (ham çekim gerekir)
|
||||
const hamArg = argOku("ham");
|
||||
const hamDosya = hamArg
|
||||
? path.resolve(process.cwd(), hamArg)
|
||||
: sonHamDosya(yil, path.resolve(process.cwd(), "data", "ham"));
|
||||
const onarimlar = onarimKayitlari(db);
|
||||
if (hamDosya) {
|
||||
const { kayitlar } = await hamOku(hamDosya, yil);
|
||||
const getir = db.prepare("SELECT id, isim, tur, onlisans, fakulte, sure, universite FROM programs WHERE id = ?");
|
||||
const uyumsuz: string[] = [];
|
||||
let kiyaslanan = 0;
|
||||
let ekFarki = 0;
|
||||
for (const r of kayitlar) {
|
||||
if (!r.kilavuzKodu) continue;
|
||||
const satir = getir.get(String(r.kilavuzKodu)) as (DbKimlik & { universite: string }) | undefined;
|
||||
if (!satir) continue;
|
||||
kiyaslanan++;
|
||||
const farklar = kimlikFarklari(satir, r, TUR_MAP[r.puanTuru?.trim() ?? ""]);
|
||||
if (farklar.length) {
|
||||
uyumsuz.push(`${satir.id}[${farklar.join("+")}] DB="${satir.isim}"/${satir.tur}/${satir.onlisans} API="${r.birimAdi}"/${r.puanTuru}/${r.birimTuruId === 47 ? 1 : 0} (${satir.universite})`);
|
||||
} else if (satir.isim !== r.birimAdi) {
|
||||
ekFarki++;
|
||||
}
|
||||
}
|
||||
zorunlu(
|
||||
"kimlik (tur/önlisans/baz ad = API)",
|
||||
uyumsuz.length === 0,
|
||||
`${kiyaslanan} satır kıyaslandı, uyumsuz: ${uyumsuz.length}${uyumsuz.length ? ` ör. ${uyumsuz.slice(0, 3).join(" ; ")}` : ""} [${path.basename(hamDosya)}]`
|
||||
);
|
||||
bilgi("isim eki (burs/dil) API'den farklı", false, `${ekFarki} satır — kimlik aynı, DB adı eksiz/farklı yazımlı (aynı üniversitede adaş varyantlar ayırt edilemez)`);
|
||||
} else {
|
||||
bilgi("kimlik", true, "ATLANDI: --ham verilmedi ve data/ham altında çekim yok; kod devri denetlenemedi");
|
||||
}
|
||||
if (onarimlar.size) {
|
||||
// Onarılan satırda devir öncesi hiçbir yıl kolonu dolu kalmış olamaz.
|
||||
const yk = yilKolonlari(db);
|
||||
let sizinti = 0;
|
||||
const satirGetir = db.prepare("SELECT * FROM programs WHERE id = ?");
|
||||
for (const [id, k] of onarimlar) {
|
||||
const s = satirGetir.get(id) as Record<string, unknown> | undefined;
|
||||
if (!s) continue;
|
||||
if (yk.some((c) => c.yil < k.devirYili && s[c.kolon] != null)) sizinti++;
|
||||
}
|
||||
zorunlu("kimliği onarılanlar: devir öncesi boş", sizinti === 0, `${onarimlar.size} onarılmış satırın ${sizinti} tanesinde devir öncesi yıl yeniden dolmuş`);
|
||||
}
|
||||
|
||||
// 10. netler
|
||||
if (tabloVar(db, "netler")) {
|
||||
const arsivVar = tabloVar(db, "programs_arsiv");
|
||||
@@ -202,11 +255,29 @@ async function main() {
|
||||
const kaybolan = sayi(`SELECT COUNT(*) AS n FROM eski.programs e WHERE e.id NOT IN (SELECT id FROM ${simdi})`);
|
||||
zorunlu("önceki DB: satır kaybı", kaybolan === 0, `önceki DB'de olup şimdi ne programs ne arşivde olan: ${kaybolan}`);
|
||||
// Dolu değer değişmiş ya da NULL'a dönmüş olamaz; NULL → dolu (gecmis-doldur) serbest.
|
||||
// Kimliği onarılan satırlarda devir öncesi yıllar BİLEREK NULL'landı; onlar
|
||||
// ayrı sayılır: devir yılı ve sonrası yine birebir korunmuş olmalı.
|
||||
const onarilanJson = JSON.stringify([...onarimlar.keys()]);
|
||||
const degisen = sayi(
|
||||
`SELECT COUNT(*) AS n FROM eski.programs e JOIN ${simdi} s ON s.id = e.id
|
||||
WHERE ${ortak.map((k) => `(e.${k} IS NOT NULL AND e.${k} IS NOT s.${k})`).join(" OR ")}`
|
||||
WHERE e.id NOT IN (SELECT value FROM json_each(?))
|
||||
AND (${ortak.map((k) => `(e.${k} IS NOT NULL AND e.${k} IS NOT s.${k})`).join(" OR ")})`,
|
||||
onarilanJson
|
||||
);
|
||||
zorunlu("önceki DB: geçmiş yıllar korunmuş", degisen === 0, `${ortak.length} kolonda dolu değeri değişen/silinen satır: ${degisen}`);
|
||||
zorunlu("önceki DB: geçmiş yıllar korunmuş", degisen === 0, `${ortak.length} kolonda dolu değeri değişen/silinen satır: ${degisen} (kimliği onarılan ${onarimlar.size} satır hariç)`);
|
||||
if (onarimlar.size) {
|
||||
let bozulan = 0;
|
||||
const eskiGetir = db.prepare("SELECT * FROM eski.programs WHERE id = ?");
|
||||
const yeniGetir = db.prepare("SELECT * FROM main.programs WHERE id = ?");
|
||||
for (const [id, k] of onarimlar) {
|
||||
const e = eskiGetir.get(id) as Record<string, unknown> | undefined;
|
||||
const s = yeniGetir.get(id) as Record<string, unknown> | undefined;
|
||||
if (!e || !s) continue;
|
||||
const korunmali = ortak.filter((kolon) => Number(kolon.slice(-4)) >= k.devirYili);
|
||||
if (korunmali.some((kolon) => e[kolon] != null && e[kolon] !== s[kolon])) bozulan++;
|
||||
}
|
||||
zorunlu("önceki DB: onarılanlarda devir sonrası korunmuş", bozulan === 0, `devir yılı ve sonrasında değeri değişen onarılmış satır: ${bozulan}/${onarimlar.size}`);
|
||||
}
|
||||
const toplamlar = (tablo: string) =>
|
||||
(db
|
||||
.prepare(
|
||||
|
||||
Reference in New Issue
Block a user