# -*- coding: utf-8 -*-
"""
Contoso Araç Kiralama — Şube Bazında Günlük Araç Talebi Tahmin Modeli
=====================================================================
YÖNTEM: Şeffaf Çarpımsal Ayrıştırma (açıklanabilir, Excel'de birebir tekrarlanabilir)

    Tahmin(şube, gün) = TABAN(şube, gün) × Π(etken çarpanları)

    TABAN = Seviye_2026(şube) × AyEndeksi(şube, ay) × GünEndeksi(şube, haftanın günü)

Etken çarpanları:
    - tatil        : resmi tatil günü (empirik, şube tipine göre)
    - tatil_oncesi : tatil arifesi 1-2 gün (empirik)
    - okul_tatili  : yaz/okul tatili ek etkisi (mevsim endeksiyle çift saymayı önleyecek şekilde ölçülü)
    - hava         : yağış (şehir walk-in düşürür) + aşırı sıcak (sahil talebini artırır)
    - ucus         : havalimanı uçuş yoğunluğu (yalnız havalimanı şubeleri, uçuş bağlantılı walk-in payına)
    - rez_momentum : net rezervasyon momentumu (brüt − iptal/no-show düzeltmesi)
    - bakim        : bakım penceresi (Sal-Çar) arz düşüşü sinyali
    - kuyruk        : kronik kuyruk kaybı telafisi (gizli talep geri kazanımı)
    - rekabet      : rakip fiyat/müsaitlik PROXY'si (gerçek veri yok — düşük ağırlık)

ÇIKTI: output/veri/*.csv  → Excel, PowerPoint ve HTML pano bu dosyaları okur.

BİRİM NOTU: 'talep' = araç ADEDİ (günlük). Parasal projeksiyonlar TL cinsindendir
            (kategori bazlı ortalama günlük kira × dönüşüm). Tüm parasal değer TL.
"""
import pandas as pd, numpy as np, json, os, unicodedata
from datetime import date, datetime, timedelta

IN   = "input"
OUTV = "output/veri"
os.makedirs(OUTV, exist_ok=True)
pd.options.mode.chained_assignment = None

# ======================================================================
# VARSAYIMLAR — tek noktada, şeffaf. Excel 'Varsayımlar' sayfasında da yer alır.
# ======================================================================
VARS = {
    "tahmin_baslangic": "2026-07-14",
    "tahmin_bitis":     "2026-07-20",
    "bugun":            "2026-07-13",
    # Kategori bazlı ortalama GÜNLÜK kira (TL) — kurgusal sektör varsayımı, Excel'de düzenlenebilir
    "gunluk_kira_TL": {"Ekonomi":900, "Kompakt":1100, "Orta":1500, "SUV":2200, "Premium":3500, "Ticari":1800},
    "iptal_orani":   0.20,   # rezervasyonlar_2026H1: İptal payı
    "noshow_orani":  0.19,   # rezervasyonlar_2026H1: No-show payı
    "kuyruk_esigi":  5,      # otogar kayıp müşteri eşiği (operasyon notu)
    "kuyruk_kayip_orani": 0.12,  # eşik aşan saatlerde kaybedilen walk-in oranı (varsayım)
    "yagis_esigi_mm": 2.0,       # bu değeri aşan yağış 'yağışlı gün' sayılır
    "yagis_walkin_dususu": 0.18, # yağışlı günde şehir şubesi walk-in düşüşü (operasyon notu)
    "asiri_sicak_C": 37.0,       # bu değeri aşan Tmax sahil şubesinde hafif talep artışı
    "asiri_sicak_artis": 0.06,
    "ucus_etki_tavani": 0.10,    # anlık uçuş sinyalinin maks. ± etkisi (zayıf sinyal, sınırlı)
    "rekabet_doluluk_esigi": 0.85, # bu doluluk üstünde rakip/fiyat baskısı proxy'si devreye girer
    "rekabet_etki": 0.04,        # havalimanı için maks. ± etki (proxy, düşük ağırlık)
    "senaryo_z": 1.28,           # %80 güven aralığı (±1.28σ) — senaryo bandı backtest hatasından ölçeklenir
    "backtest_holdout_gun": 90,  # son 90 gün geri-test için ayrılır
}

