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:
@@ -21,11 +21,23 @@
|
||||
* 4. DB yazımı TEK transaction; önceki yılların kolon sağlaması transaction
|
||||
* içinde karşılaştırılır, fark varsa geri alınır.
|
||||
*
|
||||
* 5. KİMLİK KİLİDİ: mevcut satırın puan türü / önlisans bayrağı / baz ad
|
||||
* slug'ı API kaydıyla uyuşmuyorsa kılavuz kodu BAŞKA programa devredilmiştir
|
||||
* (bkz. kimlik.ts). Liste basılır ve HİÇBİR ŞEY yazılmadan durulur;
|
||||
* liste gözle incelendikten sonra `--kimlik-onar` ile yeniden koşulur.
|
||||
*
|
||||
* Yazdıkları: sira/puan/kontenjan/yerlesen<yil>, son_kilavuz_yili, eski_kod.
|
||||
* Mevcut satırların ÖNCEKİ yıl kolonlarına dokunmaz (onları yalnız NULL ise
|
||||
* gecmis-doldur.ts doldurur). Yeni program eklerken `…1/…2/…3` alanlarından
|
||||
* önceki üç yılı da yazar. API "veri yok" için 0 döndürür → NULL.
|
||||
*
|
||||
* `--kimlik-onar`: kimliği kaymış satırlarda isim/tur/onlisans/fakulte/sure
|
||||
* API'den yazılır; devirden ÖNCEKİ yılların tüm kolonları NULL'lanır (o geçmiş
|
||||
* başka programa ait), eski kimlik + geçmişi `programs_arsiv`'e kopyalanır.
|
||||
* `--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.
|
||||
*
|
||||
* Kaynağa nazik: sayfa başı 500 kayıt, istekler arası 0,5 sn, tek bağlantı.
|
||||
* `--ham <dosya>` verilirse API'ye HİÇ gidilmez; daha önce kaydedilmiş ham
|
||||
* dosyadan aynı işlem tekrarlanır (kopyada deneme → gerçek DB'ye uygulama).
|
||||
@@ -40,9 +52,20 @@ import readline from "node:readline";
|
||||
import zlib from "node:zlib";
|
||||
import { ILK_YIL } from "../src/lib/veri-yillari";
|
||||
import { efektifIndeksiKur } from "./db-index";
|
||||
import {
|
||||
apiOnlisans,
|
||||
devirYiliBul,
|
||||
kimlikFarklari,
|
||||
kimlikOnar,
|
||||
netTurleriGetir,
|
||||
type DbKimlik,
|
||||
type KimlikFarki,
|
||||
type OnarimKaydi,
|
||||
} from "./kimlik";
|
||||
import { uniAdiNormalize, uniturOnar } from "./unitur-onar";
|
||||
import {
|
||||
argOku,
|
||||
bayrakVar,
|
||||
calistir,
|
||||
dbAc,
|
||||
dbArgumani,
|
||||
@@ -371,7 +394,13 @@ async function apidenCek(
|
||||
return { toplam: first.totalElements, kayitlar, hamDosya };
|
||||
}
|
||||
|
||||
function oncekiYilSaglamasi(db: Db, yil: number, azamiRowid: number): string {
|
||||
/** Önceki yıl kolonlarının sağlaması; `haricIdler` (kimliği onarılanlar) dışarıda. */
|
||||
function oncekiYilSaglamasi(
|
||||
db: Db,
|
||||
yil: number,
|
||||
azamiRowid: number,
|
||||
haricIdler: string[]
|
||||
): string {
|
||||
const kolonlar: string[] = [];
|
||||
for (let y = ILK_YIL; y < yil; y++) {
|
||||
for (const olcu of ["sira", "puan", "kontenjan", "yerlesen"]) {
|
||||
@@ -379,9 +408,12 @@ function oncekiYilSaglamasi(db: Db, yil: number, azamiRowid: number): string {
|
||||
}
|
||||
}
|
||||
const r = db
|
||||
.prepare(`SELECT COUNT(*), ${kolonlar.join(", ")} FROM programs WHERE rowid <= ?`)
|
||||
.prepare(
|
||||
`SELECT COUNT(*), ${kolonlar.join(", ")} FROM programs
|
||||
WHERE rowid <= ? AND id NOT IN (SELECT value FROM json_each(?))`
|
||||
)
|
||||
.raw()
|
||||
.get(azamiRowid) as number[];
|
||||
.get(azamiRowid, JSON.stringify(haricIdler)) as number[];
|
||||
return r.join("|");
|
||||
}
|
||||
|
||||
@@ -497,15 +529,124 @@ async function main() {
|
||||
return mevcut || api;
|
||||
};
|
||||
|
||||
// ── Kimlik kilidi: kod aynı, program aynı mı? ──────────────────────────
|
||||
const kimlikOnarIste = bayrakVar("kimlik-onar");
|
||||
const isimEsitleIste = bayrakVar("isim-esitle");
|
||||
const kimlikGetir = db.prepare("SELECT * FROM programs WHERE id = ?");
|
||||
type Uyumsuz = {
|
||||
id: string;
|
||||
r: KilavuzKaydi;
|
||||
satir: DbKimlik & Record<string, unknown>;
|
||||
farklar: KimlikFarki[];
|
||||
apiTur: string | undefined;
|
||||
devirYili: number;
|
||||
};
|
||||
const uyumsuzlar: Uyumsuz[] = [];
|
||||
const ekFarkliIdler: string[] = [];
|
||||
for (const [id, r] of benzersiz) {
|
||||
const satir = kimlikGetir.get(id) as Uyumsuz["satir"] | undefined;
|
||||
if (!satir) continue;
|
||||
const apiTur = TUR_MAP[r.puanTuru?.trim() ?? ""];
|
||||
const farklar = kimlikFarklari(satir, r, apiTur);
|
||||
if (farklar.length) {
|
||||
uyumsuzlar.push({
|
||||
id,
|
||||
r,
|
||||
satir,
|
||||
farklar,
|
||||
apiTur,
|
||||
devirYili: devirYiliBul(
|
||||
satir,
|
||||
{
|
||||
gecmis: gecmisAlanlari(r),
|
||||
netTurleri: netTurleriGetir(db, id, TUR_MAP),
|
||||
eskiTur: satir.tur,
|
||||
yeniTur: apiTur ?? satir.tur,
|
||||
farklar,
|
||||
},
|
||||
yil
|
||||
),
|
||||
});
|
||||
} else if (satir.isim !== r.birimAdi) {
|
||||
ekFarkliIdler.push(id);
|
||||
}
|
||||
}
|
||||
if (uyumsuzlar.length) {
|
||||
console.log(`\nKİMLİĞİ KAYMIŞ ${uyumsuzlar.length} satır (kod aynı, program farklı):`);
|
||||
for (const u of uyumsuzlar) {
|
||||
const gecmis = [];
|
||||
for (let y = ILK_YIL; y <= yil; y++) gecmis.push(u.satir[`sira${y}`] ?? "-");
|
||||
const netOzet = [...netTurleriGetir(db, u.id, TUR_MAP)]
|
||||
.sort((a, b) => a[0] - b[0])
|
||||
.map(([y, t]) => `${y}=${t}`)
|
||||
.join(" ");
|
||||
console.log(
|
||||
` ${u.id} [${u.farklar.join("+")}] ${u.satir.universite}\n` +
|
||||
` DB : ${u.satir.isim} | ${u.satir.tur} | onlisans=${u.satir.onlisans} | ${u.satir.fakulte}\n` +
|
||||
` API: ${u.r.birimAdi} | ${u.apiTur ?? `?(${u.r.puanTuru})`} | onlisans=${apiOnlisans(u.r)} | ${u.r.fymkAdi}\n` +
|
||||
` sira ${ILK_YIL}…${yil}: ${gecmis.join(", ")} | net türleri: ${netOzet || "-"} → ` +
|
||||
(u.devirYili === ILK_YIL
|
||||
? "AD DEĞİŞİMİ (API penceresinin tamamı aynı program; geçmiş korunur)"
|
||||
: `devir yılı ${u.devirYili} (öncesi silinir)`)
|
||||
);
|
||||
}
|
||||
const tursuz = uyumsuzlar.filter((u) => !u.apiTur);
|
||||
if (!kimlikOnarIste) {
|
||||
dur(
|
||||
`${uyumsuzlar.length} satırın kimliği API ile uyuşmuyor; DB'ye HİÇBİR ŞEY yazılmadı. ` +
|
||||
"Listeyi incele; devirse aynı komutu --kimlik-onar ile yeniden koş " +
|
||||
"(isim/tur/onlisans/fakulte/sure API'den yazılır, devir öncesi yıllar NULL'lanır, eski kimlik arşive kopyalanır)."
|
||||
);
|
||||
}
|
||||
if (tursuz.length) {
|
||||
dur(
|
||||
`${tursuz.length} uyumsuz satırda API puan türü vermiyor (${tursuz.map((u) => u.id).join(", ")}); ` +
|
||||
"tür UYDURULMAZ, onarım yapılamaz."
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
`Kimlik: ${uyumsuzlar.length} uyumsuz; yalnız adı (burs/dil eki) farklı ${ekFarkliIdler.length} satır` +
|
||||
(isimEsitleIste ? " → isim API'den yazılacak (--isim-esitle)" : " → isim yazılmıyor (--isim-esitle verilmedi)")
|
||||
);
|
||||
|
||||
let updated = 0;
|
||||
let inserted = 0;
|
||||
const atlananlar: string[] = [];
|
||||
const onarimlar: { id: string; universite: string; kayit: OnarimKaydi }[] = [];
|
||||
let isimEsitlenen = 0;
|
||||
|
||||
db.transaction(() => {
|
||||
const azamiRowid = (
|
||||
db.prepare("SELECT COALESCE(MAX(rowid), 0) AS m FROM programs").get() as { m: number }
|
||||
).m;
|
||||
const once = oncekiYilSaglamasi(db, yil, azamiRowid);
|
||||
const onarilanIdler = uyumsuzlar.map((u) => u.id);
|
||||
const once = oncekiYilSaglamasi(db, yil, azamiRowid, onarilanIdler);
|
||||
|
||||
// Kimlik onarımı yıl yazımından ÖNCE: arşiv kopyası eski hâliyle alınır.
|
||||
for (const u of uyumsuzlar) {
|
||||
const universite = String(u.satir.universite);
|
||||
const kayit = kimlikOnar(db, {
|
||||
id: u.id,
|
||||
yil,
|
||||
devirYili: u.devirYili,
|
||||
yeni: {
|
||||
isim: u.r.birimAdi,
|
||||
tur: u.apiTur as string,
|
||||
onlisans: apiOnlisans(u.r),
|
||||
fakulte: fakulteSec(universite, u.r.fymkAdi ?? null),
|
||||
sure: u.r.ogrenimSuresi ?? null,
|
||||
},
|
||||
});
|
||||
onarimlar.push({ id: u.id, universite, kayit });
|
||||
}
|
||||
if (isimEsitleIste) {
|
||||
const isimYaz = db.prepare("UPDATE programs SET isim = ? WHERE id = ?");
|
||||
for (const id of ekFarkliIdler) {
|
||||
isimYaz.run((benzersiz.get(id) as KilavuzKaydi).birimAdi, id);
|
||||
isimEsitlenen++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, r] of benzersiz) {
|
||||
const tur = TUR_MAP[r.puanTuru?.trim() ?? ""];
|
||||
@@ -549,10 +690,10 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
const sonra = oncekiYilSaglamasi(db, yil, azamiRowid);
|
||||
const sonra = oncekiYilSaglamasi(db, yil, azamiRowid, onarilanIdler);
|
||||
if (once !== sonra) {
|
||||
dur(
|
||||
"Mevcut satırların önceki yıl kolonları değişti — bu betik onlara dokunmamalı. Geri alındı.\n" +
|
||||
"Kimliği onarılanlar DIŞINDAKİ satırların önceki yıl kolonları değişti — bu betik onlara dokunmamalı. Geri alındı.\n" +
|
||||
`önce : ${once}\nsonra: ${sonra}`
|
||||
);
|
||||
}
|
||||
@@ -577,6 +718,20 @@ async function main() {
|
||||
`Güncellenen: ${updated}, eklenen: ${inserted}, atlanan: ${kodsuz + atlananlar.length}`
|
||||
);
|
||||
for (const a of atlananlar) console.log(` atlandı: ${a}`);
|
||||
if (isimEsitlenen) console.log(`İsmi API'den eşitlenen (yalnız ek farkı): ${isimEsitlenen}`);
|
||||
if (onarimlar.length) {
|
||||
console.log(`\nKİMLİĞİ ONARILAN ${onarimlar.length} satır:`);
|
||||
console.log(" id | üniversite | eski ad [tür/önlisans] → yeni ad [tür/önlisans] | devir | NULL'lanan yıllar | arşiv | taşınan net yılları");
|
||||
for (const o of onarimlar) {
|
||||
const k = o.kayit;
|
||||
console.log(
|
||||
` ${o.id} | ${o.universite} | ${k.eski.isim} [${k.eski.tur}/${k.eski.onlisans}] → ` +
|
||||
`${k.yeni.isim} [${k.yeni.tur}/${k.yeni.onlisans}] | ${k.devirYili} | ` +
|
||||
`${k.silinenYillar.join(",") || "-"} | ${k.arsivId ?? "-"} | ${k.tasinanNetYillari.join(",") || "-"}`
|
||||
);
|
||||
}
|
||||
metaYaz(db, `kimlik_onar_${yil}_satir`, onarimlar.length);
|
||||
}
|
||||
|
||||
// API'nin universiteTuru döndürmediği kayıtlar devlet/vakıf filtrelerinden
|
||||
// düşmesin: her tazelemeden sonra unitur onarımı koşulur (idempotent).
|
||||
|
||||
Reference in New Issue
Block a user