- Danışman: program_netleri, program_karsilastir ve bolum_istihdam araçları; program_ara'ya devlet/vakıf, öğretim türü ve sıralama parametreleri; program_detay'a trend cümlesi; bozuk araç argümanı modele hata döner. - TÜİK Yükseköğretim İstihdam Göstergeleri 2025: bölüm bazında istihdam oranı, ilk işi bulma süresi ve kazanç grubu (src/lib/bolum-istihdam.ts, üretici scripts/tuik-istihdam-uret.py, kaynak Excel'ler data/kaynak/). - Bölüm sayfasında "Mezun istihdamı" ayrıntı satırı ve alt sayfası; AkademikAyrinti başlık/ikon alır. - Jev (TypeSafe AI) ile danışman cevabında garanti/olasılık dili denetimi; ihlalde cevabın sonuna düzeltme notu. Anahtar yoksa denetim atlanır. - /gizlilik: TypeSafe AI (ABD) sağlayıcı satırı. KARARLAR.md: 5 Ekim kaydı. - Üç katalog dosyasındaki bg-white → bg-card değişiklikleri de bu commit'te. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
169 lines
6.9 KiB
Python
169 lines
6.9 KiB
Python
"""TÜİK Yükseköğretim İstihdam Göstergeleri Excel'lerinden src/lib/bolum-istihdam.ts sabitini üretir.
|
||
|
||
Kullanım (yılda bir, TÜİK bülteni Temmuz'da yayımlayınca):
|
||
python3 scripts/tuik-istihdam-uret.py 2025
|
||
|
||
Girdi: data/kaynak/tuik-istihdam-{lisans,onlisans}-bolum-{istihdam-orani,is-bulma-suresi,kazanc-gruplari}-<YIL>.xls
|
||
(veriportali.tuik.gov.tr → "Yükseköğretim İstihdam Göstergeleri, <YIL>" bülteni →
|
||
Tablolar; tarayıcıyla tek tek indirilir — portal komut satırını engelliyor ve
|
||
art arda istekte boş sayfa dönüyor). Bağımlılık: xlrd.
|
||
|
||
Kurallar:
|
||
- Veri ÜLKE GENELİ ve bölüm bazındadır; üniversite kırılımı TÜİK'te yok.
|
||
- Anahtar: /bolum/[slug] sayfa slug'ı (bolumBazAdi + turkishSlugify). Yalnız
|
||
yokatlas.db'de sayfası olan bölümler yazılır; eşleşmeyenler ekrana basılır.
|
||
Takma ad tablosu yok: eşleşmeyenler henüz mezun vermemiş yeni bölümler ya da
|
||
bizde olmayan özel yetenek / kapanmış bölümlerdir.
|
||
- "." = TÜİK'in yayımlamadığı hücre → null. Mezun sayısı medyanın altındaki
|
||
bölümler tablolarda hiç yok.
|
||
- Kazanç TL değil, TÜİK'in beş kademeli grubudur (Çok düşük … Çok yüksek).
|
||
"""
|
||
|
||
import re
|
||
import sqlite3
|
||
import sys
|
||
import unicodedata
|
||
from pathlib import Path
|
||
|
||
import xlrd
|
||
|
||
KOK = Path(__file__).resolve().parent.parent
|
||
YIL = sys.argv[1] if len(sys.argv) > 1 else "2025"
|
||
BULTEN_URL = "https://veriportali.tuik.gov.tr/tr/press/58027"
|
||
DUZEYLER = (("lisans", 0), ("onlisans", 1))
|
||
KAZANC_GRUPLARI = ("Çok düşük", "Düşük", "Orta", "Yüksek", "Çok yüksek")
|
||
|
||
|
||
def slugify(s: str) -> str:
|
||
# src/lib/slug.ts turkishSlugify ile aynı kural.
|
||
s = s.replace("I", "ı").replace("İ", "i").lower()
|
||
for a, b in [("ı", "i"), ("ş", "s"), ("ğ", "g"), ("ü", "u"), ("ö", "o"),
|
||
("ç", "c"), ("â", "a"), ("î", "i"), ("û", "u")]:
|
||
s = s.replace(a, b)
|
||
s = unicodedata.normalize("NFKD", s)
|
||
s = re.sub(r"[̀-ͯ]", "", s)
|
||
s = re.sub(r"[^a-z0-9]+", "-", s)
|
||
return s.strip("-")
|
||
|
||
|
||
def bolum_slug(isim: str) -> str:
|
||
# src/lib/slug.ts bolumSayfaSlug ile aynı kural.
|
||
return slugify(re.sub(r"\s+", " ", re.sub(r"\s*\([^)]*\)", "", isim)).strip())
|
||
|
||
|
||
def tr_satir(v) -> str:
|
||
"""Hücreler 'Türkçe\\r\\nEnglish' biçiminde; Türkçe satırı al."""
|
||
return str(v).replace("\r", "").split("\n")[0].strip()
|
||
|
||
|
||
def tablo(duzey: str, ad: str) -> tuple[dict[str, tuple[str, object]], object]:
|
||
"""slug → (TÜİK adı, son yıl değeri) ve 'Toplam' satırının değeri."""
|
||
yol = KOK / "data" / "kaynak" / f"tuik-istihdam-{duzey}-bolum-{ad}-{YIL}.xls"
|
||
s = xlrd.open_workbook(yol).sheet_by_index(0)
|
||
baslik = next(r for r in range(s.nrows) if str(s.cell_value(r, 0)).startswith("Bölüm"))
|
||
kolon = next(c for c in range(s.ncols) if str(s.cell_value(baslik, c)).startswith(YIL))
|
||
satirlar: dict[str, tuple[str, object]] = {}
|
||
toplam = None
|
||
for r in range(baslik + 1, s.nrows):
|
||
bolum = tr_satir(s.cell_value(r, 0))
|
||
if not bolum or bolum.startswith(("TÜİK", "TurkStat", ".")):
|
||
continue
|
||
v = s.cell_value(r, kolon)
|
||
if bolum == "Toplam":
|
||
toplam = v
|
||
continue
|
||
satirlar[slugify(bolum)] = (bolum, v)
|
||
return satirlar, toplam
|
||
|
||
|
||
def sayi(v) -> float | None:
|
||
return round(float(v), 1) if isinstance(v, (int, float)) else None
|
||
|
||
|
||
def kazanc(v) -> str | None:
|
||
g = tr_satir(v)
|
||
if g in KAZANC_GRUPLARI:
|
||
return g
|
||
if g not in (".", ""):
|
||
sys.exit(f"Bilinmeyen kazanç grubu: {g!r}")
|
||
return None
|
||
|
||
|
||
def ts(v) -> str:
|
||
if v is None:
|
||
return "null"
|
||
return f'"{v}"' if isinstance(v, str) else str(v)
|
||
|
||
|
||
db = sqlite3.connect(f"file:{KOK / 'data' / 'yokatlas.db'}?mode=ro", uri=True)
|
||
bloklar: list[str] = []
|
||
ortalamalar: list[str] = []
|
||
for duzey, onlisans in DUZEYLER:
|
||
sayfalar = {bolum_slug(i) for (i,) in db.execute("SELECT DISTINCT isim FROM programs WHERE onlisans = ?", (onlisans,))}
|
||
oran, oran_toplam = tablo(duzey, "istihdam-orani")
|
||
sure, sure_toplam = tablo(duzey, "is-bulma-suresi")
|
||
kaz, _ = tablo(duzey, "kazanc-gruplari")
|
||
hepsi = sorted(set(oran) | set(sure) | set(kaz))
|
||
eslesen = [k for k in hepsi if k in sayfalar]
|
||
eslesmeyen = [(oran.get(k) or sure.get(k) or kaz[k])[0] for k in hepsi if k not in sayfalar]
|
||
print(f"{duzey}: TÜİK {len(hepsi)} bölüm, sayfası olan {len(eslesen)}")
|
||
if eslesmeyen:
|
||
print(" sayfası olmayan (yazılmadı):", ", ".join(eslesmeyen))
|
||
satirlar = []
|
||
for k in eslesen:
|
||
o = sayi(oran[k][1]) if k in oran else None
|
||
s = sayi(sure[k][1]) if k in sure else None
|
||
g = kazanc(kaz[k][1]) if k in kaz else None
|
||
if o is None and s is None and g is None:
|
||
continue
|
||
satirlar.append(f' "{k}": {{ istihdam: {ts(o)}, isBulmaAy: {ts(s)}, kazanc: {ts(g)} }},')
|
||
bloklar.append(f" {duzey}: {{\n" + "\n".join(satirlar) + "\n },")
|
||
ortalamalar.append(f" {duzey}: {{ istihdam: {ts(sayi(oran_toplam))}, isBulmaAy: {ts(sayi(sure_toplam))} }},")
|
||
|
||
cikti = f"""// TÜİK Yükseköğretim İstihdam Göstergeleri {YIL}: bölüm başına mezun istihdamı.
|
||
//
|
||
// ÜRETİLMİŞ DOSYA — elle düzenleme. Kaynak Excel'ler data/kaynak/ altında;
|
||
// yeniden üretmek için: python3 scripts/tuik-istihdam-uret.py {YIL}
|
||
//
|
||
// Veri ÜLKE GENELİ ve bölüm bazındadır: aynı bölümün her üniversitesi için
|
||
// aynı sayı döner; üniversite kırılımı TÜİK'te yok. Kayıtlı istihdam = mezunun
|
||
// SGK kaydıyla çalışıyor olması. Kazanç TL değil, beş kademeli gruptur.
|
||
// Anahtar: /bolum/[slug] sayfa slug'ı. Burada olmayan bölüm ya henüz yeterli
|
||
// mezun vermemiştir ya da TÜİK tablosunda yoktur — "kötü" demek değildir.
|
||
|
||
export const BOLUM_ISTIHDAM_YIL = {YIL};
|
||
|
||
export const BOLUM_ISTIHDAM_KAYNAK = "TÜİK, Yükseköğretim İstihdam Göstergeleri, {YIL}";
|
||
|
||
export const BOLUM_ISTIHDAM_KAYNAK_URL = "{BULTEN_URL}";
|
||
|
||
export type KazancGrubu = {" | ".join(f'"{g}"' for g in KAZANC_GRUPLARI)};
|
||
|
||
export type IstihdamDuzeyi = "lisans" | "onlisans";
|
||
|
||
export type BolumIstihdam = {{
|
||
/** Kayıtlı istihdam oranı (%) */
|
||
istihdam: number | null;
|
||
/** Mezuniyetten sonra ilk işi bulma süresi (ay, ortalama) */
|
||
isBulmaAy: number | null;
|
||
/** Aylık ortalama kazanç grubu (bölümler arası göreli) */
|
||
kazanc: KazancGrubu | null;
|
||
}};
|
||
|
||
/** Tüm mezunların ortalaması — kıyas çizgisi. */
|
||
export const ISTIHDAM_ORTALAMA: Record<IstihdamDuzeyi, {{ istihdam: number | null; isBulmaAy: number | null }}> = {{
|
||
{chr(10).join(ortalamalar)}
|
||
}};
|
||
|
||
export const BOLUM_ISTIHDAM: Record<IstihdamDuzeyi, Record<string, BolumIstihdam>> = {{
|
||
{chr(10).join(bloklar)}
|
||
}};
|
||
|
||
/** Bölüm sayfa slug'ının TÜİK satırı; yoksa null. */
|
||
export function bolumIstihdamBul(slug: string, duzey: IstihdamDuzeyi): BolumIstihdam | null {{
|
||
return BOLUM_ISTIHDAM[duzey][slug] ?? null;
|
||
}}
|
||
"""
|
||
(KOK / "src" / "lib" / "bolum-istihdam.ts").write_text(cikti, encoding="utf-8")
|
||
print("yazıldı: src/lib/bolum-istihdam.ts")
|