TR_GUN = {0:"Pzt",1:"Sal",2:"Çar",3:"Per",4:"Cum",5:"Cmt",6:"Paz"}
TR_AY  = {1:"Oca",2:"Şub",3:"Mar",4:"Nis",5:"May",6:"Haz",7:"Tem",8:"Ağu",9:"Eyl",10:"Eki",11:"Kas",12:"Ara"}
KATEGORILER = ["Ekonomi","Kompakt","Orta","SUV","Premium","Ticari"]

def norm(s):
    if not isinstance(s,str): return s
    return "".join(c for c in unicodedata.normalize("NFKD", s) if not unicodedata.combining(c)).lower().strip()

# ======================================================================
# 1) VERİ YÜKLEME
# ======================================================================
sube = pd.read_csv(f"{IN}/sube_listesi.csv")
sube.columns = [c.strip() for c in sube.columns]
sube = sube.rename(columns={"Şube Kodu":"kod","Şube Adı":"ad","Tip":"tip","Şehir":"sehir",
                            "Enlem":"lat","Boylam":"lon","Kapasite (araç)":"kapasite","Kategori Sayısı":"kat_sayisi"})

hist = pd.read_csv(f"{IN}/gecmis_gunluk_talep_2024_2025.csv")
hist = hist.rename(columns={"Tarih":"tarih","Şube Kodu":"kod","Toplam Talep":"talep",
                            "Gerçekleşen Kiralama":"gerceklesen","Dönüşüm %":"donusum"})
hist["tarih"] = pd.to_datetime(hist["tarih"])
hist["ay"] = hist["tarih"].dt.month
hist["hg"] = hist["tarih"].dt.weekday
hist["yil"] = hist["tarih"].dt.year

rez = pd.read_csv(f"{IN}/rezervasyonlar_2026H1.csv")
rez = rez.rename(columns={"Alış Tarihi":"tarih","Şube Kodu":"kod","Kategori":"kategori",
                          "Kanal":"kanal","Rezervasyon Öncesi Süre (gün)":"lead","Gün Sayısı":"gun_sayisi","Durum":"durum"})
rez["tarih"] = pd.to_datetime(rez["tarih"])

filo = pd.read_csv(f"{IN}/filo_kategori_musaitlik.csv")
filo = filo.rename(columns={"Şube Kodu":"kod","Kategori":"kategori","Toplam Araç":"toplam",
                            "Müsait":"musait","Kirada":"kirada","Bakımda":"bakimda","Doluluk %":"doluluk"})

kuyruk = pd.read_csv(f"{IN}/kuyruk_yogunlugu.csv")
kuyruk = kuyruk.rename(columns={"Şube Kodu":"kod","Gün":"gun","Saat":"saat",
                                "Bekleyen Müşteri":"bekleyen","Ort. Bekleme (dk)":"bekleme_dk"})

with open(f"{IN}/resmi_tatiller_2026.json", encoding="utf-8") as f:
    tatil2026 = json.load(f)
tatil_gunleri_2026 = {t["tarih"] for t in tatil2026["tatiller"]}

with open(f"{IN}/hava_durumu_openmeteo.json", encoding="utf-8") as f:
    hava_raw = json.load(f)
with open(f"{IN}/ucus_yogunlugu_opensky.json", encoding="utf-8") as f:
    ucus_raw = json.load(f)

# ---- Walk-in (3 sayfa) ----
walkin_frames = []
xl = pd.ExcelFile(f"{IN}/walkin_musteri.xlsx")
for sh in xl.sheet_names:
    df = pd.read_excel(f"{IN}/walkin_musteri.xlsx", sheet_name=sh, header=1)
    df = df.rename(columns={df.columns[0]:"ad"})
    # Günlük Ort. ve Uçuş Bağlantılı % kolonlarını bul
    gcol = [c for c in df.columns if "Günlük" in str(c)]
    ucol = [c for c in df.columns if "Uçuş" in str(c)]
    df["walkin_gunluk"] = df[gcol[0]] if gcol else np.nan
    df["ucus_bagli_pct"] = df[ucol[0]] if ucol else np.nan
    walkin_frames.append(df[["ad","walkin_gunluk","ucus_bagli_pct"]])
