diff --git a/data/app.db b/data/app.db index 2438917..92847bb 100644 Binary files a/data/app.db and b/data/app.db differ diff --git a/data/yokatlas.db b/data/yokatlas.db index 591c0b6..50f932d 100644 Binary files a/data/yokatlas.db and b/data/yokatlas.db differ diff --git a/data/yokatlas.db-shm b/data/yokatlas.db-shm index fe9ac28..72e3de7 100644 Binary files a/data/yokatlas.db-shm and b/data/yokatlas.db-shm differ diff --git a/data/yokatlas.db-wal b/data/yokatlas.db-wal index e69de29..caa4552 100644 Binary files a/data/yokatlas.db-wal and b/data/yokatlas.db-wal differ diff --git a/package.json b/package.json index 8714076..81a7da1 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "eslint", "ingest": "tsx scripts/ingest.ts", "refresh": "tsx scripts/refresh.ts", + "detay": "tsx scripts/detay.ts", "logolar": "tsx scripts/logo-indir.ts" }, "dependencies": { diff --git a/scripts/detay.ts b/scripts/detay.ts new file mode 100644 index 0000000..502b3c0 --- /dev/null +++ b/scripts/detay.ts @@ -0,0 +1,267 @@ +/** + * Canlı YÖK Atlas API'sinden program detay verilerini çekip DB'ye işler: + * + * 1. Kontenjan türü kırılımı (son yerleştirme yılı, ör. 2025): + * Genel / Okul Birincisi / Şehit-Gazi / Deprem / 34 Yaş Üstü Kadın + * için kontenjan + yerleşen. Kaynak: POST /api/tercih-kilavuz/search + * (gk1/gkY1, obk1/obkY1, sgy1/sgyY1, dprm1/dprmY1, y34_1/y34Y1 — + * "1" son yerleştirme yılı demek; kayıttaki `yil` kılavuz yılıdır, + * veri yılı = yil - 1). + * + * 2. Yerleşen son kişinin netleri (yıl bazında, tüm puan türleri): + * Kaynak: POST /api/netler/search → `netler` tablosu. + * + * Not: YÖK Atlas 2026'da SPA'ya geçti; eski content/lisans-dynamic/*.php + * panelleri (ve onlarla birlikte kadın-erkek dağılımı) yayından kalktı. + * Cinsiyet verisi yeni API'lerin hiçbirinde yok. + * + * Kullanım: npx tsx scripts/detay.ts + */ +import path from "node:path"; +import Database from "better-sqlite3"; + +const KILAVUZ_API = "https://yokatlas.yok.gov.tr/api/tercih-kilavuz/search"; +const NETLER_API = "https://yokatlas.yok.gov.tr/api/netler/search"; +const PAGE_SIZE = 500; +const DELAY_MS = 300; + +type KilavuzRecord = { + kilavuzKodu: number; + eskiKilavuzKodu?: number | null; + yil: number; + gk1?: number | null; + gkY1?: number | null; + obk1?: number | null; + obkY1?: number | null; + sgy1?: number | null; + sgyY1?: number | null; + dprm1?: number | null; + dprmY1?: number | null; + y34_1?: number | null; + y34Y1?: number | null; +}; + +type NetlerRecord = { + kilavuzKodu: number; + yil: number; + puanTuru: string | null; + tabanPuan?: number | null; + obp?: number | null; + katsayi?: number | null; + tytTrkNet?: number | null; + tytSosNet?: number | null; + tytMatNet?: number | null; + tytFenNet?: number | null; + aytMatNet?: number | null; + aytFizNet?: number | null; + aytKimNet?: number | null; + aytBioNet?: number | null; + aytTdeNet?: number | null; + aytTrh1Net?: number | null; + aytCog1Net?: number | null; + aytTrh2Net?: number | null; + aytCog2Net?: number | null; + aytDinNet?: number | null; + aytFelNet?: number | null; + ydtYdilNet?: number | null; +}; + +type ApiPage = { content: T[]; totalPages: number; totalElements: number }; + +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +async function fetchPage(api: string, page: number): Promise> { + const res = await fetch(api, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + filters: {}, + page, + size: PAGE_SIZE, + sortBy: "kilavuzKodu", + direction: "ASC", + }), + }); + if (!res.ok) throw new Error(`${api} ${res.status} (sayfa ${page})`); + return (await res.json()) as ApiPage; +} + +function num(v: unknown): number | null { + return typeof v === "number" && Number.isFinite(v) ? v : null; +} + +const NET_COLS = [ + "puanTuru", + "tabanPuan", + "obp", + "katsayi", + "tytTrkNet", + "tytSosNet", + "tytMatNet", + "tytFenNet", + "aytMatNet", + "aytFizNet", + "aytKimNet", + "aytBioNet", + "aytTdeNet", + "aytTrh1Net", + "aytCog1Net", + "aytTrh2Net", + "aytCog2Net", + "aytDinNet", + "aytFelNet", + "ydtYdilNet", +] as const; + +async function main() { + const db = new Database(path.join(process.cwd(), "data", "yokatlas.db")); + db.pragma("journal_mode = WAL"); + + // ── 1. Kontenjan türü kırılımı ───────────────────────────────────────── + const first = await fetchPage(KILAVUZ_API, 0); + // API kaydındaki yil kılavuz yılı; "1" ekli alanlar bir önceki + // yerleştirme dönemine ait (2026 kılavuzu → 2025 verisi) + const veriYili = first.content[0].yil - 1; + + const kontCols = [ + `gk${veriYili}`, + `gkY${veriYili}`, + `obk${veriYili}`, + `obkY${veriYili}`, + `sgy${veriYili}`, + `sgyY${veriYili}`, + `dprm${veriYili}`, + `dprmY${veriYili}`, + `y34k${veriYili}`, + `y34kY${veriYili}`, + ]; + const mevcut = new Set( + (db.prepare("PRAGMA table_info(programs)").all() as { name: string }[]).map( + (c) => c.name + ) + ); + for (const col of kontCols) { + if (!mevcut.has(col)) { + db.exec(`ALTER TABLE programs ADD COLUMN ${col} INTEGER`); + } + } + + const updateKont = db.prepare(` + UPDATE programs SET ${kontCols.map((c) => `${c} = ?`).join(", ")} + WHERE id = ? + `); + const exists = db.prepare("SELECT 1 FROM programs WHERE id = ?"); + + // Kılavuz kodu yıllar arasında değişebiliyor (eskiKilavuzKodu); netler + // API'si yeni kodla döndüğü için eşleşen DB id'sini haritada tutuyoruz. + const dbId = new Map(); + let kontUpdated = 0; + let kontMissing = 0; + + const processKilavuz = db.transaction((records: KilavuzRecord[]) => { + for (const r of records) { + if (!r.kilavuzKodu) continue; + let id = String(r.kilavuzKodu); + if (!exists.get(id)) { + const eski = r.eskiKilavuzKodu ? String(r.eskiKilavuzKodu) : null; + if (eski && exists.get(eski)) { + id = eski; + } else { + kontMissing++; + continue; + } + } + dbId.set(r.kilavuzKodu, id); + updateKont.run( + num(r.gk1), + num(r.gkY1), + num(r.obk1), + num(r.obkY1), + num(r.sgy1), + num(r.sgyY1), + num(r.dprm1), + num(r.dprmY1), + num(r.y34_1), + num(r.y34Y1), + id + ); + kontUpdated++; + } + }); + + console.log( + `Kontenjan kırılımı (${veriYili}): ${first.totalElements} kayıt, ${first.totalPages} sayfa…` + ); + processKilavuz(first.content); + for (let p = 1; p < first.totalPages; p++) { + await sleep(DELAY_MS); + processKilavuz((await fetchPage(KILAVUZ_API, p)).content); + process.stdout.write(`\rkontenjan sayfa ${p + 1}/${first.totalPages}`); + } + console.log( + `\nKontenjan kırılımı: ${kontUpdated} güncellendi, ${kontMissing} DB'de yok` + ); + + // ── 2. Yerleşen son kişinin netleri ──────────────────────────────────── + db.exec(` + CREATE TABLE IF NOT EXISTS netler ( + program_id TEXT NOT NULL, + yil INTEGER NOT NULL, + ${NET_COLS.map((c) => `${c} ${c === "puanTuru" ? "TEXT" : "REAL"}`).join( + ",\n " + )}, + PRIMARY KEY (program_id, yil) + ); + `); + + const insertNet = db.prepare(` + INSERT OR REPLACE INTO netler (program_id, yil, ${NET_COLS.join(", ")}) + VALUES (${["?", "?", ...NET_COLS.map(() => "?")].join(", ")}) + `); + + let netRows = 0; + const processNetler = db.transaction((records: NetlerRecord[]) => { + for (const r of records) { + if (!r.kilavuzKodu || !r.yil) continue; + const id = dbId.get(r.kilavuzKodu) ?? String(r.kilavuzKodu); + insertNet.run( + id, + r.yil, + r.puanTuru?.trim() ?? null, + ...NET_COLS.slice(1).map((c) => num(r[c as keyof NetlerRecord])) + ); + netRows++; + } + }); + + const netFirst = await fetchPage(NETLER_API, 0); + console.log( + `Netler: ${netFirst.totalElements} kayıt, ${netFirst.totalPages} sayfa…` + ); + processNetler(netFirst.content); + for (let p = 1; p < netFirst.totalPages; p++) { + await sleep(DELAY_MS); + processNetler((await fetchPage(NETLER_API, p)).content); + process.stdout.write(`\rnetler sayfa ${p + 1}/${netFirst.totalPages}`); + } + console.log(`\nNetler: ${netRows} satır yazıldı`); + + const ozet = db + .prepare( + `SELECT + (SELECT COUNT(*) FROM programs WHERE gkY${veriYili} IS NOT NULL) AS kontlu, + (SELECT COUNT(*) FROM netler) AS net, + (SELECT COUNT(DISTINCT program_id) FROM netler) AS netli_program` + ) + .get() as { kontlu: number; net: number; netli_program: number }; + console.log( + `Özet → kontenjan kırılımı olan program: ${ozet.kontlu}, ` + + `net satırı: ${ozet.net} (${ozet.netli_program} program)` + ); + db.close(); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/app/listem/listem-sekmeleri.tsx b/src/app/listem/listem-sekmeleri.tsx index f05ef03..6f15506 100644 --- a/src/app/listem/listem-sekmeleri.tsx +++ b/src/app/listem/listem-sekmeleri.tsx @@ -1,36 +1,85 @@ "use client"; +import { useState } from "react"; import { ListChecks, Sparkles } from "lucide-react"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Tabs as TabsPrimitive } from "radix-ui"; import { SecimlerimPaneli } from "@/components/manuel-liste/secimlerim-paneli"; +import { useManuelListe } from "@/components/manuel-liste/store"; export type ListemSekme = "secimlerim" | "ai"; export function ListemSekmeleri({ varsayilan, aiIcerik, + aiHazir, }: { varsayilan: ListemSekme; aiIcerik: React.ReactNode; + aiHazir?: boolean; }) { + const [sekme, setSekme] = useState(varsayilan); + const liste = useManuelListe(); + + function degistir(deger: string) { + const yeni = deger as ListemSekme; + setSekme(yeni); + const url = new URL(window.location.href); + url.searchParams.set("sekme", yeni); + window.history.replaceState(null, "", url); + } + return ( - - - + + + + - Seçtiklerim - - + Kendi Listem + {liste.length > 0 ? ( + + {liste.length} + + ) : null} + + AI Listem - - - + {aiHazir ? ( + + ) : null} + + + - - + + {aiIcerik} - - + + ); } diff --git a/src/app/listem/page.tsx b/src/app/listem/page.tsx index 14da066..73e4509 100644 --- a/src/app/listem/page.tsx +++ b/src/app/listem/page.tsx @@ -85,7 +85,7 @@ export default async function ListemPage({

Sıralamanı girip birkaç adımda 24 tercihlik risk analizli listeni kur; sonra burada görüntüleyip AI danışmanla üzerinde konuşabilirsin. - Seçtiklerim sekmesinde ise tablolardan eklediğin programlar durur. + Kendi Listem sekmesinde ise tablolardan eklediğin programlar durur.

+ ) : ( + <> + + Nasıl çalışır? + + + Karşılaştır + + + )}