feat: tadımlık havuzuna üniversite tipi boyutu
Tadımlık satırı batch üretildiği için sihirbazdaki devlet/vakıf seçimini bilmiyor, vakıf seçen kullanıcıya devlet örneği gösteriyordu. Havuz anahtarı kova×tür×kategori×tip'e genişledi (migration 0001; mevcut satırlar tip='genel'). Üretim scripti tip varyantlarını uniturGrubu filtresiyle seçer; tadimlikSec tip verildiğinde kategori→genel fallback'i korur ama tip fallback'i YAPMAZ — uygun satır yoksa bileşen gizlenir, yanlış tipte örnek asla gösterilmez. Client profildeki tipi de gönderir ve SSR "genel" satırı tipe uymuyorsa fetch bitene kadar gizler. Not: prod app.db volume'de — `pnpm tadimlik` prod DB'ye karşı ayrıca koşulmalı. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
/**
|
||||
* Tadımlık havuzu batch üretimi: kova × puan türü × (kategori + "genel")
|
||||
* kombinasyonları için 1'er örnek tercih satırı (gerekce/riskNotu/trendOzeti)
|
||||
* üretip app.db'deki tadimlik_havuzu tablosuna yazar.
|
||||
* Tadımlık havuzu batch üretimi: kova × puan türü × (kategori + "genel") ×
|
||||
* (üniversite tipi: genel/devlet/vakıf) kombinasyonları için 1'er örnek
|
||||
* tercih satırı (gerekce/riskNotu/trendOzeti) üretip app.db'deki
|
||||
* tadimlik_havuzu tablosuna yazar.
|
||||
*
|
||||
* pnpm tadimlik # boş anahtarları doldurur (idempotent)
|
||||
* pnpm tadimlik --force # mevcut satırları da yeniden üretir
|
||||
* pnpm tadimlik --kova=10-50k --tur=say --kategori=muhendislik-teknoloji
|
||||
* pnpm tadimlik --tip=vakif # yalnızca vakıf varyantları
|
||||
* pnpm tadimlik --mock # anahtar olsa da mock metin (dev/test)
|
||||
* pnpm tadimlik --bekleme=13000 # LLM çağrıları arası bekleme (ms)
|
||||
*
|
||||
@@ -24,10 +26,16 @@ import {
|
||||
type DilimKey,
|
||||
type Program,
|
||||
type PuanTuruKey,
|
||||
type UniturGrubu,
|
||||
} from "../src/types/yokatlas";
|
||||
import { KATEGORILER, kategoriEslesir } from "../src/lib/kategoriler";
|
||||
import { havuzuKompaktJson, turLabel } from "../src/lib/rapor-havuzu";
|
||||
import { GENEL_KATEGORI, KOVALAR, kategoriSlugBul } from "../src/lib/tadimlik-havuzu";
|
||||
import {
|
||||
GENEL_KATEGORI,
|
||||
KOVALAR,
|
||||
kategoriSlugBul,
|
||||
type TadimlikTip,
|
||||
} from "../src/lib/tadimlik-havuzu";
|
||||
import { anahtarVar } from "../src/lib/ai/client";
|
||||
import { yapilandirilmisUret } from "../src/lib/ai/cagri";
|
||||
|
||||
@@ -95,18 +103,22 @@ function programBilgisi(p: Program) {
|
||||
|
||||
/**
|
||||
* Deterministik program seçimi: kovanın temsili sırası için "dengeli" dilimden
|
||||
* (boşsa "garanti") kategoriye uyan, 2025'te en çok yerleşen alan program.
|
||||
* Tanınır/dolu programlar tadımlıkta daha ikna edici.
|
||||
* (boşsa "garanti") kategoriye ve üniversite tipine uyan, 2025'te en çok
|
||||
* yerleşen alan program. Tanınır/dolu programlar tadımlıkta daha ikna edici.
|
||||
*/
|
||||
function programSec(
|
||||
temsiliSira: number,
|
||||
tur: PuanTuruKey,
|
||||
kategori: string | null,
|
||||
uniturGrubu?: UniturGrubu,
|
||||
): { program: Program; dilim: DilimKey } | null {
|
||||
for (const dilim of ["dengeli", "garanti"] as const) {
|
||||
const { programlar } = dilimAra(temsiliSira, tur, dilim, { limit: 60 });
|
||||
const { programlar } = dilimAra(temsiliSira, tur, dilim, {
|
||||
limit: 60,
|
||||
uniturGrubu,
|
||||
});
|
||||
const uygunlar = kategori
|
||||
? programlar.filter((p) => kategoriEslesir(p.isim, [kategori]))
|
||||
? programlar.filter((p) => kategoriEslesir(p.isim, [kategori], p.fakulte))
|
||||
: programlar;
|
||||
if (uygunlar.length === 0) continue;
|
||||
const secilen = [...uygunlar].sort(
|
||||
@@ -152,6 +164,7 @@ async function main() {
|
||||
const kovaFiltre = bayrak("kova");
|
||||
const turFiltre = bayrak("tur");
|
||||
const kategoriFiltre = bayrak("kategori");
|
||||
const tipFiltre = bayrak("tip");
|
||||
|
||||
const turlar = (Object.keys(PUAN_TURLERI) as PuanTuruKey[]).filter(
|
||||
(t) => !turFiltre || t === turFiltre,
|
||||
@@ -161,6 +174,9 @@ async function main() {
|
||||
...KATEGORILER.map((ad) => ({ ad: ad as string, slug: kategoriSlugBul(ad) })),
|
||||
].filter((k) => !kategoriFiltre || k.slug === kategoriFiltre);
|
||||
const kovalar = KOVALAR.filter((k) => !kovaFiltre || k.slug === kovaFiltre);
|
||||
const tipler = (["genel", "devlet", "vakif"] as TadimlikTip[]).filter(
|
||||
(t) => !tipFiltre || t === tipFiltre,
|
||||
);
|
||||
|
||||
const mevcutlar = new Set(
|
||||
(
|
||||
@@ -169,9 +185,10 @@ async function main() {
|
||||
kovaSlug: schema.tadimlikHavuzu.kovaSlug,
|
||||
tur: schema.tadimlikHavuzu.tur,
|
||||
kategoriSlug: schema.tadimlikHavuzu.kategoriSlug,
|
||||
tip: schema.tadimlikHavuzu.tip,
|
||||
})
|
||||
.from(schema.tadimlikHavuzu)
|
||||
).map((s) => `${s.kovaSlug}|${s.tur}|${s.kategoriSlug}`),
|
||||
).map((s) => `${s.kovaSlug}|${s.tur}|${s.kategoriSlug}|${s.tip}`),
|
||||
);
|
||||
|
||||
if (mockAktif()) {
|
||||
@@ -185,55 +202,46 @@ async function main() {
|
||||
for (const kova of kovalar) {
|
||||
for (const tur of turlar) {
|
||||
for (const kategori of kategoriler) {
|
||||
const anahtar = `${kova.slug}|${tur}|${kategori.slug}`;
|
||||
if (!force && mevcutlar.has(anahtar)) {
|
||||
atlananMevcut++;
|
||||
continue;
|
||||
}
|
||||
for (const tip of tipler) {
|
||||
const anahtar = `${kova.slug}|${tur}|${kategori.slug}|${tip}`;
|
||||
if (!force && mevcutlar.has(anahtar)) {
|
||||
atlananMevcut++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const secim = programSec(kova.temsiliSira, tur, kategori.ad);
|
||||
if (!secim) {
|
||||
atlananBos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const metin = await metinUret(
|
||||
secim.program,
|
||||
secim.dilim,
|
||||
kova.temsiliSira,
|
||||
tur,
|
||||
kategori.ad,
|
||||
);
|
||||
if (!metin) {
|
||||
console.warn(`Şemaya uymayan çıktı, atlandı: ${anahtar}`);
|
||||
atlananBos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
await appDb
|
||||
.insert(schema.tadimlikHavuzu)
|
||||
.values({
|
||||
id: randomUUID(),
|
||||
kovaSlug: kova.slug,
|
||||
const secim = programSec(
|
||||
kova.temsiliSira,
|
||||
tur,
|
||||
kategoriSlug: kategori.slug,
|
||||
programId: secim.program.id,
|
||||
dilim: secim.dilim,
|
||||
gerekce: metin.gerekce,
|
||||
riskNotu: metin.riskNotu,
|
||||
trendOzeti: metin.trendOzeti,
|
||||
program: programBilgisi(secim.program),
|
||||
ornekSira: kova.temsiliSira,
|
||||
uretimAt: now,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: [
|
||||
schema.tadimlikHavuzu.kovaSlug,
|
||||
schema.tadimlikHavuzu.tur,
|
||||
schema.tadimlikHavuzu.kategoriSlug,
|
||||
],
|
||||
set: {
|
||||
kategori.ad,
|
||||
tip === "genel" ? undefined : tip,
|
||||
);
|
||||
if (!secim) {
|
||||
atlananBos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const metin = await metinUret(
|
||||
secim.program,
|
||||
secim.dilim,
|
||||
kova.temsiliSira,
|
||||
tur,
|
||||
kategori.ad,
|
||||
);
|
||||
if (!metin) {
|
||||
console.warn(`Şemaya uymayan çıktı, atlandı: ${anahtar}`);
|
||||
atlananBos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
await appDb
|
||||
.insert(schema.tadimlikHavuzu)
|
||||
.values({
|
||||
id: randomUUID(),
|
||||
kovaSlug: kova.slug,
|
||||
tur,
|
||||
kategoriSlug: kategori.slug,
|
||||
tip,
|
||||
programId: secim.program.id,
|
||||
dilim: secim.dilim,
|
||||
gerekce: metin.gerekce,
|
||||
@@ -242,15 +250,35 @@ async function main() {
|
||||
program: programBilgisi(secim.program),
|
||||
ornekSira: kova.temsiliSira,
|
||||
uretimAt: now,
|
||||
},
|
||||
});
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: [
|
||||
schema.tadimlikHavuzu.kovaSlug,
|
||||
schema.tadimlikHavuzu.tur,
|
||||
schema.tadimlikHavuzu.kategoriSlug,
|
||||
schema.tadimlikHavuzu.tip,
|
||||
],
|
||||
set: {
|
||||
programId: secim.program.id,
|
||||
dilim: secim.dilim,
|
||||
gerekce: metin.gerekce,
|
||||
riskNotu: metin.riskNotu,
|
||||
trendOzeti: metin.trendOzeti,
|
||||
program: programBilgisi(secim.program),
|
||||
ornekSira: kova.temsiliSira,
|
||||
uretimAt: now,
|
||||
},
|
||||
});
|
||||
|
||||
uretilen++;
|
||||
console.log(`✓ ${anahtar} → ${secim.program.isim} (${secim.dilim})`);
|
||||
// Gerçek LLM'de rate limit dostu ardışık tempo
|
||||
if (!mockAktif()) {
|
||||
const bekleme = Number.parseInt(bayrak("bekleme") ?? "500", 10);
|
||||
await new Promise((r) => setTimeout(r, bekleme));
|
||||
uretilen++;
|
||||
console.log(
|
||||
`✓ ${anahtar} → ${secim.program.isim} (${secim.dilim})`,
|
||||
);
|
||||
// Gerçek LLM'de rate limit dostu ardışık tempo
|
||||
if (!mockAktif()) {
|
||||
const bekleme = Number.parseInt(bayrak("bekleme") ?? "500", 10);
|
||||
await new Promise((r) => setTimeout(r, bekleme));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user