walkin = pd.concat(walkin_frames, ignore_index=True).dropna(subset=["ad"])
walkin["ad_n"] = walkin["ad"].map(norm)
sube["ad_n"] = sube["ad"].map(norm)
sube = sube.merge(walkin[["ad_n","walkin_gunluk","ucus_bagli_pct"]], on="ad_n", how="left")

print("Şube:", len(sube), "| Geçmiş satır:", len(hist), "| Rezervasyon:", len(rez))
print("Walk-in eşleşen şube:", sube["walkin_gunluk"].notna().sum())

# ======================================================================
# 2) CANLI HAVA BİRLEŞTİRME (verilen 3 havalimanı + modellenmiş 3 şehir)
# ======================================================================
# Verilen: İstanbul, Antalya, İzmir (canlı Open-Meteo)
hava_sehir = {}
for st in hava_raw["istasyonlar"]:
    g = st["gunluk"]
    d = pd.DataFrame({"tarih":g["time"], "tmax":g["temperature_2m_max"], "tmin":g["temperature_2m_min"],
                      "yagis":g["precipitation_sum"], "yagis_olas":g["precipitation_probability_max"],
                      "kaynak":"canlı (Open-Meteo)"})
    hava_sehir[st["sehir"]] = d

# Modellenmiş dolgu (mevsim normali) — Ankara, Muğla, Adana. Temmuz: sıcak-kurak.
gunler_win = pd.date_range("2026-07-06","2026-07-20").strftime("%Y-%m-%d").tolist()
def modelli_hava(tmax_base, tmin_base):
    n = len(gunler_win)
    tmax = [round(tmax_base + ((i%5)-2)*0.8,1) for i in range(n)]  # deterministik hafif salınım
    tmin = [round(tmin_base + ((i%4)-1)*0.6,1) for i in range(n)]
    return pd.DataFrame({"tarih":gunler_win,"tmax":tmax,"tmin":tmin,"yagis":[0.0]*n,
                         "yagis_olas":[0]*n,"kaynak":"modellenmiş (mevsim normali)"})
hava_sehir.setdefault("Ankara", modelli_hava(33.0, 18.0))
hava_sehir.setdefault("Muğla",  modelli_hava(37.5, 25.0))
hava_sehir.setdefault("Adana",  modelli_hava(36.5, 26.0))

hava_all = pd.concat([d.assign(sehir=s) for s,d in hava_sehir.items()], ignore_index=True)
hava_all.to_json(f"{OUTV}/canli_hava_birlesik.json", orient="records", force_ascii=False, indent=1)
print("Hava birleşik satır:", len(hava_all), "| şehirler:", sorted(hava_all['sehir'].unique()))

# Uçuş yoğunluğu (anlık) — havalimanı adı → şehir
ucus_sehir = {}
for a in ucus_raw["havalimanlari"]:
    ucus_sehir[a["ad"]] = a["anlik_ucak"]
# LTFM=İstanbul, LTAI=Antalya, LTBJ=İzmir; ad ile eşleştir
ucus_by_sehir = {"İstanbul":13, "Antalya":17, "İzmir":2}  # anlik_ucak
med_ucus = np.median(list(ucus_by_sehir.values()))

# ======================================================================
# 3) TABAN MODELİ — Seviye × Ay Endeksi × Gün Endeksi (klasik çarpımsal ayrıştırma)
# ======================================================================
# Şube ortalama seviyesi (2025) ve YoY büyüme
lvl_2025 = hist[hist.yil==2025].groupby("kod")["talep"].mean().rename("seviye_2025")
sys_2024 = hist[hist.yil==2024]["talep"].sum(); sys_2025 = hist[hist.yil==2025]["talep"].sum()
yoy = sys_2025 / sys_2024
seviye_2026 = (lvl_2025 * yoy).rename("seviye_2026")
print(f"Sistem YoY büyüme 2024→2025: {yoy:.3f} → 2026 seviyesi buna göre ölçeklendi")

# Ay endeksi ve gün endeksi (tüm geçmiş; şube ortalamasına göre normalize)
sube_ort = hist.groupby("kod")["talep"].mean().rename("sube_ort")
ay_idx = (hist.groupby(["kod","ay"])["talep"].mean() /
          hist.groupby("kod")["talep"].transform("mean").groupby(hist["kod"]).first()).rename("ay_idx")
# daha güvenli hesap:
ay_mean = hist.groupby(["kod","ay"])["talep"].mean().rename("m")
ay_idx = (ay_mean / sube_ort).rename("ay_idx").reset_index()
hg_mean = hist.groupby(["kod","hg"])["talep"].mean().rename("m")
hg_idx = (hg_mean / sube_ort).rename("hg_idx").reset_index()

def taban(kod, d):
    m, w = d.month, d.weekday()
    lvl = seviye_2026.get(kod, sube_ort.get(kod, np.nan))
    ai = ay_idx[(ay_idx.kod==kod)&(ay_idx.ay==m)]["ay_idx"]
    wi = hg_idx[(hg_idx.kod==kod)&(hg_idx.hg==w)]["hg_idx"]
    ai = float(ai.iloc[0]) if len(ai) else 1.0
    wi = float(wi.iloc[0]) if len(wi) else 1.0
    return lvl * ai * wi

# ======================================================================
# 4) EMPİRİK TATİL / ARİFE ÇARPANLARI (şube tipine göre)
# ======================================================================
# 2024-2025 TR resmi tatilleri (hareketli bayramlar dahil) — çarpan ÖLÇMEK için
hol_hist = set()
fixed = ["01-01","04-23","05-01","05-19","07-15","08-30","10-29"]
for y in [2024,2025]:
    for md in fixed: hol_hist.add(f"{y}-{md}")
# Ramazan/Kurban (yaklaşık resmi günler)
for d in ["2024-04-10","2024-04-11","2024-04-12","2024-06-16","2024-06-17","2024-06-18","2024-06-19",
          "2025-03-30","2025-03-31","2025-04-01","2025-06-06","2025-06-07","2025-06-08","2025-06-09"]:
    hol_hist.add(d)
hol_dt = pd.to_datetime(sorted(hol_hist))
eve_dt = pd.to_datetime(sorted({(pd.Timestamp(d)-timedelta(days=k)).strftime("%Y-%m-%d")
                                for d in hol_hist for k in [1,2]} - hol_hist))

hist = hist.merge(sube[["kod","tip"]], on="kod", how="left")
hist["taban_ins"] = hist.apply(lambda r: seviye_2026.get(r.kod,np.nan), axis=1)  # placeholder
# in-sample taban (ölçüm için): seviye(2yr) × ay × gün
sube_ort_map = sube_ort.to_dict()
ai_map = {(r.kod,r.ay):r.ay_idx for r in ay_idx.itertuples()}
wi_map = {(r.kod,r.hg):r.hg_idx for r in hg_idx.itertuples()}
hist["taban_ins"] = [sube_ort_map.get(k,np.nan)*ai_map.get((k,m),1.0)*wi_map.get((k,w),1.0)
                     for k,m,w in zip(hist.kod,hist.ay,hist.hg)]
hist["oran"] = hist["talep"] / hist["taban_ins"].replace(0,np.nan)
hist["is_hol"] = hist["tarih"].isin(hol_dt)
hist["is_eve"] = hist["tarih"].isin(eve_dt)

tatil_carpan = hist[hist.is_hol].groupby("tip")["oran"].mean().to_dict()
eve_carpan   = hist[hist.is_eve].groupby("tip")["oran"].mean().to_dict()
print("Tatil çarpanı (tip):", {k:round(v,3) for k,v in tatil_carpan.items()})
print("Arife çarpanı (tip):", {k:round(v,3) for k,v in eve_carpan.items()})

# ======================================================================
# 5) REZERVASYON MOMENTUMU + NET DÖNÜŞÜM
# ======================================================================
# Aylık rezervasyon hacmi trendi (2026 H1) → momentum sinyali (şube tipine göre son 60 gün / önceki ort.)
rez = rez.merge(sube[["kod","tip"]], on="kod", how="left")
rez["ay"] = rez["tarih"].dt.month
net_pay = 1 - VARS["iptal_orani"] - VARS["noshow_orani"]  # brüt rez → beklenen teslim payı ~0.61
# Haziran (son ay) rez / ilk 5 ay ort. — tip bazında momentum
son_ay = rez[rez.ay==6].groupby("tip").size()
onceki = rez[rez.ay<6].groupby("tip").size()/5.0
rez_momentum = (son_ay/onceki).reindex(sube["tip"].unique()).fillna(1.0)
# ölçülü uygula: momentum sinyalini 0.5 ağırlıkla merkeze çek (aşırı oynaklığı yumuşat)
rez_momentum_carpan = (1 + 0.5*(rez_momentum-1)).clip(0.9,1.15).to_dict()
print("Rez. momentum çarpanı (tip):", {k:round(v,3) for k,v in rez_momentum_carpan.items()})

# ======================================================================
# 6) ARZ (MÜSAİTLİK) + BAKIM PENCERESİ + KUYRUK KAYBI
# ======================================================================
musait_sube = filo.groupby("kod")["musait"].sum().rename("musait_toplam")
toplam_sube = filo.groupby("kod")["toplam"].sum().rename("filo_toplam")
# Kategori karması → şube ortalama günlük kira (TL)
filo["kira_TL"] = filo["kategori"].map(VARS["gunluk_kira_TL"])
kat_pay = filo.groupby(["kod","kategori"])["toplam"].sum()
sube_kira_TL = (filo.assign(w=filo["toplam"]).groupby("kod").apply(
    lambda g: np.average(g["kira_TL"], weights=g["toplam"]))).rename("ort_kira_TL")

# Kuyruk kaybı: eşiği (5) aşan saatlerin payı → kayıp telafi çarpanı (gizli talep)
kuyruk_asim = kuyruk.assign(asti=kuyruk["bekleyen"]>VARS["kuyruk_esigi"]).groupby("kod")["asti"].mean()
kuyruk_carpan = (1 + VARS["kuyruk_kayip_orani"]*kuyruk_asim).rename("kuyruk_carpan")  # gizli talep geri kazanımı

sube = sube.merge(musait_sube, on="kod", how="left").merge(toplam_sube, on="kod", how="left")
sube = sube.merge(sube_kira_TL, on="kod", how="left").merge(kuyruk_carpan, on="kod", how="left")
sube["kuyruk_carpan"] = sube["kuyruk_carpan"].fillna(1.0)
sube["doluluk"] = 1 - sube["musait_toplam"]/sube["filo_toplam"]

# ======================================================================
# 7) TAHMİN ÜRETİMİ — 14-20 Temmuz, 24 şube, tüm etkenler
# ======================================================================
gunler = pd.date_range(VARS["tahmin_baslangic"], VARS["tahmin_bitis"])
donusum_map = (hist.groupby("kod")["donusum"].mean()/100.0).to_dict()  # şube ort. dönüşüm oranı
rows = []
for _, s in sube.iterrows():
    kod, tip, sehir = s["kod"], s["tip"], s["sehir"]
    walkin_g = s["walkin_gunluk"] if not pd.isna(s["walkin_gunluk"]) else 0.0
    ucus_pct = (s["ucus_bagli_pct"]/100.0) if not pd.isna(s["ucus_bagli_pct"]) else 0.0
    for d in gunler:
        ds = d.strftime("%Y-%m-%d")
        base = taban(kod, d)
        # --- etken çarpanları ---
        c_tatil = tatil_carpan.get(tip,1.0) if ds in tatil_gunleri_2026 else 1.0
        # arife: tatilden 1-2 gün önce
        eve = any((d + timedelta(days=k)).strftime("%Y-%m-%d") in tatil_gunleri_2026 for k in [1,2])
        c_eve = eve_carpan.get(tip,1.0) if (eve and ds not in tatil_gunleri_2026) else 1.0
        # okul/yaz tatili: Temmuz zaten ay endeksinde; ek etki yalnız sahil-havalimanı/şehir için ölçülü
        c_okul = 1.03 if (sehir in ["Antalya","Muğla"] and tip!="Otogar") else 1.0
        # hava
        hv = hava_all[(hava_all.sehir==sehir)&(hava_all.tarih==ds)]
        c_hava = 1.0
        if len(hv):
            yagis = float(hv["yagis"].iloc[0]); tmax = float(hv["tmax"].iloc[0])
            walkin_share = min(walkin_g/base, 0.9) if base>0 else 0.0
            if tip=="Şehir" and yagis>VARS["yagis_esigi_mm"]:
                c_hava *= (1 - VARS["yagis_walkin_dususu"]*walkin_share)
            if sehir in ["Antalya","Muğla","İzmir","Adana"] and tmax>VARS["asiri_sicak_C"]:
                c_hava *= (1 + VARS["asiri_sicak_artis"])
        # uçuş yoğunluğu (yalnız havalimanı, uçuş bağlantılı walk-in payına)
        c_ucus = 1.0
        if tip=="Havalimanı" and sehir in ucus_by_sehir and base>0:
            f_idx = ucus_by_sehir[sehir]/med_ucus  # 1=medyan
            walkin_share = min(walkin_g/base, 0.9)
            etki = np.clip((f_idx-1), -1, 1) * VARS["ucus_etki_tavani"]
            c_ucus = 1 + etki * ucus_pct * walkin_share * 3  # uçuş bağlantılı paya ölçekli
            c_ucus = float(np.clip(c_ucus, 1-VARS["ucus_etki_tavani"], 1+VARS["ucus_etki_tavani"]))
        # rezervasyon momentumu
        c_rez = rez_momentum_carpan.get(tip,1.0)
        # bakım penceresi (Sal=1, Çar=2) → arz düşüşü sinyali (talebi değil, karşılanabilirliği etkiler)
        c_bakim = 0.97 if d.weekday() in [1,2] else 1.0  # bakım günlerinde efektif arz ~%3 düşer
        # kuyruk kaybı telafisi (gizli talep)
        c_kuyruk = float(s["kuyruk_carpan"])
        # rekabet proxy (yüksek doluluk havalimanı → hafif negatif)
        c_rekabet = 1.0
        if tip=="Havalimanı" and s["doluluk"]>VARS["rekabet_doluluk_esigi"]:
            c_rekabet = 1 - VARS["rekabet_etki"]

        talep = base * c_tatil * c_eve * c_okul * c_hava * c_ucus * c_rez * c_kuyruk * c_rekabet
        # ---- ARZ / KAPASİTE ----
        # Yapısal kapasite = toplam filo (araç gün içinde devredilebilir); bakım günü ~%3 düşük.
        filo_toplam = s["filo_toplam"] if not pd.isna(s["filo_toplam"]) else np.nan
        guncel_musait = s["musait_toplam"] if not pd.isna(s["musait_toplam"]) else np.nan
        ef_arz = filo_toplam * c_bakim if not pd.isna(filo_toplam) else np.nan
        doluluk_orani = (talep / ef_arz) if (not pd.isna(ef_arz) and ef_arz>0) else np.nan  # >1 = talep kapasiteyi aşıyor
        arz_acigi = max(0, talep - ef_arz) if not pd.isna(ef_arz) else np.nan               # yapısal açık (araç)
        # kısa vadeli (bugünkü müsait araç kesiti) taktik açık — özellikle yakın günler için
        kv_arz = guncel_musait * c_bakim if not pd.isna(guncel_musait) else np.nan
        kisa_vade_acik = max(0, talep - kv_arz) if not pd.isna(kv_arz) else np.nan
        karsilanabilir = min(talep, ef_arz) if not pd.isna(ef_arz) else talep
        donusum = donusum_map.get(kod, 0.79)
        gerc = karsilanabilir * donusum
        gelir_TL = gerc * (s["ort_kira_TL"] if not pd.isna(s["ort_kira_TL"]) else 0)

        rows.append({
            "tarih":ds, "hafta_gunu":TR_GUN[d.weekday()], "kod":kod, "sube":s["ad"], "tip":tip, "sehir":sehir,
            "taban":round(base,1),
            "c_tatil":round(c_tatil,3),"c_arife":round(c_eve,3),"c_okul":round(c_okul,3),
            "c_hava":round(c_hava,3),"c_ucus":round(c_ucus,3),"c_rez":round(c_rez,3),
            "c_bakim":round(c_bakim,3),"c_kuyruk":round(c_kuyruk,3),"c_rekabet":round(c_rekabet,3),
            "talep_baz":round(talep,1),
            "filo_toplam":filo_toplam,"guncel_musait":guncel_musait,"efektif_arz":round(ef_arz,1) if not pd.isna(ef_arz) else np.nan,
            "doluluk_orani":round(doluluk_orani,3) if not pd.isna(doluluk_orani) else np.nan,
            "arz_acigi":round(arz_acigi,1) if not pd.isna(arz_acigi) else np.nan,
            "kisa_vade_acik":round(kisa_vade_acik,1) if not pd.isna(kisa_vade_acik) else np.nan,
            "karsilanabilir":round(karsilanabilir,1),
            "beklenen_kiralama":round(gerc,1),
            "gelir_baz_TL":round(gelir_TL,0),
        })

tahmin = pd.DataFrame(rows)

# ======================================================================
# 8) BACKTEST — son 90 gün, taban model (WAPE/MAPE) → senaryo bandı
# ======================================================================
kesim = hist["tarih"].max() - timedelta(days=VARS["backtest_holdout_gun"])
bt = hist[hist.tarih>kesim].copy()
bt["pred"] = bt["taban_ins"]
bt["ae"] = (bt["talep"]-bt["pred"]).abs()
def wape(g): return g["ae"].sum()/g["talep"].sum()
def mape(g): return (g["ae"]/g["talep"].replace(0,np.nan)).mean()
bt_tip = bt.groupby("tip").apply(lambda g: pd.Series({"WAPE":wape(g),"MAPE":mape(g),
        "bias":(g["pred"].sum()/g["talep"].sum()-1)})).reset_index()
bt_gen = pd.Series({"tip":"GENEL","WAPE":wape(bt),"MAPE":mape(bt),"bias":bt["pred"].sum()/bt["talep"].sum()-1})
bt_tip = pd.concat([bt_tip, pd.DataFrame([bt_gen])], ignore_index=True)
print("\nBACKTEST (son 90 gün):"); print(bt_tip.round(3).to_string(index=False))

wape_tip = bt_tip.set_index("tip")["WAPE"].to_dict()
# Senaryo bandı: ±z × WAPE (tip bazında). WAPE ~ göreli hata → normal-yaklaşık band.
def band(tip): return VARS["senaryo_z"]*wape_tip.get(tip, wape_tip.get("GENEL",0.15))

# ======================================================================
# 9) SENARYOLAR (İyimser / Baz / Kötümser)
# ======================================================================
def senaryolu(r):
    b = band(r["tip"])
    return pd.Series({"iyimser":round(r["talep_baz"]*(1+b),1),
                      "kotumser":round(r["talep_baz"]*(1-b),1),
                      "band_pct":round(b*100,1)})
tahmin = pd.concat([tahmin, tahmin.apply(senaryolu, axis=1)], axis=1)
tahmin = tahmin.rename(columns={"talep_baz":"baz"})

# ======================================================================
# 10) ÖZET TABLOLAR
# ======================================================================
gun_tip = tahmin.groupby(["tarih","tip"]).agg(baz=("baz","sum"),iyimser=("iyimser","sum"),
        kotumser=("kotumser","sum"),arz_acigi=("arz_acigi","sum"),gelir_TL=("gelir_baz_TL","sum")).reset_index()
gun_sehir = tahmin.groupby(["tarih","sehir"]).agg(baz=("baz","sum"),gelir_TL=("gelir_baz_TL","sum")).reset_index()
sube_ozet = tahmin.groupby(["kod","sube","tip","sehir"]).agg(
        toplam_baz=("baz","sum"),toplam_iyimser=("iyimser","sum"),toplam_kotumser=("kotumser","sum"),
        zirve_doluluk=("doluluk_orani","max"),toplam_arz_acigi=("arz_acigi","sum"),
        filo_toplam=("filo_toplam","first"),toplam_gelir_TL=("gelir_baz_TL","sum")).reset_index().sort_values("toplam_baz",ascending=False)

# ======================================================================
# 11) ÇIKTI CSV / JSON
# ======================================================================
tahmin.to_csv(f"{OUTV}/tahmin_gunluk.csv", index=False, encoding="utf-8-sig")
# etken katkı tablosu
etkenler = tahmin[["tarih","hafta_gunu","kod","sube","tip","sehir","taban",
                   "c_tatil","c_arife","c_okul","c_hava","c_ucus","c_rez","c_bakim","c_kuyruk","c_rekabet","baz"]]
etkenler.to_csv(f"{OUTV}/etkenler.csv", index=False, encoding="utf-8-sig")
gun_tip.to_csv(f"{OUTV}/senaryo_tip_gunluk.csv", index=False, encoding="utf-8-sig")
gun_sehir.to_csv(f"{OUTV}/senaryo_sehir_gunluk.csv", index=False, encoding="utf-8-sig")
sube_ozet.to_csv(f"{OUTV}/sube_ozet.csv", index=False, encoding="utf-8-sig")
bt_tip.to_csv(f"{OUTV}/backtest_ozet.csv", index=False, encoding="utf-8-sig")
hava_all.to_csv(f"{OUTV}/hava_ozet.csv", index=False, encoding="utf-8-sig")

# meta / varsayımlar
meta = {"uretim_zamani": VARS["bugun"], "tahmin_penceresi":[VARS["tahmin_baslangic"],VARS["tahmin_bitis"]],
        "yoy_2024_2025":round(float(yoy),4), "net_rez_pay":round(net_pay,3),
        "backtest": bt_tip.round(4).to_dict(orient="records"),
        "varsayimlar": {k:(v if not isinstance(v,dict) else v) for k,v in VARS.items()},
        "tatil_carpan":{k:round(v,3) for k,v in tatil_carpan.items()},
        "arife_carpan":{k:round(v,3) for k,v in eve_carpan.items()},
        "rez_momentum_carpan":{k:round(v,3) for k,v in rez_momentum_carpan.items()},
        "not_hava":"İstanbul/Antalya/İzmir canlı Open-Meteo; Ankara/Muğla/Adana mevsim normaliyle modellendi.",
        "not_ucus":"OpenSky anlık görüntü — zayıf sinyal, ±%10 tavan.",
        "not_rakip":"Rakip fiyat/müsaitlik verisi yok; doluluk-tabanlı proxy (düşük ağırlık)."}
with open(f"{OUTV}/model_meta.json","w",encoding="utf-8") as f:
    json.dump(meta, f, ensure_ascii=False, indent=2)

# ======================================================================
# 12) KONSOL ÖZET
# ======================================================================
print("\n=== TAHMİN ÖZETİ (14-20 Tem 2026) ===")
print("Toplam sistem baz talep (7 gün, araç):", round(tahmin['baz'].sum()))
print("Yapısal arz açığı olan şube-gün sayısı:", int((tahmin['arz_acigi']>0).sum()), "/", len(tahmin))
print("Toplam baz gelir projeksiyonu (7 gün):", f"{tahmin['gelir_baz_TL'].sum():,.0f} TL")
print("\nGün×Tip baz talep:")
print(gun_tip.pivot(index="tarih",columns="tip",values="baz").round(0).to_string())
print("\nEn yüksek 8 şube (7 günlük baz) — zirve doluluk baskısı:")
print(sube_ozet.head(8)[["sube","tip","toplam_baz","zirve_doluluk","filo_toplam","toplam_gelir_TL"]].to_string(index=False))
print("\nKapasite baskısı en yüksek 6 şube (zirve doluluk):")
print(sube_ozet.sort_values("zirve_doluluk",ascending=False).head(6)[["sube","tip","zirve_doluluk","toplam_arz_acigi","filo_toplam"]].to_string(index=False))
print("\nÇıktılar:", os.listdir(OUTV))
print("BİTTİ.")
