{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fb07e291",
   "metadata": {
    "id": "fb07e291"
   },
   "source": "# 🌊 TR-Flood Engine v1.0 — Türkiye Kuantum-Destekli Taşkın Simülasyon Motoru\n**BVK Technology · İTÜ ARI Teknokent · Eylül 2026**\n **Q-Flood / MQFSE** motorunun **Türkiye ülke paketi**. Aynı mimari (veri → arazi → hidroloji → 2B taşma → kuantum → Monte Carlo → etki → rapor), yalnızca **Türkiye'ye özel kamu ve açık veri kaynaklarıyla** uçtan uca çalışır.\n\n| Katman | Türkiye kaynağı | Erişim |\n|---|---|---|\n| Gözlem / uyarı | **MGM** `servis.mgm.gov.tr` — anlık AWOS, son 7 gün 24 s yağış (~1.700 istasyon), **MeteoUyarı** sel uyarıları, 5 gün tahmin | anahtarsız (Origin başlığı) |\n| Yağış (geçmiş/tahmin) | **Open-Meteo** ERA5/ERA5-Land arşivi, çok-modelli tahmin (ICON/GFS/ECMWF) | anahtarsız |\n| Yağış kontrol | **CHIRPS v2.0** 0,05° günlük · İBB İSKİ günlük yağış | anahtarsız |\n| Nehir debisi | **GloFAS v4** (Open-Meteo Flood API) · Copernicus EWDS (GloFAS/EFAS-geçmiş) · Google Flood Forecasting API | anahtarsız / jeton / bekleme listesi |\n| Yükseklik | **Copernicus GLO-30 DEM** (AWS) → dolgu, D8, birikim, dere ağı, **HAND** | anahtarsız |\n| Arazi örtüsü | **ESA WorldCover 10 m** (2021) → CN, Manning n, sızma | anahtarsız |\n| Nüfus | **WorldPop TUR 2025** 100 m (R2024B) | anahtarsız |\n| İdari sınır | **geoBoundaries TUR ADM1/ADM2** (ODbL) | anahtarsız |\n| Toprak | **ISRIC SoilGrids** → hidrolojik toprak grubu | anahtarsız |\n| Doğrulama | **CEMS Küresel Taşkın İzleme (GFM)** Sentinel-1 taşkın yayılımı (2015→) | anahtarsız |\n| Hidrolik | 2B yerel-eylemsizlik sığ su çözücüsü (Bates 2010) + alt-ızgara kanal + makullük kapısı | — |\n| Veri analizi | Dağılım uyumu (gamma/log-normal/Weibull/GEV), ADF, Mann–Kendall, aykırı değer, MI, Moran I, semivariogram, G* | — |\n| Kuantum | Kontrol düzlemi: simülatör tavanı **20 kübit**, cihaz hedefleri (IBM Heron 20, Wukong 20, D-Wave 500) · VQC · QAE · QSVM · QLSTM · QUBO/QAOA · zaman defteri ve QPU maliyet projeksiyonu | hesap/jeton |\n\n**Modlar:** `historical` (referans olay yeniden üretimi) · `forecast` (5 gün) · `nowcast` (son 72 s + 24 s).\n**Kapsam:** referans olay · 81 il · 25 havza · tüm Türkiye (tarama katmanı) · serbest bbox.\n\n**Referans olay çifti :** 🏞️ *Kastamonu Bozkurt – Ezine Çayı, 10–12 Ağustos 2021* (nehir/ani taşkın, 451,7 mm/3 gün) ↔ 🏙️ *İstanbul Başakşehir–Küçükçekmece, 5 Eylül 2023* (kentsel-plüvyal, 125 mm/3 saat).\n\n> ⚠️ Bu motor bir **tarama/erken uyarı** aracıdır; HEC-RAS 2D / LISFLOOD-FP sınıfı hidrodinamik modelin ve DSİ gözlem verisiyle kalibrasyonun yerini tutmaz. Hasar ve sigorta rakamları kalibre edilmeden karar amaçlı kullanılmamalıdır.\n"
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "bd83f2f7",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "bd83f2f7",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1790706960527,
     "user_tz": -180,
     "elapsed": 42664,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "520ea581-99c1-45c2-b306-dc0b88b542ba"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Kurulum tamam · rasterio 1.5.1 · pennylane 0.45.1\n"
     ]
    }
   ],
   "source": "#@title 1 · Kurulum (≈1–2 dk) { display-mode: \"form\" }\n#@markdown Gerekli paketler kurulur. Colab'da rasterio, pennylane, python-docx ve isteğe bağlı istemciler.\nimport subprocess, sys, importlib\ndef _pip(*pkgs):\n    subprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", *pkgs], check=False)\nneed = []\nfor mod, pkg in [(\"rasterio\",\"rasterio\"), (\"pennylane\",\"pennylane\"), (\"docx\",\"python-docx\"), (\"openpyxl\",\"openpyxl\"), (\"folium\",\"folium\")]:\n    try: importlib.import_module(mod)\n    except Exception: need.append(pkg)\nif need: _pip(*need)\noptional = \"cdsapi google-genai qiskit-ibm-runtime pennylane-qiskit\"  #@param {type:\"string\"}\ninstall_optional = False  #@param {type:\"boolean\"}\nif install_optional and optional.strip(): _pip(*optional.split())\nimport rasterio, pennylane, docx\nprint(\"Kurulum tamam · rasterio\", rasterio.__version__, \"· pennylane\", pennylane.__version__)\n"
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2aa7047c",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "2aa7047c",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1790707182778,
     "user_tz": -180,
     "elapsed": 204,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "0f9aa325-e0f4-4c8e-b711-76716f12147b"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Writing trflood_engine.py\n"
     ]
    }
   ],
   "source": "%%writefile trflood_engine.py\n# -*- coding: utf-8 -*-\n\"\"\"\nTR-Flood Engine v1.0  —  Türkiye Kuantum-Destekli Taşkın Simülasyon Motoru\nBVK Technology · İTÜ ARI Teknokent · 2026\n\nMalezya Q-Flood Engine (MQFSE) mimarisinin Türkiye ülke paketi.\nYalnızca Türkiye'ye özel kamu ve açık veri kaynakları kullanılır:\n\n  MGM servis.mgm.gov.tr (gözlem, 7 gün yağış, MeteoUyarı, tahmin)\n  Open-Meteo (ERA5 arşivi, tahmin, GloFAS nehir debisi)          CHIRPS v2/v3 (UCSB)\n  Copernicus GLO-30 DEM + 30 m HAND (AWS açık veri)               ESA WorldCover 10 m\n  WorldPop TR 2025 (100 m)                                        geoBoundaries il/ilçe (ODbL)\n  HydroRIVERS / HydroBASINS                                       SoilGrids (ISRIC)\n  CEMS Küresel Taşkın İzleme (Sentinel-1 taşkın yayılımı)         İBB Açık Veri (İSKİ yağış)\n  İsteğe bağlı (ücretsiz kayıt): Copernicus CDS (ERA5-Land, GloFAS, EFAS), NASA IMERG,\n  Google Flood Forecasting API, OpenTopography.\n\nKatmanlar:  sources → terrain → hydrology → inundation → quantum → montecarlo → impact → report\nModlar:     historical | forecast | nowcast        Kapsam: il | havza | turkiye\n\"\"\"\nfrom __future__ import annotations\n\nimport gzip\nimport io\nimport json\nimport logging\nimport math\nimport os\nimport shutil\nimport time\nimport zipfile\nfrom dataclasses import dataclass, field, asdict\nfrom datetime import date, datetime, timedelta\nfrom pathlib import Path\nfrom typing import Any, Dict, List, Optional, Tuple\n\nimport numpy as np\nimport pandas as pd\nimport requests\n\nENGINE_VERSION = \"1.0.0\"\nENGINE_NAME = \"TR-Flood Engine\"\n\nlog = logging.getLogger(\"trflood\")\nif not log.handlers:\n    _h = logging.StreamHandler()\n    _h.setFormatter(logging.Formatter(\"%(asctime)s %(levelname)s %(message)s\", \"%H:%M:%S\"))\n    log.addHandler(_h)\nlog.setLevel(logging.INFO)\n\nCACHE_DIR = Path(os.environ.get(\"TRFLOOD_CACHE\", \"trflood_cache\"))\nCACHE_DIR.mkdir(parents=True, exist_ok=True)\n\n_SESSION = requests.Session()\n_SESSION.headers.update({\"User-Agent\": f\"{ENGINE_NAME}/{ENGINE_VERSION} (BVK Technology)\"})\n\n\n# ---------------------------------------------------------------------------\n# Yardımcılar\n# ---------------------------------------------------------------------------\ndef http_get(url: str, params: dict | None = None, headers: dict | None = None,\n             timeout: int = 60, retries: int = 3, stream: bool = False) -> requests.Response:\n    \"\"\"Yeniden denemeli GET. Son hata yeniden fırlatılır.\"\"\"\n    last = None\n    for i in range(retries):\n        try:\n            r = _SESSION.get(url, params=params, headers=headers, timeout=timeout, stream=stream)\n            if r.status_code == 200:\n                return r\n            last = RuntimeError(f\"HTTP {r.status_code} {url}\" + (f\" — {r.text[:120]}\" if r.status_code == 429 else \"\"))\n            if r.status_code in (400, 401, 403, 404, 429):\n                break\n        except Exception as e:  # noqa: BLE001\n            last = e\n        time.sleep(1.5 * (i + 1))\n    raise last  # type: ignore[misc]\n\n\ndef http_json(url: str, **kw) -> Any:\n    return http_get(url, **kw).json()\n\n\ndef cached_download(url: str, filename: str | None = None, headers: dict | None = None) -> Path:\n    \"\"\"URL'yi önbelleğe indirir, yolu döndürür.\"\"\"\n    filename = filename or url.split(\"?\")[0].rstrip(\"/\").split(\"/\")[-1]\n    p = CACHE_DIR / filename\n    if p.exists() and p.stat().st_size > 0:\n        return p\n    r = http_get(url, headers=headers, stream=True, timeout=600)\n    tmp = p.with_suffix(p.suffix + \".part\")\n    with open(tmp, \"wb\") as f:\n        for chunk in r.iter_content(1 << 20):\n            f.write(chunk)\n    tmp.rename(p)\n    return p\n\n\nclass Provenance:\n    \"\"\"Her veri katmanının nereden geldiğini (gerçek kaynak / yedek / sentetik) kaydeder.\"\"\"\n\n    def __init__(self):\n        self.rows: List[dict] = []\n\n    def add(self, layer: str, source: str, status: str, detail: str = \"\", url: str = \"\"):\n        self.rows.append(dict(katman=layer, kaynak=source, durum=status, detay=detail, url=url,\n                              zaman=datetime.utcnow().isoformat(timespec=\"seconds\") + \"Z\"))\n        lvl = logging.INFO if status == \"ok\" else logging.WARNING\n        log.log(lvl, f\"[{layer}] {source}: {status} {detail}\")\n\n    def frame(self) -> pd.DataFrame:\n        return pd.DataFrame(self.rows)\n\n\ndef slugify(s: str) -> str:\n    tr = str.maketrans(\"çğıöşüÇĞİÖŞÜ \", \"cgiosuCGIOSU_\")\n    return \"\".join(ch for ch in s.translate(tr) if ch.isalnum() or ch in \"_-\").lower()\n\n\ndef bbox_from_center(lat: float, lon: float, half_deg: float) -> Tuple[float, float, float, float]:\n    \"\"\"(south, west, north, east)\"\"\"\n    return (lat - half_deg, lon - half_deg, lat + half_deg, lon + half_deg)\n\n\ndef bbox_area_km2(b: Tuple[float, float, float, float]) -> float:\n    s, w, n, e = b\n    return abs(n - s) * 111.0 * abs(e - w) * 111.0 * math.cos(math.radians((n + s) / 2))\n\n\n# ---------------------------------------------------------------------------\n# Kayıt defterleri: 81 il, 25 havza, referans taşkın olayları\n# ---------------------------------------------------------------------------\n# plaka: (il adı, enlem, boylam) — il merkezi yaklaşık koordinatı. Kesin sınırlar\n# çalışma zamanında geoBoundaries (ODbL) ADM1 poligonlarından alınır.\nPROVINCES: Dict[int, Tuple[str, float, float]] = {\n    1: (\"Adana\", 37.00, 35.32), 2: (\"Adıyaman\", 37.76, 38.28), 3: (\"Afyonkarahisar\", 38.76, 30.54),\n    4: (\"Ağrı\", 39.72, 43.05), 5: (\"Amasya\", 40.65, 35.83), 6: (\"Ankara\", 39.93, 32.86),\n    7: (\"Antalya\", 36.90, 30.70), 8: (\"Artvin\", 41.18, 41.82), 9: (\"Aydın\", 37.85, 27.85),\n    10: (\"Balıkesir\", 39.65, 27.89), 11: (\"Bilecik\", 40.14, 29.98), 12: (\"Bingöl\", 38.88, 40.50),\n    13: (\"Bitlis\", 38.40, 42.11), 14: (\"Bolu\", 40.74, 31.61), 15: (\"Burdur\", 37.72, 30.29),\n    16: (\"Bursa\", 40.19, 29.06), 17: (\"Çanakkale\", 40.15, 26.41), 18: (\"Çankırı\", 40.60, 33.62),\n    19: (\"Çorum\", 40.55, 34.95), 20: (\"Denizli\", 37.78, 29.09), 21: (\"Diyarbakır\", 37.91, 40.24),\n    22: (\"Edirne\", 41.68, 26.56), 23: (\"Elazığ\", 38.68, 39.22), 24: (\"Erzincan\", 39.75, 39.49),\n    25: (\"Erzurum\", 39.90, 41.27), 26: (\"Eskişehir\", 39.78, 30.52), 27: (\"Gaziantep\", 37.07, 37.38),\n    28: (\"Giresun\", 40.91, 38.39), 29: (\"Gümüşhane\", 40.46, 39.48), 30: (\"Hakkari\", 37.58, 43.74),\n    31: (\"Hatay\", 36.20, 36.16), 32: (\"Isparta\", 37.76, 30.55), 33: (\"Mersin\", 36.81, 34.64),\n    34: (\"İstanbul\", 41.01, 28.98), 35: (\"İzmir\", 38.42, 27.14), 36: (\"Kars\", 40.60, 43.10),\n    37: (\"Kastamonu\", 41.38, 33.78), 38: (\"Kayseri\", 38.72, 35.49), 39: (\"Kırklareli\", 41.73, 27.22),\n    40: (\"Kırşehir\", 39.15, 34.16), 41: (\"Kocaeli\", 40.77, 29.92), 42: (\"Konya\", 37.87, 32.49),\n    43: (\"Kütahya\", 39.42, 29.98), 44: (\"Malatya\", 38.35, 38.31), 45: (\"Manisa\", 38.61, 27.43),\n    46: (\"Kahramanmaraş\", 37.58, 36.93), 47: (\"Mardin\", 37.31, 40.74), 48: (\"Muğla\", 37.22, 28.36),\n    49: (\"Muş\", 38.73, 41.49), 50: (\"Nevşehir\", 38.62, 34.71), 51: (\"Niğde\", 37.97, 34.68),\n    52: (\"Ordu\", 40.98, 37.88), 53: (\"Rize\", 41.02, 40.52), 54: (\"Sakarya\", 40.77, 30.40),\n    55: (\"Samsun\", 41.29, 36.33), 56: (\"Siirt\", 37.93, 41.94), 57: (\"Sinop\", 42.03, 35.15),\n    58: (\"Sivas\", 39.75, 37.02), 59: (\"Tekirdağ\", 40.98, 27.51), 60: (\"Tokat\", 40.31, 36.55),\n    61: (\"Trabzon\", 41.00, 39.72), 62: (\"Tunceli\", 39.11, 39.55), 63: (\"Şanlıurfa\", 37.17, 38.79),\n    64: (\"Uşak\", 38.68, 29.41), 65: (\"Van\", 38.49, 43.38), 66: (\"Yozgat\", 39.82, 34.81),\n    67: (\"Zonguldak\", 41.45, 31.79), 68: (\"Aksaray\", 38.37, 34.03), 69: (\"Bayburt\", 40.26, 40.23),\n    70: (\"Karaman\", 37.18, 33.22), 71: (\"Kırıkkale\", 39.85, 33.51), 72: (\"Batman\", 37.88, 41.13),\n    73: (\"Şırnak\", 37.52, 42.46), 74: (\"Bartın\", 41.64, 32.34), 75: (\"Ardahan\", 41.11, 42.70),\n    76: (\"Iğdır\", 39.92, 44.04), 77: (\"Yalova\", 40.65, 29.27), 78: (\"Karabük\", 41.20, 32.63),\n    79: (\"Kilis\", 36.72, 37.12), 80: (\"Osmaniye\", 37.07, 36.25), 81: (\"Düzce\", 40.84, 31.16),\n}\nPROVINCE_BY_NAME: Dict[str, int] = {slugify(v[0]): k for k, v in PROVINCES.items()}\n\n# SYGM 25 havza — yaklaşık kapsayan kutu (south, west, north, east). Kesin sınır için\n# HydroBASINS (lev05/06) ile kesiştirme çalışma zamanında yapılabilir.\nBASINS: Dict[str, Tuple[float, float, float, float]] = {\n    \"Meriç-Ergene\": (40.55, 26.05, 42.10, 28.05), \"Marmara\": (40.00, 26.20, 41.60, 30.50),\n    \"Susurluk\": (39.10, 27.30, 40.60, 29.60), \"Kuzey Ege\": (38.90, 26.10, 40.10, 27.90),\n    \"Gediz\": (38.20, 26.60, 39.30, 29.20), \"Küçük Menderes\": (37.90, 26.80, 38.50, 28.30),\n    \"Büyük Menderes\": (37.30, 27.20, 38.60, 30.30), \"Batı Akdeniz\": (36.20, 27.90, 37.60, 30.10),\n    \"Antalya\": (36.30, 29.80, 37.90, 32.20), \"Burdur\": (37.30, 29.80, 38.00, 30.60),\n    \"Akarçay\": (38.40, 30.20, 39.10, 31.40), \"Sakarya\": (38.90, 29.70, 41.20, 33.00),\n    \"Batı Karadeniz\": (40.60, 31.00, 42.10, 35.20), \"Yeşilırmak\": (39.60, 34.70, 41.40, 38.20),\n    \"Kızılırmak\": (38.40, 32.50, 41.80, 38.30), \"Konya Kapalı\": (36.90, 31.60, 39.30, 34.90),\n    \"Doğu Akdeniz\": (36.00, 32.10, 37.60, 35.00), \"Seyhan\": (36.60, 34.80, 39.20, 36.60),\n    \"Asi\": (35.85, 35.80, 36.90, 36.70), \"Ceyhan\": (36.50, 35.50, 38.50, 37.60),\n    \"Fırat-Dicle\": (36.60, 36.30, 40.40, 44.80), \"Doğu Karadeniz\": (40.30, 36.80, 41.60, 41.60),\n    \"Çoruh\": (40.20, 39.80, 41.60, 42.10), \"Aras\": (39.20, 41.50, 41.40, 44.80),\n    \"Van Gölü\": (38.00, 42.20, 39.30, 44.30),\n}\n\n# Referans taşkın olayları — MGM Yıllık İklim Değerlendirmesi raporları, İstanbul Valiliği,\n# NHESS 2022, Su Kaynakları Dergisi, AFAD/TRT/Milliyet haberleri (Eylül 2026 araştırması).\nEVENTS: Dict[str, dict] = {\n    \"bozkurt_2021\": dict(\n        ad=\"Kastamonu Bozkurt – Ezine Çayı Taşkını\", il=\"Kastamonu\", ilce=\"Bozkurt\",\n        havza=\"Batı Karadeniz\", tip=\"riverine_flash\", baslangic=\"2021-08-10\", bitis=\"2021-08-12\",\n        lat=41.875, lon=33.975, half_deg=0.15,\n        yagis_mm=451.7, yagis_sure_saat=72, tepe_saat_utc=\"2021-08-11T11:00\", yagis_kaynak=\"MGM 2021 Yıllık İklim Raporu (Bozkurt/Mamatlar, 3 gün)\",\n        kayip=\"71 can kaybı Kastamonu + 10 Sinop (MGM); 82 (NHESS 2022)\",\n        gfm_kontrol=True, notlar=\"Sentinel-1A 11 Ağu / S1B 12 Ağu 2021 sahneleri CDSE'de; CEMS GFM taşkın yayılımı arşivde\"),\n    \"istanbul_2023\": dict(\n        ad=\"İstanbul Başakşehir–Küçükçekmece Kentsel Taşkını\", il=\"İstanbul\", ilce=\"Başakşehir\",\n        havza=\"Marmara\", tip=\"urban_pluvial\", baslangic=\"2023-09-05\", bitis=\"2023-09-05\",\n        lat=41.055, lon=28.780, half_deg=0.09,\n        yagis_mm=125.0, yagis_sure_saat=3, tepe_saat_utc=\"2023-09-05T17:00\", yagis_kaynak=\"İstanbul Valiliği basın açıklaması 2023/72 (18:30–21:30 yerel)\",\n        kayip=\"2 can kaybı, 12 yaralı, 1.754 konut/iş yeri (Valilik)\",\n        gfm_kontrol=False, notlar=\"Arnavutköy 128 mm, yerel 150 mm'ye kadar\"),\n    \"sanliurfa_2023\": dict(\n        ad=\"Şanlıurfa–Adıyaman Taşkını\", il=\"Şanlıurfa\", ilce=\"Haliliye\",\n        havza=\"Fırat-Dicle\", tip=\"flash_mixed\", baslangic=\"2023-03-14\", bitis=\"2023-03-15\",\n        lat=37.16, lon=38.80, half_deg=0.15,\n        yagis_mm=119.0, yagis_sure_saat=24, yagis_kaynak=\"MGM 2023 Yıllık İklim Raporu (günlük rekor, önceki 90,5 mm)\",\n        kayip=\"21 can kaybı (17 Şanlıurfa / 4 Adıyaman)\", gfm_kontrol=True, notlar=\"Deprem sonrası konteyner kentler etkilendi\"),\n    \"giresun_dereli_2020\": dict(\n        ad=\"Giresun Dereli Taşkını\", il=\"Giresun\", ilce=\"Dereli\", havza=\"Doğu Karadeniz\",\n        tip=\"riverine_flash\", baslangic=\"2020-08-22\", bitis=\"2020-08-22\", lat=40.74, lon=38.45, half_deg=0.15,\n        yagis_mm=137.6, yagis_sure_saat=24, yagis_kaynak=\"Su Kaynakları Dergisi (Yağlıdere Sınırköy, 133,1 mm/8 saat)\",\n        kayip=\"11 can kaybı + 4 kayıp\", gfm_kontrol=True, notlar=\"~12 saatlik konvektif yağış, 48 yıl tekerrür\"),\n    \"rize_2021\": dict(\n        ad=\"Rize Güneysu–Çayeli Taşkını\", il=\"Rize\", ilce=\"Güneysu\", havza=\"Doğu Karadeniz\",\n        tip=\"riverine_flash\", baslangic=\"2021-07-14\", bitis=\"2021-07-15\", lat=40.98, lon=40.60, half_deg=0.15,\n        yagis_mm=213.0, yagis_sure_saat=24, yagis_kaynak=\"TRT Haber (Güneysu 213 kg/m²)\",\n        kayip=\"6 can kaybı, 2 kayıp, 87 konut\", gfm_kontrol=True, notlar=\"\"),\n    \"rize_2025\": dict(\n        ad=\"Rize Ardeşen–İkizdere Taşkını\", il=\"Rize\", ilce=\"Ardeşen\", havza=\"Doğu Karadeniz\",\n        tip=\"riverine_flash\", baslangic=\"2025-09-20\", bitis=\"2025-09-21\", lat=41.05, lon=40.95, half_deg=0.18,\n        yagis_mm=161.8, yagis_sure_saat=24, yagis_kaynak=\"AFAD (İkizdere Çağrankaya)\", kayip=\"Can kaybı yok, 2 köprü yıkıldı\",\n        gfm_kontrol=True, notlar=\"MGM turuncu uyarı\"),\n    \"izmir_2021\": dict(\n        ad=\"İzmir Kemalpaşa–Bornova Taşkını\", il=\"İzmir\", ilce=\"Bornova\", havza=\"Gediz\",\n        tip=\"urban_pluvial\", baslangic=\"2021-02-02\", bitis=\"2021-02-02\", lat=38.44, lon=27.22, half_deg=0.12,\n        yagis_mm=None, yagis_sure_saat=12, yagis_kaynak=\"Doğrulanamadı — Open-Meteo/ERA5'ten türetilir\",\n        kayip=\"2 can kaybı\", gfm_kontrol=False, notlar=\"Kıyı kabarması ile birleşik\"),\n    \"izmir_2026\": dict(\n        ad=\"İzmir Buca–Konak Kentsel Taşkını\", il=\"İzmir\", ilce=\"Buca\", havza=\"Küçük Menderes\",\n        tip=\"urban_pluvial\", baslangic=\"2026-01-29\", bitis=\"2026-01-29\", lat=38.39, lon=27.17, half_deg=0.10,\n        yagis_mm=80.0, yagis_sure_saat=6, yagis_kaynak=\"İlkses (Buca 80 kg/m², Konak 63)\", kayip=\"963 su baskını ihbarı\",\n        gfm_kontrol=False, notlar=\"\"),\n    \"antalya_manavgat_2024\": dict(\n        ad=\"Antalya Manavgat Taşkını\", il=\"Antalya\", ilce=\"Manavgat\", havza=\"Antalya\",\n        tip=\"flash_mixed\", baslangic=\"2024-12-21\", bitis=\"2024-12-21\", lat=36.79, lon=31.44, half_deg=0.15,\n        yagis_mm=106.6, yagis_sure_saat=2, yagis_kaynak=\"Milliyet (2 saatte 106,6 kg/m²)\", kayip=\"2 yaralı, 236 kurtarma\",\n        gfm_kontrol=True, notlar=\"\"),\n    \"ankara_mamak_2018\": dict(\n        ad=\"Ankara Mamak Kentsel Taşkını\", il=\"Ankara\", ilce=\"Mamak\", havza=\"Sakarya\",\n        tip=\"urban_pluvial\", baslangic=\"2018-05-05\", bitis=\"2018-05-06\", lat=39.92, lon=32.92, half_deg=0.08,\n        yagis_mm=16.0, yagis_sure_saat=0.33, yagis_kaynak=\"Sabah (20 dakikada 16 kg/m²)\", kayip=\"6 yaralı, 100–150 araç\",\n        gfm_kontrol=False, notlar=\"\"),\n    \"hatay_samandag_2026\": dict(\n        ad=\"Hatay Samandağ Taşkını\", il=\"Hatay\", ilce=\"Samandağ\", havza=\"Asi\",\n        tip=\"riverine_flash\", baslangic=\"2026-05-21\", bitis=\"2026-05-23\", lat=36.09, lon=35.98, half_deg=0.14,\n        yagis_mm=None, yagis_sure_saat=48, yagis_kaynak=\"Doğrulanamadı — Open-Meteo/ERA5'ten türetilir\",\n        kayip=\"4 can kaybı, 1 kayıp, 1.600+ ihbar\", gfm_kontrol=True, notlar=\"\"),\n    \"rize_2026\": dict(\n        ad=\"Rize İyidere–Pazar Taşkını\", il=\"Rize\", ilce=\"İyidere\", havza=\"Doğu Karadeniz\",\n        tip=\"riverine_flash\", baslangic=\"2026-09-19\", bitis=\"2026-09-19\", lat=41.02, lon=40.35, half_deg=0.15,\n        yagis_mm=200.0, yagis_sure_saat=24, yagis_kaynak=\"Star/Yeni Şafak (200 kg/m²'ye kadar)\", kayip=\"1,04–1,42 milyar TL hasar\",\n        gfm_kontrol=True, notlar=\"\"),\n}\n\nDEFAULT_POC_PAIR = (\"bozkurt_2021\", \"istanbul_2023\")\n\n\n# ---------------------------------------------------------------------------\n# Çalıştırma yapılandırması\n# ---------------------------------------------------------------------------\n@dataclass\nclass RunConfig:\n    # Kapsam ve mod\n    mode: str = \"historical\"            # historical | forecast | nowcast\n    scope: str = \"event\"                # event | il | havza | turkiye | bbox\n    event_key: str = \"bozkurt_2021\"     # scope=event ise\n    il: str = \"Kastamonu\"               # scope=il ise\n    havza: str = \"Batı Karadeniz\"       # scope=havza ise\n    bbox: Optional[Tuple[float, float, float, float]] = None  # (S, W, N, E) scope=bbox\n    half_deg: float = 0.15              # il/tur. modunda merkez etrafı yarı genişlik\n    start: Optional[str] = None         # historical: olay başlangıcı; None ise olaydan\n    end: Optional[str] = None\n    forecast_days: int = 5\n    nowcast_hours: int = 12\n    run_frequency: str = \"daily\"        # hourly | daily | weekly (zamanlayıcı notu + pencere)\n    flood_type: str = \"auto\"            # auto | riverine | pluvial | both\n\n    # Çözünürlük / hesap bütçesi\n    grid_max: int = 300                 # DEM ızgarası en fazla NxN (yeniden örneklenir)\n    pluvial_grid_max: int = 300\n    mc_runs: int = 24\n    mc_grid_max: int = 120\n\n    # Kuantum\n    quantum_backend: str = \"default.qubit\"   # default.qubit | lightning.qubit | ibm | origin | none\n    target_device: str = \"simulator\"         # simulator | ibm_heron | origin_wukong | dwave_advantage (kontrol düzlemi boyutlandırması)\n    run_profile: str = \"default\"             # min-cost | default | max-fidelity\n    hydraulics: str = \"auto\"                 # auto | on | off — 2B sığ su çözücüsü (D); auto: grid ≤ 200 ise açık\n    hydraulics_grid_max: int = 150\n    hydraulics_minutes: float = 6.0\n    eda: bool = True                         # E: tam EDA takımı\n    quantum_portfolio: bool = True           # G: QAE + QSVM + QLSTM\n    qlstm_epochs: int = 12\n    n_qubits: int = 6\n    qml_epochs: int = 60\n    qml_layers: int = 3\n    qubo_solver: str = \"anneal\"              # anneal | dwave | none\n\n    # LLM anlatı\n    llm_provider: str = \"template\"           # template | gemini\n    gemini_model: str = \"gemini-2.5-flash\"\n\n    # Çıktı\n    out_dir: str = \"trflood_out\"\n    report_lang: str = \"tr\"                  # tr | en | both\n    seed: int = 42\n\n    # Etki parametreleri (kalibrasyon gerekli)\n    residential_max_damage_try_m2: float = 9000.0     # 2026 TL/m² (yaklaşık; Huizinga 2017 ölçek)\n    commercial_max_damage_try_m2: float = 14000.0\n    insurance_penetration: float = 0.12               # özel taşkın poliçesi tahmini oranı (DASK taşkını kapsamaz)\n    persons_per_building: float = 3.2\n    sewer_capacity_mm_h: float = 10.0\n    exposure_depth_m: float = 0.3                      # nüfus/yapı maruziyeti için derinlik eşiği                  # yapılaşmış hücrelerde yağmur suyu şebekesi kapasitesi (kalibrasyon gerekli)\n\n    def resolve(self) -> \"RunConfig\":\n        \"\"\"Kapsamdan bbox ve tarih penceresini türetir (yerinde günceller).\"\"\"\n        if self.scope == \"event\":\n            ev = EVENTS[self.event_key]\n            self.il = ev[\"il\"]\n            self.havza = ev[\"havza\"]\n            self.bbox = self.bbox or bbox_from_center(ev[\"lat\"], ev[\"lon\"], ev[\"half_deg\"])\n            if self.mode == \"historical\":\n                self.start = self.start or ev[\"baslangic\"]\n                self.end = self.end or ev[\"bitis\"]\n            if self.flood_type == \"auto\":\n                self.flood_type = \"pluvial\" if ev[\"tip\"] == \"urban_pluvial\" else (\"both\" if ev[\"tip\"] == \"flash_mixed\" else \"riverine\")\n        elif self.scope == \"il\":\n            code = PROVINCE_BY_NAME[slugify(self.il)]\n            _, lat, lon = PROVINCES[code]\n            self.bbox = self.bbox or bbox_from_center(lat, lon, self.half_deg)\n        elif self.scope == \"havza\":\n            self.bbox = self.bbox or BASINS[self.havza]\n        elif self.scope == \"turkiye\":\n            self.bbox = self.bbox or (35.8, 25.6, 42.2, 44.9)\n        elif self.scope == \"bbox\":\n            assert self.bbox is not None, \"scope=bbox için bbox verin\"\n        if self.flood_type == \"auto\":\n            self.flood_type = \"both\"\n        today = date.today()\n        if self.mode == \"forecast\":\n            self.start = today.isoformat()\n            self.end = (today + timedelta(days=self.forecast_days)).isoformat()\n        elif self.mode == \"nowcast\":\n            self.start = (today - timedelta(days=3)).isoformat()\n            self.end = (today + timedelta(days=1)).isoformat()\n        elif self.mode == \"historical\" and not self.start:\n            self.start = (today - timedelta(days=10)).isoformat()\n            self.end = (today - timedelta(days=7)).isoformat()\n        return self\n\n    @property\n    def center(self) -> Tuple[float, float]:\n        s, w, n, e = self.bbox  # type: ignore[misc]\n        return ((s + n) / 2, (w + e) / 2)\n\n    def to_json(self) -> str:\n        return json.dumps(asdict(self), ensure_ascii=False, indent=2, default=str)\n\n# ===========================================================================\n# 1. VERİ BAĞLAYICILARI (Türkiye kamu + açık veri)\n# ===========================================================================\nMGM_ORIGIN = {\"Origin\": \"https://www.mgm.gov.tr\", \"Referer\": \"https://www.mgm.gov.tr/\"}\n\n\nclass MGMClient:\n    \"\"\"servis.mgm.gov.tr — mgm.gov.tr ön yüzünün kullandığı JSON uçları (resmî değil, anahtarsız).\n    Zorunlu: Origin başlığı. Yağış toplamları yalnızca son 7 gün için sunulur.\"\"\"\n    BASE = \"https://servis.mgm.gov.tr/web\"\n\n    def _get(self, path: str, **params):\n        return http_json(f\"{self.BASE}/{path}\", params=params or None, headers=MGM_ORIGIN, timeout=40)\n\n    def iller(self) -> pd.DataFrame:\n        return pd.DataFrame(self._get(\"merkezler/iller\"))\n\n    def ilceler(self, il: str) -> pd.DataFrame:\n        return pd.DataFrame(self._get(\"merkezler/ililcesi\", il=il))\n\n    def merkez_lokasyon(self, lat: float, lon: float) -> dict:\n        return self._get(\"merkezler/lokasyon\", enlem=lat, boylam=lon)\n\n    def sondurum(self, merkez_id: int) -> dict:\n        j = self._get(\"sondurumlar\", merkezid=merkez_id)\n        return j[0] if j else {}\n\n    def toplam_yagis(self, tarih: str) -> pd.DataFrame:\n        \"\"\"~1.700 AWOS istasyonu için 24 saatlik (06Z–06Z) yağış toplamı; son 7 gün.\"\"\"\n        df = pd.DataFrame(self._get(\"sondurumlar/toplamyagis\", tarih=tarih))\n        if not df.empty:\n            df = df.rename(columns={\"deger\": \"yagis_mm\", \"enlem\": \"lat\", \"boylam\": \"lon\"})\n        return df\n\n    def toplam_yagis_bbox(self, tarih: str, bbox) -> pd.DataFrame:\n        df = self.toplam_yagis(tarih)\n        s, w, n, e = bbox\n        if df.empty:\n            return df\n        return df[(df.lat >= s) & (df.lat <= n) & (df.lon >= w) & (df.lon <= e)].copy()\n\n    def meteoalarm(self, gun: str = \"today\") -> List[dict]:\n        return self._get(f\"meteoalarm/{gun}\")\n\n    def alarmlar(self) -> List[dict]:\n        return self._get(\"alarmlar\")\n\n    def tahmin_saatlik(self, ist_no: int) -> pd.DataFrame:\n        j = self._get(\"tahminler/saatlik\", istno=ist_no)\n        return pd.DataFrame(j[0][\"tahmin\"]) if j else pd.DataFrame()\n\n    def tahmin_gunluk(self, ist_no: int) -> dict:\n        j = self._get(\"tahminler/gunluk\", istno=ist_no)\n        return j[0] if j else {}\n\n    def uyari_seviyesi(self, merkez_ids: List[int]) -> Tuple[str, str]:\n        \"\"\"Verilen ilçe merkezId'leri için bugünkü MeteoUyarı seviyesi (red>orange>yellow>none) ve metin.\"\"\"\n        try:\n            alarms = self.meteoalarm(\"today\")\n        except Exception as e:  # noqa: BLE001\n            log.warning(f\"MeteoUyarı alınamadı: {e}\")\n            return \"unknown\", \"\"\n        best, text = \"none\", \"\"\n        order = {\"none\": 0, \"yellow\": 1, \"orange\": 2, \"red\": 3}\n        for a in alarms:\n            towns = a.get(\"towns\", {})\n            for lvl in (\"red\", \"orange\", \"yellow\"):\n                if set(towns.get(lvl, [])) & set(merkez_ids) and order[lvl] > order[best]:\n                    best, text = lvl, (a.get(\"text\", {}) or {}).get(lvl, \"\")\n        return best, text\n\n\nclass OpenMeteoClient:\n    \"\"\"Open-Meteo: ERA5/ERA5-Land arşivi, çok modelli tahmin ve GloFAS v4 nehir debisi. Anahtarsız,\n    ticari olmayan kullanımda 10.000 çağrı/gün.\"\"\"\n    ARCHIVE = \"https://archive-api.open-meteo.com/v1/archive\"\n    FORECAST = \"https://api.open-meteo.com/v1/forecast\"\n    FLOOD = \"https://flood-api.open-meteo.com/v1/flood\"\n\n    def archive_hourly(self, lat, lon, start, end,\n                       variables=(\"precipitation\", \"temperature_2m\", \"soil_moisture_0_to_7cm\")) -> pd.DataFrame:\n        key = CACHE_DIR / f\"om_hourly_{lat:.3f}_{lon:.3f}_{start}_{end}.csv\"\n        if key.exists():\n            df = pd.read_csv(key, parse_dates=[\"time\"]); df[\"time\"] = pd.to_datetime(df[\"time\"], utc=True); return df.set_index(\"time\")\n        j = http_json(self.ARCHIVE, params=dict(latitude=lat, longitude=lon, start_date=start, end_date=end,\n                                                hourly=\",\".join(variables), timezone=\"UTC\"))\n        df = pd.DataFrame(j[\"hourly\"])\n        df[\"time\"] = pd.to_datetime(df[\"time\"], utc=True)\n        df.to_csv(key, index=False)\n        return df.set_index(\"time\")\n\n    def archive_daily(self, lat, lon, start, end) -> pd.DataFrame:\n        key = CACHE_DIR / f\"om_daily_{lat:.3f}_{lon:.3f}_{start}_{end}.csv\"\n        if key.exists():\n            df = pd.read_csv(key, parse_dates=[\"time\"]); df[\"time\"] = pd.to_datetime(df[\"time\"], utc=True); return df.set_index(\"time\")\n        j = http_json(self.ARCHIVE, params=dict(latitude=lat, longitude=lon, start_date=start, end_date=end,\n                                                daily=\"precipitation_sum,temperature_2m_mean,et0_fao_evapotranspiration\",\n                                                timezone=\"UTC\"))\n        df = pd.DataFrame(j[\"daily\"])\n        df[\"time\"] = pd.to_datetime(df[\"time\"], utc=True)\n        df.to_csv(key, index=False)\n        return df.set_index(\"time\")\n\n    def forecast_hourly(self, lat, lon, days=5, past_days=2, models=\"best_match\") -> pd.DataFrame:\n        j = http_json(self.FORECAST, params=dict(latitude=lat, longitude=lon, forecast_days=days, past_days=past_days,\n                                                 hourly=\"precipitation,precipitation_probability,temperature_2m,soil_moisture_0_to_1cm\",\n                                                 models=models, timezone=\"UTC\"))\n        df = pd.DataFrame(j[\"hourly\"])\n        df[\"time\"] = pd.to_datetime(df[\"time\"], utc=True)\n        return df.set_index(\"time\")\n\n    def flood_daily(self, lat, lon, start=None, end=None, past_days=None, forecast_days=None,\n                    ensemble=False, model=\"seamless_v4\") -> pd.DataFrame:\n        p = dict(latitude=lat, longitude=lon, models=model,\n                 daily=\"river_discharge,river_discharge_mean,river_discharge_max,river_discharge_min\")\n        if start:\n            p.update(start_date=start, end_date=end)\n        if past_days is not None:\n            p[\"past_days\"] = past_days\n        if forecast_days is not None:\n            p[\"forecast_days\"] = forecast_days\n        if ensemble:\n            p[\"ensemble\"] = \"true\"\n        key = CACHE_DIR / f\"om_flood_{lat:.3f}_{lon:.3f}_{start}_{end}_{model}.csv\" if start else None\n        if key is not None and key.exists():\n            df = pd.read_csv(key, parse_dates=[\"time\"]); df[\"time\"] = pd.to_datetime(df[\"time\"], utc=True); return df.set_index(\"time\")\n        j = http_json(self.FLOOD, params=p)\n        df = pd.DataFrame(j[\"daily\"])\n        df[\"time\"] = pd.to_datetime(df[\"time\"], utc=True)\n        if key is not None:\n            df.to_csv(key, index=False)\n        return df.set_index(\"time\")\n\n\nclass CHIRPSClient:\n    \"\"\"CHIRPS v2.0 günlük 0,05° GeoTIFF (UCSB CHC). Son ~1 ay 'prelim', öncesi 'final'.\"\"\"\n    FINAL = \"https://data.chc.ucsb.edu/products/CHIRPS-2.0/global_daily/tifs/p05/{y}/chirps-v2.0.{y}.{m:02d}.{d:02d}.tif.gz\"\n    PRELIM = \"https://data.chc.ucsb.edu/products/CHIRPS-2.0/prelim/global_daily/tifs/p05/{y}/chirps-v2.0.{y}.{m:02d}.{d:02d}.tif\"\n\n    def daily_mean_mm(self, bbox, d: date) -> Optional[float]:\n        import rasterio\n        from rasterio.windows import from_bounds\n        s, w, n, e = bbox\n        for tpl, gz in ((self.FINAL, True), (self.PRELIM, False)):\n            url = tpl.format(y=d.year, m=d.month, d=d.day)\n            try:\n                p = cached_download(url)\n                if gz:\n                    out = p.with_suffix(\"\")\n                    if not out.exists():\n                        with gzip.open(p, \"rb\") as fi, open(out, \"wb\") as fo:\n                            shutil.copyfileobj(fi, fo)\n                    p = out\n                with rasterio.open(p) as src:\n                    win = from_bounds(w, s, e, n, src.transform)\n                    arr = src.read(1, window=win).astype(float)\n                    arr[arr < 0] = np.nan\n                    return float(np.nanmean(arr))\n            except Exception:  # noqa: BLE001\n                continue\n        return None\n\n\nclass CopernicusDEM:\n    \"\"\"Copernicus GLO-30 DEM (1\", ~30 m) ve türev 30 m HAND — AWS açık veri, anahtarsız, COG.\n    Okuma /vsicurl/ pencereleri ile yapılır; yalnızca gereken kısım indirilir.\"\"\"\n    DEM = \"https://copernicus-dem-30m.s3.amazonaws.com/Copernicus_DSM_COG_10_{lat}_00_{lon}_00_DEM/Copernicus_DSM_COG_10_{lat}_00_{lon}_00_DEM.tif\"\n    HAND = \"https://glo-30-hand.s3.amazonaws.com/v1/2021/Copernicus_DSM_COG_10_{lat}_00_{lon}_00_HAND.tif\"\n\n    @staticmethod\n    def _tiles(bbox):\n        s, w, n, e = bbox\n        out = []\n        for la in range(math.floor(s), math.ceil(n)):\n            for lo in range(math.floor(w), math.ceil(e)):\n                out.append((f\"{'N' if la >= 0 else 'S'}{abs(la):02d}\", f\"{'E' if lo >= 0 else 'W'}{abs(lo):03d}\"))\n        return out\n\n    def read(self, bbox, kind=\"DEM\", max_px=1200) -> Tuple[np.ndarray, Any]:\n        \"\"\"Returns (array, affine transform) in EPSG:4326, mosaicked & clipped to bbox.\"\"\"\n        import rasterio\n        from rasterio.merge import merge\n        s, w, n, e = bbox\n        tpl = self.DEM if kind == \"DEM\" else self.HAND\n        srcs = []\n        for la, lo in self._tiles(bbox):\n            url = tpl.format(lat=la, lon=lo)\n            try:\n                srcs.append(rasterio.open(f\"/vsicurl/{url}\"))\n            except Exception as ex:  # noqa: BLE001\n                log.warning(f\"{kind} karo açılamadı {la}{lo}: {ex}\")\n        if not srcs:\n            raise RuntimeError(f\"{kind} karosu yok\")\n        res = srcs[0].res[0]\n        # bbox'u en fazla max_px piksele sığdırmak için çözünürlük kabalaştır\n        need = max((e - w) / res, (n - s) / res)\n        factor = max(1.0, need / max_px)\n        arr, transform = merge(srcs, bounds=(w, s, e, n), res=(res * factor, res * factor), nodata=-32767.0)\n        for sd in srcs:\n            sd.close()\n        arr = arr[0].astype(\"float32\")\n        arr[arr <= -32767.0] = np.nan\n        return arr, transform\n\n\nclass WorldCoverClient:\n    \"\"\"ESA WorldCover 10 m (2021, v200) — 3°×3° COG karoları, AWS, CC BY 4.0.\"\"\"\n    URL = \"https://esa-worldcover.s3.eu-central-1.amazonaws.com/v200/2021/map/ESA_WorldCover_10m_2021_v200_{lat}{lon}_Map.tif\"\n    CLASSES = {10: \"Ağaç örtüsü\", 20: \"Çalılık\", 30: \"Otlak\", 40: \"Tarım\", 50: \"Yapılaşmış\",\n               60: \"Çıplak/seyrek\", 70: \"Kar/buz\", 80: \"Su\", 90: \"Sulak alan\", 95: \"Mangrov\", 100: \"Yosun/liken\"}\n\n    def read_to_grid(self, bbox, shape) -> np.ndarray:\n        import rasterio\n        from rasterio.merge import merge\n        from rasterio.enums import Resampling\n        s, w, n, e = bbox\n        srcs = []\n        for la in range(math.floor(s / 3) * 3, math.ceil(n / 3) * 3, 3):\n            for lo in range(math.floor(w / 3) * 3, math.ceil(e / 3) * 3, 3):\n                url = self.URL.format(lat=f\"{'N' if la >= 0 else 'S'}{abs(la):02d}\", lon=f\"{'E' if lo >= 0 else 'W'}{abs(lo):03d}\")\n                try:\n                    srcs.append(rasterio.open(f\"/vsicurl/{url}\"))\n                except Exception as ex:  # noqa: BLE001\n                    log.warning(f\"WorldCover karo yok {la},{lo}: {ex}\")\n        if not srcs:\n            raise RuntimeError(\"WorldCover karosu yok\")\n        res_y = (n - s) / shape[0]\n        res_x = (e - w) / shape[1]\n        arr, _ = merge(srcs, bounds=(w, s, e, n), res=(res_x, res_y), nodata=0, resampling=Resampling.mode)\n        for sd in srcs:\n            sd.close()\n        out = arr[0].astype(\"uint8\")\n        return _fit_shape(out, shape)\n\n\nclass WorldPopClient:\n    \"\"\"WorldPop Türkiye 2025, 100 m, kısıtlı (constrained) — R2024B v1. Piksel = kişi sayısı.\"\"\"\n    URL = \"https://data.worldpop.org/GIS/Population/Global_2015_2030/R2024B/2025/TUR/v1/100m/constrained/tur_pop_2025_CN_100m_R2024B_v1.tif\"\n    FALLBACK = \"https://data.worldpop.org/GIS/Population/Global_2000_2020_Constrained/2020/BSGM/TUR/tur_ppp_2020_constrained.tif\"\n\n    def read_to_grid(self, bbox, shape) -> np.ndarray:\n        import rasterio\n        from rasterio.windows import from_bounds\n        s, w, n, e = bbox\n        for url in (self.URL, self.FALLBACK):\n            # WorldPop sunucusu HTTP Range desteklemez → dosya (73 MB) bir kez indirilip önbellekten okunur\n            for path in (f\"/vsicurl/{url}\", None):\n                try:\n                    p = path or str(cached_download(url))\n                    with rasterio.open(p) as src:\n                        win = from_bounds(w, s, e, n, src.transform)\n                        pop = src.read(1, window=win).astype(\"float64\")\n                        nd = src.nodata\n                        if nd is not None:\n                            pop[pop == nd] = 0\n                        pop[pop < 0] = 0\n                        return _resum_to_grid(pop, shape)\n                except Exception as ex:  # noqa: BLE001\n                    log.warning(f\"WorldPop okunamadı ({'vsicurl' if path else 'indirme'}) {url.split('/')[-1]}: {str(ex)[:80]}\")\n        raise RuntimeError(\"WorldPop okunamadı\")\n\n\nclass GeoBoundariesClient:\n    \"\"\"geoBoundaries gbOpen TUR ADM1 (il) / ADM2 (ilçe) — ODbL, GitHub raw GeoJSON.\"\"\"\n    API = \"https://www.geoboundaries.org/api/current/gbOpen/TUR/{lvl}/\"\n\n    def __init__(self):\n        self._cache: Dict[str, dict] = {}\n\n    def geojson(self, lvl=\"ADM2\") -> dict:\n        if lvl in self._cache:\n            return self._cache[lvl]\n        meta = http_json(self.API.format(lvl=lvl))\n        simp = meta.get(\"simplifiedGeometryGeoJSON\") or meta[\"gjDownloadURL\"]\n        candidates = [simp, simp.replace(\"https://github.com/\", \"https://media.githubusercontent.com/media/\").replace(\"/raw/\", \"/\"),\n                      meta[\"gjDownloadURL\"], f\"https://www.geoboundaries.org/data/geoBoundaries-3_0_0/TUR/{lvl}/geoBoundaries-3_0_0-TUR-{lvl}.geojson\"]\n        gj, last = None, None\n        for url in candidates:\n            try:\n                p = cached_download(url, f\"geoBoundaries_TUR_{lvl}.geojson\")\n                with open(p, encoding=\"utf-8\") as f:\n                    head = f.read(64)\n                if head.startswith(\"version https://git-lfs\"):\n                    p.unlink(); raise RuntimeError(\"LFS işaretçisi\")\n                with open(p, encoding=\"utf-8\") as f:\n                    gj = json.load(f)\n                break\n            except Exception as ex:  # noqa: BLE001\n                last = ex\n        if gj is None:\n            raise RuntimeError(f\"geoBoundaries indirilemedi: {last}\")\n        self._cache[lvl] = gj\n        return gj\n\n    @staticmethod\n    def _bbox_of(geom) -> Tuple[float, float, float, float]:\n        xs, ys = [], []\n        polys = geom[\"coordinates\"] if geom[\"type\"] == \"MultiPolygon\" else [geom[\"coordinates\"]]\n        for poly in polys:\n            for x, y in poly[0]:\n                xs.append(x); ys.append(y)\n        return (min(ys), min(xs), max(ys), max(xs))\n\n    def features_in_bbox(self, bbox, lvl=\"ADM2\") -> List[dict]:\n        s, w, n, e = bbox\n        out = []\n        for ft in self.geojson(lvl)[\"features\"]:\n            fs, fw, fn, fe = self._bbox_of(ft[\"geometry\"])\n            if fn < s or fs > n or fe < w or fw > e:\n                continue\n            out.append(dict(name=ft[\"properties\"].get(\"shapeName\"), bbox=(fs, fw, fn, fe), geometry=ft[\"geometry\"]))\n        return out\n\n    def province_bbox(self, il: str) -> Optional[Tuple[float, float, float, float]]:\n        target = slugify(il)\n        for ft in self.geojson(\"ADM1\")[\"features\"]:\n            if slugify(ft[\"properties\"].get(\"shapeName\", \"\")) == target:\n                return self._bbox_of(ft[\"geometry\"])\n        return None\n\n\ndef rasterize_features(features: List[dict], transform, shape) -> np.ndarray:\n    \"\"\"Poligon listesini ızgaraya (int etiket) çevirir; shapely gerekmez (matplotlib.path).\"\"\"\n    from matplotlib.path import Path as MPath\n    H, W = shape\n    cols, rows = np.meshgrid(np.arange(W) + 0.5, np.arange(H) + 0.5)\n    xs = transform.c + cols * transform.a\n    ys = transform.f + rows * transform.e\n    pts = np.column_stack([xs.ravel(), ys.ravel()])\n    labels = np.zeros(H * W, dtype=np.int32)\n    for i, ft in enumerate(features, start=1):\n        geom = ft[\"geometry\"]\n        polys = geom[\"coordinates\"] if geom[\"type\"] == \"MultiPolygon\" else [geom[\"coordinates\"]]\n        for poly in polys:\n            inside = MPath(np.array(poly[0])).contains_points(pts)\n            for hole in poly[1:]:\n                inside &= ~MPath(np.array(hole)).contains_points(pts)\n            labels[inside & (labels == 0)] = i\n    return labels.reshape(H, W)\n\n\nclass SoilGridsClient:\n    \"\"\"ISRIC SoilGrids v2.0 REST — kil/kum yüzdesinden hidrolojik toprak grubu (A–D).\"\"\"\n    URL = \"https://rest.isric.org/soilgrids/v2.0/properties/query\"\n\n    def hsg(self, lat, lon) -> Tuple[str, dict]:\n        j = http_json(self.URL, params=[(\"lon\", lon), (\"lat\", lat), (\"property\", \"clay\"), (\"property\", \"sand\"),\n                                        (\"depth\", \"0-5cm\"), (\"value\", \"mean\")], timeout=25, retries=1)\n        vals = {}\n        for layer in j[\"properties\"][\"layers\"]:\n            v = layer[\"depths\"][0][\"values\"][\"mean\"]\n            vals[layer[\"name\"]] = (v / 10.0) if v is not None else None  # g/kg → %\n        clay, sand = vals.get(\"clay\") or 20, vals.get(\"sand\") or 40\n        if sand > 70:\n            g = \"A\"\n        elif clay > 40:\n            g = \"D\"\n        elif clay > 27:\n            g = \"C\"\n        else:\n            g = \"B\"\n        return g, vals\n\n\nclass GFMClient:\n    \"\"\"CEMS Küresel Taşkın İzleme (GFM) — Sentinel-1 tabanlı 20 m taşkın yayılımı, EODC STAC (2015→).\n    Geçmiş olay doğrulaması için kullanılır.\"\"\"\n    STAC = \"https://stac.eodc.eu/api/v1/search\"\n\n    def flood_extent_to_grid(self, bbox, start: str, end: str, shape) -> Optional[Tuple[np.ndarray, List[str]]]:\n        import rasterio\n        from rasterio.windows import from_bounds\n        from rasterio.enums import Resampling\n        s, w, n, e = bbox\n        body = {\"collections\": [\"GFM\"], \"bbox\": [w, s, e, n], \"datetime\": f\"{start}T00:00:00Z/{end}T23:59:59Z\", \"limit\": 50}\n        r = _SESSION.post(self.STAC, json=body, timeout=60)\n        r.raise_for_status()\n        items = r.json().get(\"features\", [])\n        if not items:\n            return None\n        acc = np.zeros(shape, dtype=\"float32\")\n        used = []\n        for it in items:\n            href = None\n            for k, a in it.get(\"assets\", {}).items():\n                if \"flood_extent\" in k.lower() or \"ensemble_flood\" in k.lower():\n                    href = a[\"href\"]; break\n            if not href:\n                continue\n            try:\n                with rasterio.open(href if href.startswith(\"/vsicurl/\") else f\"/vsicurl/{href}\") as src:\n                    from rasterio.warp import transform_bounds\n                    b = transform_bounds(\"EPSG:4326\", src.crs, w, s, e, n)\n                    win = from_bounds(*b, src.transform)\n                    arr = src.read(1, window=win, out_shape=shape, resampling=Resampling.max, boundless=True, fill_value=0)\n                    acc = np.maximum(acc, (arr == 1).astype(\"float32\"))\n                    used.append(it[\"id\"])\n            except Exception as ex:  # noqa: BLE001\n                log.warning(f\"GFM okunamadı {it['id']}: {ex}\")\n        return (acc, used) if used else None\n\n\nclass IBBOpenData:\n    \"\"\"İBB Açık Veri Portalı (CKAN) — İSKİ baraj havzalarına düşen günlük yağış (2011→2024).\"\"\"\n    API = \"https://data.ibb.gov.tr/api/3/action/package_show\"\n\n    def iski_daily_rain(self) -> Optional[pd.DataFrame]:\n        try:\n            j = http_json(self.API, params={\"id\": \"istanbul-barajlarina-dusen-gunluk-toplam-yagis-miktari\"})\n            for res in j[\"result\"][\"resources\"]:\n                if res.get(\"format\", \"\").lower() in (\"xlsx\", \"csv\"):\n                    p = cached_download(res[\"url\"], \"iski_gunluk_yagis.\" + res[\"format\"].lower())\n                    return pd.read_excel(p) if p.suffix == \".xlsx\" else pd.read_csv(p)\n        except Exception as ex:  # noqa: BLE001\n            log.warning(f\"İBB açık veri alınamadı: {ex}\")\n        return None\n\n\nclass GoogleFloodClient:\n    \"\"\"Google Flood Forecasting API (bekleme listesi + API anahtarı; GOOGLE_FLOOD_API_KEY ortam değişkeni).\"\"\"\n    BASE = \"https://floodforecasting.googleapis.com/v1\"\n\n    def __init__(self):\n        self.key = os.environ.get(\"GOOGLE_FLOOD_API_KEY\")\n\n    def gauges_in_bbox(self, bbox) -> List[dict]:\n        if not self.key:\n            return []\n        r = _SESSION.post(f\"{self.BASE}/gauges:searchGaugesByArea\", params={\"key\": self.key},\n                          json={\"regionCode\": \"TR\"}, timeout=60)\n        r.raise_for_status()\n        s, w, n, e = bbox\n        out = []\n        for g in r.json().get(\"gauges\", []):\n            loc = g.get(\"location\", {})\n            if s <= loc.get(\"latitude\", -99) <= n and w <= loc.get(\"longitude\", -999) <= e:\n                out.append(g)\n        return out\n\n\nclass CDSClient:\n    \"\"\"Copernicus CDS/EWDS (ücretsiz jeton: ~/.cdsapirc). ERA5-Land saatlik yağış, GloFAS, EFAS-geçmiş.\"\"\"\n\n    def available(self) -> bool:\n        try:\n            import cdsapi  # noqa: F401\n            return Path.home().joinpath(\".cdsapirc\").exists() or bool(os.environ.get(\"CDSAPI_KEY\"))\n        except Exception:  # noqa: BLE001\n            return False\n\n    def era5_land_precip(self, bbox, start: str, end: str) -> Optional[Path]:\n        if not self.available():\n            return None\n        import cdsapi\n        s, w, n, e = bbox\n        d0, d1 = pd.Timestamp(start), pd.Timestamp(end)\n        target = CACHE_DIR / f\"era5land_{start}_{end}.nc\"\n        if target.exists():\n            return target\n        c = cdsapi.Client()\n        c.retrieve(\"reanalysis-era5-land\", {\n            \"variable\": [\"total_precipitation\"], \"year\": sorted({str(y) for y in range(d0.year, d1.year + 1)}),\n            \"month\": sorted({f\"{m:02d}\" for m in pd.date_range(d0, d1).month}),\n            \"day\": sorted({f\"{d:02d}\" for d in pd.date_range(d0, d1).day}),\n            \"time\": [f\"{h:02d}:00\" for h in range(24)], \"area\": [n, w, s, e],\n            \"data_format\": \"netcdf\", \"download_format\": \"unarchived\"}, str(target))\n        return target\n\n\n# ---- ızgara yardımcıları ----------------------------------------------------\ndef _fit_shape(a: np.ndarray, shape) -> np.ndarray:\n    H, W = shape\n    if a.shape == (H, W):\n        return a\n    ri = np.clip((np.arange(H) * a.shape[0] / H).astype(int), 0, a.shape[0] - 1)\n    ci = np.clip((np.arange(W) * a.shape[1] / W).astype(int), 0, a.shape[1] - 1)\n    return a[np.ix_(ri, ci)]\n\n\ndef _resum_to_grid(a: np.ndarray, shape) -> np.ndarray:\n    \"\"\"Sayım rasterını (kişi/piksel) hedef ızgaraya toplayarak yeniden örnekler (toplam korunur).\"\"\"\n    H, W = shape\n    h, w = a.shape\n    ri = np.minimum((np.arange(h) * H / h).astype(int), H - 1)\n    ci = np.minimum((np.arange(w) * W / w).astype(int), W - 1)\n    out = np.zeros((H, W))\n    np.add.at(out, (ri[:, None].repeat(w, 1), ci[None, :].repeat(h, 0)), a)\n    return out\n\n\ndef block_reduce(a: np.ndarray, target_max: int, func=np.nanmean) -> np.ndarray:\n    f = int(math.ceil(max(a.shape) / target_max))\n    if f <= 1:\n        return a\n    H = (a.shape[0] // f) * f\n    W = (a.shape[1] // f) * f\n    b = a[:H, :W].reshape(H // f, f, W // f, f)\n    return func(func(b, axis=3), axis=1)\n\n\n# ---- sentetik yedekler -------------------------------------------------------\ndef synthetic_dem(bbox, n=200, seed=0) -> Tuple[np.ndarray, Any]:\n    \"\"\"Vadi + menderesli akarsu + kentsel düzlük içeren sentetik arazi (bağlantı yoksa).\"\"\"\n    from rasterio.transform import from_bounds\n    rng = np.random.default_rng(seed)\n    s, w, nn, e = bbox\n    y, x = np.mgrid[0:n, 0:n] / n\n    river = 0.5 + 0.12 * np.sin(2 * np.pi * y * 1.5) + 0.04 * np.sin(2 * np.pi * y * 5)\n    dist = np.abs(x - river)\n    z = 120 + 260 * dist ** 0.9 + 60 * (1 - y) + 6 * rng.random((n, n)) + 15 * np.sin(6 * x + 3 * y)\n    z[dist < 0.012] -= 8\n    return z.astype(\"float32\"), from_bounds(w, s, e, nn, n, n)\n\n\ndef synthetic_landcover(dem: np.ndarray, seed=0) -> np.ndarray:\n    rng = np.random.default_rng(seed)\n    z = (dem - np.nanmin(dem)) / (np.nanmax(dem) - np.nanmin(dem) + 1e-9)\n    lc = np.full(dem.shape, 30, dtype=\"uint8\")\n    lc[z < 0.18] = 50\n    lc[(z >= 0.18) & (z < 0.45)] = 40\n    lc[z >= 0.6] = 10\n    lc[rng.random(dem.shape) < 0.03] = 80\n    return lc\n\n\ndef synthetic_population(lc: np.ndarray, seed=0) -> np.ndarray:\n    rng = np.random.default_rng(seed)\n    pop = np.zeros(lc.shape)\n    pop[lc == 50] = rng.gamma(2.0, 25.0, size=int((lc == 50).sum()))\n    pop[lc == 40] = rng.gamma(1.0, 1.5, size=int((lc == 40).sum()))\n    return pop\n\n\ndef scs_type2_hyetograph(total_mm: float, hours: float, dt_h: float = 1.0) -> pd.Series:\n    \"\"\"SCS Tip-II benzeri (merkezi tepe) tasarım hiyetografı; toplam total_mm, süre hours.\"\"\"\n    n = max(1, int(round(hours / dt_h)))\n    t = (np.arange(n) + 0.5) / n\n    sigma = max(0.2, 0.6 / max(hours, 1e-6))  # süre kısaldıkça tepe daha yayvan (3 s: 17/66/17)\n    w = np.exp(-((t - 0.5) ** 2) / (2 * sigma ** 2))\n    w = w / w.sum() * total_mm\n    idx = pd.date_range(\"2000-01-01\", periods=n, freq=f\"{int(dt_h*60)}min\", tz=\"UTC\")\n    return pd.Series(w, index=idx, name=\"precipitation\")\n\n# ===========================================================================\n# 2. ARAZİ ANALİZİ — DEM doldurma, D8 akış yönü, birikim, dere ağı, HAND\n# ===========================================================================\n_D8_OFF = [(-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1)]\n_D8_DIST = np.array([1, math.sqrt(2), 1, math.sqrt(2), 1, math.sqrt(2), 1, math.sqrt(2)])\n\n\ndef cell_size_m(transform, lat_center: float) -> Tuple[float, float]:\n    \"\"\"(dy_m, dx_m) EPSG:4326 dönüşümünden.\"\"\"\n    dy = abs(transform.e) * 111_320.0\n    dx = abs(transform.a) * 111_320.0 * math.cos(math.radians(lat_center))\n    return dy, dx\n\n\ndef fill_sinks(dem: np.ndarray) -> np.ndarray:\n    \"\"\"Priority-Flood (Barnes ve diğ. 2014) çukur doldurma; NaN'lar çevre ortalaması ile kapatılır.\"\"\"\n    import heapq\n    z = dem.astype(\"float64\").copy()\n    if np.isnan(z).any():\n        m = np.isnan(z)\n        z[m] = np.nanmean(z) if not np.isnan(np.nanmean(z)) else 0.0\n    H, W = z.shape\n    out = np.full_like(z, np.inf)\n    closed = np.zeros_like(z, dtype=bool)\n    heap = []\n    for i in range(H):\n        for j in (0, W - 1):\n            heap.append((z[i, j], i, j)); closed[i, j] = True\n    for j in range(1, W - 1):\n        for i in (0, H - 1):\n            heap.append((z[i, j], i, j)); closed[i, j] = True\n    heapq.heapify(heap)\n    while heap:\n        zc, i, j = heapq.heappop(heap)\n        out[i, j] = zc\n        for di, dj in _D8_OFF:\n            ni, nj = i + di, j + dj\n            if 0 <= ni < H and 0 <= nj < W and not closed[ni, nj]:\n                closed[ni, nj] = True\n                heapq.heappush(heap, (max(z[ni, nj], zc + 1e-4), ni, nj))\n    return out.astype(\"float32\")\n\n\ndef flow_direction_d8(zf: np.ndarray) -> np.ndarray:\n    \"\"\"Her hücre için en dik iniş yönü (0–7), akış yoksa -1.\"\"\"\n    H, W = zf.shape\n    zp = np.pad(zf, 1, mode=\"edge\")\n    best = np.full((H, W), -1, dtype=np.int8)\n    bestdrop = np.zeros((H, W))\n    for k, (di, dj) in enumerate(_D8_OFF):\n        zn = zp[1 + di:1 + di + H, 1 + dj:1 + dj + W]\n        drop = (zf - zn) / _D8_DIST[k]\n        m = drop > bestdrop\n        best[m] = k; bestdrop[m] = drop[m]\n    # kenarlara doğru akış izin ver (sınırda hücre dışına çıkış)\n    return best\n\n\ndef flow_accumulation(zf: np.ndarray, fdir: np.ndarray) -> np.ndarray:\n    H, W = zf.shape\n    acc = np.ones((H, W), dtype=\"float64\")\n    order = np.argsort(zf, axis=None)[::-1]  # yüksekten alçağa\n    ii, jj = np.unravel_index(order, (H, W))\n    for i, j in zip(ii, jj):\n        k = fdir[i, j]\n        if k < 0:\n            continue\n        di, dj = _D8_OFF[k]\n        ni, nj = i + di, j + dj\n        if 0 <= ni < H and 0 <= nj < W:\n            acc[ni, nj] += acc[i, j]\n    return acc\n\n\ndef compute_hand(zf: np.ndarray, fdir: np.ndarray, streams: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:\n    \"\"\"Height Above Nearest Drainage — akış yolundaki ilk dere hücresine göre yükseklik.\n    Döndürür: (hand, drain_idx) — drain_idx her hücrenin boşaldığı dere hücresinin düz indeksi.\"\"\"\n    H, W = zf.shape\n    drain = np.full((H, W), np.nan)\n    drain_idx = np.full((H, W), -1, dtype=np.int64)\n    flat = np.arange(H * W).reshape(H, W)\n    drain[streams] = zf[streams]; drain_idx[streams] = flat[streams]\n    order = np.argsort(zf, axis=None)  # alçaktan yükseğe: mansap önce işlenir\n    ii, jj = np.unravel_index(order, (H, W))\n    for i, j in zip(ii, jj):\n        if not np.isnan(drain[i, j]):\n            continue\n        k = fdir[i, j]\n        if k >= 0:\n            di, dj = _D8_OFF[k]\n            ni, nj = i + di, j + dj\n            if 0 <= ni < H and 0 <= nj < W and not np.isnan(drain[ni, nj]):\n                drain[i, j] = drain[ni, nj]; drain_idx[i, j] = drain_idx[ni, nj]\n                continue\n        drain[i, j] = zf[i, j]; drain_idx[i, j] = flat[i, j]  # kenardan çıkan hücre: kendi kotu (HAND=0 kabul)\n    return np.maximum(zf - drain, 0).astype(\"float32\"), drain_idx\n\n\ndef slope_fraction(zf: np.ndarray, dy_m: float, dx_m: float) -> np.ndarray:\n    gy, gx = np.gradient(zf, dy_m, dx_m)\n    return np.sqrt(gx ** 2 + gy ** 2)\n\n\n@dataclass\nclass Terrain:\n    dem: np.ndarray\n    filled: np.ndarray\n    fdir: np.ndarray\n    acc: np.ndarray\n    streams: np.ndarray\n    hand: np.ndarray\n    slope: np.ndarray\n    transform: Any\n    dy_m: float\n    dx_m: float\n    hand_source: str = \"computed\"\n    drain_idx: Optional[np.ndarray] = None\n    main_path: Optional[np.ndarray] = None      # ana kanal hücreleri (bool)\n    stage_scale: Optional[np.ndarray] = None    # yerel seviye ölçeği (memba alanına göre)\n    valid: Optional[np.ndarray] = None          # kara hücreleri (deniz/göl dışı)\n    props: Optional[dict] = None\n\n    @property\n    def cell_area_km2(self) -> float:\n        return self.dy_m * self.dx_m / 1e6\n\n    def catchment_props(self) -> dict:\n        \"\"\"Çıkış noktası (en büyük birikim), alan, ana kanal uzunluğu/eğimi, Kirpich tc, ana kanal maskesi.\"\"\"\n        if self.props is not None:\n            return self.props\n        H, W = self.filled.shape\n        acc = np.where(self.valid, self.acc, 0) if self.valid is not None else self.acc\n        oi, oj = np.unravel_index(np.argmax(acc), (H, W))\n        area_km2 = float(acc[oi, oj] * self.cell_area_km2)\n        path = np.zeros((H, W), dtype=bool); path[oi, oj] = True\n        i, j, length_m, path_z = oi, oj, 0.0, [self.filled[oi, oj]]\n        for _ in range(H * W):\n            best, bacc = None, 0\n            for k, (di, dj) in enumerate(_D8_OFF):\n                ni, nj = i + di, j + dj\n                if 0 <= ni < H and 0 <= nj < W and not path[ni, nj]:\n                    kk = self.fdir[ni, nj]\n                    if kk >= 0 and (ni + _D8_OFF[kk][0], nj + _D8_OFF[kk][1]) == (i, j) and self.acc[ni, nj] > bacc:\n                        best, bacc = (ni, nj, k), self.acc[ni, nj]\n            if best is None or bacc < 2:\n                break\n            ni, nj, k = best\n            length_m += _D8_DIST[k] * (self.dy_m + self.dx_m) / 2\n            path_z.append(self.filled[ni, nj]); path[ni, nj] = True; i, j = ni, nj\n        drop = max(path_z[-1] - path_z[0], 1.0)\n        s = drop / max(length_m, 100.0)\n        tc_min = 0.0195 * (max(length_m, 100.0) ** 0.77) * (s ** -0.385)  # Kirpich\n        self.main_path = path\n        self.props = dict(outlet_rc=(int(oi), int(oj)), area_km2=area_km2, channel_length_km=float(length_m / 1000),\n                          channel_slope=float(s), tc_hours=float(tc_min / 60), stream_cells=int(self.streams.sum()),\n                          outlet_acc=float(acc[oi, oj]))\n        return self.props\n\n\ndef build_terrain(dem: np.ndarray, transform, lat_center: float, stream_area_km2: float = 1.0,\n                  water_mask: Optional[np.ndarray] = None) -> Terrain:\n    dy, dx = cell_size_m(transform, lat_center)\n    valid = np.isfinite(dem) & (dem > 0.5)\n    if water_mask is not None:\n        valid &= ~water_mask\n    zf = fill_sinks(dem)\n    fdir = flow_direction_d8(zf)\n    acc = flow_accumulation(zf, fdir)\n    thr = max(2.0, stream_area_km2 / (dy * dx / 1e6))\n    streams = (acc >= thr) & valid\n    hand, drain_idx = compute_hand(zf, fdir, streams)\n    T = Terrain(dem=dem, filled=zf, fdir=fdir, acc=acc, streams=streams, hand=hand, slope=slope_fraction(zf, dy, dx),\n                transform=transform, dy_m=dy, dx_m=dx, hand_source=\"computed (GLO-30 DEM, D8)\", drain_idx=drain_idx, valid=valid)\n    props = T.catchment_props()\n    # yerel seviye ölçeği: boşaldığı dere hücresinin memba alanı / çıkış alanı (Q ~ A, h ~ Q^0.4)\n    acc_drain = acc.ravel()[drain_idx.ravel()].reshape(dem.shape)\n    T.stage_scale = np.clip((acc_drain / max(props[\"outlet_acc\"], 1.0)) ** 0.4, 0.12, 1.0).astype(\"float32\")\n    return T\n\n\n# ===========================================================================\n# 3. HİDROLOJİ — SCS-CN, Nash, HBV-light, GR4J, Sacramento (SAC-SMA), doğrusal hazne\n# ===========================================================================\n# WorldCover sınıfı × hidrolojik toprak grubu → SCS eğri numarası (AMC II, TR-55 tabloları)\nCN_TABLE = {\n    10: dict(A=36, B=60, C=73, D=79), 20: dict(A=35, B=56, C=70, D=77), 30: dict(A=49, B=69, C=79, D=84),\n    40: dict(A=67, B=78, C=85, D=89), 50: dict(A=89, B=92, C=94, D=95), 60: dict(A=77, B=86, C=91, D=94),\n    70: dict(A=98, B=98, C=98, D=98), 80: dict(A=100, B=100, C=100, D=100), 90: dict(A=90, B=93, C=95, D=97),\n    95: dict(A=90, B=93, C=95, D=97), 100: dict(A=60, B=75, C=83, D=87),\n}\n# Manning n ve sızma (mm/saat) — plüvyal CA modeli için\nMANNING_BY_LC = {10: 0.12, 20: 0.08, 30: 0.045, 40: 0.05, 50: 0.018, 60: 0.03, 70: 0.03, 80: 0.03, 90: 0.07, 95: 0.07, 100: 0.04}\nINFIL_BY_LC = {10: 18.0, 20: 12.0, 30: 9.0, 40: 7.0, 50: 0.8, 60: 4.0, 70: 0.0, 80: 0.0, 90: 2.0, 95: 2.0, 100: 6.0}\n\n\ndef composite_cn(lc: np.ndarray, hsg: str, amc: str = \"II\") -> Tuple[float, np.ndarray]:\n    cn = np.vectorize(lambda c: CN_TABLE.get(int(c), CN_TABLE[30])[hsg])(lc).astype(\"float32\")\n    if amc == \"I\":\n        cn = 4.2 * cn / (10 - 0.058 * cn)\n    elif amc == \"III\":\n        cn = 23 * cn / (10 + 0.13 * cn)\n    return float(np.clip(cn.mean(), 30, 100)), np.clip(cn, 30, 100)\n\n\ndef scs_excess(rain_mm: np.ndarray, cn: float, ia_ratio: float = 0.2) -> np.ndarray:\n    \"\"\"Kümülatif SCS-CN yöntemi ile artımlı etkin yağış (mm).\"\"\"\n    s = 25400.0 / cn - 254.0\n    ia = ia_ratio * s\n    P = np.cumsum(rain_mm)\n    Pe = np.where(P > ia, (P - ia) ** 2 / (P - ia + s), 0.0)\n    return np.diff(np.concatenate([[0.0], Pe]))\n\n\ndef scs_unit_hydrograph(tp_h: float, dt_h: float) -> np.ndarray:\n    \"\"\"SCS boyutsuz birim hidrografı (t/tp, q/qp tablosu) — 1 mm etkin yağış için m³/s·km².\"\"\"\n    t_ratio = np.array([0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.8, 2, 2.2, 2.4, 2.6, 2.8, 3, 3.5, 4, 4.5, 5])\n    q_ratio = np.array([0, .03, .1, .19, .31, .47, .66, .82, .93, .99, 1, .99, .93, .86, .78, .68, .56, .39, .28, .207, .147, .107, .077, .055, .025, .011, .005, 0])\n    qp = 0.208 / tp_h  # m³/s per km² per mm (SCS: qp = 0.208 A Q / tp)\n    t = np.arange(0, 5 * tp_h + dt_h, dt_h)\n    return np.interp(t / tp_h, t_ratio, q_ratio) * qp\n\n\ndef route_uh(excess_mm: np.ndarray, uh: np.ndarray, area_km2: float) -> np.ndarray:\n    return np.convolve(excess_mm, uh)[: len(excess_mm)] * area_km2\n\n\ndef model_scs_uh(rain: pd.Series, area_km2: float, cn: float, tc_h: float, baseflow: float = 0.0) -> pd.Series:\n    dt_h = _dt_hours(rain)\n    ex = scs_excess(rain.values, cn)\n    tp = max(0.6 * tc_h + dt_h / 2, dt_h)\n    q = route_uh(ex, scs_unit_hydrograph(tp, dt_h), area_km2) + baseflow\n    return pd.Series(q, index=rain.index, name=\"SCS-CN + SCS UH\")\n\n\ndef model_nash(rain: pd.Series, area_km2: float, cn: float, n_res: float = 3.0, k_h: float | None = None,\n               tc_h: float = 3.0, baseflow: float = 0.0) -> pd.Series:\n    from scipy.special import gamma as G\n    dt_h = _dt_hours(rain)\n    k = k_h or max(tc_h / n_res, dt_h)\n    t = np.arange(dt_h / 2, 8 * n_res * k, dt_h)\n    iuh = (1 / (k * G(n_res))) * (t / k) ** (n_res - 1) * np.exp(-t / k) * dt_h  # birim: 1/dt\n    uh = iuh / 3.6 / dt_h  # mm·km² → m³/s\n    q = route_uh(scs_excess(rain.values, cn), uh, area_km2) + baseflow\n    return pd.Series(q, index=rain.index, name=\"Nash kaskadı\")\n\n\ndef model_linear_reservoir(rain: pd.Series, area_km2: float, runoff_coef: float = 0.6, k_h: float = 4.0,\n                           baseflow: float = 0.0) -> pd.Series:\n    dt_h = _dt_hours(rain)\n    S, out = 0.0, []\n    for p in rain.values:\n        S += runoff_coef * p\n        q_mm = S * (1 - math.exp(-dt_h / k_h))\n        S -= q_mm\n        out.append(q_mm * area_km2 / 3.6 / dt_h + baseflow)\n    return pd.Series(out, index=rain.index, name=\"Doğrusal hazne\")\n\n\ndef model_hbv_light(rain: pd.Series, pet_mm_h: np.ndarray | float, area_km2: float, temp_c: np.ndarray | float = 15.0,\n                    params: dict | None = None, baseflow: float = 0.0) -> pd.Series:\n    \"\"\"HBV-light (Seibert 2005) — saatlik zaman adımına ölçeklenmiş; kar rutini dahil.\"\"\"\n    p = dict(TT=0.0, CFMAX=3.5, SFCF=0.9, CFR=0.05, CWH=0.1, FC=150.0, LP=0.7, BETA=1.8,\n             PERC=1.5, UZL=25.0, K0=0.4, K1=0.12, K2=0.01, MAXBAS=0.25, SM0=0.6)  # MAXBAS gün; olay ölçeğinde ≈ tc\n    if params:\n        p.update(params)\n    dt_h = _dt_hours(rain)\n    f = dt_h / 24.0  # günlük parametrelerin saatliğe ölçeği\n    P = rain.values\n    n = len(P)\n    T = np.broadcast_to(np.asarray(temp_c, dtype=float), (n,))\n    E = np.broadcast_to(np.asarray(pet_mm_h, dtype=float), (n,))\n    snow = swe = 0.0\n    sm = p[\"SM0\"] * p[\"FC\"]\n    suz = slz = 0.0\n    qs = np.zeros(n)\n    for i in range(n):\n        # kar\n        if T[i] < p[\"TT\"]:\n            snow += P[i] * p[\"SFCF\"]; rain_in = 0.0\n        else:\n            rain_in = P[i]\n        melt = min(snow, p[\"CFMAX\"] * f * max(T[i] - p[\"TT\"], 0))\n        snow -= melt; swe += melt\n        refr = min(swe, p[\"CFR\"] * p[\"CFMAX\"] * f * max(p[\"TT\"] - T[i], 0))\n        snow += refr; swe -= refr\n        excess_snow = max(swe - p[\"CWH\"] * snow, 0); swe -= excess_snow\n        inp = rain_in + excess_snow\n        # toprak\n        recharge = inp * (sm / p[\"FC\"]) ** p[\"BETA\"]\n        sm += inp - recharge\n        et = E[i] * min(sm / (p[\"LP\"] * p[\"FC\"]), 1.0)\n        sm = max(sm - et, 0.0)\n        if sm > p[\"FC\"]:\n            recharge += sm - p[\"FC\"]; sm = p[\"FC\"]\n        # tepki\n        suz += recharge\n        perc = min(suz, p[\"PERC\"] * f); suz -= perc; slz += perc\n        q0 = p[\"K0\"] * f * max(suz - p[\"UZL\"], 0)\n        q1 = p[\"K1\"] * f * suz\n        q2 = p[\"K2\"] * f * slz\n        suz -= q0 + q1; slz -= q2\n        qs[i] = q0 + q1 + q2\n    mb = max(int(round(p[\"MAXBAS\"] / f)), 1)\n    w = np.array([min(k + 1, mb - k) for k in range(mb)], dtype=float); w /= w.sum()\n    q = np.convolve(qs, w)[:n] * area_km2 / 3.6 / dt_h + baseflow\n    return pd.Series(q, index=rain.index, name=\"HBV-light\")\n\n\ndef model_gr4j(rain_daily: pd.Series, pet_daily: pd.Series, area_km2: float,\n               x1=350.0, x2=0.0, x3=90.0, x4=1.7) -> pd.Series:\n    \"\"\"GR4J (Perrin ve diğ. 2003) günlük — akış m³/s.\"\"\"\n    P, E = rain_daily.values, np.broadcast_to(pet_daily.values if hasattr(pet_daily, \"values\") else pet_daily, rain_daily.shape)\n    n = len(P)\n    nUH1, nUH2 = int(math.ceil(x4)), int(math.ceil(2 * x4))\n\n    def SH1(t):\n        return 0 if t <= 0 else (t / x4) ** 2.5 if t < x4 else 1\n\n    def SH2(t):\n        if t <= 0: return 0\n        if t < x4: return 0.5 * (t / x4) ** 2.5\n        if t < 2 * x4: return 1 - 0.5 * (2 - t / x4) ** 2.5\n        return 1\n    UH1 = np.array([SH1(j + 1) - SH1(j) for j in range(nUH1)])\n    UH2 = np.array([SH2(j + 1) - SH2(j) for j in range(nUH2)])\n    S, R = 0.3 * x1, 0.5 * x3\n    q1 = np.zeros(nUH1); q2 = np.zeros(nUH2)\n    out = np.zeros(n)\n    for i in range(n):\n        if P[i] >= E[i]:\n            Pn, En = P[i] - E[i], 0.0\n            ws = np.tanh(Pn / x1)\n            Ps = x1 * (1 - (S / x1) ** 2) * ws / (1 + S / x1 * ws)\n            S += Ps; Es = 0.0\n        else:\n            Pn, En = 0.0, E[i] - P[i]\n            we = np.tanh(En / x1)\n            Es = S * (2 - S / x1) * we / (1 + (1 - S / x1) * we)\n            S -= Es; Ps = 0.0\n        perc = S * (1 - (1 + (4 / 9 * S / x1) ** 4) ** -0.25)\n        S -= perc\n        Pr = perc + (Pn - Ps)\n        q1 = np.roll(q1, -1); q1[-1] = 0; q1 += UH1 * 0.9 * Pr\n        q2 = np.roll(q2, -1); q2[-1] = 0; q2 += UH2 * 0.1 * Pr\n        F = x2 * (R / x3) ** 3.5\n        R = max(R + q1[0] + F, 0)\n        Qr = R * (1 - (1 + (R / x3) ** 4) ** -0.25)\n        R -= Qr\n        Qd = max(q2[0] + F, 0)\n        out[i] = (Qr + Qd) * area_km2 / 86.4\n    return pd.Series(out, index=rain_daily.index, name=\"GR4J\")\n\n\ndef model_sacramento(rain: pd.Series, pet_mm_step: np.ndarray | float, area_km2: float,\n                     params: dict | None = None, baseflow: float = 0.0) -> pd.Series:\n    \"\"\"SAC-SMA (Burnash 1973) — sadeleştirilmiş uygulama (donmuş toprak yok). Adım: rain serisinin adımı.\"\"\"\n    p = dict(UZTWM=60.0, UZFWM=40.0, LZTWM=130.0, LZFPM=80.0, LZFSM=40.0, UZK=0.25, LZPK=0.01, LZSK=0.08,\n             ZPERC=60.0, REXP=1.5, PCTIM=0.02, ADIMP=0.05, PFREE=0.1, RIVA=0.0, SIDE=0.0, UNIT_K=6.0)\n    if params:\n        p.update(params)\n    dt_h = _dt_hours(rain)\n    f = dt_h / 24.0\n    P = rain.values; n = len(P)\n    E = np.broadcast_to(np.asarray(pet_mm_step, dtype=float), (n,))\n    uztwc, uzfwc = 0.5 * p[\"UZTWM\"], 0.2 * p[\"UZFWM\"]\n    lztwc, lzfpc, lzfsc = 0.5 * p[\"LZTWM\"], 0.5 * p[\"LZFPM\"], 0.3 * p[\"LZFSM\"]\n    adimc = uztwc + lztwc\n    tci = np.zeros(n)\n    parea = 1 - p[\"PCTIM\"] - p[\"ADIMP\"]\n    for i in range(n):\n        # ET\n        e1 = E[i] * uztwc / p[\"UZTWM\"]; red = E[i] - e1\n        uztwc -= e1\n        e2 = 0.0\n        if uztwc < 0:\n            e1 += uztwc; uztwc = 0\n            e2 = min(uzfwc, red); uzfwc -= e2; red -= e2\n        if uztwc / p[\"UZTWM\"] < uzfwc / p[\"UZFWM\"]:\n            tot = uztwc + uzfwc; r = tot / (p[\"UZTWM\"] + p[\"UZFWM\"])\n            uztwc, uzfwc = p[\"UZTWM\"] * r, p[\"UZFWM\"] * r\n        e3 = red * lztwc / (p[\"UZTWM\"] + p[\"LZTWM\"]); lztwc = max(lztwc - e3, 0)\n        ratlzt = lztwc / p[\"LZTWM\"]; ratlz = (lztwc + lzfpc + lzfsc) / (p[\"LZTWM\"] + p[\"LZFPM\"] + p[\"LZFSM\"])\n        if ratlzt < ratlz:\n            d = (ratlz - ratlzt) * p[\"LZTWM\"]; lztwc += d; lzfsc -= d\n            if lzfsc < 0:\n                lzfpc += lzfsc; lzfsc = 0\n        e5 = e1 + (red + e2) * (adimc - e1 - uztwc) / (p[\"UZTWM\"] + p[\"LZTWM\"]); adimc = max(adimc - e5, 0)\n        # yağış\n        twx = P[i] + uztwc - p[\"UZTWM\"]\n        if twx < 0:\n            uztwc += P[i]; twx = 0.0\n        else:\n            uztwc = p[\"UZTWM\"]\n        adimc += P[i] - twx\n        roimp = P[i] * p[\"PCTIM\"]\n        # perkolasyon & yüzey akışı\n        sbf = lzfpc * p[\"LZPK\"] * f + lzfsc * p[\"LZSK\"] * f\n        lzfpc -= lzfpc * p[\"LZPK\"] * f; lzfsc -= lzfsc * p[\"LZSK\"] * f\n        ssur = 0.0; sif = 0.0\n        pbase = p[\"LZFPM\"] * p[\"LZPK\"] * f + p[\"LZFSM\"] * p[\"LZSK\"] * f\n        defr = 1 - (lztwc + lzfpc + lzfsc) / (p[\"LZTWM\"] + p[\"LZFPM\"] + p[\"LZFSM\"])\n        perc = pbase * (1 + p[\"ZPERC\"] * defr ** p[\"REXP\"]) * uzfwc / p[\"UZFWM\"]\n        perc = min(perc, uzfwc, (p[\"LZTWM\"] - lztwc) + (p[\"LZFPM\"] - lzfpc) + (p[\"LZFSM\"] - lzfsc))\n        uzfwc -= perc\n        perct = perc * (1 - p[\"PFREE\"]); lztwc += perct\n        if lztwc > p[\"LZTWM\"]:\n            perc_free = perc * p[\"PFREE\"] + (lztwc - p[\"LZTWM\"]); lztwc = p[\"LZTWM\"]\n        else:\n            perc_free = perc * p[\"PFREE\"]\n        hpl = p[\"LZFPM\"] / (p[\"LZFPM\"] + p[\"LZFSM\"])\n        ratlp, ratls = lzfpc / p[\"LZFPM\"], lzfsc / p[\"LZFSM\"]\n        fracp = min(1.0, hpl * 2 * (1 - ratlp) / ((1 - ratlp) + (1 - ratls) + 1e-9))\n        percp, percs = perc_free * fracp, perc_free * (1 - fracp)\n        lzfsc += percs\n        if lzfsc > p[\"LZFSM\"]:\n            percp += lzfsc - p[\"LZFSM\"]; lzfsc = p[\"LZFSM\"]\n        lzfpc = min(lzfpc + percp, p[\"LZFPM\"])\n        # ara akış\n        sif = uzfwc * p[\"UZK\"] * f; uzfwc -= sif\n        # üst bölge serbest su\n        uzfwc += twx\n        if uzfwc > p[\"UZFWM\"]:\n            ssur = uzfwc - p[\"UZFWM\"]; uzfwc = p[\"UZFWM\"]\n        addro = twx * ((adimc - uztwc) / p[\"LZTWM\"]) ** 2 if adimc > uztwc else 0.0\n        adimc = min(adimc + twx - addro, p[\"UZTWM\"] + p[\"LZTWM\"])\n        tci[i] = roimp + addro * p[\"ADIMP\"] + (ssur + sif) * parea + sbf * parea * (1 - p[\"SIDE\"])\n    k = max(p[\"UNIT_K\"], dt_h)\n    t = np.arange(0, 6 * k, dt_h); uh = np.exp(-t / k); uh /= uh.sum()\n    q = np.convolve(tci, uh)[:n] * area_km2 / 3.6 / dt_h + baseflow\n    return pd.Series(q, index=rain.index, name=\"Sacramento SAC-SMA\")\n\n\ndef _dt_hours(s: pd.Series) -> float:\n    if len(s.index) < 2:\n        return 1.0\n    return float((s.index[1] - s.index[0]).total_seconds() / 3600.0)\n\n\n# ---- değerlendirme ölçütleri -------------------------------------------------\ndef nse(obs, sim) -> float:\n    obs, sim = np.asarray(obs, float), np.asarray(sim, float)\n    m = np.isfinite(obs) & np.isfinite(sim)\n    if m.sum() < 2:\n        return float(\"nan\")\n    return float(1 - np.sum((obs[m] - sim[m]) ** 2) / (np.sum((obs[m] - obs[m].mean()) ** 2) + 1e-12))\n\n\ndef kge(obs, sim) -> float:\n    obs, sim = np.asarray(obs, float), np.asarray(sim, float)\n    m = np.isfinite(obs) & np.isfinite(sim)\n    if m.sum() < 2:\n        return float(\"nan\")\n    r = np.corrcoef(obs[m], sim[m])[0, 1] if np.std(sim[m]) > 0 else 0.0\n    a = np.std(sim[m]) / (np.std(obs[m]) + 1e-12)\n    b = np.mean(sim[m]) / (np.mean(obs[m]) + 1e-12)\n    return float(1 - math.sqrt((r - 1) ** 2 + (a - 1) ** 2 + (b - 1) ** 2))\n\n\ndef evaluate_models(sims: Dict[str, pd.Series], reference: Optional[pd.Series]) -> pd.DataFrame:\n    rows = []\n    for name, q in sims.items():\n        row = dict(model=name, peak_m3s=float(q.max()), t_peak=q.idxmax(), volume_hm3=float(q.sum() * _dt_hours(q) * 3600 / 1e6))\n        if reference is not None and len(reference) > 2:\n            qd = q.resample(\"1D\").mean()\n            j = pd.concat([reference.rename(\"ref\"), qd.rename(\"sim\")], axis=1).dropna()\n            if len(j) >= 2:\n                row.update(NSE=nse(j.ref, j.sim), KGE=kge(j.ref, j.sim),\n                           peak_err_pct=float((j.sim.max() - j.ref.max()) / (j.ref.max() + 1e-9) * 100))\n        rows.append(row)\n    df = pd.DataFrame(rows)\n    if \"KGE\" in df:\n        df = df.sort_values(\"KGE\", ascending=False)\n    return df.reset_index(drop=True)\n\n\ndef run_classical_models(rain: pd.Series, area_km2: float, cn: float, tc_h: float, pet_mm_h: float = 0.15,\n                         temp_c: float = 15.0, baseflow: float = 0.0, daily_rain: pd.Series | None = None,\n                         daily_pet: pd.Series | None = None) -> Dict[str, pd.Series]:\n    \"\"\"Tüm klasik modelleri ayrı ayrı çalıştırır (Malezya v6.0 gereksinimi: her model bağımsız).\"\"\"\n    sims = {\n        \"SCS-CN + SCS UH\": model_scs_uh(rain, area_km2, cn, tc_h, baseflow),\n        \"Nash kaskadı\": model_nash(rain, area_km2, cn, tc_h=tc_h, baseflow=baseflow),\n        \"Doğrusal hazne\": model_linear_reservoir(rain, area_km2, runoff_coef=min(0.95, cn / 100), k_h=max(tc_h, 1.0), baseflow=baseflow),\n        \"HBV-light\": model_hbv_light(rain, pet_mm_h, area_km2, temp_c, params=dict(MAXBAS=max(tc_h, 1.0) / 24.0), baseflow=baseflow),\n        \"Sacramento SAC-SMA\": model_sacramento(rain, pet_mm_h, area_km2, baseflow=baseflow),\n    }\n    if daily_rain is not None:\n        pet_d = daily_pet if daily_pet is not None else pd.Series(pet_mm_h * 24, index=daily_rain.index)\n        sims[\"GR4J\"] = model_gr4j(daily_rain, pet_d, area_km2)\n    return sims\n\n# ===========================================================================\n# 4. TAŞMA — HAND tabanlı nehir taşkını ve 2B hücresel otomat plüvyal model\n# ===========================================================================\ndef synthetic_rating_curve(terrain: Terrain, channel_slope: float, manning_n: float = 0.045,\n                           max_stage: float = 15.0, step: float = 0.1) -> pd.DataFrame:\n    \"\"\"HAND'den sentetik anahtar eğrisi (Zheng ve diğ. 2018): yalnızca ana kanala boşalan koridor hücreleri\n    kullanılır; kesit alanı ve hidrolik yarıçap seviyeye bağlanır, Q = A R^(2/3) S^(1/2) / n (erişim ortalaması).\"\"\"\n    cell_area = terrain.dy_m * terrain.dx_m\n    cell_len = (terrain.dy_m + terrain.dx_m) / 2\n    props = terrain.catchment_props()\n    main = terrain.main_path if terrain.main_path is not None else terrain.streams\n    L = max(props[\"channel_length_km\"] * 1000.0, cell_len)\n    corridor = np.isin(terrain.drain_idx, np.flatnonzero(main)) if terrain.drain_idx is not None else np.ones_like(terrain.hand, bool)\n    if terrain.valid is not None:\n        corridor &= terrain.valid\n    hand = np.where(corridor, terrain.hand, np.inf)\n    rows = []\n    for h in np.arange(step, max_stage + step, step):\n        wet = hand < h\n        depth = (h - hand[wet])\n        A = depth.sum() * cell_area / L\n        B = wet.sum() * cell_area / L  # ıslak üst genişlik yaklaşımı\n        R = A / (B + 2 * h + 1e-6)\n        Q = A * R ** (2 / 3) * math.sqrt(max(channel_slope, 1e-4)) / manning_n\n        rows.append(dict(stage_m=h, Q_m3s=Q, area_m2=A, top_width_m=B, wet_cells=int(wet.sum())))\n    return pd.DataFrame(rows)\n\n\ndef stage_from_discharge(rating: pd.DataFrame, Q: float) -> float:\n    return float(np.interp(Q, rating.Q_m3s.values, rating.stage_m.values))\n\n\ndef hand_inundation(terrain: Terrain, stage_m: float) -> np.ndarray:\n    \"\"\"Derinlik = yerel seviye − HAND; yerel seviye, hücrenin boşaldığı dere kolunun memba alanına göre ölçeklenir\n    (ana kanalda tam seviye, küçük yan kollarda daha düşük).\"\"\"\n    scale = terrain.stage_scale if terrain.stage_scale is not None else 1.0\n    d = stage_m * scale - terrain.hand\n    d[d < 0] = 0\n    if terrain.valid is not None:\n        d[~terrain.valid] = 0\n    return d.astype(\"float32\")\n\n\ndef pluvial_ca(zf: np.ndarray, rain_mm_h: pd.Series, lc: np.ndarray, dy_m: float, dx_m: float,\n               drain_hours: float = 2.0, dt_max: float = 30.0, max_minutes: float = 30.0,\n               manning_scale: float = 1.0, infil_scale: float = 1.0, record_every_min: float = 15.0,\n               sink: Optional[np.ndarray] = None, sewer_mm_h: float = 0.0) -> dict:\n    \"\"\"Ağırlıklı hücresel otomat (Ghimire ve diğ. 2013 / CADDIES tarzı) yüzeysel akış modeli.\n    Yağış fazlası her hücreye eklenir; su, Manning hızıyla 8 komşuya baş farkına göre dağılır.\n    sink: deniz/göl hücreleri (su emilir, derinlik birikmez).\"\"\"\n    t0 = time.time()\n    H_, W_ = zf.shape\n    if sink is None:\n        sink = (lc == 80) | (zf <= 0.5)\n    n_map = np.vectorize(lambda c: MANNING_BY_LC.get(int(c), 0.05))(lc) * manning_scale\n    infil = np.vectorize(lambda c: INFIL_BY_LC.get(int(c), 6.0))(lc) * infil_scale\n    infil = (infil + np.where(lc == 50, sewer_mm_h, 0.0)) / 3600.0 / 1000.0  # m/s (sızma + şebeke kapasitesi)\n    zpad = np.pad(zf, 1, mode=\"edge\") - 0.0\n    zpad[0, :] -= 5; zpad[-1, :] -= 5; zpad[:, 0] -= 5; zpad[:, -1] -= 5  # sınırlar: çıkış\n    h = np.zeros((H_ + 2, W_ + 2))\n    hmax = np.zeros((H_, W_))\n    dt_h = _dt_hours(rain_mm_h)\n    rain = rain_mm_h.values / 1000.0 / (dt_h * 3600.0)  # m/s\n    T_total = (len(rain) * dt_h + drain_hours) * 3600.0\n    dist = np.array([(dy_m + dx_m) / 2 if k % 2 == 0 else math.hypot(dy_m, dx_m) for k in range(8)])\n    dist[0] = dist[4] = dy_m; dist[2] = dist[6] = dx_m\n    t, steps, snapshots = 0.0, 0, []\n    next_snap = record_every_min * 60\n    while t < T_total:\n        if time.time() - t0 > max_minutes * 60:\n            log.warning(\"pluvial_ca: süre bütçesi doldu, erken bitirildi\"); break\n        idx = min(int(t // (dt_h * 3600.0)), len(rain) - 1) if t < len(rain) * dt_h * 3600.0 else None\n        core = h[1:-1, 1:-1]\n        wl = zpad + h\n        outflow = np.zeros((8, H_, W_))\n        vmax = 0.0\n        for k, (di, dj) in enumerate(_D8_OFF):\n            wn = wl[1 + di:1 + di + H_, 1 + dj:1 + dj + W_]\n            dh = wl[1:-1, 1:-1] - wn\n            pos = dh > 1e-4\n            if not pos.any():\n                continue\n            hc = core[pos]\n            v = (1.0 / n_map[pos]) * np.maximum(hc, 1e-4) ** (2 / 3) * np.sqrt(dh[pos] / dist[k])\n            v = np.minimum(v, 6.0)\n            vmax = max(vmax, float(v.max()) if v.size else 0.0)\n            flux = np.zeros_like(dh); flux[pos] = v * hc / dist[k]  # m/s (derinlik/s)\n            outflow[k] = flux\n        dt = min(dt_max, 0.6 * dist.min() / (vmax + 1e-3))\n        dt = max(dt, 0.5)\n        tot = outflow.sum(axis=0) * dt\n        cap = 0.5 * core\n        scale = np.where(tot > cap, cap / (tot + 1e-12), 1.0)\n        outflow *= scale * dt\n        newh = h.copy()\n        newh[1:-1, 1:-1] -= outflow.sum(axis=0)\n        for k, (di, dj) in enumerate(_D8_OFF):\n            newh[1 + di:1 + di + H_, 1 + dj:1 + dj + W_] += outflow[k]\n        # yağış ve sızma\n        core_new = newh[1:-1, 1:-1]\n        if idx is not None:\n            core_new += rain[idx] * dt\n        core_new -= np.minimum(core_new, infil * dt)\n        core_new[sink] = 0.0\n        newh[1:-1, 1:-1] = core_new\n        newh[0, :] = 0; newh[-1, :] = 0; newh[:, 0] = 0; newh[:, -1] = 0  # sınırdan çıkan su kaybolur\n        h = newh\n        hmax = np.maximum(hmax, h[1:-1, 1:-1])\n        t += dt; steps += 1\n        if t >= next_snap:\n            snapshots.append((t / 3600.0, h[1:-1, 1:-1].copy())); next_snap += record_every_min * 60\n    return dict(max_depth=hmax.astype(\"float32\"), final_depth=h[1:-1, 1:-1].astype(\"float32\"), steps=steps,\n                sim_hours=t / 3600.0, runtime_s=time.time() - t0, snapshots=snapshots)\n\n\n# ===========================================================================\n# 5. KUANTUM KATMANI — VQC tabanlı QML tahmin, QUBO/QAOA kaynak tahsisi\n# ===========================================================================\ndef build_forecast_dataset(daily_rain: pd.Series, daily_q: pd.Series, lags: int = 7, horizon: int = 1) -> Tuple[pd.DataFrame, pd.Series]:\n    df = pd.concat([daily_rain.rename(\"P\"), daily_q.rename(\"Q\")], axis=1).dropna()\n    X = pd.DataFrame(index=df.index)\n    for L in range(0, lags):\n        X[f\"P_l{L}\"] = df.P.shift(L)\n    for L in range(1, 4):\n        X[f\"Q_l{L}\"] = df.Q.shift(L)\n    X[\"P_3d\"] = df.P.rolling(3).sum(); X[\"P_7d\"] = df.P.rolling(7).sum()\n    doy = df.index.dayofyear\n    X[\"s\"] = np.sin(2 * np.pi * doy / 365.25); X[\"c\"] = np.cos(2 * np.pi * doy / 365.25)\n    y = df.Q.shift(-horizon)\n    m = X.notna().all(axis=1) & y.notna()\n    return X[m], y[m]\n\n\nclass QMLForecaster:\n    \"\"\"Değişkensel kuantum devresi (VQC) ile regresyon: PCA → açı kodlama (RY) → StronglyEntanglingLayers → <Z0>.\n    Eğitim: default.qubit simülatörü (parametre yayını + backprop; 6 kübit/60 adım ≈ saniyeler).\n    Donanım çıkarımı (isteğe bağlı): backend='ibm' → Qiskit Runtime (pennylane-qiskit), backend='origin' →\n    Origin Wukong / pyqpanda3 (QCloud jetonu varsa donanım, yoksa yerel simülatör). Test kümesinin bir alt kümesi\n    donanımda yeniden değerlendirilir; simülatör tahminleri her zaman üretilir.\"\"\"\n\n    def __init__(self, n_qubits=6, layers=3, epochs=60, backend=\"default.qubit\", seed=42, lr=0.05, batch=48, hw_samples=8):\n        self.n, self.layers, self.epochs, self.backend, self.seed, self.lr, self.batch, self.hw_samples = n_qubits, layers, epochs, backend, seed, lr, batch, hw_samples\n        self.history: List[float] = []\n        self.info = dict(backend=backend, n_qubits=n_qubits, layers=layers, train_device=\"default.qubit (backprop)\")\n\n    def _prep(self, X, fit=False):\n        from sklearn.decomposition import PCA\n        from sklearn.preprocessing import StandardScaler, MinMaxScaler\n        if fit:\n            self.sc = StandardScaler().fit(X)\n            k = min(self.n, X.shape[1])\n            self.pca = PCA(n_components=k, random_state=self.seed).fit(self.sc.transform(X))\n            Z = self.pca.transform(self.sc.transform(X))\n            self.mm = MinMaxScaler((0, np.pi)).fit(Z)\n        Z = self.pca.transform(self.sc.transform(X))\n        Z = self.mm.transform(Z)\n        if Z.shape[1] < self.n:\n            Z = np.hstack([Z, np.zeros((Z.shape[0], self.n - Z.shape[1]))])\n        return np.clip(Z, 0, np.pi)\n\n    def fit(self, X: pd.DataFrame, y: pd.Series):\n        import pennylane as qml\n        from pennylane import numpy as pnp\n        Z = self._prep(X.values, fit=True)\n        self.ymu, self.ysd = float(y.mean()), float(y.std() + 1e-9)\n        yy = ((y.values - self.ymu) / self.ysd).astype(float)\n        dev = qml.device(\"default.qubit\", wires=self.n)\n        n = self.n\n\n        @qml.qnode(dev, diff_method=\"backprop\")\n        def circuit(x, w):\n            qml.AngleEmbedding(x, wires=range(n), rotation=\"Y\")\n            qml.StronglyEntanglingLayers(w, wires=range(n))\n            return qml.expval(qml.PauliZ(0))\n\n        self.circuit = circuit\n        rng = np.random.default_rng(self.seed)\n        w = pnp.array(rng.normal(0, 0.3, (self.layers, n, 3)), requires_grad=True)\n        a = pnp.array(2.0, requires_grad=True); b = pnp.array(0.0, requires_grad=True)\n        opt = qml.AdamOptimizer(self.lr)\n\n        def loss(w, a, b, Xb, yb):\n            return pnp.mean((a * circuit(Xb, w) + b - yb) ** 2)\n        N = len(Z)\n        for _ in range(self.epochs):\n            idx = rng.permutation(N)[: self.batch]\n            (w, a, b), L = opt.step_and_cost(loss, w, a, b, Xb=Z[idx], yb=yy[idx])\n            self.history.append(float(L))\n        self.w, self.a, self.b = np.array(w), float(a), float(b)\n        return self\n\n    def predict(self, X: pd.DataFrame) -> np.ndarray:\n        Z = self._prep(X.values)\n        out = np.asarray(self.circuit(Z, self.w), dtype=float) * self.a + self.b\n        return out * self.ysd + self.ymu\n\n    # ---- donanım çıkarımı ------------------------------------------------\n    def predict_hardware(self, X: pd.DataFrame) -> Optional[np.ndarray]:\n        \"\"\"Test alt kümesini gerçek QPU'da (IBM veya Origin) değerlendirir; kullanılamazsa None.\"\"\"\n        Z = self._prep(X.values)[: self.hw_samples]\n        if self.backend == \"ibm\":\n            try:\n                import pennylane as qml\n                from qiskit_ibm_runtime import QiskitRuntimeService\n                svc = QiskitRuntimeService()\n                bk = svc.least_busy(operational=True, simulator=False, min_num_qubits=self.n)\n                dev = qml.device(\"qiskit.remote\", wires=self.n, backend=bk, shots=1024)\n                n = self.n\n\n                @qml.qnode(dev)\n                def hw(x, w):\n                    qml.AngleEmbedding(x, wires=range(n), rotation=\"Y\")\n                    qml.StronglyEntanglingLayers(w, wires=range(n))\n                    return qml.expval(qml.PauliZ(0))\n                vals = np.array([float(hw(z, self.w)) for z in Z])\n                self.info.update(hw_backend=bk.name, hw_samples=len(Z))\n                return vals * self.a * self.ysd + self.b * self.ysd + self.ymu\n            except Exception as ex:  # noqa: BLE001\n                self.info[\"hw_note\"] = f\"IBM çıkarımı yapılamadı: {str(ex)[:100]}\"\n                return None\n        if self.backend == \"origin\":\n            try:\n                import pyqpanda3.core as pq\n                n = self.n\n                vals = []\n                for z in Z:\n                    prog = pq.QProg()\n                    for i in range(n):\n                        prog << pq.RY(i, float(z[i]))\n                    for L in range(self.layers):\n                        for i in range(n):\n                            phi, th, om = (float(v) for v in self.w[L, i])\n                            prog << pq.RZ(i, phi) << pq.RY(i, th) << pq.RZ(i, om)\n                        r = (L % (n - 1)) + 1 if n > 1 else 0\n                        for i in range(n):\n                            if n > 1:\n                                prog << pq.CNOT(i, (i + r) % n)\n                    prog << pq.measure_all()\n                    if os.environ.get(\"ORIGIN_QCLOUD_TOKEN\"):\n                        from pyqpanda3.qcloud import QCloudService  # type: ignore\n                        svc = QCloudService(os.environ[\"ORIGIN_QCLOUD_TOKEN\"])\n                        res = svc.run(prog, 1024)\n                        counts = res.get_counts() if hasattr(res, \"get_counts\") else res\n                        self.info[\"hw_backend\"] = \"Origin Wukong (QCloud)\"\n                    else:\n                        qvm = pq.CPUQVM(); qvm.run(prog, 1024); counts = qvm.result().get_counts()\n                        self.info[\"hw_backend\"] = \"pyqpanda3 CPUQVM (yerel simülatör)\"\n                    tot = sum(counts.values()); z0 = sum(c * (1 if k[-1] == \"0\" else -1) for k, c in counts.items()) / tot\n                    vals.append(z0)\n                vals = np.array(vals); self.info[\"hw_samples\"] = len(Z)\n                return vals * self.a * self.ysd + self.b * self.ysd + self.ymu\n            except Exception as ex:  # noqa: BLE001\n                self.info[\"hw_note\"] = f\"Origin çıkarımı yapılamadı: {str(ex)[:100]}\"\n                return None\n        return None\n\n\ndef classical_baselines(Xtr, ytr, Xte) -> Dict[str, np.ndarray]:\n    from sklearn.linear_model import Ridge\n    from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor\n    out = {}\n    out[\"Ridge\"] = Ridge(alpha=1.0).fit(Xtr, ytr).predict(Xte)\n    out[\"RandomForest\"] = RandomForestRegressor(200, random_state=0, min_samples_leaf=3).fit(Xtr, ytr).predict(Xte)\n    out[\"GradientBoosting\"] = GradientBoostingRegressor(random_state=0).fit(Xtr, ytr).predict(Xte)\n    return out\n\n\ndef qubo_team_allocation(risk: np.ndarray, teams: int, solver: str = \"anneal\", seed: int = 0) -> Tuple[np.ndarray, dict]:\n    \"\"\"Acil müdahale ekiplerini ilçelere atar: max Σ r_i x_i, Σ x_i = teams (QUBO; SA / D-Wave / QAOA).\"\"\"\n    M = len(risk)\n    r = risk / (risk.max() + 1e-9)\n    lam = 2.0\n    Q = np.zeros((M, M))\n    for i in range(M):\n        Q[i, i] = -r[i] + lam * (1 - 2 * teams)\n        for j in range(i + 1, M):\n            Q[i, j] = 2 * lam\n    info = dict(solver=solver, M=M, teams=teams)\n    if solver == \"dwave\":\n        try:\n            import dimod\n            from dwave.system import DWaveSampler, EmbeddingComposite\n            bqm = dimod.BinaryQuadraticModel.from_qubo({(i, j): Q[i, j] for i in range(M) for j in range(i, M) if Q[i, j] != 0})\n            ss = EmbeddingComposite(DWaveSampler()).sample(bqm, num_reads=200)\n            x = np.array([ss.first.sample[i] for i in range(M)]); info[\"note\"] = \"D-Wave QPU\"\n            return x, info\n        except Exception as ex:  # noqa: BLE001\n            info[\"note\"] = f\"D-Wave kullanılamadı ({ex}); SA'ya dönüldü\"\n    if solver == \"qaoa\" and M <= 10:\n        try:\n            import pennylane as qml\n            from pennylane import numpy as pnp\n            Hc = qml.Hamiltonian([], [])\n            coeffs, ops = [], []\n            for i in range(M):\n                coeffs.append(-0.5 * Q[i, i]); ops.append(qml.PauliZ(i))\n                for j in range(i + 1, M):\n                    if Q[i, j] != 0:\n                        coeffs += [0.25 * Q[i, j], -0.25 * Q[i, j], -0.25 * Q[i, j]]\n                        ops += [qml.PauliZ(i) @ qml.PauliZ(j), qml.PauliZ(i), qml.PauliZ(j)]\n            Hc = qml.Hamiltonian(coeffs, ops)\n            Hm = qml.Hamiltonian([1.0] * M, [qml.PauliX(i) for i in range(M)])\n            dev = qml.device(\"default.qubit\", wires=M)\n\n            def layer(g, b):\n                qml.qaoa.cost_layer(g, Hc); qml.qaoa.mixer_layer(b, Hm)\n\n            @qml.qnode(dev)\n            def cost(p):\n                for i in range(M): qml.Hadamard(i)\n                qml.layer(layer, 2, p[0], p[1])\n                return qml.expval(Hc)\n\n            @qml.qnode(dev)\n            def probs(p):\n                for i in range(M): qml.Hadamard(i)\n                qml.layer(layer, 2, p[0], p[1])\n                return qml.probs(wires=range(M))\n            p = pnp.array([[0.5, 0.5], [0.5, 0.5]], requires_grad=True)\n            opt = qml.AdamOptimizer(0.1)\n            for _ in range(80):\n                p = opt.step(cost, p)\n            pr = np.asarray(probs(p))\n            # son-seçim: en olası bit dizileri arasında Σx = K kısıtını sağlayan ilk çözüm\n            order = np.argsort(pr)[::-1]\n            x = None\n            for code in order[:64]:\n                v = np.array([(int(code) >> (M - 1 - i)) & 1 for i in range(M)])\n                if v.sum() == teams:\n                    x = v; break\n            if x is None:\n                best = int(order[0]); x = np.array([(best >> (M - 1 - i)) & 1 for i in range(M)])\n            info[\"note\"] = f\"QAOA (2 katman, 80 adım, simülatör; olasılık {pr[order[0]]:.3f}, son-seçim Σx=K)\"\n            return x, info\n        except Exception as ex:  # noqa: BLE001\n            info[\"note\"] = f\"QAOA başarısız ({ex}); SA'ya dönüldü\"\n    # M ≤ 16: tam sayım (kesin çözüm); aksi halde takas hamleli tavlama benzetimi (Σx = K korunur, en iyi çözüm izlenir)\n    rng = np.random.default_rng(seed)\n\n    def energy(v):\n        return float(v @ Q @ v)\n    if M <= 16:\n        best_x, best_E = None, np.inf\n        for code in range(1 << M):\n            v = np.array([(code >> i) & 1 for i in range(M)])\n            E = energy(v)\n            if E < best_E:\n                best_x, best_E = v, E\n        x = best_x; info.setdefault(\"note\", \"Tam sayım (kesin QUBO çözümü, klasik)\")\n        return x, info\n    x = np.zeros(M, dtype=int); x[rng.choice(M, min(teams, M), replace=False)] = 1\n    E = energy(x); best_x, best_E = x.copy(), E; T = 1.0\n    for it in range(6000):\n        y = x.copy()\n        if rng.random() < 0.7 and 0 < y.sum() < M:  # takas: bir aç, bir kapat\n            on = np.flatnonzero(y == 1); off = np.flatnonzero(y == 0)\n            y[rng.choice(on)] = 0; y[rng.choice(off)] = 1\n        else:\n            y[rng.integers(M)] ^= 1\n        Ey = energy(y)\n        if Ey < E or rng.random() < math.exp(-(Ey - E) / T):\n            x, E = y, Ey\n            if E < best_E:\n                best_x, best_E = x.copy(), E\n        T = max(0.01, T * 0.999)\n    x = best_x\n    info.setdefault(\"note\", \"Takas hamleli tavlama benzetimi (klasik yedek)\")\n    return x, info\n\n\n# ===========================================================================\n# 6. MONTE CARLO — yağış/CN/Manning belirsizliği → aşılma olasılığı haritaları\n# ===========================================================================\ndef monte_carlo_riverine(rain: pd.Series, area_km2: float, cn: float, tc_h: float, terrain: Terrain,\n                         rating_fn, runs: int = 24, baseflow: float = 0.0, seed: int = 42,\n                         depth_threshold: float = 0.3) -> dict:\n    rng = np.random.default_rng(seed)\n    peaks, stages = [], []\n    exceed = np.zeros(terrain.hand.shape)\n    depth_stack = []\n    for _ in range(runs):\n        mult = float(np.exp(rng.normal(0, 0.25)))\n        cn_i = float(np.clip(cn + rng.normal(0, 4), 35, 99))\n        n_i = float(np.clip(0.045 * np.exp(rng.normal(0, 0.2)), 0.02, 0.12))\n        q = model_scs_uh(rain * mult, area_km2, cn_i, tc_h, baseflow)\n        rating = rating_fn(n_i)\n        st = stage_from_discharge(rating, float(q.max()))\n        d = hand_inundation(terrain, st)\n        exceed += (d > depth_threshold)\n        depth_stack.append(d)\n        peaks.append(float(q.max())); stages.append(st)\n    stack = np.stack(depth_stack)\n    return dict(peaks=np.array(peaks), stages=np.array(stages), exceed_prob=exceed / runs,\n                depth_p50=np.median(stack, 0), depth_p90=np.percentile(stack, 90, 0),\n                summary=dict(peak_p10=float(np.percentile(peaks, 10)), peak_p50=float(np.percentile(peaks, 50)),\n                             peak_p90=float(np.percentile(peaks, 90)), stage_p50=float(np.median(stages)), runs=runs))\n\n\ndef monte_carlo_pluvial(zf: np.ndarray, rain: pd.Series, lc: np.ndarray, dy: float, dx: float, runs: int = 8,\n                        seed: int = 42, depth_threshold: float = 0.2, max_minutes_each: float = 3.0, sewer_mm_h: float = 0.0) -> dict:\n    rng = np.random.default_rng(seed)\n    exceed = np.zeros(zf.shape); stack = []\n    for _ in range(runs):\n        mult = float(np.exp(rng.normal(0, 0.25)))\n        res = pluvial_ca(zf, rain * mult, lc, dy, dx, drain_hours=1.0, max_minutes=max_minutes_each,\n                         manning_scale=float(np.exp(rng.normal(0, 0.15))), infil_scale=float(np.exp(rng.normal(0, 0.3))),\n                         record_every_min=1e9, sewer_mm_h=sewer_mm_h * float(np.exp(rng.normal(0, 0.3))))\n        exceed += res[\"max_depth\"] > depth_threshold; stack.append(res[\"max_depth\"])\n    stack = np.stack(stack)\n    return dict(exceed_prob=exceed / runs, depth_p50=np.median(stack, 0), depth_p90=np.percentile(stack, 90, 0), runs=runs)\n\n# ===========================================================================\n# 7. ETKİ ANALİZİ — nüfus, yapı, arazi örtüsü, hasar, sigorta, risk skoru\n# ===========================================================================\nDEPTH_CLASSES = [(0.1, 0.3, \"0,1–0,3 m (sığ)\"), (0.3, 1.0, \"0,3–1 m (orta)\"), (1.0, 2.0, \"1–2 m (yüksek)\"), (2.0, 99.0, \">2 m (çok yüksek)\")]\n# JRC küresel derinlik-hasar eğrileri (Huizinga ve diğ. 2017) — Avrupa konut/ticari, hasar oranı\n_JRC_DEPTH = np.array([0, 0.5, 1, 1.5, 2, 3, 4, 5, 6])\n_JRC_RES = np.array([0, 0.25, 0.40, 0.50, 0.60, 0.75, 0.85, 0.95, 1.00])\n_JRC_COM = np.array([0, 0.15, 0.30, 0.45, 0.55, 0.75, 0.90, 1.00, 1.00])\n\n\ndef damage_fraction(depth: np.ndarray, kind=\"res\") -> np.ndarray:\n    curve = _JRC_RES if kind == \"res\" else _JRC_COM\n    return np.interp(depth, _JRC_DEPTH, curve)\n\n\ndef impact_assessment(depth: np.ndarray, pop: np.ndarray, lc: np.ndarray, cell_area_m2: float, cfg: RunConfig,\n                      district_labels: Optional[np.ndarray] = None, district_names: Optional[List[str]] = None,\n                      depth_p90: Optional[np.ndarray] = None) -> dict:\n    \"\"\"wet (>0,1 m) alan/arazi istatistikleri için; nüfus/yapı maruziyeti cfg.exposure_depth_m (vars. 0,3 m) eşiğiyle.\"\"\"\n    wet = depth > 0.1\n    exp_m = depth > cfg.exposure_depth_m\n    cls_rows = []\n    for lo, hi, name in DEPTH_CLASSES:\n        m = (depth >= lo) & (depth < hi)\n        cls_rows.append(dict(sinif=name, alan_km2=float(m.sum() * cell_area_m2 / 1e6), nufus=float(pop[m].sum()),\n                             yapilasmis_km2=float(((lc == 50) & m).sum() * cell_area_m2 / 1e6)))\n    classes = pd.DataFrame(cls_rows)\n    lc_rows = [dict(sinif=WorldCoverClient.CLASSES.get(int(c), str(c)), alan_km2=float(((lc == c) & wet).sum() * cell_area_m2 / 1e6))\n               for c in np.unique(lc)]\n    landcover = pd.DataFrame(lc_rows).sort_values(\"alan_km2\", ascending=False)\n    # hasar: yapılaşmış hücrelerde konut/ticari bölünmesi 70/30, taban alanı oranı 0,35\n    built = (lc == 50) & exp_m\n    fp = 0.35 * cell_area_m2\n    res_loss = float((damage_fraction(depth[built], \"res\") * fp * 0.7 * cfg.residential_max_damage_try_m2).sum())\n    com_loss = float((damage_fraction(depth[built], \"com\") * fp * 0.3 * cfg.commercial_max_damage_try_m2).sum())\n    total_loss = res_loss + com_loss\n    pml90 = None\n    if depth_p90 is not None:\n        b90 = (lc == 50) & (depth_p90 > cfg.exposure_depth_m)\n        pml90 = float((damage_fraction(depth_p90[b90], \"res\") * fp * 0.7 * cfg.residential_max_damage_try_m2).sum()\n                      + (damage_fraction(depth_p90[b90], \"com\") * fp * 0.3 * cfg.commercial_max_damage_try_m2).sum())\n    exposed_pop = float(pop[exp_m].sum()); exposed_pop_shallow = float(pop[wet & ~exp_m].sum())\n    buildings_est = float(built.sum() * cell_area_m2 * 0.35 / 120.0)  # ~120 m² ortalama taban alanı\n    # ilçe tablosu\n    districts = None\n    if district_labels is not None and district_names:\n        rows = []\n        for i, name in enumerate(district_names, start=1):\n            m = district_labels == i\n            if not m.any():\n                continue\n            pw = float(pop[m & exp_m].sum()); dmax = float(depth[m].max()) if m.any() else 0.0\n            dmean = float(depth[m & exp_m].mean()) if (m & exp_m).any() else 0.0\n            rows.append(dict(ilce=name, toplam_nufus=float(pop[m].sum()), etkilenen_nufus=pw,\n                             etkilenen_alan_km2=float((m & wet).sum() * cell_area_m2 / 1e6), maks_derinlik_m=dmax,\n                             ort_derinlik_m=dmean, risk_skoru=pw * (0.5 + dmean)))\n        districts = pd.DataFrame(rows).sort_values(\"risk_skoru\", ascending=False).reset_index(drop=True) if rows else None\n    return dict(classes=classes, landcover=landcover, exposed_pop=exposed_pop, exposed_pop_shallow=exposed_pop_shallow,\n                exposure_depth_m=cfg.exposure_depth_m, exposed_area_km2=float(wet.sum() * cell_area_m2 / 1e6),\n                exposed_area_deep_km2=float(exp_m.sum() * cell_area_m2 / 1e6),\n                buildings_est=buildings_est, loss_try=total_loss, res_loss_try=res_loss, com_loss_try=com_loss,\n                insured_try=total_loss * cfg.insurance_penetration, uninsured_try=total_loss * (1 - cfg.insurance_penetration),\n                pml90_try=pml90, districts=districts, max_depth=float(depth.max()))\n\n\ndef warning_level(exposed_pop: float, max_depth: float, mgm_level: str) -> Tuple[str, str]:\n    \"\"\"Motor seviyesi (MGM MeteoUyarı ile birleşik): yesil / sari / turuncu / kirmizi.\"\"\"\n    score = 0\n    if exposed_pop > 500 or max_depth > 0.5: score = 1\n    if exposed_pop > 5000 or max_depth > 1.0: score = 2\n    if exposed_pop > 25000 or max_depth > 2.0: score = 3\n    mgm = {\"none\": 0, \"unknown\": 0, \"yellow\": 1, \"orange\": 2, \"red\": 3}.get(mgm_level, 0)\n    lvl = max(score, mgm)\n    names = {0: (\"yesil\", \"Yeşil — izleme\"), 1: (\"sari\", \"Sarı — dikkat\"), 2: (\"turuncu\", \"Turuncu — hazırlık / kısmi tahliye\"), 3: (\"kirmizi\", \"Kırmızı — acil tahliye\")}\n    return names[lvl]\n\n\n# ===========================================================================\n# 8. EKRANLAR — Acil durum bilgi paylaşımı, sigorta, bildirim merkezi (HTML)\n# ===========================================================================\n_CSS = \"\"\"<meta charset=\"utf-8\"><style>\n.trf{font-family:Segoe UI,Arial,sans-serif;max-width:1100px;margin:8px 0;color:#1b2430}\n.trf h2{margin:0 0 6px;font-size:20px}.trf .sub{color:#5b6472;font-size:12px;margin-bottom:10px}\n.trf .kpis{display:flex;flex-wrap:wrap;gap:10px;margin:8px 0 14px}\n.trf .kpi{flex:1 1 150px;background:#f4f6f9;border-radius:10px;padding:10px 12px;border-left:5px solid #2b6cb0}\n.trf .kpi b{display:block;font-size:20px}.trf .kpi span{font-size:12px;color:#5b6472}\n.trf table{border-collapse:collapse;width:100%;font-size:13px}.trf th,.trf td{border-bottom:1px solid #e3e7ee;padding:6px 8px;text-align:left}\n.trf th{background:#eef2f7}.trf .lvl{display:inline-block;padding:3px 10px;border-radius:14px;color:#fff;font-weight:600}\n.trf .yesil{background:#2f855a}.trf .sari{background:#d69e2e}.trf .turuncu{background:#dd6b20}.trf .kirmizi{background:#c53030}\n.trf pre{background:#f4f6f9;padding:10px;border-radius:8px;white-space:pre-wrap;font-size:12px}\n</style>\"\"\"\n\n\ndef html_document(body: str, title: str) -> str:\n    \"\"\"Diskten açılan ekranlar için tam HTML belgesi (UTF-8 bildirimi ile).\"\"\"\n    return f\"<!DOCTYPE html>\\n<html lang=\\\"tr\\\"><head><meta charset=\\\"utf-8\\\"><meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1\\\"><title>{title}</title></head><body>{body}</body></html>\"\n\n\ndef _fmt_try(v: float) -> str:\n    if v is None: return \"—\"\n    if v >= 1e9: return f\"{v/1e9:,.2f} milyar ₺\"\n    if v >= 1e6: return f\"{v/1e6:,.1f} milyon ₺\"\n    return f\"{v:,.0f} ₺\"\n\n\ndef screen_emergency(res: dict) -> str:\n    imp, cfg = res[\"impact\"], res[\"cfg\"]\n    lvl, lvl_txt = res[\"warning\"]\n    d = imp.get(\"districts\")\n    rows = \"\"\n    if d is not None:\n        alloc = res.get(\"qubo\", {}).get(\"allocation\", {})\n        for _, r in d.head(12).iterrows():\n            team = \"✅ ekip\" if alloc.get(r.ilce) else \"\"\n            rows += f\"<tr><td>{r.ilce}</td><td>{r.etkilenen_nufus:,.0f}</td><td>{r.etkilenen_alan_km2:.2f}</td><td>{r.maks_derinlik_m:.2f}</td><td>{r.risk_skoru:,.0f}</td><td>{team}</td></tr>\"\n    mgm = res.get(\"mgm_alert\", (\"unknown\", \"\"))\n    return f\"\"\"{_CSS}<div class=\"trf\"><h2>🚨 Acil Durum Bilgi Paylaşım Panosu — {res['title']}</h2>\n<div class=\"sub\">{ENGINE_NAME} v{ENGINE_VERSION} · mod: {cfg.mode} · pencere: {cfg.start} → {cfg.end} · üretim: {res['generated']}</div>\n<div class=\"kpis\">\n<div class=\"kpi\"><b><span class=\"lvl {lvl}\">{lvl_txt}</span></b><span>Motor uyarı seviyesi (MGM MeteoUyarı: {mgm[0]})</span></div>\n<div class=\"kpi\"><b>{imp['exposed_pop']:,.0f}</b><span>Etkilenen nüfus (&gt;{imp['exposure_depth_m']:.1f} m, WorldPop 2025) · sığ (&gt;0,1 m): +{imp['exposed_pop_shallow']:,.0f}</span></div>\n<div class=\"kpi\"><b>{imp['exposed_area_km2']:.2f} km²</b><span>Su altında alan (&gt;0,1 m) · {imp['exposed_area_deep_km2']:.2f} km² (&gt;{imp['exposure_depth_m']:.1f} m)</span></div>\n<div class=\"kpi\"><b>{imp['max_depth']:.2f} m</b><span>Maksimum su derinliği</span></div>\n<div class=\"kpi\"><b>{res['hydro'].get('peak_m3s', 0):,.0f} m³/s</b><span>Tepe debi ({res['hydro'].get('best_model','—')})</span></div>\n<div class=\"kpi\"><b>{res.get('qubo',{}).get('teams','—')}</b><span>Atanan müdahale ekibi (QUBO)</span></div>\n</div>\n<table><tr><th>İlçe</th><th>Etkilenen nüfus</th><th>Alan km²</th><th>Maks derinlik m</th><th>Risk skoru</th><th>Ekip</th></tr>{rows}</table>\n<p class=\"sub\">Paylaşım: AFAD İl Müdürlüğü · Valilik / Kaymakamlık · DSİ Bölge · İtfaiye · 112 · Belediye Fen İşleri · Kızılay. Veri kaynakları ve durumları rapor paketindeki <i>provenance.csv</i> dosyasındadır.</p></div>\"\"\"\n\n\ndef screen_insurance(res: dict) -> str:\n    imp, cfg = res[\"impact\"], res[\"cfg\"]\n    mc = res.get(\"mc_summary\", {})\n    return f\"\"\"{_CSS}<div class=\"trf\"><h2>🛡️ Sigorta ve Finansal Etki Panosu — {res['title']}</h2>\n<div class=\"sub\">Derinlik-hasar: JRC (Huizinga 2017) Avrupa eğrileri · maksimum hasar {cfg.residential_max_damage_try_m2:,.0f} ₺/m² konut, {cfg.commercial_max_damage_try_m2:,.0f} ₺/m² ticari · <b>kalibrasyon gerektirir</b></div>\n<div class=\"kpis\">\n<div class=\"kpi\"><b>{_fmt_try(imp['loss_try'])}</b><span>Tahmini doğrudan yapısal hasar</span></div>\n<div class=\"kpi\"><b>{_fmt_try(imp['insured_try'])}</b><span>Sigortalı pay (penetrasyon %{cfg.insurance_penetration*100:.0f})</span></div>\n<div class=\"kpi\"><b>{_fmt_try(imp['uninsured_try'])}</b><span>Sigortasız pay (DASK taşkını kapsamaz)</span></div>\n<div class=\"kpi\"><b>{_fmt_try(imp.get('pml90_try'))}</b><span>PML (Monte Carlo %90)</span></div>\n<div class=\"kpi\"><b>{imp['buildings_est']:,.0f}</b><span>Etkilenen yapı (tahmini)</span></div>\n<div class=\"kpi\"><b>{(f\"{mc.get('peak_p90'):,.0f} m³/s\" if mc.get('peak_p90') else '—')}</b><span>Tepe debi %90 (MC, n={mc.get('runs','—')})</span></div>\n</div>\n<table><tr><th>Derinlik sınıfı</th><th>Alan km²</th><th>Nüfus</th><th>Yapılaşmış km²</th></tr>\n{''.join(f\"<tr><td>{r.sinif}</td><td>{r.alan_km2:.2f}</td><td>{r.nufus:,.0f}</td><td>{r.yapilasmis_km2:.2f}</td></tr>\" for _, r in imp['classes'].iterrows())}</table>\n<p class=\"sub\">Kullanım: reasürans/portföy PML, TARSİM tarım, belediye altyapı bütçesi. Zarar değerleri il bazlı yeniden inşa maliyetleriyle (ÇŞB birim fiyatları) kalibre edilmelidir.</p></div>\"\"\"\n\n\ndef build_notifications(res: dict) -> List[dict]:\n    lvl, lvl_txt = res[\"warning\"]\n    imp, cfg = res[\"impact\"], res[\"cfg\"]\n    d = imp.get(\"districts\")\n    top = \", \".join(d.head(3).ilce.tolist()) if d is not None and len(d) else cfg.il\n    when = cfg.start if cfg.mode == \"historical\" else \"önümüzdeki saatler\"\n    msgs = []\n    sms = f\"[{lvl_txt.split(' — ')[0].upper()}] {cfg.il}: {top} çevresinde taşkın riski ({when}). Dere yataklarından, alt geçitlerden uzak durun. Bilgi: AFAD 122 / 112.\"\n    msgs.append(dict(kanal=\"SMS (halk)\", hedef=\"Etkilenen ilçe sakinleri\", metin=sms[:160]))\n    msgs.append(dict(kanal=\"Cell Broadcast / Uyarı Sistemi\", hedef=f\"{cfg.il} — riskli ilçeler\", metin=f\"AFAD UYARI: {top} için {lvl_txt}. Tahmini etkilenen nüfus {imp['exposed_pop']:,.0f}. Maks derinlik {imp['max_depth']:.1f} m.\"))\n    msgs.append(dict(kanal=\"E-posta (kurumsal)\", hedef=\"Valilik, AFAD İl Md., DSİ Bölge, Belediye, İtfaiye, 112\",\n                     metin=f\"Konu: {res['title']} — {lvl_txt}\\n\\nMotor çıktısı ({res['generated']}): tepe debi {res['hydro'].get('peak_m3s',0):,.0f} m³/s, su altında alan {imp['exposed_area_km2']:.2f} km², etkilenen nüfus {imp['exposed_pop']:,.0f}, öncelikli ilçeler: {top}. Rapor paketi ektedir.\"))\n    msgs.append(dict(kanal=\"WhatsApp Business / Telegram\", hedef=\"Muhtarlar, mahalle afet gönüllüleri\", metin=f\"⚠️ {top}: {lvl_txt}. Riskli sokaklar için harita paketteki 'derinlik_haritasi.png'. Tahliye noktaları: okul/cami bahçeleri (yüksek kot).\"))\n    msgs.append(dict(kanal=\"API (JSON) — belediye/kurum entegrasyonu\", hedef=\"Webhook\", metin=json.dumps(dict(engine=ENGINE_NAME, version=ENGINE_VERSION, level=lvl, il=cfg.il, districts=(d.head(5).ilce.tolist() if d is not None else []),\n                     exposed_pop=round(imp['exposed_pop']), max_depth_m=round(imp['max_depth'], 2), peak_m3s=round(res['hydro'].get('peak_m3s', 0)), generated=res['generated']), ensure_ascii=False)))\n    return msgs\n\n\ndef screen_notifications(res: dict) -> str:\n    rows = \"\".join(f\"<tr><td><b>{m['kanal']}</b><br><span class='sub'>{m['hedef']}</span></td><td><pre>{m['metin']}</pre></td></tr>\" for m in res[\"notifications\"])\n    return f\"\"\"{_CSS}<div class=\"trf\"><h2>📣 Bildirim Merkezi — {res['title']}</h2>\n<div class=\"sub\">Şablonlar otomatik üretilir; gönderim resmî kanallar (AFAD, Valilik, belediye) üzerinden yapılmalıdır. Çalıştırma sıklığı: {res['cfg'].run_frequency}.</div>\n<table><tr><th style=\"width:26%\">Kanal / Hedef</th><th>Mesaj</th></tr>{rows}</table></div>\"\"\"\n\n# ===========================================================================\n# 9. GÖRSELLER ve RAPOR (DOCX + anlatı + paket)\n# ===========================================================================\ndef _extent(bbox):\n    s, w, n, e = bbox\n    return [w, e, s, n]\n\n\ndef _hillshade(z, az=315, alt=45):\n    az, alt = math.radians(az), math.radians(alt)\n    gy, gx = np.gradient(np.nan_to_num(z))\n    slope = np.pi / 2 - np.arctan(np.hypot(gx, gy) / 30)\n    aspect = np.arctan2(-gx, gy)\n    return np.sin(alt) * np.sin(slope) + np.cos(alt) * np.cos(slope) * np.cos(az - aspect)\n\n\ndef make_figures(res: dict, out: Path) -> Dict[str, Path]:\n    import matplotlib\n    matplotlib.use(\"Agg\")\n    import matplotlib.pyplot as plt\n    figs: Dict[str, Path] = {}\n    cfg, bbox = res[\"cfg\"], res[\"cfg\"].bbox\n    ext = _extent(bbox)\n    rain = res[\"rain\"][\"rain\"]\n    # 1 — hiyetograf + hidrograflar\n    fig, ax = plt.subplots(figsize=(10, 5))\n    for name, q in res[\"hydro\"][\"sims\"].items():\n        ax.plot(q.index, q.values, lw=1.6 if name == res[\"hydro\"].get(\"best_model\") else 1.0, label=name)\n    ref = res[\"hydro\"].get(\"reference\")\n    if ref is not None and len(ref):\n        ax.plot(ref.index, ref.values, \"ko\", ms=4, label=\"GloFAS v4 günlük (Open-Meteo)\")\n    ax.set_ylabel(\"Debi (m³/s)\"); ax.set_xlabel(\"Zaman (UTC)\"); ax.legend(fontsize=8, loc=\"upper left\")\n    ax2 = ax.twinx(); ax2.bar(rain.index, rain.values, width=0.03, color=\"#2b6cb0\", alpha=0.5); ax2.invert_yaxis()\n    ax2.set_ylabel(\"Yağış (mm/saat)\")\n    ax.set_title(f\"{res['title']} — yağış-akış modelleri\", fontsize=11); fig.tight_layout()\n    figs[\"hidrograf\"] = out / \"hidrograf.png\"; fig.savefig(figs[\"hidrograf\"], dpi=130); plt.close(fig)\n    # 2 — model skorları\n    met = res[\"hydro\"][\"metrics\"]\n    if \"KGE\" in met:\n        fig, ax = plt.subplots(figsize=(8, 3.8))\n        x = np.arange(len(met)); ax.bar(x - 0.2, met.KGE.fillna(0), 0.4, label=\"KGE\"); ax.bar(x + 0.2, met.NSE.fillna(0), 0.4, label=\"NSE\")\n        ax.set_xticks(x); ax.set_xticklabels(met.model, rotation=20, ha=\"right\", fontsize=8); ax.axhline(0, color=\"k\", lw=0.5)\n        ax.set_ylim(min(-1, float(np.nanmin(met[[\"KGE\", \"NSE\"]].values)) - 0.1), 1.05); ax.legend(); ax.set_title(\"Klasik modellerin GloFAS'a göre uyumu (günlük)\")\n        fig.tight_layout(); figs[\"model_skor\"] = out / \"model_skorlari.png\"; fig.savefig(figs[\"model_skor\"], dpi=130); plt.close(fig)\n\n    def depth_map(arr, title, fname, cmap=\"Blues\", vmax=None, streams=None):\n        fig, ax = plt.subplots(figsize=(8, 7))\n        ax.imshow(_hillshade(res[\"terrain\"].filled if arr.shape == res[\"terrain\"].filled.shape else block_reduce(res[\"terrain\"].filled, arr.shape[0])),\n                  cmap=\"gray\", extent=ext, alpha=0.85)\n        m = np.ma.masked_less_equal(arr, 0.05 if vmax is None or vmax > 1 else 0.02)\n        im = ax.imshow(m, cmap=cmap, extent=ext, vmin=0, vmax=vmax or max(float(np.nanpercentile(arr, 99.5)), 0.5), alpha=0.9)\n        if streams is not None and streams.shape == arr.shape:\n            ax.contour(np.flipud(streams.astype(float)), levels=[0.5], colors=\"#1a365d\", linewidths=0.4, extent=ext, origin=\"lower\")\n        plt.colorbar(im, ax=ax, shrink=0.7, label=\"m\" if vmax is None or vmax > 1 else \"olasılık\")\n        ax.set_title(title); ax.set_xlabel(\"Boylam\"); ax.set_ylabel(\"Enlem\"); fig.tight_layout()\n        p = out / fname; fig.savefig(p, dpi=130); plt.close(fig); return p\n    if res.get(\"depth_riverine\") is not None:\n        figs[\"derinlik_nehir\"] = depth_map(res[\"depth_riverine\"], f\"Nehir taşkını derinliği — HAND (seviye {res['hydro'].get('stage_m', 0):.2f} m)\", \"derinlik_nehir.png\", streams=res[\"terrain\"].streams)\n    if res.get(\"pluvial\") is not None:\n        figs[\"derinlik_pluvyal\"] = depth_map(res[\"pluvial\"][\"max_depth\"], \"Kentsel (plüvyal) taşkın — 2B hücresel otomat maks. derinlik\", \"derinlik_pluvyal.png\", cmap=\"PuBu\")\n    figs[\"derinlik_birlesik\"] = depth_map(res[\"depth\"], \"Birleşik maksimum su derinliği\", \"derinlik_haritasi.png\", streams=res[\"terrain\"].streams)\n    if res.get(\"mc_riv\") is not None:\n        figs[\"asilma\"] = depth_map(res[\"mc_riv\"][\"exceed_prob\"], f\"Monte Carlo — derinlik >0,3 m aşılma olasılığı (n={res['mc_riv']['summary']['runs']})\", \"asilma_olasiligi.png\", cmap=\"YlOrRd\", vmax=1.0)\n    if res.get(\"mc_plu\") is not None:\n        figs[\"asilma_pluvyal\"] = depth_map(res[\"mc_plu\"][\"exceed_prob\"], f\"Monte Carlo (plüvyal) — >0,2 m aşılma olasılığı (n={res['mc_plu']['runs']})\", \"asilma_pluvyal.png\", cmap=\"YlOrRd\", vmax=1.0)\n    if res.get(\"gfm\") is not None:\n        g = res[\"gfm\"]\n        fig, ax = plt.subplots(figsize=(8, 7))\n        ax.imshow(_hillshade(res[\"terrain\"].filled), cmap=\"gray\", extent=ext, alpha=0.85)\n        both = (res[\"depth\"] > 0.1).astype(int) + 2 * (g[\"extent\"] > 0.5).astype(int)\n        cmap = matplotlib.colors.ListedColormap([\"none\", \"#3182ce\", \"#e53e3e\", \"#805ad5\"])\n        ax.imshow(both, cmap=cmap, extent=ext, vmin=0, vmax=3, alpha=0.75)\n        ax.set_title(f\"Doğrulama — mavi: model, kırmızı: Sentinel-1 GFM, mor: örtüşme · CSI={g['csi']:.2f}\")\n        fig.tight_layout(); figs[\"dogrulama\"] = out / \"dogrulama_gfm.png\"; fig.savefig(figs[\"dogrulama\"], dpi=130); plt.close(fig)\n    q = res.get(\"qml\")\n    if q:\n        fig, axs = plt.subplots(1, 2, figsize=(11, 4))\n        axs[0].plot(q[\"history\"]); axs[0].set_title(f\"VQC eğitim kaybı ({q['info'].get('backend')}, {q['info'].get('n_qubits')} kübit)\"); axs[0].set_xlabel(\"adım\")\n        axs[1].plot(q[\"y_test\"].values, \"k-\", lw=1.5, label=\"GloFAS (gerçek)\")\n        for k, v in q[\"preds\"].items():\n            axs[1].plot(v, lw=1, label=f\"{k} (RMSE {q['scores'][k]['RMSE']:.1f})\")\n        axs[1].legend(fontsize=7); axs[1].set_title(\"Test dönemi 1 gün ileri debi tahmini (m³/s)\")\n        fig.tight_layout(); figs[\"qml\"] = out / \"qml_tahmin.png\"; fig.savefig(figs[\"qml\"], dpi=130); plt.close(fig)\n    d = res[\"impact\"].get(\"districts\")\n    if d is not None and len(d):\n        fig, ax = plt.subplots(figsize=(8, 4))\n        top = d.head(10); ax.barh(top.ilce[::-1], top.etkilenen_nufus[::-1], color=\"#c53030\")\n        ax.set_xlabel(\"Etkilenen nüfus\"); ax.set_title(\"İlçelere göre etkilenen nüfus (WorldPop 2025)\"); fig.tight_layout()\n        figs[\"ilce\"] = out / \"ilce_etki.png\"; fig.savefig(figs[\"ilce\"], dpi=130); plt.close(fig)\n    return figs\n\n\ndef narrative(res: dict, lang=\"tr\") -> str:\n    imp, cfg, hy = res[\"impact\"], res[\"cfg\"], res[\"hydro\"]\n    lvl, lvl_txt = res[\"warning\"]\n    if cfg.llm_provider == \"gemini\" and os.environ.get(\"GEMINI_API_KEY\"):\n        try:\n            from google import genai\n            client = genai.Client(api_key=os.environ[\"GEMINI_API_KEY\"])\n            facts = dict(baslik=res[\"title\"], mod=cfg.mode, pencere=f\"{cfg.start}→{cfg.end}\", yagis_toplam_mm=round(float(res[\"rain\"][\"rain\"].sum()), 1),\n                         yagis_kaynak=res[\"rain\"][\"source\"], tepe_debi=round(hy.get(\"peak_m3s\", 0)), en_iyi_model=hy.get(\"best_model\"),\n                         seviye_m=round(hy.get(\"stage_m\", 0), 2), etkilenen_nufus=round(imp[\"exposed_pop\"]), alan_km2=round(imp[\"exposed_area_km2\"], 2),\n                         maks_derinlik=round(imp[\"max_depth\"], 2), hasar_try=round(imp[\"loss_try\"]), uyari=lvl_txt,\n                         ilceler=(imp[\"districts\"].head(5).ilce.tolist() if imp.get(\"districts\") is not None else []),\n                         qml=res.get(\"qml\", {}).get(\"scores\"), mc=res.get(\"mc_summary\"))\n            lang_txt = \"Türkçe\" if lang == \"tr\" else \"İngilizce\"\n            prompt = (f\"Sen bir taşkın risk analistisin. Aşağıdaki simülasyon sonuçlarını {lang_txt} olarak, kamu yöneticilerine yönelik, \"\n                      f\"4 paragraflık sade bir brifing metnine dönüştür (durum, hidrolojik bulgular, etki ve öneriler). Rakam uydurma. JSON: {json.dumps(facts, ensure_ascii=False, default=str)}\")\n            r = client.models.generate_content(model=cfg.gemini_model, contents=prompt)\n            return r.text\n        except Exception as ex:  # noqa: BLE001\n            log.warning(f\"Gemini anlatısı üretilemedi ({ex}); şablon kullanılıyor\")\n    top = \", \".join(imp[\"districts\"].head(3).ilce.tolist()) if imp.get(\"districts\") is not None and len(imp[\"districts\"]) else cfg.il\n    if lang == \"en\":\n        return (f\"{res['title']}: the {ENGINE_NAME} run ({cfg.mode} mode, {cfg.start} to {cfg.end}) used a rainfall input of \"\n                f\"{float(res['rain']['rain'].sum()):.0f} mm ({res['rain']['source']}). The best-fitting classical model ({hy.get('best_model')}) gives a peak discharge of \"\n                f\"{hy.get('peak_m3s', 0):,.0f} m³/s and a river stage of {hy.get('stage_m', 0):.2f} m. Inundation covers {imp['exposed_area_km2']:.2f} km² with a maximum depth of \"\n                f\"{imp['max_depth']:.2f} m; about {imp['exposed_pop']:,.0f} residents are exposed, mostly in {top}. Estimated direct building damage is {_fmt_try(imp['loss_try'])}. \"\n                f\"Engine warning level: {lvl_txt}. Recommended actions: pre-position teams in the priority districts, issue public warnings through official channels, and verify the modelled extent with field reports.\")\n    return (f\"{res['title']} için {ENGINE_NAME} çalıştırması ({cfg.mode} modu, {cfg.start} – {cfg.end}) toplam {float(res['rain']['rain'].sum()):.0f} mm yağış girdisi \"\n            f\"({res['rain']['source']}) kullanmıştır. En iyi uyumu veren klasik model ({hy.get('best_model')}) {hy.get('peak_m3s', 0):,.0f} m³/s tepe debi ve \"\n            f\"{hy.get('stage_m', 0):.2f} m su seviyesi üretmiştir. Taşma alanı {imp['exposed_area_km2']:.2f} km², maksimum derinlik {imp['max_depth']:.2f} m; \"\n            f\"yaklaşık {imp['exposed_pop']:,.0f} kişi etkilenmektedir (öncelikli ilçeler: {top}). Tahmini doğrudan yapısal hasar {_fmt_try(imp['loss_try'])}. \"\n            f\"Motor uyarı seviyesi: {lvl_txt}. Öneriler: öncelikli ilçelerde ekiplerin önceden konuşlandırılması, resmî kanallardan halkın uyarılması ve modellenen \"\n            f\"yayılımın saha bildirimleriyle doğrulanması.\")\n\n\ndef write_docx(res: dict, figs: Dict[str, Path], out: Path, lang=\"tr\") -> Path:\n    from docx import Document\n    from docx.shared import Inches, Pt\n    cfg, imp, hy = res[\"cfg\"], res[\"impact\"], res[\"hydro\"]\n    doc = Document()\n    st = doc.styles[\"Normal\"]; st.font.name = \"Calibri\"; st.font.size = Pt(10.5)\n    T = (lambda tr, en: tr if lang == \"tr\" else en)\n    doc.add_heading(T(f\"{ENGINE_NAME} — Taşkın Simülasyon Raporu\", f\"{ENGINE_NAME} — Flood Simulation Report\"), 0)\n    doc.add_paragraph(f\"{res['title']}\\nBVK Technology · {T('Üretim', 'Generated')}: {res['generated']} · v{ENGINE_VERSION}\")\n    doc.add_heading(T(\"1. Yönetici özeti\", \"1. Executive summary\"), 1)\n    doc.add_paragraph(narrative(res, lang))\n    lvl, lvl_txt = res[\"warning\"]\n    tbl = doc.add_table(rows=0, cols=2); tbl.style = \"Light Grid Accent 1\"\n    for k, v in [(T(\"Mod / kapsam\", \"Mode / scope\"), f\"{cfg.mode} / {cfg.scope}\"), (T(\"Zaman penceresi\", \"Time window\"), f\"{cfg.start} → {cfg.end}\"),\n                 (T(\"Çalışma alanı (S,W,N,E)\", \"Study area (S,W,N,E)\"), \", \".join(f\"{v:.3f}\" for v in cfg.bbox)),\n                 (T(\"Yağış girdisi\", \"Rainfall input\"), f\"{float(res['rain']['rain'].sum()):.1f} mm — {res['rain']['source']}\"),\n                 (T(\"Tepe debi / seviye\", \"Peak discharge / stage\"), f\"{hy.get('peak_m3s', 0):,.0f} m³/s / {hy.get('stage_m', 0):.2f} m ({hy.get('best_model')})\"),\n                 (T(\"Taşma alanı / maks derinlik\", \"Flooded area / max depth\"), f\"{imp['exposed_area_km2']:.2f} km² / {imp['max_depth']:.2f} m\"),\n                 (T(\"Etkilenen nüfus\", \"Exposed population\"), f\"{imp['exposed_pop']:,.0f}\"), (T(\"Tahmini hasar\", \"Estimated damage\"), _fmt_try(imp[\"loss_try\"])),\n                 (T(\"Uyarı seviyesi\", \"Warning level\"), lvl_txt)]:\n        r = tbl.add_row().cells; r[0].text, r[1].text = k, str(v)\n    doc.add_heading(T(\"2. Veri kaynakları ve izlenebilirlik\", \"2. Data sources & provenance\"), 1)\n    prov = res[\"provenance\"]\n    tbl = doc.add_table(rows=1, cols=4); tbl.style = \"Light Grid Accent 1\"\n    for i, h in enumerate([T(\"Katman\", \"Layer\"), T(\"Kaynak\", \"Source\"), T(\"Durum\", \"Status\"), T(\"Detay\", \"Detail\")]): tbl.rows[0].cells[i].text = h\n    for _, r in prov.iterrows():\n        c = tbl.add_row().cells; c[0].text, c[1].text, c[2].text, c[3].text = str(r.katman), str(r.kaynak), str(r.durum), str(r.detay)[:120]\n    doc.add_heading(T(\"3. Yağış ve hidroloji\", \"3. Rainfall & hydrology\"), 1)\n    cat = hy[\"catchment\"]\n    doc.add_paragraph(T(f\"Havza alanı {cat['area_km2']:.1f} km², ana kanal {cat['channel_length_km']:.1f} km, eğim {cat['channel_slope']:.4f}, \"\n                        f\"Kirpich tc {cat['tc_hours']:.2f} saat. Bileşik CN = {hy['cn']:.1f} (hidrolojik toprak grubu {hy['hsg']}, SoilGrids + WorldCover). \"\n                        f\"Modeller ayrı ayrı çalıştırılmış, GloFAS v4 günlük debisi ile karşılaştırılmıştır (GloFAS bir model ürünüdür; DSİ AGİ gözlemi yüklenirse onunla değerlendirilir).\",\n                        f\"Catchment {cat['area_km2']:.1f} km², main channel {cat['channel_length_km']:.1f} km, slope {cat['channel_slope']:.4f}, Kirpich tc {cat['tc_hours']:.2f} h. \"\n                        f\"Composite CN = {hy['cn']:.1f} (HSG {hy['hsg']}). All models run independently and compared with GloFAS v4 daily discharge.\"))\n    met = hy[\"metrics\"]\n    tbl = doc.add_table(rows=1, cols=len(met.columns)); tbl.style = \"Light Grid Accent 1\"\n    for i, h in enumerate(met.columns): tbl.rows[0].cells[i].text = str(h)\n    for _, r in met.iterrows():\n        c = tbl.add_row().cells\n        for i, v in enumerate(r.values): c[i].text = f\"{v:.3f}\" if isinstance(v, float) else str(v)[:19]\n    for key in (\"hidrograf\", \"model_skor\"):\n        if key in figs: doc.add_picture(str(figs[key]), width=Inches(6.2))\n    doc.add_heading(T(\"4. Taşma haritaları\", \"4. Inundation maps\"), 1)\n    plu = res.get(\"pluvial\") or {}\n    doc.add_paragraph(T(f\"Nehir taşkını: HAND ({res['terrain'].hand_source}) + sentetik anahtar eğrisi; kentsel taşkın: 2B ağırlıklı hücresel otomat \"\n                        f\"({plu.get('steps', 0)} adım, {plu.get('runtime_s', 0):.0f} s). Model seçimi: {hy.get('selection_note', '')}\",\n                        \"Riverine: HAND + synthetic rating curve; pluvial: 2-D weighted cellular automaton.\"))\n    for key in (\"derinlik_nehir\", \"derinlik_pluvyal\", \"derinlik_birlesik\", \"dogrulama\"):\n        if key in figs: doc.add_picture(str(figs[key]), width=Inches(5.8))\n    if res.get(\"gfm\") is not None:\n        g = res[\"gfm\"]; doc.add_paragraph(T(f\"Sentinel-1 GFM doğrulaması: isabet {g['hit']:.2f}, yanlış alarm {g['far']:.2f}, CSI {g['csi']:.2f}; S1 taşkın hücresi {g['obs_cells']}, model {g['model_cells']} \"\n                                            f\"(sahneler: {', '.join(g['items'][:3])}). Not: Dar ve dik vadilerde radar taşkın tespiti (gölge/örtme maskesi) çoğu taşmayı yakalayamaz; \"\n                                            f\"düşük CSI tek başına model hatası anlamına gelmez.\",\n                                            f\"Sentinel-1 GFM validation: hit {g['hit']:.2f}, FAR {g['far']:.2f}, CSI {g['csi']:.2f}; S1 flood cells {g['obs_cells']}, model {g['model_cells']}.\"))\n    doc.add_heading(T(\"5. Kuantum katmanı\", \"5. Quantum layer\"), 1)\n    q = res.get(\"qml\")\n    if q:\n        doc.add_paragraph(T(f\"VQC tahmincisi ({q['info']}) günlük 1 gün ileri debi tahmininde test RMSE {q['scores']['QML-VQC']['RMSE']:.1f} m³/s \"\n                            f\"(NSE {q['scores']['QML-VQC']['NSE']:.2f}); klasik referanslar: \" + \", \".join(f\"{k} RMSE {v['RMSE']:.1f}\" for k, v in q[\"scores\"].items() if k != \"QML-VQC\"),\n                            f\"VQC forecaster test RMSE {q['scores']['QML-VQC']['RMSE']:.1f} m³/s vs classical baselines.\"))\n        if \"qml\" in figs: doc.add_picture(str(figs[\"qml\"]), width=Inches(6.2))\n    qb = res.get(\"qubo\")\n    if qb:\n        doc.add_paragraph(T(f\"QUBO ekip tahsisi ({qb['info'].get('note')}): {qb['teams']} ekip → \" + \", \".join(k for k, v in qb[\"allocation\"].items() if v),\n                            f\"QUBO team allocation: {qb['teams']} teams → \" + \", \".join(k for k, v in qb[\"allocation\"].items() if v)))\n    doc.add_heading(T(\"6. Belirsizlik (Monte Carlo)\", \"6. Uncertainty (Monte Carlo)\"), 1)\n    mc = res.get(\"mc_summary\", {})\n    if mc:\n        doc.add_paragraph(T(f\"{mc.get('runs')} çalıştırma; tepe debi P10/P50/P90 = {mc.get('peak_p10', 0):,.0f} / {mc.get('peak_p50', 0):,.0f} / {mc.get('peak_p90', 0):,.0f} m³/s.\",\n                            f\"{mc.get('runs')} runs; peak P10/P50/P90 = {mc.get('peak_p10', 0):,.0f} / {mc.get('peak_p50', 0):,.0f} / {mc.get('peak_p90', 0):,.0f} m³/s.\"))\n    for key in (\"asilma\", \"asilma_pluvyal\"):\n        if key in figs: doc.add_picture(str(figs[key]), width=Inches(5.8))\n    doc.add_heading(T(\"7. Etki, sigorta ve bildirim\", \"7. Impact, insurance & notification\"), 1)\n    tbl = doc.add_table(rows=1, cols=4); tbl.style = \"Light Grid Accent 1\"\n    for i, h in enumerate([T(\"Derinlik sınıfı\", \"Depth class\"), \"km²\", T(\"Nüfus\", \"Population\"), T(\"Yapılaşmış km²\", \"Built-up km²\")]): tbl.rows[0].cells[i].text = h\n    for _, r in imp[\"classes\"].iterrows():\n        c = tbl.add_row().cells; c[0].text, c[1].text, c[2].text, c[3].text = r.sinif, f\"{r.alan_km2:.2f}\", f\"{r.nufus:,.0f}\", f\"{r.yapilasmis_km2:.2f}\"\n    doc.add_paragraph(T(f\"Hasar {_fmt_try(imp['loss_try'])} (konut {_fmt_try(imp['res_loss_try'])}, ticari {_fmt_try(imp['com_loss_try'])}); sigortalı {_fmt_try(imp['insured_try'])}, \"\n                        f\"sigortasız {_fmt_try(imp['uninsured_try'])}; PML90 {_fmt_try(imp.get('pml90_try'))}.\",\n                        f\"Damage {_fmt_try(imp['loss_try'])}; insured {_fmt_try(imp['insured_try'])}; PML90 {_fmt_try(imp.get('pml90_try'))}.\"))\n    if \"ilce\" in figs: doc.add_picture(str(figs[\"ilce\"]), width=Inches(5.8))\n    for m in res[\"notifications\"][:3]:\n        doc.add_paragraph(f\"[{m['kanal']} → {m['hedef']}] {m['metin']}\")\n    doc.add_heading(T(\"8. Sınırlılıklar\", \"8. Limitations\"), 1)\n    doc.add_paragraph(T(\"ERA5/Open-Meteo yağışı konvektif ekstremleri düşük gösterebilir; olaylarda MGM istasyon toplamlarına ölçekleme uygulanmıştır. \"\n                        \"GloFAS gözlem değil model ürünüdür. HAND yöntemi hidrodinamik model (HEC-RAS 2D, LISFLOOD-FP) yerine hızlı tarama içindir; hücresel otomat \"\n                        \"kanalizasyon kapasitesini temsil etmez. Hasar eğrileri ve birim maliyetler yerel verilerle kalibre edilmelidir. Copernicus 30 m DEM kentsel mikro-topografyayı çözemez; \"\n                        \"HGM 12 m SYM (kamu kurumları için ücretsiz) ile değiştirilmesi önerilir.\",\n                        \"ERA5 underestimates convective extremes; GloFAS is a model, not observation; HAND/CA are screening tools; damage curves need local calibration; \"\n                        \"30 m DEM cannot resolve urban micro-topography — HGM 12 m DSM recommended for public bodies.\"))\n    p = out / (f\"TRFlood_Rapor_{slugify(res['title'])}.docx\" if lang == \"tr\" else f\"TRFlood_Report_{slugify(res['title'])}.docx\")\n    doc.save(p)\n    return p\n\n\n# ===========================================================================\n# 10. BORU HATTI — uçtan uca çalıştırma\n# ===========================================================================\ndef _rain_index(start: str, hours: int, dt_h: float = 1.0) -> pd.DatetimeIndex:\n    return pd.date_range(pd.Timestamp(start, tz=\"UTC\"), periods=int(hours / dt_h), freq=f\"{int(dt_h*60)}min\")\n\n\ndef acquire_terrain(cfg: RunConfig, prov: Provenance) -> Tuple[Terrain, np.ndarray, bool]:\n    \"\"\"DEM (Copernicus GLO-30) + arazi örtüsü (WorldCover) → su maskesi ile arazi analizi. (terrain, lc, synthetic)\"\"\"\n    lat_c, _ = cfg.center\n    synthetic = False\n    try:\n        dem, tr = CopernicusDEM().read(cfg.bbox, \"DEM\", max_px=cfg.grid_max)\n        prov.add(\"DEM\", \"Copernicus GLO-30 (AWS, 1\\\")\", \"ok\", f\"ızgara {dem.shape}, ~{abs(tr.a)*111320*math.cos(math.radians(lat_c)):.0f} m\")\n    except Exception as ex:  # noqa: BLE001\n        prov.add(\"DEM\", \"Copernicus GLO-30\", \"sentetik\", f\"okunamadı: {str(ex)[:80]}\")\n        dem, tr = synthetic_dem(cfg.bbox, n=min(cfg.grid_max, 200), seed=cfg.seed); synthetic = True\n    lc = None\n    if not synthetic:\n        try:\n            lc = WorldCoverClient().read_to_grid(cfg.bbox, dem.shape)\n            prov.add(\"Arazi örtüsü\", \"ESA WorldCover 10 m (2021, CC BY 4.0)\", \"ok\", f\"yapılaşmış %{(lc == 50).mean()*100:.1f}, su %{(lc == 80).mean()*100:.1f}\")\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"Arazi örtüsü\", \"ESA WorldCover\", \"sentetik\", str(ex)[:80])\n    if lc is None:\n        lc = synthetic_landcover(dem, cfg.seed)\n    stream_area = 1.0 if bbox_area_km2(cfg.bbox) > 300 else 0.3\n    terrain = build_terrain(dem, tr, lat_c, stream_area_km2=stream_area, water_mask=(lc == 80))\n    prov.add(\"HAND\", terrain.hand_source, \"ok\", f\"dere hücresi {int(terrain.streams.sum())}, kara %{terrain.valid.mean()*100:.0f}\")\n    return terrain, lc, synthetic\n\n\ndef acquire_layers(cfg: RunConfig, prov: Provenance, terrain: Terrain, lc: np.ndarray, synthetic: bool) -> dict:\n    shape = terrain.dem.shape\n    pop = None\n    if not synthetic:\n        try:\n            pop = WorldPopClient().read_to_grid(cfg.bbox, shape); prov.add(\"Nüfus\", \"WorldPop TUR 2025 100 m (R2024B)\", \"ok\", f\"toplam {pop.sum():,.0f}\")\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"Nüfus\", \"WorldPop\", \"sentetik\", str(ex)[:80])\n    if pop is None:\n        pop = synthetic_population(lc, cfg.seed)\n    labels, names = None, None\n    try:\n        feats = GeoBoundariesClient().features_in_bbox(cfg.bbox, \"ADM2\")\n        if feats:\n            labels = rasterize_features(feats, terrain.transform, shape); names = [f[\"name\"] for f in feats]\n            prov.add(\"İdari sınır\", \"geoBoundaries TUR ADM2 (ODbL)\", \"ok\", f\"{len(feats)} ilçe\")\n    except Exception as ex:  # noqa: BLE001\n        prov.add(\"İdari sınır\", \"geoBoundaries\", \"yok\", str(ex)[:80])\n    hsg, soil = \"B\", {}\n    try:\n        hsg, soil = SoilGridsClient().hsg(*cfg.center); prov.add(\"Toprak\", \"ISRIC SoilGrids v2\", \"ok\", f\"HSG {hsg} {soil}\")\n    except Exception as ex:  # noqa: BLE001\n        prov.add(\"Toprak\", \"SoilGrids\", \"varsayılan B\", str(ex)[:80])\n    return dict(lc=lc, pop=pop, labels=labels, names=names, hsg=hsg, soil=soil)\n\n\ndef acquire_rainfall(cfg: RunConfig, prov: Provenance, lat: float, lon: float) -> dict:\n    om = OpenMeteoClient()\n    ev = EVENTS.get(cfg.event_key) if cfg.scope == \"event\" else None\n    out: dict = dict(checks={}, source=\"\", scale=1.0)\n    rain = temp = None\n    if cfg.mode == \"historical\":\n        s = (pd.Timestamp(cfg.start) - pd.Timedelta(days=2)).date().isoformat()\n        e = (pd.Timestamp(cfg.end) + pd.Timedelta(days=2)).date().isoformat()\n        try:\n            h = om.archive_hourly(lat, lon, s, e)\n            rain = h[\"precipitation\"].fillna(0.0); temp = h[\"temperature_2m\"].ffill().fillna(15.0)\n            prov.add(\"Yağış\", \"Open-Meteo arşiv (ERA5/ERA5-Land)\", \"ok\", f\"{s}→{e}, toplam {rain.sum():.1f} mm\")\n            out[\"source\"] = \"Open-Meteo/ERA5 saatlik\"\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"Yağış\", \"Open-Meteo arşiv\", \"sentetik\", str(ex)[:80])\n        try:  # CHIRPS kontrolü (en fazla 3 gün)\n            days = pd.date_range(cfg.start, cfg.end)[:3]\n            vals = [CHIRPSClient().daily_mean_mm(cfg.bbox, d.date()) for d in days]\n            vals = [v for v in vals if v is not None]\n            if vals:\n                out[\"checks\"][\"CHIRPS_gunluk_ort_mm\"] = round(float(np.nansum(vals)), 1)\n                prov.add(\"Yağış kontrol\", \"CHIRPS v2.0 0,05°\", \"ok\", f\"olay günleri toplam {np.nansum(vals):.1f} mm\")\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"Yağış kontrol\", \"CHIRPS\", \"yok\", str(ex)[:60])\n        if (pd.Timestamp.utcnow().tz_localize(None) - pd.Timestamp(cfg.end)).days <= 6:\n            try:\n                st = MGMClient().toplam_yagis_bbox(cfg.end, cfg.bbox)\n                if len(st):\n                    out[\"checks\"][\"MGM_istasyon_maks_mm\"] = float(st.yagis_mm.max()); prov.add(\"Yağış kontrol\", \"MGM AWOS 24 s toplam\", \"ok\", f\"{len(st)} istasyon, maks {st.yagis_mm.max():.1f} mm\")\n            except Exception as ex:  # noqa: BLE001\n                prov.add(\"Yağış kontrol\", \"MGM toplamyagis\", \"yok\", str(ex)[:60])\n    else:\n        try:\n            f = om.forecast_hourly(lat, lon, days=cfg.forecast_days if cfg.mode == \"forecast\" else 2, past_days=1 if cfg.mode == \"forecast\" else 3)\n            rain = f[\"precipitation\"].fillna(0.0); temp = f[\"temperature_2m\"].ffill().fillna(15.0)\n            out[\"prob\"] = f.get(\"precipitation_probability\")\n            prov.add(\"Yağış\", \"Open-Meteo tahmin (best_match: ICON/GFS/ECMWF)\", \"ok\", f\"toplam {rain.sum():.1f} mm, {len(rain)} saat\")\n            out[\"source\"] = \"Open-Meteo çok-modelli tahmin\" if cfg.mode == \"forecast\" else \"Open-Meteo analiz+tahmin (nowcast)\"\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"Yağış\", \"Open-Meteo tahmin\", \"sentetik\", str(ex)[:80])\n        try:\n            mgm = MGMClient()\n            loc = mgm.merkez_lokasyon(lat, lon)\n            if loc.get(\"sondurumIstNo\"):\n                sd = mgm.sondurum(loc[\"merkezId\"])\n                out[\"checks\"][\"MGM_sondurum\"] = {k: sd.get(k) for k in (\"veriZamani\", \"yagis1Saat\", \"yagis6Saat\", \"yagis12Saat\", \"yagis24Saat\", \"sicaklik\")}\n                prov.add(\"Gözlem\", f\"MGM AWOS {loc.get('ilce')} ({loc.get('sondurumIstNo')})\", \"ok\", f\"24 s {sd.get('yagis24Saat')} mm\")\n            if loc.get(\"gunlukTahminIstNo\"):\n                out[\"checks\"][\"MGM_gunluk_tahmin\"] = mgm.tahmin_gunluk(loc[\"gunlukTahminIstNo\"])\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"Gözlem\", \"MGM sondurum\", \"yok\", str(ex)[:60])\n    if rain is None:\n        total = (ev or {}).get(\"yagis_mm\") or 120.0\n        hours = max(int(round((ev or {}).get(\"yagis_sure_saat\") or 24)), 1)\n        core = scs_type2_hyetograph(total, hours)\n        start0 = pd.Timestamp(cfg.start or date.today().isoformat(), tz=\"UTC\") - pd.Timedelta(days=1)\n        idx = pd.date_range(start0, periods=hours + 72, freq=\"h\")\n        rain = pd.Series(0.0, index=idx, name=\"precipitation\")\n        peak_ts = pd.Timestamp(ev[\"tepe_saat_utc\"], tz=\"UTC\") if ev and ev.get(\"tepe_saat_utc\") else start0 + pd.Timedelta(hours=36)\n        first = (peak_ts - pd.Timedelta(hours=hours / 2)).floor(\"h\")\n        for k, v in enumerate(core.values):\n            ts = first + pd.Timedelta(hours=k)\n            if ts in rain.index:\n                rain[ts] = v\n        temp = pd.Series(18.0, index=rain.index); out[\"source\"] = f\"Sentetik SCS Tip-II hiyetografı ({total} mm / {hours} s, {(ev or {}).get('yagis_kaynak', 'varsayılan')})\"\n    out[\"rain_raw\"] = rain.copy()\n    # Olay ölçekleme: ERA5 konvektif ekstremleri düşük gösterir → MGM istasyon toplamına ölçekle\n    if ev and ev.get(\"yagis_mm\") and cfg.mode == \"historical\" and \"Sentetik\" not in out[\"source\"]:\n        w0 = pd.Timestamp(cfg.start, tz=\"UTC\"); w1 = pd.Timestamp(cfg.end, tz=\"UTC\") + pd.Timedelta(days=1)\n        win = (rain.index >= w0) & (rain.index < w1)\n        tot = float(rain[win].sum())\n        out[\"checks\"][\"ERA5_olay_toplam_mm\"] = round(tot, 1); out[\"checks\"][\"MGM_rapor_mm\"] = ev[\"yagis_mm\"]\n        if tot < max(0.12 * ev[\"yagis_mm\"], 10.0):\n            # ERA5 olayı neredeyse görmemiş (<%12) → raporlanan toplam/süre ile olay hiyetografı yerleştirilir;\n            # aksi halde ERA5 zamansal deseni korunup MGM toplamına ölçeklenir\n            hours = max(int(round(ev.get(\"yagis_sure_saat\") or 24)), 1)\n            core = scs_type2_hyetograph(ev[\"yagis_mm\"], hours)\n            peak_ts = pd.Timestamp(ev[\"tepe_saat_utc\"], tz=\"UTC\") if ev.get(\"tepe_saat_utc\") else rain[win].idxmax()\n            start_ts = (peak_ts - pd.Timedelta(hours=hours / 2)).floor(\"h\")\n            rain = rain.copy(); rain[win] = 0.0\n            idx = pd.date_range(start_ts, periods=hours, freq=\"h\")\n            for ts, v in zip(idx, core.values):\n                if ts in rain.index:\n                    rain[ts] = v\n            out[\"scale\"] = None\n            out[\"source\"] = f\"ERA5 arka plan + MGM olay hiyetografı ({ev['yagis_mm']} mm / {hours} s, SCS Tip-II)\"\n            prov.add(\"Yağış olay hiyetografı\", ev[\"yagis_kaynak\"], \"uygulandı\", f\"ERA5 olay penceresi {tot:.0f} mm → {ev['yagis_mm']} mm / {hours} s\")\n        elif tot < 0.75 * ev[\"yagis_mm\"]:\n            k = ev[\"yagis_mm\"] / tot\n            rain = rain.copy(); rain[win] = rain[win] * k; out[\"scale\"] = round(k, 2)\n            out[\"source\"] += f\" × {k:.2f} (MGM istasyon toplamı {ev['yagis_mm']} mm'ye ölçekli)\"\n            prov.add(\"Yağış ölçekleme\", ev[\"yagis_kaynak\"], \"uygulandı\", f\"ERA5 {tot:.0f} mm → {ev['yagis_mm']} mm\")\n    out[\"rain\"] = rain; out[\"temp\"] = temp\n    return out\n\n\ndef locate_glofas_river_cell(om: \"OpenMeteoClient\", lat: float, lon: float, radius_deg: float = 0.05) -> Tuple[float, float, float]:\n    \"\"\"GloFAS 0,05° ızgarasında çıkış noktası çevresindeki 3×3 hücreyi yoklar; en yüksek ortalama debili hücreyi\n    (ana akarsu) döndürür. Open-Meteo memba alanını vermediği için bu dolaylı seçim kullanılır.\"\"\"\n    best = (lat, lon, -1.0)\n    for dlat in (-radius_deg, 0.0, radius_deg):\n        for dlon in (-radius_deg, 0.0, radius_deg):\n            try:\n                d = om.flood_daily(lat + dlat, lon + dlon, past_days=30, forecast_days=1, model=\"seamless_v4\")\n                m = float(d[\"river_discharge\"].mean())\n                if m > best[2]:\n                    best = (lat + dlat, lon + dlon, m)\n            except Exception:  # noqa: BLE001\n                continue\n    return best\n\n\ndef acquire_discharge(cfg: RunConfig, prov: Provenance, lat: float, lon: float) -> dict:\n    om = OpenMeteoClient(); out: dict = {}\n    glat, glon, gmean = locate_glofas_river_cell(om, lat, lon)\n    out[\"glofas_point\"] = (glat, glon)\n    if gmean >= 0:\n        prov.add(\"GloFAS hücre seçimi\", \"Open-Meteo Flood API 3×3 yoklama\", \"ok\", f\"({glat:.3f}, {glon:.3f}) ort. {gmean:.1f} m³/s\")\n    lat, lon = glat, glon\n    try:\n        if cfg.mode == \"historical\":\n            s = (pd.Timestamp(cfg.start) - pd.Timedelta(days=10)).date().isoformat(); e = (pd.Timestamp(cfg.end) + pd.Timedelta(days=5)).date().isoformat()\n            d = om.flood_daily(lat, lon, start=s, end=e, model=\"consolidated_v4\")\n        else:\n            d = om.flood_daily(lat, lon, past_days=30, forecast_days=max(cfg.forecast_days, 3), model=\"seamless_v4\")\n        out[\"reference\"] = d[\"river_discharge\"]\n        out[\"baseflow\"] = float(d[\"river_discharge\"].iloc[:5].mean())\n        prov.add(\"Nehir debisi\", \"GloFAS v4 (Open-Meteo Flood API)\", \"ok\", f\"{len(d)} gün, maks {d.river_discharge.max():.1f} m³/s\")\n    except Exception as ex:  # noqa: BLE001\n        out[\"reference\"] = None; out[\"baseflow\"] = 0.5\n        prov.add(\"Nehir debisi\", \"Open-Meteo Flood API\", \"yok\", str(ex)[:80])\n    # QML eğitim serisi: 3 yıl günlük yağış + GloFAS\n    try:\n        e = (pd.Timestamp(cfg.end) if cfg.mode == \"historical\" else pd.Timestamp.utcnow().tz_localize(None)) - pd.Timedelta(days=8)\n        s = e - pd.Timedelta(days=3 * 365)\n        pr = om.archive_daily(lat, lon, s.date().isoformat(), e.date().isoformat())[\"precipitation_sum\"]\n        q = om.flood_daily(lat, lon, start=s.date().isoformat(), end=e.date().isoformat(), model=\"consolidated_v4\")[\"river_discharge\"]\n        out[\"train_rain\"], out[\"train_q\"] = pr, q\n        prov.add(\"QML eğitim serisi\", \"Open-Meteo arşiv + GloFAS consolidated\", \"ok\", f\"{len(q)} gün\")\n    except Exception as ex:  # noqa: BLE001\n        prov.add(\"QML eğitim serisi\", \"Open-Meteo\", \"sentetik\", str(ex)[:80])\n        idx = pd.date_range(\"2023-01-01\", periods=900, freq=\"D\", tz=\"UTC\"); rng = np.random.default_rng(cfg.seed)\n        pr = pd.Series(np.maximum(rng.gamma(0.4, 6.0, 900) * (1 + 0.6 * np.sin(2 * np.pi * np.arange(900) / 365)), 0), index=idx)\n        q = pd.Series(np.convolve(pr.values, np.exp(-np.arange(12) / 3.0))[:900] * 0.8 + 3, index=idx)\n        out[\"train_rain\"], out[\"train_q\"] = pr, q\n    return out\n\n\ndef run_pipeline(cfg: RunConfig, progress=None) -> dict:\n    \"\"\"Tüm katmanları çalıştırır; sözlük döndürür ve rapor paketini out_dir altına yazar.\"\"\"\n    t0 = time.time()\n    cfg.resolve()\n    np.random.seed(cfg.seed)\n    prov = Provenance()\n    out = Path(cfg.out_dir) / f\"{slugify(cfg.event_key if cfg.scope == 'event' else cfg.il)}_{cfg.mode}_{datetime.utcnow():%Y%m%d_%H%M}\"\n    out.mkdir(parents=True, exist_ok=True)\n    title = EVENTS[cfg.event_key][\"ad\"] if cfg.scope == \"event\" else f\"{cfg.il} — {cfg.mode}\"\n    res: dict = dict(cfg=cfg, title=title, generated=datetime.utcnow().strftime(\"%Y-%m-%d %H:%M UTC\"), out_dir=out)\n    say = progress or (lambda m: log.info(m))\n    ledger = TimeLedger(); res[\"ledger\"] = ledger\n\n    say(\"1/8 Arazi: DEM + arazi örtüsü + HAND\"); terrain, lc, synthetic = acquire_terrain(cfg, prov); res[\"terrain\"] = terrain\n    say(\"2/8 Katmanlar: nüfus, ilçe sınırları, toprak\"); lay = acquire_layers(cfg, prov, terrain, lc, synthetic)\n    cat = terrain.catchment_props()\n    oi, oj = cat[\"outlet_rc\"]; olon = terrain.transform.c + (oj + 0.5) * terrain.transform.a; olat = terrain.transform.f + (oi + 0.5) * terrain.transform.e\n    cat[\"outlet_latlon\"] = (float(olat), float(olon))\n    lat_c, lon_c = cfg.center\n    say(\"3/8 Yağış ve gözlemler\"); rain = acquire_rainfall(cfg, prov, lat_c, lon_c); res[\"rain\"] = rain\n    say(\"4/8 Nehir debisi (GloFAS) ve eğitim serileri\"); dis = acquire_discharge(cfg, prov, olat, olon)\n    # MGM uyarı seviyesi (bugün) — nowcast/forecast'ta ana sinyal, historical'da bilgi\n    mgm_level = (\"unknown\", \"\")\n    try:\n        ilceler = MGMClient().ilceler(cfg.il)\n        mgm_level = MGMClient().uyari_seviyesi(ilceler.merkezId.tolist()) if len(ilceler) else (\"none\", \"\")\n        prov.add(\"MeteoUyarı\", \"MGM meteoalarm/today\", \"ok\", f\"{cfg.il}: {mgm_level[0]}\")\n    except Exception as ex:  # noqa: BLE001\n        prov.add(\"MeteoUyarı\", \"MGM\", \"yok\", str(ex)[:60])\n    res[\"mgm_alert\"] = mgm_level\n\n    # ---- hidroloji\n    say(\"5/8 Hidroloji: 5 klasik model + GR4J\")\n    cn_mean, cn_grid = composite_cn(lay[\"lc\"], lay[\"hsg\"], amc=\"III\" if cfg.mode == \"historical\" else \"II\")\n    r = rain[\"rain\"]; temp = rain[\"temp\"].reindex(r.index).ffill().fillna(15.0)\n    daily_rain = r.resample(\"1D\").sum()\n    sims = run_classical_models(r, cat[\"area_km2\"], cn_mean, cat[\"tc_hours\"], pet_mm_h=0.15, temp_c=temp.values,\n                                baseflow=dis[\"baseflow\"], daily_rain=daily_rain)\n    metrics = evaluate_models(sims, dis.get(\"reference\"))\n    peaks = {k: float(q.max()) for k, q in sims.items()}\n    peak_med = float(np.median(list(peaks.values())))\n    ref = dis.get(\"reference\")\n    ref_peak = float(ref.max()) if ref is not None and len(ref) else None\n    if \"KGE\" in metrics and metrics.KGE.notna().any() and ref_peak and 0.2 < ref_peak / max(peak_med, 1e-6) < 5:\n        best = metrics.iloc[0][\"model\"]; sel_note = \"KGE sıralaması (GloFAS günlük referans)\"\n    else:\n        best = \"SCS-CN + SCS UH\"\n        sel_note = (\"GloFAS tepe değeri (%s m³/s) model medyanıyla (%s m³/s) tutarsız — 5 km GloFAS ızgarası küçük/dik havzaları çözemez; \"\n                    \"DSİ uygulamasına uygun olarak olay tabanlı SCS-CN + sentetik birim hidrograf esas alındı. DSİ AGİ gözlemi yüklenirse sıralama gözleme göre yapılır.\"\n                    % (f\"{ref_peak:.1f}\" if ref_peak else \"yok\", f\"{peak_med:.0f}\"))\n    q_best = sims[best]; peak = float(q_best.max())\n    hydro = dict(sims=sims, metrics=metrics, best_model=best, selection_note=sel_note, peak_m3s=peak, peak_median_m3s=peak_med, catchment=cat,\n                 cn=cn_mean, hsg=lay[\"hsg\"], reference=dis.get(\"reference\"), baseflow=dis[\"baseflow\"], glofas_point=dis.get(\"glofas_point\"))\n    res[\"hydro\"] = hydro\n\n    # ---- taşma\n    say(\"6/8 Taşma: HAND nehir taşkını + 2B plüvyal otomat + Monte Carlo\")\n    depth_riv = None; depth = np.zeros(terrain.dem.shape, dtype=\"float32\")\n    rating_fn = lambda n: synthetic_rating_curve(terrain, cat[\"channel_slope\"], manning_n=n)  # noqa: E731\n    if cfg.flood_type in (\"riverine\", \"both\"):\n        rating = rating_fn(0.045); stage = stage_from_discharge(rating, peak)\n        depth_riv = hand_inundation(terrain, stage); hydro[\"stage_m\"] = stage; hydro[\"rating\"] = rating\n        depth = np.maximum(depth, depth_riv)\n        res[\"mc_riv\"] = monte_carlo_riverine(r, cat[\"area_km2\"], cn_mean, cat[\"tc_hours\"], terrain, rating_fn, runs=cfg.mc_runs,\n                                             baseflow=dis[\"baseflow\"], seed=cfg.seed)\n        res[\"mc_summary\"] = res[\"mc_riv\"][\"summary\"]\n    res[\"depth_riverine\"] = depth_riv\n    plu = None; plu_window = None\n    if cfg.flood_type in (\"pluvial\", \"both\"):\n        zf_p = block_reduce(terrain.filled, cfg.pluvial_grid_max)\n        lc_p = _fit_shape(lay[\"lc\"], zf_p.shape)\n        fac = terrain.filled.shape[0] / zf_p.shape[0]\n        # olay penceresi: en yağışlı 12 saat + 2 saat boşaltım\n        wlen = min(12, len(r)); roll = r.rolling(wlen, min_periods=1).sum(); c_end = roll.idxmax()\n        win = r[(r.index > c_end - pd.Timedelta(hours=wlen)) & (r.index <= c_end)]\n        if len(win) == 0 or win.sum() < 1:\n            win = r.iloc[:wlen]\n        plu = pluvial_ca(zf_p, win, lc_p, terrain.dy_m * fac, terrain.dx_m * fac, drain_hours=2.0, max_minutes=8.0, sewer_mm_h=cfg.sewer_capacity_mm_h)\n        plu[\"grid_shape\"] = zf_p.shape; plu[\"window\"] = (str(win.index[0]), str(win.index[-1]), float(win.sum())); plu_window = win\n        depth = np.maximum(depth, _fit_shape(plu[\"max_depth\"], depth.shape))\n        zf_mc = block_reduce(terrain.filled, cfg.mc_grid_max); fac_mc = terrain.filled.shape[0] / zf_mc.shape[0]\n        res[\"mc_plu\"] = monte_carlo_pluvial(zf_mc, win, _fit_shape(lay[\"lc\"], zf_mc.shape), terrain.dy_m * fac_mc, terrain.dx_m * fac_mc,\n                                            runs=max(4, cfg.mc_runs // 4), seed=cfg.seed, sewer_mm_h=cfg.sewer_capacity_mm_h)\n        if \"mc_summary\" not in res:\n            res[\"mc_summary\"] = dict(runs=res[\"mc_plu\"][\"runs\"], peak_p10=0, peak_p50=0, peak_p90=0)\n    res[\"pluvial\"] = plu; res[\"depth\"] = depth\n    # ---- D · 2B sığ su çözücüsü + makullük kapısı\n    res[\"hydraulics\"] = None\n    use_hyd = cfg.hydraulics == \"on\" or (cfg.hydraulics == \"auto\" and max(terrain.dem.shape) <= 320)\n    if use_hyd:\n        with _timed(ledger, \"step\", \"2B sığ su çözücüsü\"):\n            try:\n                zf_h = block_reduce(terrain.filled, cfg.hydraulics_grid_max); fac_h = terrain.filled.shape[0] / zf_h.shape[0]\n                # kaba ızgarada arazi nesnesini yeniden kur (dere ağı, HAND, kanal geometrisi tutarlı olsun)\n                if fac_h > 1.01:\n                    from rasterio.transform import Affine\n                    tr_h = Affine(terrain.transform.a * fac_h, 0, terrain.transform.c, 0, terrain.transform.e * fac_h, terrain.transform.f)\n                    T_h = build_terrain(block_reduce(terrain.dem, cfg.hydraulics_grid_max), tr_h, lat_c, stream_area_km2=1.0 if bbox_area_km2(cfg.bbox) > 300 else 0.3,\n                                        water_mask=(_fit_shape(lay[\"lc\"], zf_h.shape) == 80))\n                else:\n                    T_h = terrain\n                lc_h = _fit_shape(lay[\"lc\"], T_h.dem.shape)\n                zh = hydraulic_dem(T_h); chg = channel_geometry(T_h); sink_h = (lc_h == 80) | (T_h.dem <= 0.5)\n                if cfg.flood_type in (\"riverine\", \"both\"):\n                    cat_h = T_h.catchment_props(); area_bbox = float(T_h.valid.sum() * T_h.cell_area_km2)\n                    q_lat = q_best * (area_bbox / max(cat[\"area_km2\"], 1e-6)); w_lat = lateral_inflow_weights(T_h)\n                    sw = shallow_water_2d(zh, T_h.dx_m, T_h.dy_m, lateral=(w_lat, q_lat), lc=lc_h, t_end_h=float(len(q_best) * _dt_hours(q_best)),\n                                          max_minutes=cfg.hydraulics_minutes, record_every_min=360, sink=sink_h, channel=chg, sewer_mm_h=cfg.sewer_capacity_mm_h)\n                    forcing = \"dere ağı boyunca yanal giriş (SCS hidrografı, memba alanı ağırlıklı) + alt-ızgara kanal\"\n                else:\n                    win_h = plu_window if plu is not None else r.iloc[:12]\n                    sw = shallow_water_2d(zh, T_h.dx_m, T_h.dy_m, rain_mm_h=win_h, lc=lc_h, t_end_h=float(len(win_h) * _dt_hours(win_h) + 2),\n                                          max_minutes=cfg.hydraulics_minutes, record_every_min=60, sink=sink_h, channel=chg, sewer_mm_h=cfg.sewer_capacity_mm_h)\n                    forcing = \"doğrudan yağış (olay penceresi) + şebeke kapasitesi + alt-ızgara kanal\"\n                hand_ref = hand_inundation(T_h, hydro.get(\"stage_m\", 2.0)) if cfg.flood_type in (\"riverine\", \"both\") else _fit_shape(depth, T_h.dem.shape)\n                gate = plausibility_gate(sw, T_h, depth_hand=hand_ref)\n                res[\"hydraulics\"] = dict(max_depth=sw[\"max_depth\"], max_velocity=sw[\"max_velocity\"], mass=sw[\"mass\"], steps=sw[\"steps\"], runtime_s=sw[\"runtime_s\"],\n                                         grid_shape=T_h.dem.shape, cell_m=(T_h.dy_m, T_h.dx_m), gate=gate, forcing=forcing, terrain=T_h)\n                # kapı geçildiyse birleşik derinliği hidrolik çözümle güncelle (tarama yöntemleri yedek olarak kalır)\n                if gate[\"passed\"]:\n                    depth = np.maximum(depth, _fit_shape(sw[\"max_depth\"], depth.shape)); res[\"depth\"] = depth\n                    prov.add(\"Hidrolik\", \"2B yerel-eylemsizlik sığ su (Bates 2010) + alt-ızgara kanal\", \"ok\",\n                             f\"{T_h.dem.shape} ızgara, {sw['steps']} adım, {sw['runtime_s']:.0f} s, maks {sw['max_depth'].max():.1f} m, kapı geçildi\")\n                else:\n                    prov.add(\"Hidrolik\", \"2B sığ su\", \"kapı geçilmedi\", \"; \".join(f\"{c['check']}: {c['value']}\" for _, c in gate[\"checks\"].iterrows() if not c[\"ok\"]))\n            except Exception as ex:  # noqa: BLE001\n                prov.add(\"Hidrolik\", \"2B sığ su\", \"hata\", str(ex)[:100])\n    # GFM doğrulaması\n    res[\"gfm\"] = None\n    if cfg.mode == \"historical\" and (EVENTS.get(cfg.event_key, {}).get(\"gfm_kontrol\") if cfg.scope == \"event\" else True) and not synthetic:\n        try:\n            g = GFMClient().flood_extent_to_grid(cfg.bbox, cfg.start, (pd.Timestamp(cfg.end) + pd.Timedelta(days=4)).date().isoformat(), depth.shape)\n            if g:\n                ext, items = g; mod = depth > 0.1; obs = ext > 0.5\n                hit = float((mod & obs).sum() / max(obs.sum(), 1)); far = float((mod & ~obs).sum() / max(mod.sum(), 1))\n                csi = float((mod & obs).sum() / max((mod | obs).sum(), 1))\n                res[\"gfm\"] = dict(extent=ext, items=items, hit=hit, far=far, csi=csi, obs_cells=int(obs.sum()), model_cells=int(mod.sum()))\n                prov.add(\"Doğrulama\", \"CEMS GFM Sentinel-1 taşkın yayılımı (EODC)\", \"ok\", f\"{len(items)} sahne, S1 {int(obs.sum())} hücre, CSI {csi:.2f}\")\n            else:\n                prov.add(\"Doğrulama\", \"CEMS GFM\", \"sahne yok\", \"olay penceresinde Sentinel-1 geçişi bulunamadı\")\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"Doğrulama\", \"CEMS GFM\", \"yok\", str(ex)[:80])\n\n    # ---- kuantum\n    say(\"7/8 Kuantum: kontrol düzlemi, VQC tahmin, QUBO tahsis, portföy (QAE · QSVM · QLSTM)\")\n    res[\"qml\"] = None; res[\"train_rain\"] = dis.get(\"train_rain\"); res[\"train_q\"] = dis.get(\"train_q\")\n    n_feat = 14\n    n_dist = len(lay[\"names\"]) if lay.get(\"names\") else 1\n    sizes = size_circuits(cfg, n_feat, terrain.dem.shape, n_dist); res[\"sizes\"] = sizes\n    dev_sel = DEVICE_REGISTRY.get(cfg.target_device, DEVICE_REGISTRY[\"simulator\"])\n    res[\"device_fit\"] = {name: effective_budget(d, depth_needed=15 + 3 * sizes[\"vqc_layers\"], n_qubits_needed=sizes[\"vqc\"], shots=sizes[\"shots\"]) for name, d in DEVICE_REGISTRY.items()}\n    prov.add(\"Kontrol düzlemi\", f\"profil {cfg.run_profile}, hedef {dev_sel.name}\", \"ok\", f\"tavan {sizes['cap']} kübit; VQC {sizes['vqc']}, QAE {sizes['qae']}, QSVM {sizes['qsvm']}, QLSTM {sizes['qlstm']}, genlik {sizes['amplitude']}/{sizes['amplitude_needed']}\")\n    if cfg.quantum_backend != \"none\":\n        try:\n            X, y = build_forecast_dataset(dis[\"train_rain\"], dis[\"train_q\"])\n            n_te = max(30, int(len(X) * 0.2)); Xtr, Xte, ytr, yte = X.iloc[:-n_te], X.iloc[-n_te:], y.iloc[:-n_te], y.iloc[-n_te:]\n            with _timed(ledger, \"gradient\", \"VQC eğitimi\", n_circuits=cfg.qml_epochs * 48, n_qubits=sizes[\"vqc\"]):\n                qf = QMLForecaster(sizes[\"vqc\"], sizes[\"vqc_layers\"], cfg.qml_epochs, cfg.quantum_backend, cfg.seed).fit(Xtr, ytr)\n            with _timed(ledger, \"circuit\", \"VQC test çıkarımı\", n_circuits=len(Xte), n_qubits=sizes[\"vqc\"]):\n                preds = {\"QML-VQC\": qf.predict(Xte)}\n            preds.update(classical_baselines(Xtr, ytr, Xte))\n            scores = {k: dict(RMSE=float(np.sqrt(np.mean((v - yte.values) ** 2))), NSE=nse(yte.values, v)) for k, v in preds.items()}\n            hw = qf.predict_hardware(Xte) if cfg.quantum_backend in (\"ibm\", \"origin\") else None\n            if hw is not None:\n                scores[\"QML-VQC (donanım)\"] = dict(RMSE=float(np.sqrt(np.mean((hw - yte.values[: len(hw)]) ** 2))), NSE=nse(yte.values[: len(hw)], hw))\n            res[\"qml\"] = dict(history=qf.history, preds=preds, scores=scores, y_test=yte, info=qf.info, next_day=float(preds[\"QML-VQC\"][-1]), hw_preds=hw)\n            prov.add(\"QML\", f\"PennyLane VQC ({qf.info.get('backend')})\", \"ok\", f\"test RMSE {scores['QML-VQC']['RMSE']:.1f}\")\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"QML\", \"PennyLane\", \"hata\", str(ex)[:100])\n    # ---- G · kuantum portföyü\n    res[\"portfolio\"] = None\n    if cfg.quantum_portfolio and cfg.quantum_backend != \"none\":\n        try:\n            res[\"portfolio\"] = run_quantum_portfolio(res, sizes, ledger=ledger, epochs_qlstm=cfg.qlstm_epochs)\n            pf = res[\"portfolio\"]; bits = []\n            if pf.get(\"qae\"): bits.append(f\"QAE p={pf['qae']['p_qae']:.3f} (MC {pf['qae']['p_mc']:.3f})\")\n            if pf.get(\"qsvm\"): bits.append(\"QSVM AUC %.2f / RBF %.2f\" % (pf['qsvm']['scores']['QSVM (kuantum çekirdek)']['AUC'], pf['qsvm']['scores']['RBF-SVM']['AUC']))\n            if pf.get(\"qlstm\"): bits.append(\"QLSTM NSE %.2f / LSTM %.2f\" % (pf['qlstm']['scores']['QLSTM']['NSE'], pf['qlstm']['scores']['LSTM (numpy, aynı boyut)']['NSE']))\n            prov.add(\"Kuantum portföyü\", \"QAE · QSVM · QLSTM (simülatör)\", \"ok\", \"; \".join(bits))\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"Kuantum portföyü\", \"QAE · QSVM · QLSTM\", \"hata\", str(ex)[:100])\n    # ---- etki\n    say(\"8/8 Etki, EDA, ekranlar, rapor\")\n    mc90 = res[\"mc_riv\"][\"depth_p90\"] if res.get(\"mc_riv\") else (_fit_shape(res[\"mc_plu\"][\"depth_p90\"], depth.shape) if res.get(\"mc_plu\") else None)\n    imp = impact_assessment(depth, lay[\"pop\"], lay[\"lc\"], terrain.dy_m * terrain.dx_m, cfg, lay[\"labels\"], lay[\"names\"], mc90)\n    res[\"impact\"] = imp\n    res[\"warning\"] = warning_level(imp[\"exposed_pop\"], imp[\"max_depth\"], mgm_level[0])\n    res[\"qubo\"] = None\n    if cfg.qubo_solver != \"none\" and imp.get(\"districts\") is not None and len(imp[\"districts\"]) >= 2:\n        d = imp[\"districts\"]; teams = int(np.clip(math.ceil(len(d) / 3), 1, 6))\n        x, info = qubo_team_allocation(d.risk_skoru.values, teams, solver=cfg.qubo_solver, seed=cfg.seed)\n        res[\"qubo\"] = dict(teams=int(x.sum()), allocation=dict(zip(d.ilce, x.tolist())), info=info)\n    res[\"notifications\"] = build_notifications(res)\n    # ---- E · EDA\n    res[\"eda\"] = None\n    if cfg.eda:\n        try:\n            with _timed(ledger, \"step\", \"EDA takımı\"):\n                res[\"eda\"] = run_eda(res)\n            sp = res[\"eda\"][\"spatial\"]\n            prov.add(\"EDA\", \"envanter · dağılım uyumu · testler · aykırı · korelasyon · mekânsal\", \"ok\", f\"Moran I {sp['moran_I']:.2f} (p {sp['moran_p']:.2f}), sıcak nokta {sp['hot_cells']}\")\n        except Exception as ex:  # noqa: BLE001\n            prov.add(\"EDA\", \"EDA takımı\", \"hata\", str(ex)[:100])\n    res[\"provenance\"] = prov.frame()\n    # ---- çıktılar\n    figs = make_figures(res, out); res[\"figures\"] = figs\n    for lang in ([\"tr\", \"en\"] if cfg.report_lang == \"both\" else [cfg.report_lang]):\n        res.setdefault(\"reports\", []).append(write_docx(res, figs, out, lang))\n    (out / \"acil_durum_panosu.html\").write_text(html_document(screen_emergency(res), f\"Acil Durum Panosu — {title}\"), encoding=\"utf-8\")\n    (out / \"sigorta_panosu.html\").write_text(html_document(screen_insurance(res), f\"Sigorta Panosu — {title}\"), encoding=\"utf-8\")\n    (out / \"bildirim_merkezi.html\").write_text(html_document(screen_notifications(res), f\"Bildirim Merkezi — {title}\"), encoding=\"utf-8\")\n    res[\"provenance\"].to_csv(out / \"provenance.csv\", index=False, encoding=\"utf-8-sig\")\n    ledger.frame().to_csv(out / \"zaman_defteri.csv\", index=False, encoding=\"utf-8-sig\")\n    res[\"ledger_summary\"] = ledger.summary(time.time() - t0)\n    res[\"qpu_projection\"] = ledger.qpu_projection(sizes); res[\"qpu_projection\"].to_csv(out / \"qpu_projeksiyon.csv\", index=False, encoding=\"utf-8-sig\")\n    if res.get(\"eda\"):\n        for k in (\"inventory\", \"fits\", \"tests\", \"outliers\", \"correlations\", \"cell_correlations\"):\n            df_ = res[\"eda\"].get(k)\n            if df_ is not None and len(df_): df_.to_csv(out / f\"eda_{k}.csv\", index=False, encoding=\"utf-8-sig\")\n        res[\"eda\"][\"spatial\"][\"variogram\"].to_csv(out / \"eda_variogram.csv\", index=False, encoding=\"utf-8-sig\")\n    if res.get(\"hydraulics\"):\n        hy_ = res[\"hydraulics\"]; hy_[\"gate\"][\"checks\"].to_csv(out / \"hidrolik_kapi.csv\", index=False, encoding=\"utf-8-sig\")\n        np.save(out / \"hidrolik_derinlik.npy\", hy_[\"max_depth\"]); np.save(out / \"hidrolik_hiz.npy\", hy_[\"max_velocity\"])\n    hydro[\"metrics\"].to_csv(out / \"model_skorlari.csv\", index=False, encoding=\"utf-8-sig\")\n    pd.DataFrame({k: v for k, v in sims.items()}).to_csv(out / \"hidrograflar.csv\", encoding=\"utf-8-sig\")\n    if imp.get(\"districts\") is not None:\n        imp[\"districts\"].to_csv(out / \"ilce_etki.csv\", index=False, encoding=\"utf-8-sig\")\n    np.save(out / \"derinlik_haritasi.npy\", depth)\n    _write_geotiff(out / \"derinlik_haritasi.tif\", depth, terrain.transform)\n    manifest = dict(engine=ENGINE_NAME, version=ENGINE_VERSION, title=title, generated=res[\"generated\"], config=json.loads(cfg.to_json()),\n                    catchment=cat, hydro=dict(best_model=best, peak_m3s=peak, stage_m=hydro.get(\"stage_m\")), impact={k: v for k, v in imp.items() if isinstance(v, (int, float, str))},\n                    warning=res[\"warning\"], mgm_alert=mgm_level, qml=(res[\"qml\"] or {}).get(\"scores\"), qubo=res.get(\"qubo\"), rain_checks=rain[\"checks\"],\n                    sizes=sizes, device_fit=res.get(\"device_fit\"), ledger=res.get(\"ledger_summary\"),\n                    hydraulics=({k: v for k, v in res[\"hydraulics\"].items() if k in (\"mass\", \"steps\", \"runtime_s\", \"grid_shape\", \"forcing\")} | {\"gate_passed\": res[\"hydraulics\"][\"gate\"][\"passed\"], \"max_depth\": float(res[\"hydraulics\"][\"max_depth\"].max()), \"max_velocity\": float(res[\"hydraulics\"][\"max_velocity\"].max())}) if res.get(\"hydraulics\") else None,\n                    portfolio=({k: (v.get(\"scores\") if isinstance(v, dict) and \"scores\" in v else {kk: vv for kk, vv in v.items() if kk in (\"p_qae\", \"se_qae\", \"p_mc\", \"se_mc\", \"n_qubits\", \"n_oracle\", \"eşik_aciklama\")}) for k, v in res[\"portfolio\"].items()} if res.get(\"portfolio\") else None),\n                    eda=({\"moran_I\": res[\"eda\"][\"spatial\"][\"moran_I\"], \"moran_p\": res[\"eda\"][\"spatial\"][\"moran_p\"], \"hot_cells\": res[\"eda\"][\"spatial\"][\"hot_cells\"], \"best_fits\": res[\"eda\"][\"fits\"].groupby(\"seri\").first()[\"dagilim\"].to_dict() if len(res[\"eda\"][\"fits\"]) else {}} if res.get(\"eda\") else None),\n                    runtime_s=round(time.time() - t0, 1))\n    (out / \"manifest.json\").write_text(json.dumps(manifest, ensure_ascii=False, indent=2, default=str), encoding=\"utf-8\")\n    bundle = Path(cfg.out_dir) / f\"{out.name}_paket.zip\"\n    with zipfile.ZipFile(bundle, \"w\", zipfile.ZIP_DEFLATED) as z:\n        for p in out.rglob(\"*\"):\n            if p.is_file(): z.write(p, p.relative_to(out.parent))\n    res[\"bundle\"] = bundle; res[\"runtime_s\"] = time.time() - t0\n    say(f\"Tamamlandı: {out} ({res['runtime_s']:.0f} s) → paket {bundle.name}\")\n    return res\n\n\ndef _write_geotiff(path: Path, arr: np.ndarray, transform):\n    try:\n        import rasterio\n        with rasterio.open(path, \"w\", driver=\"GTiff\", height=arr.shape[0], width=arr.shape[1], count=1, dtype=\"float32\",\n                           crs=\"EPSG:4326\", transform=transform, compress=\"deflate\") as dst:\n            dst.write(arr.astype(\"float32\"), 1)\n    except Exception as ex:  # noqa: BLE001\n        log.warning(f\"GeoTIFF yazılamadı: {ex}\")\n\n\n# ===========================================================================\n# 11. ULUSAL TARAMA — 81 il için hızlı risk endeksi (tahmin + GloFAS anomali + MGM uyarı)\n# ===========================================================================\ndef national_screening(days: int = 3, provinces: Optional[List[str]] = None, progress=None) -> pd.DataFrame:\n    om, mgm = OpenMeteoClient(), MGMClient()\n    alarms = None\n    try:\n        alarms = mgm.meteoalarm(\"today\")\n    except Exception:  # noqa: BLE001\n        pass\n    rows = []\n    items = [(k, v) for k, v in PROVINCES.items() if not provinces or slugify(v[0]) in {slugify(p) for p in provinces}]\n    for code, (name, lat, lon) in items:\n        row = dict(plaka=code, il=name, lat=lat, lon=lon, yagis_mm=np.nan, glofas_oran=np.nan, mgm_uyari=\"unknown\")\n        try:\n            f = om.forecast_hourly(lat, lon, days=days, past_days=0)\n            row[\"yagis_mm\"] = float(f[\"precipitation\"].sum()); row[\"maks_saatlik_mm\"] = float(f[\"precipitation\"].max())\n        except Exception:  # noqa: BLE001\n            pass\n        try:\n            d = om.flood_daily(lat, lon, past_days=30, forecast_days=days, model=\"seamless_v4\")\n            base = d[\"river_discharge\"].iloc[:30].mean(); row[\"glofas_oran\"] = float(d[\"river_discharge\"].iloc[30:].max() / (base + 1e-6))\n        except Exception:  # noqa: BLE001\n            pass\n        if alarms is not None:\n            try:\n                ids = mgm.ilceler(name).merkezId.tolist(); lvl = \"none\"\n                order = {\"none\": 0, \"yellow\": 1, \"orange\": 2, \"red\": 3}\n                for a in alarms:\n                    for L in (\"red\", \"orange\", \"yellow\"):\n                        if set(a.get(\"towns\", {}).get(L, [])) & set(ids) and order[L] > order[lvl]: lvl = L\n                row[\"mgm_uyari\"] = lvl\n            except Exception:  # noqa: BLE001\n                pass\n        rows.append(row)\n        if progress: progress(f\"{name}: {row['yagis_mm']:.0f} mm, GloFAS ×{row['glofas_oran']:.1f}, MGM {row['mgm_uyari']}\")\n    df = pd.DataFrame(rows)\n    z = lambda s: (s - s.mean()) / (s.std() + 1e-9)  # noqa: E731\n    df[\"risk_endeksi\"] = (0.5 * z(df.yagis_mm.fillna(0)) + 0.3 * z(np.log1p(df.glofas_oran.fillna(1)))\n                          + 0.2 * df.mgm_uyari.map({\"none\": 0, \"unknown\": 0, \"yellow\": 1, \"orange\": 2, \"red\": 3}).fillna(0))\n    return df.sort_values(\"risk_endeksi\", ascending=False).reset_index(drop=True)\n\n\ndef load_observed_discharge(csv_path: str, time_col: str = \"tarih\", q_col: str = \"debi_m3s\") -> pd.Series:\n    \"\"\"DSİ AGİ / kullanıcı gözlem serisi (CSV) — GloFAS yerine referans olarak kullanılır.\"\"\"\n    df = pd.read_csv(csv_path)\n    s = pd.Series(df[q_col].values, index=pd.to_datetime(df[time_col], utc=True)).sort_index()\n    return s.resample(\"1D\").mean()\n\n# ===========================================================================\n# 12. KONTROL DÜZLEMİ (H) — cihaz kayıt defteri, kübit bütçesi, devre uygunluğu, profiller\n#     ZAMAN DEFTERİ (L) — devre/gradyan/adım süreleri, kuantum payı, QPU maliyet projeksiyonu\n# ===========================================================================\nSIM_QUBIT_CAP = 20  # Colab (12–13 GB RAM) için durum-vektörü tavanı: 2^20 × 16 B = 16 MB, gradyanla ~1 GB\n\n\n@dataclass\nclass QuantumDevice:\n    name: str\n    vendor: str\n    kind: str                 # gate | annealer | simulator\n    qubits: int               # fiziksel kübit\n    target_qubits: int        # bu programda hedeflenen kullanılabilir kübit (topoloji/kalibrasyon sonrası)\n    reliable_depth: int       # güvenilir devre derinliği (2q kapı katmanı)\n    two_q_error: float        # ortalama 2q kapı hatası\n    queue_s: float            # tipik kuyruk (s)\n    usd_per_s: float          # QPU-saniyesi (veya eşdeğer) maliyeti\n    max_shots: int = 4096\n    token_env: str = \"\"       # erişim jetonu ortam değişkeni\n    note: str = \"\"\n\n    def available(self) -> bool:\n        return self.kind == \"simulator\" or (bool(self.token_env) and bool(os.environ.get(self.token_env)))\n\n\n# Değerler Eylül 2026 kamuya açık spesifikasyonlardan yaklaşık; kontrol düzlemi canlı telemetriyle günceller (IBM: backend.properties()).\nDEVICE_REGISTRY: Dict[str, QuantumDevice] = {\n    \"simulator\": QuantumDevice(\"PennyLane default.qubit\", \"PennyLane\", \"simulator\", SIM_QUBIT_CAP, SIM_QUBIT_CAP, 10_000, 0.0, 0.0, 0.0, 100_000, \"\", \"Colab durum-vektörü tavanı 20 kübit\"),\n    \"ibm_heron\": QuantumDevice(\"IBM Heron r2 (ibm_fez/torino)\", \"IBM\", \"gate\", 156, 20, 60, 0.003, 120.0, 1.60, 4096, \"IBM_QUANTUM_TOKEN\", \"Heavy-hex; 20 kübitlik bağlı alt-graf hedefi\"),\n    \"origin_wukong\": QuantumDevice(\"Origin Wukong\", \"Origin Quantum\", \"gate\", 72, 20, 40, 0.01, 60.0, 0.80, 4096, \"ORIGIN_QCLOUD_TOKEN\", \"72 kübit süperiletken; 20 kübit hedef\"),\n    \"dwave_advantage\": QuantumDevice(\"D-Wave Advantage2\", \"D-Wave\", \"annealer\", 4400, 500, 0, 0.0, 10.0, 0.20, 10_000, \"DWAVE_API_TOKEN\", \"Pegasus/Zephyr; QUBO değişken hedefi 500 (gömme sonrası)\"),\n}\n\n\ndef effective_budget(dev: QuantumDevice, depth_needed: int, n_qubits_needed: int, shots: int) -> dict:\n    \"\"\"Cihaz için etkin kübit/derinlik bütçesi ve devrenin sığıp sığmadığı.\"\"\"\n    if dev.kind == \"simulator\":\n        fits_q = n_qubits_needed <= SIM_QUBIT_CAP\n        return dict(device=dev.name, qubits_ok=fits_q, depth_ok=True, fidelity_est=1.0, fits=fits_q,\n                    reason=\"\" if fits_q else f\"{n_qubits_needed} kübit > simülatör tavanı {SIM_QUBIT_CAP}\")\n    if dev.kind == \"annealer\":\n        fits_q = n_qubits_needed <= dev.target_qubits\n        return dict(device=dev.name, qubits_ok=fits_q, depth_ok=True, fidelity_est=None, fits=fits_q,\n                    reason=\"\" if fits_q else f\"{n_qubits_needed} değişken > gömme hedefi {dev.target_qubits}\")\n    # kapı modeli: sadakat tahmini ≈ (1 − e2q)^(2q kapı sayısı) — 2q kapı sayısı ≈ derinlik × kübit / 2\n    two_q_gates = depth_needed * max(n_qubits_needed, 1) / 2\n    fid = (1 - dev.two_q_error) ** two_q_gates\n    fits_q = n_qubits_needed <= dev.target_qubits\n    fits_d = depth_needed <= dev.reliable_depth\n    reason = []\n    if not fits_q: reason.append(f\"{n_qubits_needed} kübit > hedef {dev.target_qubits}\")\n    if not fits_d: reason.append(f\"derinlik {depth_needed} > güvenilir {dev.reliable_depth}\")\n    return dict(device=dev.name, qubits_ok=fits_q, depth_ok=fits_d, fidelity_est=round(fid, 3), fits=fits_q and fits_d and fid > 0.3,\n                reason=\"; \".join(reason) or (\"sadakat düşük\" if fid <= 0.3 else \"\"), two_q_gates=int(two_q_gates))\n\n\nRUN_PROFILES = {\n    \"min-cost\":     dict(shots=256,  hw_samples=8,   train_on=\"simulator\", vqc_layers=2, qae_qubits=8,  desc=\"yalnızca çıkarım gölgede, 256 atış\"),\n    \"default\":      dict(shots=1024, hw_samples=32,  train_on=\"simulator\", vqc_layers=3, qae_qubits=12, desc=\"tüm modeller, 1.024 atış\"),\n    \"max-fidelity\": dict(shots=4096, hw_samples=128, train_on=\"simulator\", vqc_layers=3, qae_qubits=16, desc=\"tüm modeller, 4.096 atış, en geniş devre\"),\n}\n\n\ndef size_circuits(cfg: \"RunConfig\", n_features: int, grid_shape: Tuple[int, int], n_districts: int) -> dict:\n    \"\"\"Her kuantum bileşeni için kübit sayısını belirler: simülatör tavanı ve seçili cihaz hedefine göre.\"\"\"\n    prof = RUN_PROFILES.get(cfg.run_profile, RUN_PROFILES[\"default\"])\n    dev = DEVICE_REGISTRY.get(cfg.target_device, DEVICE_REGISTRY[\"simulator\"])\n    cap = min(SIM_QUBIT_CAP, dev.target_qubits if dev.kind != \"annealer\" else SIM_QUBIT_CAP)\n    # VQC tahmin: öznitelik sayısı ile sınırlı, cfg.n_qubits istenen; tavana kırp\n    vqc_q = int(min(cfg.n_qubits, n_features, cap))\n    # genlik kodlama (derinlik alanı): ⌈log2(hücre)⌉ ; tavana kırp → PCA/alt-örnekleme gerekir\n    cells = int(grid_shape[0] * grid_shape[1])\n    amp_needed = int(math.ceil(math.log2(max(cells, 2))))\n    amp_q = int(min(amp_needed, cap))\n    # QAE: profil kübiti (sayaç dahil), tavana kırp\n    qae_q = int(min(prof[\"qae_qubits\"], cap))\n    # QSVM çekirdek: öznitelik sayısı, tavana kırp\n    qsvm_q = int(min(max(n_features, 2), cap, 12))\n    # QLSTM: gizli boyut kübiti\n    qlstm_q = int(min(6, cap))\n    # QAOA: ilçe sayısı; simülatörde ≤ cap, tavlayıcıda değişken hedefi\n    qaoa_q = n_districts\n    qaoa_where = \"annealer\" if (dev.kind == \"annealer\" and n_districts <= dev.target_qubits) else (\"gate/sim\" if n_districts <= cap else \"exact/anneal-classical\")\n    return dict(profile=cfg.run_profile, device=dev.name, cap=cap, vqc=vqc_q, amplitude=amp_q, amplitude_needed=amp_needed,\n                qae=qae_q, qsvm=qsvm_q, qlstm=qlstm_q, qaoa=qaoa_q, qaoa_where=qaoa_where, shots=prof[\"shots\"], hw_samples=prof[\"hw_samples\"],\n                vqc_layers=prof[\"vqc_layers\"])\n\n\nclass TimeLedger:\n    \"\"\"Zaman defteri (L): her kuantum devre çalıştırması, gradyan adımı ve boru hattı adımı için süre kaydı.\"\"\"\n\n    def __init__(self):\n        self.rows: List[dict] = []\n        self.t0 = time.time()\n\n    def log(self, kind: str, name: str, seconds: float, n_circuits: int = 0, n_qubits: int = 0, shots: int = 0, device: str = \"simulator\", extra: str = \"\"):\n        self.rows.append(dict(kind=kind, name=name, seconds=round(seconds, 4), circuits=n_circuits, qubits=n_qubits, shots=shots, device=device, extra=extra,\n                              t_rel=round(time.time() - self.t0, 2)))\n\n    def frame(self) -> pd.DataFrame:\n        return pd.DataFrame(self.rows)\n\n    def summary(self, wall_s: float) -> dict:\n        df = self.frame()\n        q = df[df.kind.isin([\"circuit\", \"gradient\", \"anneal\", \"qae\", \"qsvm\", \"qlstm\"])] if len(df) else df\n        q_s = float(q.seconds.sum()) if len(q) else 0.0\n        return dict(wall_s=round(wall_s, 1), quantum_s=round(q_s, 2), quantum_share=round(q_s / max(wall_s, 1e-6), 3),\n                    circuits=int(q.circuits.sum()) if len(q) else 0, entries=len(df))\n\n    def qpu_projection(self, sizes: dict, runs_per_year: int = 365) -> pd.DataFrame:\n        \"\"\"Profil × cihaz: çalıştırma başına QPU süresi ve yıllık maliyet projeksiyonu.\n        Donanıma yalnızca ÇIKARIM gider (eğitim simülatörde): VQC test alt kümesi + QAE güç dizisi (5 devre) + QSVM test çekirdek satırları\n        (hw_samples × n_train) + QLSTM test dizileri (hw_samples × 4 kapı × 7 adım). Kapı modelinde devre+okuma ≈ 1 ms/atış (Heron/Wukong tipik),\n        tavlayıcıda 20 µs tavlama + okuma ≈ 0,3 ms/atış. runs_per_year varsayılanı 365 (günlük nowcast, tek bölge).\"\"\"\n        rows = []\n        for pname, prof in RUN_PROFILES.items():\n            hw = prof[\"hw_samples\"]\n            for dkey in (\"ibm_heron\", \"origin_wukong\", \"dwave_advantage\"):\n                dev = DEVICE_REGISTRY[dkey]\n                if dev.kind == \"annealer\":\n                    n_circ = 1; qpu_s = prof[\"shots\"] * 0.3e-3 + 0.02\n                else:\n                    # gölge çıkarım: VQC hw örnek + QAE 5 güç + QSVM hw test satırı × 10 destek vektörü + QLSTM hw/4 dizi × 4 kapı × 7 adım\n                    n_circ = hw + 5 + hw * 10 + (hw // 4) * 28\n                    qpu_s = n_circ * prof[\"shots\"] * 1.0e-3\n                rows.append(dict(profil=pname, cihaz=dev.name, devre=int(n_circ), atis=prof[\"shots\"], qpu_s_per_run=round(qpu_s, 1),\n                                 qpu_h_per_year=round(qpu_s * runs_per_year / 3600, 1), usd_per_year=round(qpu_s * runs_per_year * dev.usd_per_s, 0)))\n        return pd.DataFrame(rows)\n\n\ndef _timed(ledger: Optional[\"TimeLedger\"], kind: str, name: str, **meta):\n    \"\"\"Bağlam yöneticisi: with _timed(ledger,'circuit','vqc_test', n_circuits=…): …\"\"\"\n    class _T:\n        def __enter__(self): self.t = time.time(); return self\n        def __exit__(self, *a):\n            if ledger is not None:\n                ledger.log(kind, name, time.time() - self.t, **meta)\n    return _T()\n\n# ===========================================================================\n# 13. HİDROLİK (D) — 2B yerel-eylemsizlik sığ su çözücüsü (Bates vd. 2010) + makullük kapısı\n# ===========================================================================\ndef shallow_water_2d(z: np.ndarray, dx: float, dy: float, rain_mm_h: Optional[pd.Series] = None, lc: Optional[np.ndarray] = None,\n                     inflow: Optional[Tuple[Tuple[int, int], pd.Series]] = None, n_manning: Optional[np.ndarray] = None,\n                     lateral: Optional[Tuple[np.ndarray, pd.Series]] = None,\n                     channel: Optional[dict] = None,\n                     sink: Optional[np.ndarray] = None, sewer_mm_h: float = 0.0, t_end_h: float = 6.0, alpha: float = 0.7,\n                     h_min: float = 1e-3, max_minutes: float = 10.0, record_every_min: float = 30.0,\n                     theta: float = 0.8, v_cap: float = 8.0) -> dict:\n    \"\"\"Yerel-eylemsizlik (local-inertial) sığ su denklemleri, açık şema, uyarlanabilir CFL zaman adımı.\n    q^{t+Δt} = (q_θ − g h Δt ∂(z+h)/∂x) / (1 + g Δt n² |q| / h^{7/3})  (Bates, Horritt & Fewtrell 2010; q_θ: de Almeida vd. 2012 difüzyonu)\n    Akı sınırı: bir adımda kaynak hücrenin yarısı ve fiziksel hız tavanı v_cap (m/s).\n    Girdiler: z DEM (m), dx/dy hücre (m), yağış serisi (mm/saat) ve/veya nokta giriş debisi (m³/s), arazi örtüsü → Manning n ve sızma.\n    channel: alt-ızgara kanal (LISFLOOD-FP 'sub-grid channel' yaklaşımı) — {\"mask\": dere hücreleri, \"fdir\": D8, \"width\": m, \"depth\": m,\n             \"slope\": m/m, \"n\": Manning}; dere hücrelerinde su önce kanal kapasitesine (b·d) kadar kanalla mansaba taşınır (kinematik, Manning),\n             kapasiteyi aşan kısım 2B taşkın ovasına çıkar. Kaba ızgarada (>50 m) kanalın hücreden dar olması sorununu çözer.\n    Çıktı: maksimum derinlik, maksimum hız, son derinlik, kütle dengesi, adım sayısı, anlık görüntüler.\"\"\"\n    t0 = time.time()\n    g = 9.81\n    H_, W_ = z.shape\n    z = np.nan_to_num(z.astype(\"float64\"), nan=float(np.nanmean(z)))\n    if n_manning is None:\n        n_manning = np.vectorize(lambda c: MANNING_BY_LC.get(int(c), 0.05))(lc) if lc is not None else np.full(z.shape, 0.045)\n    infil = (np.vectorize(lambda c: INFIL_BY_LC.get(int(c), 6.0))(lc) if lc is not None else np.zeros(z.shape))\n    infil = (infil + (np.where(lc == 50, sewer_mm_h, 0.0) if lc is not None else 0.0)) / 3600.0 / 1000.0  # m/s\n    if sink is None:\n        sink = (lc == 80) if lc is not None else np.zeros(z.shape, bool)\n    h = np.zeros(z.shape); qx = np.zeros((H_, W_ + 1)); qy = np.zeros((H_ + 1, W_))\n    hmax = np.zeros(z.shape); vmax_map = np.zeros(z.shape)\n    n_face_x = 0.5 * (n_manning[:, :-1] + n_manning[:, 1:]); n_face_y = 0.5 * (n_manning[:-1, :] + n_manning[1:, :])\n    rain = None; dt_r = 3600.0\n    if rain_mm_h is not None and len(rain_mm_h):\n        dt_r = _dt_hours(rain_mm_h) * 3600.0; rain = rain_mm_h.values / 1000.0 / dt_r\n    inflow_rc, inflow_q = (inflow if inflow else (None, None))\n    lat_w, lat_q = (lateral if lateral else (None, None))  # lat_w: hücre ağırlıkları (toplam 1), lat_q: toplam yanal giriş m³/s\n    vol_in = vol_out = vol_inf = 0.0\n    ch = None\n    if channel is not None:\n        cm = channel[\"mask\"].astype(bool); fd = channel[\"fdir\"]\n        idx = np.flatnonzero(cm); H_idx, W_idx = np.divmod(idx, W_)\n        # her kanal hücresinin mansap kanal hücresi (düz indeks) veya -1 (çıkış)\n        down = np.full(len(idx), -1, dtype=np.int64); pos = {int(v): k for k, v in enumerate(idx)}\n        for k, (ii, jj) in enumerate(zip(H_idx, W_idx)):\n            d = fd[ii, jj]\n            if d >= 0:\n                di, dj = _D8_OFF[d]; ni, nj = ii + di, jj + dj\n                if 0 <= ni < H_ and 0 <= nj < W_ and cm[ni, nj]:\n                    down[k] = pos[int(ni * W_ + nj)]\n        b = np.broadcast_to(np.asarray(channel.get(\"width\", 20.0), float), (len(idx),)) if np.ndim(channel.get(\"width\", 20.0)) == 0 else np.asarray(channel[\"width\"], float).ravel()[idx]\n        dch_raw = channel.get(\"depth\", 2.0)\n        dch = np.broadcast_to(np.asarray(dch_raw, float), (len(idx),)) if np.ndim(dch_raw) == 0 else np.asarray(dch_raw, float).ravel()[idx]\n        slp = np.maximum(np.broadcast_to(np.asarray(channel.get(\"slope\", 0.01), float), (len(idx),)) if np.ndim(channel.get(\"slope\", 0.01)) == 0 else np.asarray(channel[\"slope\"], float).ravel()[idx], 1e-4)\n        nch = float(channel.get(\"n\", 0.04)); cell_len = 0.5 * (dx + dy)\n        ch = dict(idx=idx, down=down, b=b, d=dch, slope=slp, n=nch, len=cell_len, hc=np.zeros(len(idx)))  # hc: kanal içi su derinliği (m)\n    t = 0.0; T = t_end_h * 3600.0; steps = 0; snaps = []; next_snap = record_every_min * 60\n    cell_a = dx * dy; vmax_now = 0.0\n    while t < T:\n        if time.time() - t0 > max_minutes * 60:\n            log.warning(\"shallow_water_2d: süre bütçesi doldu\"); break\n        hmax_now = float(h.max())\n        if hmax_now > h_min:\n            umax = float(vmax_now) if steps > 0 else 0.0\n            dt = alpha * min(dx, dy) / (math.sqrt(g * hmax_now) + umax)\n        else:\n            dt = 30.0\n        dt = float(np.clip(dt, 0.05, 30.0))\n        eta = z + h\n        # ---- x yüzleri (iç)\n        hf = np.maximum(np.maximum(eta[:, :-1], eta[:, 1:]) - np.maximum(z[:, :-1], z[:, 1:]), 0.0)\n        wet = hf > h_min\n        q = qx[:, 1:-1]\n        qc = theta * q + 0.5 * (1 - theta) * (np.concatenate([q[:, :1], q[:, :-1]], axis=1) + np.concatenate([q[:, 1:], q[:, -1:]], axis=1))\n        slope = (eta[:, 1:] - eta[:, :-1]) / dx\n        num = qc - g * hf * dt * slope\n        den = 1.0 + g * dt * n_face_x ** 2 * np.abs(q) / np.maximum(hf, h_min) ** (7.0 / 3.0)\n        qn = np.where(wet, num / den, 0.0)\n        # akı sınırlama: bir adımda kaynak hücrenin suyunun en fazla yarısı\n        lim = np.minimum(0.5 * np.where(qn > 0, h[:, :-1], h[:, 1:]) * dx / dt, hf * v_cap)\n        qx[:, 1:-1] = np.clip(qn, -lim, lim)\n        vfx = np.where(hf > 0.1, np.abs(qx[:, 1:-1]) / np.maximum(hf, h_min), 0.0)\n        # ---- y yüzleri (iç)\n        hf = np.maximum(np.maximum(eta[:-1, :], eta[1:, :]) - np.maximum(z[:-1, :], z[1:, :]), 0.0)\n        wet = hf > h_min\n        q = qy[1:-1, :]\n        qc = theta * q + 0.5 * (1 - theta) * (np.concatenate([q[:1, :], q[:-1, :]], axis=0) + np.concatenate([q[1:, :], q[-1:, :]], axis=0))\n        slope = (eta[1:, :] - eta[:-1, :]) / dy\n        num = qc - g * hf * dt * slope\n        den = 1.0 + g * dt * n_face_y ** 2 * np.abs(q) / np.maximum(hf, h_min) ** (7.0 / 3.0)\n        qn = np.where(wet, num / den, 0.0)\n        lim = np.minimum(0.5 * np.where(qn > 0, h[:-1, :], h[1:, :]) * dy / dt, hf * v_cap)\n        qy[1:-1, :] = np.clip(qn, -lim, lim)\n        vfy = np.where(hf > 0.1, np.abs(qy[1:-1, :]) / np.maximum(hf, h_min), 0.0)\n        # ---- sınırlar: serbest çıkış (dışarı akışa izin, içeri yok)\n        qx[:, 0] = np.minimum(0.0, -np.sqrt(g * np.maximum(h[:, 0], 0)) * h[:, 0] * 0.5)\n        qx[:, -1] = np.maximum(0.0, np.sqrt(g * np.maximum(h[:, -1], 0)) * h[:, -1] * 0.5)\n        qy[0, :] = np.minimum(0.0, -np.sqrt(g * np.maximum(h[0, :], 0)) * h[0, :] * 0.5)\n        qy[-1, :] = np.maximum(0.0, np.sqrt(g * np.maximum(h[-1, :], 0)) * h[-1, :] * 0.5)\n        # ---- alt-ızgara kanal (kinematik Manning, aşağı akış sırası önemsiz: eksplisit, akı sınırlı)\n        if ch is not None:\n            hflat = h.ravel()\n            # taşkın ovasındaki su kanal kapasitesine kadar kanala alınır\n            take = np.minimum(hflat[ch[\"idx\"]] * cell_a / (ch[\"b\"] * ch[\"len\"]), np.maximum(ch[\"d\"] - ch[\"hc\"], 0.0))\n            take = np.maximum(take, 0.0); ch[\"hc\"] += take; hflat[ch[\"idx\"]] -= take * ch[\"b\"] * ch[\"len\"] / cell_a\n            # kanal akısı: Q = (1/n) A R^{2/3} S^{1/2}, A = b·hc, R ≈ b·hc/(b+2hc)\n            hc = ch[\"hc\"]; A = ch[\"b\"] * hc; R = A / (ch[\"b\"] + 2 * hc + 1e-9)\n            Q = (1.0 / ch[\"n\"]) * A * R ** (2.0 / 3.0) * np.sqrt(ch[\"slope\"])\n            Vmove = np.minimum(Q * dt, 0.9 * A * ch[\"len\"])  # bir adımda hücredeki kanal hacminin en fazla %90'ı\n            ch[\"hc\"] -= Vmove / (ch[\"b\"] * ch[\"len\"])\n            has_down = ch[\"down\"] >= 0\n            np.add.at(ch[\"hc\"], ch[\"down\"][has_down], Vmove[has_down] / (ch[\"b\"][ch[\"down\"][has_down]] * ch[\"len\"]))\n            vol_out += float(Vmove[~has_down].sum())  # ağ çıkışı (kıyı/kenar) → sistem dışı\n            # kapasite aşımı taşkın ovasına\n            over = np.maximum(ch[\"hc\"] - ch[\"d\"], 0.0); ch[\"hc\"] -= over\n            hflat[ch[\"idx\"]] += over * ch[\"b\"] * ch[\"len\"] / cell_a\n            h = hflat.reshape(H_, W_)\n        # ---- süreklilik\n        dh = -dt * ((qx[:, 1:] - qx[:, :-1]) / dx + (qy[1:, :] - qy[:-1, :]) / dy)\n        out_edge = dt * (np.abs(qx[:, 0]).sum() * dy + np.abs(qx[:, -1]).sum() * dy + np.abs(qy[0, :]).sum() * dx + np.abs(qy[-1, :]).sum() * dx)\n        vol_out += out_edge\n        h = h + dh\n        # yağış\n        if rain is not None:\n            idx = int(t // dt_r)\n            if idx < len(rain):\n                add = rain[idx] * dt; h += add; vol_in += add * cell_a * H_ * W_\n        # nokta giriş\n        if inflow_rc is not None:\n            k = min(int(t // (_dt_hours(inflow_q) * 3600.0)), len(inflow_q) - 1)\n            qin = float(inflow_q.values[k]); h[inflow_rc] += qin * dt / cell_a; vol_in += qin * dt\n        if lat_w is not None:\n            k = min(int(t // (_dt_hours(lat_q) * 3600.0)), len(lat_q) - 1)\n            qtot = float(lat_q.values[k]); h += lat_w * (qtot * dt / cell_a); vol_in += qtot * dt\n        # sızma ve yutak\n        loss = np.minimum(h, infil * dt); h -= loss; vol_inf += float(loss.sum()) * cell_a\n        if sink is not None and sink.any():\n            vol_out += float(h[sink].sum()) * cell_a; h[sink] = 0.0\n        h = np.maximum(h, 0.0)\n        hmax = np.maximum(hmax, h)\n        # hız\n        # hız: akı adımında aynı yüzey derinliğiyle hesaplanan yüzey hızları (≥ 10 cm), hücreye en büyük komşu yüzey hızı\n        v = np.zeros_like(h)\n        v[:, :-1] = np.maximum(v[:, :-1], vfx); v[:, 1:] = np.maximum(v[:, 1:], vfx)\n        v[:-1, :] = np.maximum(v[:-1, :], vfy); v[1:, :] = np.maximum(v[1:, :], vfy)\n        v[h < 0.1] = 0.0\n        vmax_map = np.maximum(vmax_map, v); vmax_now = float(v.max())\n        t += dt; steps += 1\n        if t >= next_snap:\n            snaps.append((t / 3600.0, h.copy())); next_snap += record_every_min * 60\n    stored = float(h.sum()) * cell_a + (float((ch[\"hc\"] * ch[\"b\"] * ch[\"len\"]).sum()) if ch is not None else 0.0)\n    balance = (vol_in - vol_out - vol_inf - stored) / max(vol_in, 1e-9)\n    return dict(max_depth=hmax.astype(\"float32\"), max_velocity=vmax_map.astype(\"float32\"), final_depth=h.astype(\"float32\"),\n                steps=steps, sim_hours=t / 3600.0, runtime_s=time.time() - t0, snapshots=snaps,\n                mass=dict(vol_in_m3=vol_in, vol_out_m3=vol_out, vol_infiltrated_m3=vol_inf, stored_m3=stored, closure_error=float(balance)))\n\n\ndef plausibility_gate(sw: dict, terrain: \"Terrain\", depth_hand: Optional[np.ndarray] = None, max_depth_allowed: float = 12.0,\n                      max_velocity_allowed: float = 8.0, closure_tol: float = 0.05) -> dict:\n    \"\"\"Makullük kapısı: kütle dengesi, derinlik/hız sınırları, yayılım akıl kontrolü (HAND ile örtüşme), kara maskesi.\"\"\"\n    checks = []\n    m = sw[\"mass\"]; ce = abs(m[\"closure_error\"])\n    checks.append(dict(check=\"Kütle dengesi\", value=f\"{ce*100:.1f} %\", ok=ce <= closure_tol, limit=f\"≤ {closure_tol*100:.0f} %\"))\n    md = float(sw[\"max_depth\"].max()); checks.append(dict(check=\"Maksimum derinlik\", value=f\"{md:.2f} m\", ok=md <= max_depth_allowed, limit=f\"≤ {max_depth_allowed} m\"))\n    mv = float(sw[\"max_velocity\"].max()); checks.append(dict(check=\"Maksimum hız\", value=f\"{mv:.2f} m/s\", ok=mv <= max_velocity_allowed, limit=f\"≤ {max_velocity_allowed} m/s\"))\n    wet = sw[\"max_depth\"] > 0.1\n    if terrain.valid is not None:\n        land_wet = wet & terrain.valid\n        off = float((wet & ~terrain.valid).sum() / max(wet.sum(), 1))\n        checks.append(dict(check=\"Kara dışı (deniz/göl) hücrelerde kalıcı derinlik\", value=f\"{off*100:.1f} %\", ok=off <= 0.05, limit=\"≤ 5 % (yutak hücreleri sıfırlanır)\"))\n    if depth_hand is not None:\n        hw = depth_hand > 0.1\n        both = (wet & hw).sum(); csi = float(both / max((wet | hw).sum(), 1))\n        checks.append(dict(check=\"HAND yayılımı ile örtüşme (CSI)\", value=f\"{csi:.2f}\", ok=csi >= 0.2, limit=\"≥ 0,20 (tarama tutarlılığı)\"))\n    if terrain.streams is not None:\n        near = float((wet & (terrain.hand < 15)).sum() / max(wet.sum(), 1)); checks.append(dict(check=\"Islak hücrelerin HAND < 15 m payı\", value=f\"{near*100:.0f} %\", ok=near >= 0.7, limit=\"≥ 70 %\"))\n    passed = all(c[\"ok\"] for c in checks)\n    return dict(passed=passed, checks=pd.DataFrame(checks), note=\"Kapı geçilmedi: kamu ürünleri işaretlenir, uyarı seviyesi 'incelemede'.\" if not passed else \"Kapı geçildi.\")\n\n\ndef lateral_inflow_weights(terrain: \"Terrain\") -> np.ndarray:\n    \"\"\"Dere hücrelerine, yerel (ara) memba alanıyla orantılı yanal giriş ağırlığı: w_i ∝ acc_i − Σ acc_memba_komşu.\"\"\"\n    acc = terrain.acc; fdir = terrain.fdir; H, W = acc.shape\n    local = acc.copy()\n    for k, (di, dj) in enumerate(_D8_OFF):\n        # komşu (i+di, j+dj) → (i, j) yönünde akıyorsa, onun birikimini düş\n        src = np.zeros_like(acc)\n        mask = np.zeros((H, W), bool)\n        ii, jj = np.mgrid[0:H, 0:W]\n        ni, nj = ii + di, jj + dj\n        ok = (ni >= 0) & (ni < H) & (nj >= 0) & (nj < W)\n        opp = (k + 4) % 8\n        flows_here = np.zeros((H, W), bool)\n        flows_here[ok] = fdir[ni[ok], nj[ok]] == opp\n        src[flows_here] = acc[ni[flows_here], nj[flows_here]]\n        local = local - src\n    local = np.maximum(local, 1.0)\n    w = np.where(terrain.streams & (terrain.valid if terrain.valid is not None else True), local, 0.0)\n    return (w / max(w.sum(), 1e-9)).astype(\"float64\")\n\n\ndef channel_geometry(terrain: \"Terrain\", bankfull_q_per_km2: float = 0.5) -> dict:\n    \"\"\"Dere ağı için alt-ızgara kanal geometrisi: genişlik ve derinlik memba alanına göre hidrolik geometri (Leopold–Maddock)\n    b = 3,0·A^0,5 [m], d = 0,4·A^0,35 [m] (A km²; Anadolu dereleri için yaklaşık); eğim yerel D8 eğimi.\"\"\"\n    A_km2 = terrain.acc * terrain.cell_area_km2\n    width = np.where(terrain.streams, np.clip(3.0 * np.sqrt(np.maximum(A_km2, 0.1)), 3.0, 120.0), 0.0)\n    depth = np.where(terrain.streams, np.clip(0.4 * np.maximum(A_km2, 0.1) ** 0.35, 0.5, 6.0), 0.0)  # yerel tam-kesit derinliği\n    # yerel eğim: hücre → mansap hücresi kot farkı / mesafe\n    H, W = terrain.filled.shape; slope = np.full((H, W), 0.01)\n    ii, jj = np.nonzero(terrain.streams)\n    for i, j in zip(ii, jj):\n        k = terrain.fdir[i, j]\n        if k >= 0:\n            di, dj = _D8_OFF[k]; ni, nj = i + di, j + dj\n            if 0 <= ni < H and 0 <= nj < W:\n                dist = _D8_DIST[k] * 0.5 * (terrain.dx_m + terrain.dy_m)\n                slope[i, j] = max((terrain.filled[i, j] - terrain.filled[ni, nj]) / dist, 5e-4)\n    return dict(mask=terrain.streams, fdir=terrain.fdir, width=width, depth=depth, slope=slope, n=0.04)\n\n\ndef hydraulic_dem(terrain: \"Terrain\", burn_m: float = 1.0) -> np.ndarray:\n    \"\"\"Hidrolik çözücü için DEM: dolgulu yüzey tabanı + dere ağında ham kot + mansaba monoton azalan kanal oyma (4-bağlantılı).\n    Priority-Flood dolgusu kaba ızgarada dar boğazları kapatıp 20–40 m'lik sahte havuzlar üretir; ham DEM ise dere dışı çukurlar içerir.\n    Bu birleşim ikisini de giderir.\"\"\"\n    raw = np.nan_to_num(terrain.dem.astype(\"float64\"), nan=float(np.nanmean(terrain.dem)))\n    z = terrain.filled.astype(\"float64\").copy()          # taban: dolgulu yüzey (dere dışı çukurlar kapalı)\n    z[terrain.streams] = np.minimum(z[terrain.streams], raw[terrain.streams])  # dere hücrelerinde ham (daha alçak) kot → boğazlar açılır\n    H, W = z.shape\n    order = np.argsort(terrain.filled, axis=None)[::-1]  # memba (yüksek) → mansap\n    ii, jj = np.unravel_index(order, (H, W))\n    zc = z.copy(); carved4 = np.zeros((H, W), bool)\n    for i, j in zip(ii, jj):\n        if not terrain.streams[i, j]:\n            continue\n        k = terrain.fdir[i, j]\n        if k < 0:\n            continue\n        di, dj = _D8_OFF[k]; ni, nj = i + di, j + dj\n        if 0 <= ni < H and 0 <= nj < W:\n            zc[ni, nj] = min(zc[ni, nj], zc[i, j] - burn_m * 0.1)  # mansap hücre en az 10 cm alçak\n            if di != 0 and dj != 0:  # çapraz adım: 2B çözücü yalnızca 4 komşuyla akı değiştirir → ara kardinal hücreyi de oy (4-bağlantılı yol)\n                c1, c2 = (i + di, j), (i, j + dj)\n                cand = min(c1, c2, key=lambda rc: zc[rc])\n                zc[cand] = min(zc[cand], zc[i, j] - burn_m * 0.05)\n                carved4[cand] = True\n    # dere hücrelerini (ve 4-bağlantı için oyulan ara hücreleri) komşu taşkın ovasından burn_m kadar alçalt (kanal tabanı)\n    zc[terrain.streams | carved4] -= burn_m\n    return zc.astype(\"float64\")\n\n# ===========================================================================\n# 14. KEŞİFSEL VERİ ANALİZİ (E) — envanter, dağılım uyumu, testler, aykırı değer, korelasyon, mekânsal istatistik\n# ===========================================================================\ndef eda_inventory(series: Dict[str, pd.Series]) -> pd.DataFrame:\n    rows = []\n    for name, s in series.items():\n        x = pd.Series(s).astype(float)\n        rows.append(dict(seri=name, n=int(len(x)), eksik=int(x.isna().sum()), eksik_pct=round(100 * x.isna().mean(), 1),\n                         ort=round(float(x.mean()), 3), medyan=round(float(x.median()), 3), std=round(float(x.std()), 3),\n                         min=round(float(x.min()), 3), maks=round(float(x.max()), 3), carpiklik=round(float(x.skew()), 2), basiklik=round(float(x.kurt()), 2),\n                         sifir_pct=round(100 * float((x == 0).mean()), 1)))\n    return pd.DataFrame(rows)\n\n\ndef eda_distribution_fits(x: np.ndarray, name: str = \"\") -> pd.DataFrame:\n    \"\"\"Gamma, log-normal, Weibull, GEV (yıllık maksimum) uyumu — KS p-değeri ve AIC; tekerrür seviyeleri (2/10/50/100 yıl).\"\"\"\n    from scipy import stats\n    x = np.asarray(x, float); x = x[np.isfinite(x)]\n    pos = x[x > 0]\n    rows = []\n    cands = [(\"gamma\", stats.gamma, pos), (\"log-normal\", stats.lognorm, pos), (\"Weibull\", stats.weibull_min, pos), (\"GEV\", stats.genextreme, x)]\n    for lab, dist, data in cands:\n        if len(data) < 8:\n            continue\n        try:\n            params = dist.fit(data, floc=0) if lab != \"GEV\" else dist.fit(data)\n            ks = stats.kstest(data, dist.cdf, args=params)\n            ll = float(np.sum(dist.logpdf(data, *params))); k = len(params); aic = 2 * k - 2 * ll\n            rl = {T: float(dist.ppf(1 - 1 / T, *params)) for T in (2, 10, 50, 100)}\n            rows.append(dict(seri=name, dagilim=lab, n=len(data), KS_D=round(float(ks.statistic), 3), KS_p=round(float(ks.pvalue), 3), AIC=round(aic, 1),\n                             **{f\"T{T}\": round(v, 1) for T, v in rl.items()}))\n        except Exception as ex:  # noqa: BLE001\n            rows.append(dict(seri=name, dagilim=lab, n=len(data), KS_D=None, KS_p=None, AIC=None, hata=str(ex)[:40]))\n    df = pd.DataFrame(rows)\n    if len(df) and \"AIC\" in df:\n        df = df.sort_values(\"AIC\", na_position=\"last\").reset_index(drop=True)\n    return df\n\n\ndef _mann_kendall(x: np.ndarray) -> Tuple[float, float, str]:\n    \"\"\"Mann–Kendall trend testi (bağ düzeltmeli), Sen eğimi.\"\"\"\n    from scipy import stats\n    x = np.asarray(x, float); x = x[np.isfinite(x)]; n = len(x)\n    if n < 10:\n        return float(\"nan\"), float(\"nan\"), \"yetersiz\"\n    s = 0\n    for i in range(n - 1):\n        s += np.sign(x[i + 1:] - x[i]).sum()\n    _, counts = np.unique(x, return_counts=True)\n    var = (n * (n - 1) * (2 * n + 5) - np.sum(counts * (counts - 1) * (2 * counts + 5))) / 18.0\n    z = (s - 1) / math.sqrt(var) if s > 0 else ((s + 1) / math.sqrt(var) if s < 0 else 0.0)\n    p = 2 * (1 - stats.norm.cdf(abs(z)))\n    slopes = [(x[j] - x[i]) / (j - i) for i in range(n - 1) for j in range(i + 1, min(n, i + 400))]\n    sen = float(np.median(slopes)) if slopes else float(\"nan\")\n    return float(z), float(p), (\"artan\" if z > 0 else \"azalan\") if p < 0.05 else \"anlamsız\"\n\n\ndef _adf(x: np.ndarray, lags: int = 1) -> Tuple[float, str]:\n    \"\"\"Basit ADF (sabitli, sabit gecikme): Δx_t = a + γ x_{t−1} + Σ β Δx_{t−i}; γ t-istatistiği, MacKinnon %5 kritik ≈ −2,86.\"\"\"\n    x = np.asarray(x, float); x = x[np.isfinite(x)]\n    dx = np.diff(x); n = len(dx)\n    if n < 20:\n        return float(\"nan\"), \"yetersiz\"\n    y = dx[lags:]; X = [np.ones(n - lags), x[lags:-1]]\n    for i in range(1, lags + 1):\n        X.append(dx[lags - i:n - i])\n    X = np.column_stack(X)\n    beta, res, *_ = np.linalg.lstsq(X, y, rcond=None)\n    resid = y - X @ beta; sigma2 = resid @ resid / max(len(y) - X.shape[1], 1)\n    cov = sigma2 * np.linalg.pinv(X.T @ X); t = beta[1] / math.sqrt(max(cov[1, 1], 1e-12))\n    return float(t), \"durağan (%5)\" if t < -2.86 else \"birim kök reddedilemedi\"\n\n\ndef eda_tests(series: Dict[str, pd.Series]) -> pd.DataFrame:\n    \"\"\"Normallik (Shapiro ≤5000, D'Agostino), durağanlık (ADF), otokorelasyon (lag-1, Ljung–Box benzeri), trend (Mann–Kendall).\"\"\"\n    from scipy import stats\n    rows = []\n    for name, s in series.items():\n        x = pd.Series(s).astype(float).dropna().values\n        if len(x) < 10:\n            continue\n        try:\n            sh = stats.shapiro(x[:5000]); sh_p = float(sh.pvalue)\n        except Exception:  # noqa: BLE001\n            sh_p = float(\"nan\")\n        try:\n            da_p = float(stats.normaltest(x).pvalue)\n        except Exception:  # noqa: BLE001\n            da_p = float(\"nan\")\n        r1 = float(np.corrcoef(x[:-1], x[1:])[0, 1]) if len(x) > 2 and np.std(x) > 0 else float(\"nan\")\n        acf = [float(np.corrcoef(x[:-k], x[k:])[0, 1]) for k in range(1, min(11, len(x) // 3)) if np.std(x) > 0]\n        n = len(x); lb = n * (n + 2) * sum(r ** 2 / (n - k - 1) for k, r in enumerate(acf)); lb_p = float(1 - stats.chi2.cdf(lb, len(acf))) if acf else float(\"nan\")\n        adf_t, adf_txt = _adf(x); mk_z, mk_p, mk_txt = _mann_kendall(x)\n        rows.append(dict(seri=name, n=n, shapiro_p=round(sh_p, 4), dagostino_p=round(da_p, 4), normal=(\"hayır\" if min(sh_p, da_p) < 0.05 else \"reddedilemedi\"),\n                         acf1=round(r1, 3), ljung_box_p=round(lb_p, 4), ADF_t=round(adf_t, 2), duraganlik=adf_txt, MK_z=round(mk_z, 2), MK_p=round(mk_p, 4), trend=mk_txt))\n    return pd.DataFrame(rows)\n\n\ndef eda_outliers(series: Dict[str, pd.Series]) -> pd.DataFrame:\n    \"\"\"IQR, dayanıklı z (MAD) ve isolation forest ile aykırı değer sayıları; en büyük 3 aykırı değer.\"\"\"\n    from sklearn.ensemble import IsolationForest\n    rows = []\n    for name, s in series.items():\n        x = pd.Series(s).astype(float).dropna()\n        if len(x) < 10:\n            continue\n        q1, q3 = x.quantile(0.25), x.quantile(0.75); iqr = q3 - q1\n        iqr_out = x[(x < q1 - 1.5 * iqr) | (x > q3 + 1.5 * iqr)]\n        med = x.median(); mad = float((x - med).abs().median()) or 1e-9\n        rz = 0.6745 * (x - med) / mad; rz_out = x[rz.abs() > 3.5]\n        try:\n            iso = IsolationForest(contamination=0.02, random_state=0).fit(x.values.reshape(-1, 1))\n            iso_out = x[iso.predict(x.values.reshape(-1, 1)) == -1]\n        except Exception:  # noqa: BLE001\n            iso_out = pd.Series(dtype=float)\n        top = x.sort_values(ascending=False).head(3)\n        rows.append(dict(seri=name, n=len(x), IQR_aykiri=int(len(iqr_out)), robust_z_aykiri=int(len(rz_out)), isolation_forest_aykiri=int(len(iso_out)),\n                         en_buyuk_3=\", \".join(f\"{(i.date() if hasattr(i, 'date') else i)}: {v:.1f}\" for i, v in top.items())))\n    return pd.DataFrame(rows)\n\n\ndef eda_correlations(df: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"Pearson, Spearman ve karşılıklı bilgi (sklearn mutual_info_regression) — hedefe göre.\"\"\"\n    from sklearn.feature_selection import mutual_info_regression\n    d = df.dropna(); cols = list(d.columns); target = cols[-1]\n    X = d[cols[:-1]].values; y = d[target].values\n    mi = mutual_info_regression(X, y, random_state=0) if len(d) > 10 else np.full(len(cols) - 1, np.nan)\n    rows = []\n    for k, c in enumerate(cols[:-1]):\n        rows.append(dict(degisken=c, hedef=target, pearson=round(float(d[c].corr(d[target])), 3), spearman=round(float(d[c].corr(d[target], method=\"spearman\")), 3), MI=round(float(mi[k]), 3)))\n    return pd.DataFrame(rows).sort_values(\"MI\", ascending=False).reset_index(drop=True)\n\n\ndef eda_spatial(depth: np.ndarray, dx_m: float, dy_m: float, valid: Optional[np.ndarray] = None, sample: int = 4000, seed: int = 0) -> dict:\n    \"\"\"Moran's I (kraliçe komşuluğu, permütasyon p), deneysel semivariogram, Getis-Ord G* sıcak noktalar (yerel, 3×3).\"\"\"\n    rng = np.random.default_rng(seed)\n    z = np.nan_to_num(depth.astype(float))\n    m = valid if valid is not None else np.ones(z.shape, bool)\n    # Moran's I\n    zc = np.where(m, z - z[m].mean(), 0.0)\n    W = np.zeros_like(zc); cnt = np.zeros_like(zc)\n    for di, dj in _D8_OFF:\n        sh = np.roll(np.roll(zc, di, 0), dj, 1); mm = np.roll(np.roll(m, di, 0), dj, 1)\n        W += np.where(mm, sh, 0.0); cnt += mm\n    n = int(m.sum()); S0 = float(cnt[m].sum())\n    I = (n / S0) * float((zc * W)[m].sum()) / max(float((zc ** 2)[m].sum()), 1e-12)\n    perm = []\n    flat = zc[m].copy()\n    for _ in range(99):\n        rng.shuffle(flat); zp = np.zeros_like(zc); zp[m] = flat\n        Wp = np.zeros_like(zc)\n        for di, dj in _D8_OFF:\n            Wp += np.where(np.roll(np.roll(m, di, 0), dj, 1), np.roll(np.roll(zp, di, 0), dj, 1), 0.0)\n        perm.append((n / S0) * float((zp * Wp)[m].sum()) / max(float((zp ** 2)[m].sum()), 1e-12))\n    p_moran = float((np.sum(np.abs(perm) >= abs(I)) + 1) / (len(perm) + 1))\n    # semivariogram\n    ii, jj = np.nonzero(m); k = rng.choice(len(ii), min(sample, len(ii)), replace=False)\n    pts = np.column_stack([ii[k] * dy_m, jj[k] * dx_m]); vals = z[ii[k], jj[k]]\n    d = np.sqrt(((pts[:, None, :] - pts[None, :, :]) ** 2).sum(-1)); g = 0.5 * (vals[:, None] - vals[None, :]) ** 2\n    iu = np.triu_indices(len(k), 1); d = d[iu]; g = g[iu]\n    bins = np.linspace(0, min(d.max(), 15 * max(dx_m, dy_m)), 16)\n    idx = np.digitize(d, bins); vg = [(0.5 * (bins[b - 1] + bins[b]), float(g[idx == b].mean()), int((idx == b).sum())) for b in range(1, len(bins)) if (idx == b).sum() > 20]\n    variogram = pd.DataFrame(vg, columns=[\"mesafe_m\", \"gamma\", \"cift\"])\n    # Getis-Ord G* (3×3)\n    from scipy.ndimage import uniform_filter\n    zz = np.where(m, z, 0.0); mf = m.astype(float)\n    local_sum = uniform_filter(zz, 3, mode=\"constant\") * 9; wsum = uniform_filter(mf, 3, mode=\"constant\") * 9\n    xbar = z[m].mean(); s = z[m].std() + 1e-12\n    num = local_sum - xbar * wsum; den = s * np.sqrt(np.maximum((n * wsum - wsum ** 2) / max(n - 1, 1), 1e-12))\n    gstar = np.where(m & (wsum > 0), num / den, 0.0)\n    hot = int((gstar > 2.58).sum()); cold = int((gstar < -2.58).sum())\n    return dict(moran_I=round(I, 4), moran_p=p_moran, variogram=variogram, gstar=gstar.astype(\"float32\"), hot_cells=hot, cold_cells=cold,\n                range_est_m=float(variogram.mesafe_m.iloc[int(np.argmax(variogram.gamma.values >= 0.95 * variogram.gamma.max()))]) if len(variogram) else float(\"nan\"))\n\n\ndef run_eda(res: dict) -> dict:\n    \"\"\"Boru hattı sonuçları üzerinde tam EDA takımı; tablolar ve mekânsal istatistikler döndürür.\"\"\"\n    out: dict = {}\n    r = res[\"rain\"][\"rain\"]; series = {\"yağış (saatlik, mm)\": r}\n    hy = res[\"hydro\"]\n    if hy.get(\"reference\") is not None:\n        series[\"GloFAS debi (günlük, m³/s)\"] = hy[\"reference\"]\n    if res.get(\"train_rain\") is not None:\n        series[\"eğitim yağışı (günlük, mm)\"] = res[\"train_rain\"]; series[\"eğitim debisi (günlük, m³/s)\"] = res[\"train_q\"]\n    out[\"inventory\"] = eda_inventory(series)\n    fits = []\n    if res.get(\"train_rain\") is not None:\n        pr = res[\"train_rain\"]; ann = pr.resample(\"YE\").max() if len(pr) > 400 else None\n        fits.append(eda_distribution_fits(pr[pr > 0].values, \"günlük yağış > 0\"))\n        daily_max = pr.resample(\"ME\").max().dropna()\n        if len(daily_max) >= 12:\n            fits.append(eda_distribution_fits(daily_max.values, \"aylık maksimum günlük yağış\"))\n        if res.get(\"train_q\") is not None:\n            fits.append(eda_distribution_fits(res[\"train_q\"].resample(\"ME\").max().dropna().values, \"aylık maksimum debi\"))\n    out[\"fits\"] = pd.concat(fits, ignore_index=True) if fits else pd.DataFrame()\n    out[\"tests\"] = eda_tests(series)\n    out[\"outliers\"] = eda_outliers(series)\n    if res.get(\"train_rain\") is not None:\n        X, y = build_forecast_dataset(res[\"train_rain\"], res[\"train_q\"])\n        out[\"correlations\"] = eda_correlations(pd.concat([X, y.rename(\"Q_t+1\")], axis=1))\n    else:\n        out[\"correlations\"] = pd.DataFrame()\n    T = res[\"terrain\"]; depth = res[\"depth\"]\n    out[\"spatial\"] = eda_spatial(depth, T.dx_m, T.dy_m, T.valid)\n    # arazi-yağış-derinlik ilişkisi (hücre bazlı)\n    m = T.valid & np.isfinite(depth)\n    cell = pd.DataFrame({\"kot_m\": T.filled[m], \"HAND_m\": T.hand[m], \"egim\": T.slope[m], \"memba_alani_km2\": T.acc[m] * T.cell_area_km2, \"derinlik_m\": depth[m]})\n    if len(cell) > 5000:\n        cell = cell.sample(5000, random_state=0)\n    out[\"cell_correlations\"] = eda_correlations(cell)\n    return out\n\n# ===========================================================================\n# 15. KUANTUM PORTFÖYÜ (G) — QAE (aşılma olasılığı), QSVM (ani taşkın sınıflandırıcı), QLSTM (dizi tahmini)\n#     Her biri aynı veri/aynı saatte klasik şampiyonuyla birlikte puanlanır; kübit sayıları kontrol düzleminden gelir.\n# ===========================================================================\ndef _amp_state(probs: np.ndarray) -> np.ndarray:\n    \"\"\"Olasılık vektörü → genlik durumu (√p, normalize).\"\"\"\n    a = np.sqrt(np.clip(np.asarray(probs, float), 0, None)); return a / max(np.linalg.norm(a), 1e-12)\n\n\ndef quantum_amplitude_estimation(samples: np.ndarray, threshold: float, n_qubits: int = 8, shots: int = 1024,\n                                 ledger: Optional[\"TimeLedger\"] = None, device: str = \"simulator\") -> dict:\n    \"\"\"Kuantum genlik tahmini (IQAE, Grinko vd. 2021) ile P(X > eşik):\n    dağılım 2^(n−1) kutuya ayrıklaştırılıp genlik kodlanır; 'iyi' durum = eşik üstü kutular (yardımcı kübit işareti);\n    Grover güç dizisi {0,1,2,4,…} ile iyi-durum olasılığı ölçülür ve olasılık aralığı daraltılır.\n    Klasik referans: Monte Carlo (aynı örnekler) — standart hata √(p(1−p)/N).\"\"\"\n    import pennylane as qml\n    x = np.asarray(samples, float); x = x[np.isfinite(x)]\n    n_data = max(n_qubits - 1, 2); n_bins = 2 ** n_data\n    edges = np.linspace(x.min(), x.max() + 1e-9, n_bins + 1)\n    hist, _ = np.histogram(x, bins=edges); probs = hist / hist.sum()\n    good = (edges[1:] > threshold).astype(float)   # kutu üst kenarı eşik üstü → 'iyi'\n    p_disc = float((probs * good).sum())            # ayrıklaştırılmış gerçek olasılık (QAE'nin hedefi)\n    p_mc = float((x > threshold).mean()); se_mc = math.sqrt(max(p_mc * (1 - p_mc), 1e-12) / len(x))\n    # ---- devre: A = StatePrep(√p) ⊗ |0⟩_anc ; iyi-durum işareti: ancilla = 1 ⇔ kutu eşik üstü (çoklu-kontrollü X yerine\n    #      doğrudan 'iyi' alt-uzay olasılığını ölçüyoruz: Q = A S_0 A† S_good, S_good = diag(−1 iyi kutular))\n    dev = qml.device(\"default.qubit\", wires=n_data)\n    amp = _amp_state(probs)\n    good_diag = np.where(good > 0, -1.0, 1.0)\n\n    def A():\n        qml.StatePrep(amp, wires=range(n_data))\n\n    def S_good():\n        qml.DiagonalQubitUnitary(good_diag, wires=range(n_data))\n\n    def S_0():\n        d0 = np.ones(n_bins); d0[0] = -1.0\n        qml.DiagonalQubitUnitary(d0, wires=range(n_data))\n\n    def Q():\n        S_good(); qml.adjoint(A)(); S_0(); A()\n\n    @qml.qnode(dev)\n    def circuit(k):\n        A()\n        for _ in range(int(k)):\n            Q()\n        return qml.probs(wires=range(n_data))\n    # ---- IQAE benzeri: güç dizisi, her güçte iyi-durum olasılığı → sin²((2k+1)θ)\n    powers = [0, 1, 2, 4, 8]\n    rows = []; t0 = time.time(); n_circ = 0\n    rng = np.random.default_rng(0)\n    for k in powers:\n        pr = circuit(k); p_good_exact = float((pr * good).sum())\n        # atış gürültüsü (donanım emülasyonu)\n        p_meas = rng.binomial(shots, min(max(p_good_exact, 0), 1)) / shots\n        rows.append(dict(k=k, p_iyi_olcum=p_meas, p_iyi_kesin=p_good_exact)); n_circ += 1\n    # θ tahmini: en küçük kareler ile sin²((2k+1)θ) = p_k\n    thetas = np.linspace(1e-4, math.pi / 2 - 1e-4, 4000)\n    err = sum((np.sin((2 * r[\"k\"] + 1) * thetas) ** 2 - r[\"p_iyi_olcum\"]) ** 2 for r in rows)\n    theta = float(thetas[int(np.argmin(err))]); p_qae = float(math.sin(theta) ** 2)\n    # QAE belirsizliği ~ 1/(N_toplam_oracle): Heisenberg ölçekleme; MC ~ 1/√N\n    n_oracle = sum(2 * r[\"k\"] + 1 for r in rows) * shots\n    se_qae = float(math.pi / n_oracle)\n    if ledger is not None:\n        ledger.log(\"qae\", f\"IQAE {n_qubits}q eşik>{threshold:.1f}\", time.time() - t0, n_circuits=n_circ, n_qubits=n_data, shots=shots, device=device)\n    return dict(p_qae=p_qae, se_qae=se_qae, p_mc=p_mc, se_mc=se_mc, p_disc=p_disc, n_qubits=n_data, n_bins=n_bins, shots=shots, n_oracle=int(n_oracle),\n                powers=rows, threshold=threshold, n_samples=int(len(x)), runtime_s=time.time() - t0)\n\n\ndef quantum_svm_classifier(X: np.ndarray, y: np.ndarray, n_qubits: int = 6, test_frac: float = 0.25, seed: int = 0,\n                           ledger: Optional[\"TimeLedger\"] = None, device: str = \"simulator\") -> dict:\n    \"\"\"Kuantum çekirdek SVM (Havlíček vd. 2019): ZZ-öznitelik haritası ile |⟨φ(x)|φ(x')⟩|² çekirdeği → sklearn SVC(precomputed).\n    Klasik referans: RBF-SVM ve gradyan artırma, aynı bölünme. Hedef: ani taşkın başlangıcı (ertesi gün debi artışı > eşik).\"\"\"\n    import pennylane as qml\n    from sklearn.svm import SVC\n    from sklearn.ensemble import GradientBoostingClassifier\n    from sklearn.preprocessing import StandardScaler, MinMaxScaler\n    from sklearn.decomposition import PCA\n    from sklearn.metrics import f1_score, roc_auc_score, accuracy_score\n    n = len(X); n_te = max(int(n * test_frac), 20)\n    Xtr, Xte, ytr, yte = X[:-n_te], X[-n_te:], y[:-n_te], y[-n_te:]\n    sc = StandardScaler().fit(Xtr); k = min(n_qubits, Xtr.shape[1])\n    pca = PCA(k, random_state=seed).fit(sc.transform(Xtr)); mm = MinMaxScaler((0, math.pi)).fit(pca.transform(sc.transform(Xtr)))\n    Ztr = np.clip(mm.transform(pca.transform(sc.transform(Xtr))), 0, math.pi); Zte = np.clip(mm.transform(pca.transform(sc.transform(Xte))), 0, math.pi)\n    dev = qml.device(\"default.qubit\", wires=k)\n\n    def feature_map(x):\n        for i in range(k):\n            qml.Hadamard(i); qml.RZ(x[i], wires=i)\n        for i in range(k - 1):\n            qml.CNOT([i, i + 1]); qml.RZ((math.pi - x[i]) * (math.pi - x[i + 1]) / math.pi, wires=i + 1); qml.CNOT([i, i + 1])\n\n    @qml.qnode(dev)\n    def kernel_circ(x1, x2):\n        feature_map(x1); qml.adjoint(feature_map)(x2)\n        return qml.probs(wires=range(k))\n\n    def kernel_matrix(A, B):\n        K = np.zeros((len(A), len(B)))\n        for i, a in enumerate(A):\n            for j, b in enumerate(B):\n                K[i, j] = float(kernel_circ(a, b)[0])\n        return K\n    t0 = time.time()\n    # eğitim çekirdeği simetrik: yalnızca üst üçgen\n    Ktr = np.zeros((len(Ztr), len(Ztr)))\n    for i in range(len(Ztr)):\n        for j in range(i, len(Ztr)):\n            Ktr[i, j] = Ktr[j, i] = float(kernel_circ(Ztr[i], Ztr[j])[0])\n    Kte = kernel_matrix(Zte, Ztr)\n    n_circ = len(Ztr) * (len(Ztr) + 1) // 2 + len(Zte) * len(Ztr)\n    if ledger is not None:\n        ledger.log(\"qsvm\", f\"kuantum çekirdek {k}q\", time.time() - t0, n_circuits=n_circ, n_qubits=k, shots=0, device=device)\n    pos = float(ytr.mean())\n    cw = \"balanced\" if 0.05 < pos < 0.95 else None\n    q_svm = SVC(kernel=\"precomputed\", C=1.0, class_weight=cw, probability=True, random_state=seed).fit(Ktr, ytr)\n    yq = q_svm.predict(Kte); pq = q_svm.predict_proba(Kte)[:, 1]\n    rbf = SVC(kernel=\"rbf\", C=1.0, gamma=\"scale\", class_weight=cw, probability=True, random_state=seed).fit(sc.transform(Xtr), ytr)\n    yr = rbf.predict(sc.transform(Xte)); pr_ = rbf.predict_proba(sc.transform(Xte))[:, 1]\n    gb = GradientBoostingClassifier(random_state=seed).fit(Xtr, ytr); yg = gb.predict(Xte); pg = gb.predict_proba(Xte)[:, 1]\n\n    def sc_(yp, pp):\n        try:\n            auc = float(roc_auc_score(yte, pp))\n        except Exception:  # noqa: BLE001\n            auc = float(\"nan\")\n        return dict(F1=round(float(f1_score(yte, yp, zero_division=0)), 3), AUC=round(auc, 3), dogruluk=round(float(accuracy_score(yte, yp)), 3))\n    scores = {\"QSVM (kuantum çekirdek)\": sc_(yq, pq), \"RBF-SVM\": sc_(yr, pr_), \"Gradyan artırma\": sc_(yg, pg)}\n    return dict(scores=scores, n_qubits=k, n_train=len(Ztr), n_test=len(Zte), pos_rate_test=float(yte.mean()), n_circuits=n_circ, runtime_s=time.time() - t0)\n\n\nclass QLSTMForecaster:\n    \"\"\"Kuantum LSTM (Chen vd. 2022): LSTM kapılarındaki doğrusal katmanlar yerine VQC'ler (açı kodlama + StronglyEntangling).\n    Saf numpy + PennyLane; gizli boyut = kübit sayısı. Klasik referans: aynı boyutta numpy-LSTM (aynı eğitim döngüsü).\"\"\"\n\n    def __init__(self, n_qubits=4, seq_len=7, epochs=25, lr=0.05, seed=0, quantum=True, ledger=None, device=\"simulator\"):\n        self.n, self.T, self.epochs, self.lr, self.seed, self.quantum, self.ledger, self.device = n_qubits, seq_len, epochs, lr, seed, quantum, ledger, device\n        self.history: List[float] = []\n\n    def _build(self, n_in):\n        import pennylane as qml\n        from pennylane import numpy as pnp\n        n = self.n\n        rng = np.random.default_rng(self.seed)\n        self.Win = pnp.array(rng.normal(0, 0.3, (n_in + n, n)), requires_grad=True)  # [x_t, h_{t-1}] → n açı\n        if self.quantum:\n            dev = qml.device(\"default.qubit\", wires=n)\n\n            def make(name):\n                @qml.qnode(dev, diff_method=\"backprop\", interface=\"autograd\")\n                def q(x, w):\n                    qml.AngleEmbedding(x, wires=range(n), rotation=\"Y\")\n                    qml.StronglyEntanglingLayers(w, wires=range(n))\n                    return [qml.expval(qml.PauliZ(i)) for i in range(n)]\n                return q\n            self.gates = {g: make(g) for g in (\"f\", \"i\", \"c\", \"o\")}\n            self.Wg = {g: pnp.array(rng.normal(0, 0.3, (1, n, 3)), requires_grad=True) for g in (\"f\", \"i\", \"c\", \"o\")}\n        else:\n            self.Wg = {g: pnp.array(rng.normal(0, 0.3, (n, n)), requires_grad=True) for g in (\"f\", \"i\", \"c\", \"o\")}\n            self.bg = {g: pnp.zeros(n, requires_grad=True) for g in (\"f\", \"i\", \"c\", \"o\")}\n        self.Wout = pnp.array(rng.normal(0, 0.3, (n,)), requires_grad=True); self.bout = pnp.array(0.0, requires_grad=True)\n\n    def _forward(self, seq, params):\n        from pennylane import numpy as pnp\n        Win, Wg, Wout, bout = params[0], params[1], params[2], params[3]\n        n = self.n; h = pnp.zeros(n); c = pnp.zeros(n)\n        for t in range(seq.shape[0]):\n            v = pnp.concatenate([seq[t], h]) @ Win\n            ang = pnp.pi * pnp.tanh(v)  # [−π, π]\n            if self.quantum:\n                f = (pnp.stack(self.gates[\"f\"](ang, Wg[\"f\"])) + 1) / 2; i = (pnp.stack(self.gates[\"i\"](ang, Wg[\"i\"])) + 1) / 2\n                g = pnp.stack(self.gates[\"c\"](ang, Wg[\"c\"])); o = (pnp.stack(self.gates[\"o\"](ang, Wg[\"o\"])) + 1) / 2\n            else:\n                bg = params[4]\n                f = 1 / (1 + pnp.exp(-(ang @ Wg[\"f\"] + bg[\"f\"]))); i = 1 / (1 + pnp.exp(-(ang @ Wg[\"i\"] + bg[\"i\"])))\n                g = pnp.tanh(ang @ Wg[\"c\"] + bg[\"c\"]); o = 1 / (1 + pnp.exp(-(ang @ Wg[\"o\"] + bg[\"o\"])))\n            c = f * c + i * g; h = o * pnp.tanh(c)\n        return h @ Wout + bout\n\n    def fit(self, X_seq: np.ndarray, y: np.ndarray, batch: int = 16):\n        import pennylane as qml\n        from pennylane import numpy as pnp\n        self._build(X_seq.shape[2])\n        self.ymu, self.ysd = float(y.mean()), float(y.std() + 1e-9); yy = (y - self.ymu) / self.ysd\n        self.xmu = X_seq.reshape(-1, X_seq.shape[2]).mean(0); self.xsd = X_seq.reshape(-1, X_seq.shape[2]).std(0) + 1e-9\n        Xn = (X_seq - self.xmu) / self.xsd\n        opt = qml.AdamOptimizer(self.lr); rng = np.random.default_rng(self.seed)\n        params = [self.Win, self.Wg, self.Wout, self.bout] + ([self.bg] if not self.quantum else [])\n        t0 = time.time(); n_circ = 0\n        for ep in range(self.epochs):\n            idx = rng.permutation(len(Xn))[:batch]\n\n            def loss(*p):\n                pred = pnp.stack([self._forward(pnp.array(Xn[k]), p) for k in idx])\n                return pnp.mean((pred - yy[idx]) ** 2)\n            params, L = opt.step_and_cost(loss, *params); self.history.append(float(L))\n            n_circ += (4 * self.T * len(idx)) if self.quantum else 0\n        self.params = params\n        if self.ledger is not None:\n            self.ledger.log(\"qlstm\" if self.quantum else \"lstm\", f\"{'QLSTM' if self.quantum else 'LSTM'} {self.n} gizli, {self.epochs} adım\", time.time() - t0, n_circuits=n_circ, n_qubits=self.n if self.quantum else 0, device=self.device)\n        return self\n\n    def predict(self, X_seq: np.ndarray) -> np.ndarray:\n        from pennylane import numpy as pnp\n        Xn = (X_seq - self.xmu) / self.xsd\n        return np.array([float(self._forward(pnp.array(x), self.params)) for x in Xn]) * self.ysd + self.ymu\n\n\ndef make_sequences(daily_rain: pd.Series, daily_q: pd.Series, T: int = 7):\n    df = pd.concat([daily_rain.rename(\"P\"), daily_q.rename(\"Q\")], axis=1).dropna()\n    X, y, idx = [], [], []\n    for i in range(T, len(df) - 1):\n        X.append(df.iloc[i - T:i][[\"P\", \"Q\"]].values); y.append(df.Q.iloc[i]); idx.append(df.index[i])\n    return np.array(X), np.array(y), pd.DatetimeIndex(idx)\n\n\ndef run_quantum_portfolio(res: dict, sizes: dict, ledger: Optional[\"TimeLedger\"] = None, epochs_qlstm: int = 20, qsvm_max_train: int = 120) -> dict:\n    \"\"\"G bölümü: QAE (Monte Carlo tepe debi dağılımında aşılma olasılığı), QSVM (ani taşkın başlangıcı), QLSTM (1 gün ileri debi).\"\"\"\n    out: dict = {}\n    # ---- QAE: Monte Carlo tepe debi örneklerinde P(tepe > tasarım eşiği)\n    mc = res.get(\"mc_riv\")\n    if mc is not None and len(mc[\"peaks\"]) >= 8:\n        peaks = np.asarray(mc[\"peaks\"]); thr = float(np.percentile(peaks, 75))\n        # MC örneği azsa log-normal ile 4.096'ya genişlet (QAE için dağılım örneklemi)\n        rng = np.random.default_rng(0)\n        mu, sd = np.log(peaks).mean(), np.log(peaks).std() + 1e-6\n        big = np.exp(rng.normal(mu, sd, 4096))\n        out[\"qae\"] = quantum_amplitude_estimation(big, thr, n_qubits=sizes[\"qae\"], shots=sizes[\"shots\"], ledger=ledger)\n        out[\"qae\"][\"eşik_aciklama\"] = f\"tepe debi > {thr:,.0f} m³/s (MC P75)\"\n    # ---- QSVM: ani taşkın başlangıcı = ertesi gün debi artışı > %50 ve > 90. yüzdelik\n    tr, tq = res.get(\"train_rain\"), res.get(\"train_q\")\n    if tr is not None and tq is not None:\n        X, y_next = build_forecast_dataset(tr, tq)\n        q_now = X[\"Q_l1\"].values\n        thr_q = float(np.percentile(tq.values, 85))\n        rise = (y_next.values > 1.2 * q_now) & (y_next.values > thr_q)   # 'taşkın başlangıcı': ertesi gün ≥ %20 artış ve yüksek debi\n        yb = rise.astype(int)\n        if yb.sum() < 8:  # nadir sınıf: yalnızca yüksek-debi günlerini pozitif say\n            yb = (y_next.values > thr_q).astype(int)\n        if yb.sum() >= 5:\n            # eğitim boyutunu sınırla (çekirdek O(n²)): pozitifler + rastgele negatifler, zaman sırası korunur; test = son %30\n            rng = np.random.default_rng(0)\n            pos_i = np.flatnonzero(yb); neg_i = np.flatnonzero(yb == 0)\n            keep = np.sort(np.concatenate([pos_i, rng.choice(neg_i, min(len(neg_i), max(qsvm_max_train - len(pos_i), 40)), replace=False)]))\n            Xs, ys = X.values[keep], yb[keep]\n            out[\"qsvm\"] = quantum_svm_classifier(Xs, ys, n_qubits=sizes[\"qsvm\"], test_frac=0.3, ledger=ledger)\n            out[\"qsvm\"][\"n_pos_total\"] = int(yb.sum()); out[\"qsvm\"][\"n_total\"] = int(len(yb))\n        # ---- QLSTM vs LSTM\n        Xs, ys, idx = make_sequences(tr, tq, T=7)\n        n_te = max(30, int(len(Xs) * 0.2)); Xtr, Xte, ytr, yte = Xs[:-n_te], Xs[-n_te:], ys[:-n_te], ys[-n_te:]\n        qm = QLSTMForecaster(n_qubits=sizes[\"qlstm\"], seq_len=7, epochs=epochs_qlstm, quantum=True, ledger=ledger).fit(Xtr, ytr)\n        cm = QLSTMForecaster(n_qubits=sizes[\"qlstm\"], seq_len=7, epochs=epochs_qlstm, quantum=False, ledger=ledger).fit(Xtr, ytr)\n        pq, pc = qm.predict(Xte), cm.predict(Xte)\n        out[\"qlstm\"] = dict(scores={\"QLSTM\": dict(RMSE=float(np.sqrt(np.mean((pq - yte) ** 2))), NSE=nse(yte, pq)),\n                                    \"LSTM (numpy, aynı boyut)\": dict(RMSE=float(np.sqrt(np.mean((pc - yte) ** 2))), NSE=nse(yte, pc))},\n                            history_q=qm.history, history_c=cm.history, y_test=pd.Series(yte, index=idx[-n_te:]), pred_q=pq, pred_c=pc, n_qubits=sizes[\"qlstm\"])\n    return out\n"
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "b21728db",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "b21728db",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1790707421660,
     "user_tz": -180,
     "elapsed": 10058,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "1c746b17-3ca3-4b65-e431-0a3724b1f130"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "IBM Quantum kaydı atlandı: No module named 'qiskit_ibm_runtime'\n",
      "Etkin: ['GEMINI_API_KEY'] · CDS: False · Earthdata: False · IBM: True\n"
     ]
    }
   ],
   "source": "#@title 2 · Erişim anahtarları (isteğe bağlı — boş bırakılabilir) { display-mode: \"form\" }\n#@markdown Anahtar girilmeyen kaynaklar atlanır; motor anahtarsız açık veriyle çalışmaya devam eder.\n#@markdown **Colab Secrets** (🔑 sol panel) kullanıyorsanız aynı adlarla tanımlayın; form alanları boşsa oradan okunur.\nimport os\nfrom pathlib import Path\nGEMINI_API_KEY = \"\"  #@param {type:\"string\"}\nGOOGLE_FLOOD_API_KEY = \"\"  #@param {type:\"string\"}\nCDS_API_TOKEN = \"\"  #@param {type:\"string\"}\nCDS_URL = \"https://cds.climate.copernicus.eu/api\"  #@param [\"https://cds.climate.copernicus.eu/api\", \"https://ewds.climate.copernicus.eu/api\"]\nEARTHDATA_USER = \"\"  #@param {type:\"string\"}\nEARTHDATA_PASS = \"\"  #@param {type:\"string\"}\nIBM_QUANTUM_TOKEN = \"\"  #@param {type:\"string\"}\nORIGIN_QCLOUD_TOKEN = \"\"  #@param {type:\"string\"}\nDWAVE_API_TOKEN = \"\"  #@param {type:\"string\"}\nOPENTOPO_API_KEY = \"\"  #@param {type:\"string\"}\n\ndef _secret(name, val):\n    if val: return val\n    try:\n        from google.colab import userdata\n        return userdata.get(name)\n    except Exception:\n        return \"\"\nfor k in [\"GEMINI_API_KEY\",\"GOOGLE_FLOOD_API_KEY\",\"ORIGIN_QCLOUD_TOKEN\",\"DWAVE_API_TOKEN\",\"OPENTOPO_API_KEY\"]:\n    v = _secret(k, globals()[k])\n    if v: os.environ[k] = v\ntok = _secret(\"CDS_API_TOKEN\", CDS_API_TOKEN)\nif tok:\n    Path.home().joinpath(\".cdsapirc\").write_text(f\"url: {CDS_URL}\\nkey: {tok}\\n\")\neu, ep = _secret(\"EARTHDATA_USER\", EARTHDATA_USER), _secret(\"EARTHDATA_PASS\", EARTHDATA_PASS)\nif eu and ep:\n    p = Path.home()/\".netrc\"; p.write_text(f\"machine urs.earthdata.nasa.gov login {eu} password {ep}\\n\"); p.chmod(0o600)\nit = _secret(\"IBM_QUANTUM_TOKEN\", IBM_QUANTUM_TOKEN)\nif it:\n    try:\n        from qiskit_ibm_runtime import QiskitRuntimeService\n        QiskitRuntimeService.save_account(channel=\"ibm_quantum_platform\", token=it, overwrite=True)\n        print(\"IBM Quantum hesabı kaydedildi\")\n    except Exception as e:\n        print(\"IBM Quantum kaydı atlandı:\", e)\ndw = _secret(\"DWAVE_API_TOKEN\", DWAVE_API_TOKEN)\nif dw: os.environ[\"DWAVE_API_TOKEN\"] = dw\nprint(\"Etkin:\", [k for k in [\"GEMINI_API_KEY\",\"GOOGLE_FLOOD_API_KEY\",\"ORIGIN_QCLOUD_TOKEN\",\"DWAVE_API_TOKEN\",\"OPENTOPO_API_KEY\"] if os.environ.get(k)],\n      \"· CDS:\", bool(tok), \"· Earthdata:\", bool(eu and ep), \"· IBM:\", bool(it))\n"
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "b3f8b854",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "b3f8b854",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1790707630027,
     "user_tz": -180,
     "elapsed": 281,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "e58d3b4c-010c-4393-e28e-30ac77b83f4a"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "{\n",
      "  \"mode\": \"historical\",\n",
      "  \"scope\": \"turkiye\",\n",
      "  \"event_key\": \"bozkurt_2021\",\n",
      "  \"il\": \"Kastamonu\",\n",
      "  \"havza\": \"Batı Karadeniz\",\n",
      "  \"bbox\": [\n",
      "    35.8,\n",
      "    25.6,\n",
      "    42.2,\n",
      "    44.9\n",
      "  ],\n",
      "  \"half_deg\": 0.15,\n",
      "  \"start\": \"2026-09-19\",\n",
      "  \"end\": \"2026-09-22\",\n",
      "  \"forecast_days\": 5,\n",
      "  \"nowcast_hours\": 12,\n",
      "  \"run_frequency\": \"daily\",\n",
      "  \"flood_type\": \"both\",\n",
      "  \"grid_max\": 260,\n",
      "  \"pluvial_grid_max\": 220,\n",
      "  \"mc_runs\": 16,\n",
      "  \"mc_grid_max\": 120,\n",
      "  \"quantum_backend\": \"default.qubit\",\n",
      "  \"target_device\": \"simulator\",\n",
      "  \"run_profile\": \"default\",\n",
      "  \"hydraulics\": \"auto\",\n",
      "  \"hydraulics_grid_max\": 150,\n",
      "  \"hydraulics_minutes\": 6.0,\n",
      "  \"eda\": true,\n",
      "  \"quantum_portfolio\": true,\n",
      "  \"qlstm_epochs\": 12,\n",
      "  \"n_qubits\": 6,\n",
      "  \"qml_epochs\": 60,\n",
      "  \"qml_layers\": 3,\n",
      "  \"qubo_solver\": \"anneal\",\n",
      "  \"llm_provider\": \"template\",\n",
      "  \"gemini_model\": \"gemini-2.5-flash\",\n",
      "  \"out_dir\": \"trflood_out\",\n",
      "  \"report_lang\": \"tr\",\n",
      "  \"seed\": 42,\n",
      "  \"residential_max_damage_try_m2\": 9000.0,\n",
      "  \"commercial_max_damage_try_m2\": 14000.0,\n",
      "  \"insurance_penetration\": 0.12,\n",
      "  \"persons_per_building\": 3.2,\n",
      "  \"sewer_capacity_mm_h\": 10.0,\n",
      "  \"exposure_depth_m\": 0.3\n",
      "}\n"
     ]
    }
   ],
   "source": "#@title 3 · Çalıştırma yapılandırması { display-mode: \"form\" }\nimport importlib, trflood_engine as te; importlib.reload(te)\n#@markdown ### Mod ve kapsam\nmode = \"historical\"  #@param [\"historical\", \"forecast\", \"nowcast\"]\nscope = \"turkiye\"  #@param [\"event\", \"il\", \"havza\", \"turkiye\", \"bbox\"]\nevent_key = \"bozkurt_2021\"  #@param [\"bozkurt_2021\", \"istanbul_2023\", \"sanliurfa_2023\", \"giresun_dereli_2020\", \"rize_2021\", \"rize_2025\", \"izmir_2021\", \"izmir_2026\", \"antalya_manavgat_2024\", \"ankara_mamak_2018\", \"hatay_samandag_2026\", \"rize_2026\"]\nil = \"Kastamonu\"  #@param [\"Adana\",\"Adıyaman\",\"Afyonkarahisar\",\"Ağrı\",\"Amasya\",\"Ankara\",\"Antalya\",\"Artvin\",\"Aydın\",\"Balıkesir\",\"Bilecik\",\"Bingöl\",\"Bitlis\",\"Bolu\",\"Burdur\",\"Bursa\",\"Çanakkale\",\"Çankırı\",\"Çorum\",\"Denizli\",\"Diyarbakır\",\"Edirne\",\"Elazığ\",\"Erzincan\",\"Erzurum\",\"Eskişehir\",\"Gaziantep\",\"Giresun\",\"Gümüşhane\",\"Hakkari\",\"Hatay\",\"Isparta\",\"Mersin\",\"İstanbul\",\"İzmir\",\"Kars\",\"Kastamonu\",\"Kayseri\",\"Kırklareli\",\"Kırşehir\",\"Kocaeli\",\"Konya\",\"Kütahya\",\"Malatya\",\"Manisa\",\"Kahramanmaraş\",\"Mardin\",\"Muğla\",\"Muş\",\"Nevşehir\",\"Niğde\",\"Ordu\",\"Rize\",\"Sakarya\",\"Samsun\",\"Siirt\",\"Sinop\",\"Sivas\",\"Tekirdağ\",\"Tokat\",\"Trabzon\",\"Tunceli\",\"Şanlıurfa\",\"Uşak\",\"Van\",\"Yozgat\",\"Zonguldak\",\"Aksaray\",\"Bayburt\",\"Karaman\",\"Kırıkkale\",\"Batman\",\"Şırnak\",\"Bartın\",\"Ardahan\",\"Iğdır\",\"Yalova\",\"Karabük\",\"Kilis\",\"Osmaniye\",\"Düzce\"]\nhavza = \"Batı Karadeniz\"  #@param [\"Meriç-Ergene\",\"Marmara\",\"Susurluk\",\"Kuzey Ege\",\"Gediz\",\"Küçük Menderes\",\"Büyük Menderes\",\"Batı Akdeniz\",\"Antalya\",\"Burdur\",\"Akarçay\",\"Sakarya\",\"Batı Karadeniz\",\"Yeşilırmak\",\"Kızılırmak\",\"Konya Kapalı\",\"Doğu Akdeniz\",\"Seyhan\",\"Asi\",\"Ceyhan\",\"Fırat-Dicle\",\"Doğu Karadeniz\",\"Çoruh\",\"Aras\",\"Van Gölü\"]\nbbox_SWNE = \"\"  #@param {type:\"string\"}\nhalf_deg = 0.15  #@param {type:\"number\"}\nflood_type = \"auto\"  #@param [\"auto\", \"riverine\", \"pluvial\", \"both\"]\nstart_date = \"\"  #@param {type:\"string\"}\nend_date = \"\"  #@param {type:\"string\"}\nforecast_days = 5  #@param {type:\"integer\"}\nrun_frequency = \"daily\"  #@param [\"hourly\", \"daily\", \"weekly\"]\n#@markdown ### Hesap bütçesi\ngrid_max = 260  #@param {type:\"integer\"}\npluvial_grid_max = 220  #@param {type:\"integer\"}\nmc_runs = 16  #@param {type:\"integer\"}\n#@markdown ### Kuantum — kontrol düzlemi (simülatör tavanı 20 kübit; gerçek cihaz hedefleri: IBM Heron 20, Origin Wukong 20, D-Wave 500 değişken)\nquantum_backend = \"default.qubit\"  #@param [\"default.qubit\", \"ibm\", \"origin\", \"none\"]\ntarget_device = \"simulator\"  #@param [\"simulator\", \"ibm_heron\", \"origin_wukong\", \"dwave_advantage\"]\nrun_profile = \"default\"  #@param [\"min-cost\", \"default\", \"max-fidelity\"]\nquantum_portfolio = True  #@param {type:\"boolean\"}\nqlstm_epochs = 12  #@param {type:\"integer\"}\nn_qubits = 6  #@param {type:\"integer\"}\nqml_epochs = 60  #@param {type:\"integer\"}\nqml_layers = 3  #@param {type:\"integer\"}\nqubo_solver = \"anneal\"  #@param [\"anneal\", \"qaoa\", \"dwave\", \"none\"]\n#@markdown ### Hidrolik (D) ve veri analizi (E)\nhydraulics = \"auto\"  #@param [\"auto\", \"on\", \"off\"]\nhydraulics_grid_max = 150  #@param {type:\"integer\"}\nhydraulics_minutes = 6.0  #@param {type:\"number\"}\neda = True  #@param {type:\"boolean\"}\n#@markdown ### Rapor ve etki\nllm_provider = \"template\"  #@param [\"template\", \"gemini\"]\nreport_lang = \"tr\"  #@param [\"tr\", \"en\", \"both\"]\nsewer_capacity_mm_h = 10.0  #@param {type:\"number\"}\nexposure_depth_m = 0.3  #@param {type:\"number\"}\ninsurance_penetration = 0.12  #@param {type:\"number\"}\n\nbbox = tuple(float(x) for x in bbox_SWNE.split(\",\")) if bbox_SWNE.strip() else None\ncfg = te.RunConfig(mode=mode, scope=scope, event_key=event_key, il=il, havza=havza, bbox=bbox, half_deg=half_deg,\n                   start=start_date or None, end=end_date or None, forecast_days=forecast_days, run_frequency=run_frequency,\n                   flood_type=flood_type, grid_max=grid_max, pluvial_grid_max=pluvial_grid_max, mc_runs=mc_runs,\n                   quantum_backend=quantum_backend, n_qubits=n_qubits, qml_epochs=qml_epochs, qml_layers=qml_layers,\n                   target_device=target_device, run_profile=run_profile, quantum_portfolio=quantum_portfolio, qlstm_epochs=qlstm_epochs,\n                   hydraulics=hydraulics, hydraulics_grid_max=hydraulics_grid_max, hydraulics_minutes=hydraulics_minutes, eda=eda,\n                   qubo_solver=qubo_solver, llm_provider=llm_provider, report_lang=report_lang,\n                   sewer_capacity_mm_h=sewer_capacity_mm_h, exposure_depth_m=exposure_depth_m, insurance_penetration=insurance_penetration)\ncfg.resolve()\nprint(cfg.to_json())\nif scope == \"event\":\n    ev = te.EVENTS[event_key]; print(\"\\nReferans olay:\", ev[\"ad\"], \"|\", ev[\"baslangic\"], \"→\", ev[\"bitis\"], \"|\", ev[\"yagis_mm\"], \"mm /\", ev[\"yagis_sure_saat\"], \"s |\", ev[\"kayip\"])\n"
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "1e82e79e",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "id": "1e82e79e",
    "executionInfo": {
     "status": "error",
     "timestamp": 1790707859595,
     "user_tz": -180,
     "elapsed": 224272,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "7de2217c-520a-4a8f-8174-278b2411a14f"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "▶ 1/8 Arazi: DEM + arazi örtüsü + HAND\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "18:47:16 WARNING DEM karo açılamadı N35E028: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N35E028: HTTP response code: 404\n",
      "18:47:16 WARNING DEM karo açılamadı N35E029: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N35E029: HTTP response code: 404\n",
      "18:47:16 WARNING DEM karo açılamadı N35E030: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N35E030: HTTP response code: 404\n",
      "18:47:16 WARNING DEM karo açılamadı N35E031: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N35E031: HTTP response code: 404\n",
      "18:47:40 WARNING DEM karo açılamadı N39E044: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N39E044: HTTP response code: 404\n",
      "18:47:44 WARNING DEM karo açılamadı N40E043: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N40E043: HTTP response code: 404\n",
      "18:47:44 WARNING DEM karo açılamadı N40E044: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N40E044: HTTP response code: 404\n",
      "18:47:50 WARNING DEM karo açılamadı N41E043: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N41E043: HTTP response code: 404\n",
      "18:47:50 WARNING DEM karo açılamadı N41E044: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N41E044: HTTP response code: 404\n",
      "18:47:51 WARNING DEM karo açılamadı N42E029: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N42E029: HTTP response code: 404\n",
      "18:47:51 WARNING DEM karo açılamadı N42E030: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N42E030: HTTP response code: 404\n",
      "18:47:51 WARNING DEM karo açılamadı N42E031: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N42E031: HTTP response code: 404\n",
      "18:47:51 WARNING DEM karo açılamadı N42E032: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N42E032: HTTP response code: 404\n",
      "18:47:51 WARNING DEM karo açılamadı N42E036: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N42E036: HTTP response code: 404\n",
      "18:47:51 WARNING DEM karo açılamadı N42E037: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N42E037: HTTP response code: 404\n",
      "18:47:51 WARNING DEM karo açılamadı N42E038: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N42E038: HTTP response code: 404\n",
      "18:47:52 WARNING DEM karo açılamadı N42E039: HTTP response code: 404\n",
      "WARNING:trflood:DEM karo açılamadı N42E039: HTTP response code: 404\n",
      "18:48:18 INFO [DEM] Copernicus GLO-30 (AWS, 1\"): ok ızgara (86, 260), ~6422 m\n",
      "INFO:trflood:[DEM] Copernicus GLO-30 (AWS, 1\"): ok ızgara (86, 260), ~6422 m\n",
      "18:48:22 WARNING WorldCover karo yok 42,30: HTTP response code: 404\n",
      "WARNING:trflood:WorldCover karo yok 42,30: HTTP response code: 404\n",
      "18:48:25 INFO [Arazi örtüsü] ESA WorldCover 10 m (2021, CC BY 4.0): ok yapılaşmış %0.4, su %14.7\n",
      "INFO:trflood:[Arazi örtüsü] ESA WorldCover 10 m (2021, CC BY 4.0): ok yapılaşmış %0.4, su %14.7\n",
      "18:48:26 INFO [HAND] computed (GLO-30 DEM, D8): ok dere hücresi 8412, kara %69\n",
      "INFO:trflood:[HAND] computed (GLO-30 DEM, D8): ok dere hücresi 8412, kara %69\n",
      "18:48:26 WARNING WorldPop okunamadı (vsicurl) tur_pop_2025_CN_100m_R2024B_v1.tif: Range downloading not supported by this server!\n",
      "WARNING:trflood:WorldPop okunamadı (vsicurl) tur_pop_2025_CN_100m_R2024B_v1.tif: Range downloading not supported by this server!\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "▶ 2/8 Katmanlar: nüfus, ilçe sınırları, toprak\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "18:48:46 INFO [Nüfus] WorldPop TUR 2025 100 m (R2024B): ok toplam 87,575,935\n",
      "INFO:trflood:[Nüfus] WorldPop TUR 2025 100 m (R2024B): ok toplam 87,575,935\n",
      "18:48:58 INFO [İdari sınır] geoBoundaries TUR ADM2 (ODbL): ok 973 ilçe\n",
      "INFO:trflood:[İdari sınır] geoBoundaries TUR ADM2 (ODbL): ok 973 ilçe\n",
      "18:49:24 WARNING [Toprak] SoilGrids: varsayılan B HTTPSConnectionPool(host='rest.isric.org', port=443): Read timed out. (read time\n",
      "WARNING:trflood:[Toprak] SoilGrids: varsayılan B HTTPSConnectionPool(host='rest.isric.org', port=443): Read timed out. (read time\n",
      "18:49:24 INFO [Yağış] Open-Meteo arşiv (ERA5/ERA5-Land): ok 2026-09-17→2026-09-24, toplam 12.4 mm\n",
      "INFO:trflood:[Yağış] Open-Meteo arşiv (ERA5/ERA5-Land): ok 2026-09-17→2026-09-24, toplam 12.4 mm\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "▶ 3/8 Yağış ve gözlemler\n",
      "▶ 4/8 Nehir debisi (GloFAS) ve eğitim serileri\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "18:49:31 INFO [GloFAS hücre seçimi] Open-Meteo Flood API 3×3 yoklama: ok (41.643, 35.608) ort. 1.0 m³/s\n",
      "INFO:trflood:[GloFAS hücre seçimi] Open-Meteo Flood API 3×3 yoklama: ok (41.643, 35.608) ort. 1.0 m³/s\n",
      "18:49:31 INFO [Nehir debisi] GloFAS v4 (Open-Meteo Flood API): ok 19 gün, maks nan m³/s\n",
      "INFO:trflood:[Nehir debisi] GloFAS v4 (Open-Meteo Flood API): ok 19 gün, maks nan m³/s\n",
      "18:49:31 INFO [QML eğitim serisi] Open-Meteo arşiv + GloFAS consolidated: ok 1096 gün\n",
      "INFO:trflood:[QML eğitim serisi] Open-Meteo arşiv + GloFAS consolidated: ok 1096 gün\n",
      "18:49:33 INFO [MeteoUyarı] MGM meteoalarm/today: ok Kastamonu: yellow\n",
      "INFO:trflood:[MeteoUyarı] MGM meteoalarm/today: ok Kastamonu: yellow\n",
      "/content/trflood_engine.py:1362: FutureWarning: The behavior of Series.idxmax with all-NA values, or any-NA and skipna=False, is deprecated. In a future version this will raise ValueError\n",
      "  row = dict(model=name, peak_m3s=float(q.max()), t_peak=q.idxmax(), volume_hm3=float(q.sum() * _dt_hours(q) * 3600 / 1e6))\n",
      "/content/trflood_engine.py:1362: FutureWarning: The behavior of Series.idxmax with all-NA values, or any-NA and skipna=False, is deprecated. In a future version this will raise ValueError\n",
      "  row = dict(model=name, peak_m3s=float(q.max()), t_peak=q.idxmax(), volume_hm3=float(q.sum() * _dt_hours(q) * 3600 / 1e6))\n",
      "/content/trflood_engine.py:1362: FutureWarning: The behavior of Series.idxmax with all-NA values, or any-NA and skipna=False, is deprecated. In a future version this will raise ValueError\n",
      "  row = dict(model=name, peak_m3s=float(q.max()), t_peak=q.idxmax(), volume_hm3=float(q.sum() * _dt_hours(q) * 3600 / 1e6))\n",
      "/content/trflood_engine.py:1362: FutureWarning: The behavior of Series.idxmax with all-NA values, or any-NA and skipna=False, is deprecated. In a future version this will raise ValueError\n",
      "  row = dict(model=name, peak_m3s=float(q.max()), t_peak=q.idxmax(), volume_hm3=float(q.sum() * _dt_hours(q) * 3600 / 1e6))\n",
      "/content/trflood_engine.py:1362: FutureWarning: The behavior of Series.idxmax with all-NA values, or any-NA and skipna=False, is deprecated. In a future version this will raise ValueError\n",
      "  row = dict(model=name, peak_m3s=float(q.max()), t_peak=q.idxmax(), volume_hm3=float(q.sum() * _dt_hours(q) * 3600 / 1e6))\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "▶ 5/8 Hidroloji: 5 klasik model + GR4J\n",
      "▶ 6/8 Taşma: HAND nehir taşkını + 2B plüvyal otomat + Monte Carlo\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "/content/trflood_engine.py:855: RuntimeWarning: Mean of empty slice\n",
      "  return func(func(b, axis=3), axis=1)\n",
      "18:50:26 WARNING [Hidrolik] 2B sığ su: kapı geçilmedi Kütle dengesi: nan %; Maksimum derinlik: nan m; HAND yayılımı ile örtüşme (CSI): 0.00; Islak hücrelerin HAND < 15 m payı: 0 %\n",
      "WARNING:trflood:[Hidrolik] 2B sığ su: kapı geçilmedi Kütle dengesi: nan %; Maksimum derinlik: nan m; HAND yayılımı ile örtüşme (CSI): 0.00; Islak hücrelerin HAND < 15 m payı: 0 %\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155155_VV_EU020M_E060N009T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155155_VV_EU020M_E060N009T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155155_VV_EU020M_E060N009T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155155_VV_EU020M_E060N009T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E063N009T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E063N009T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E063N009T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E063N009T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E063N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E063N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E063N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E063N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E060N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E060N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E060N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155105_VV_EU020M_E060N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155040_VV_EU020M_E066N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155040_VV_EU020M_E066N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155040_VV_EU020M_E066N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155040_VV_EU020M_E066N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155040_VV_EU020M_E063N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155040_VV_EU020M_E063N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155040_VV_EU020M_E063N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155040_VV_EU020M_E063N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E066N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E066N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E066N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E066N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E066N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E066N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E066N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E066N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E063N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E063N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E063N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E063N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E063N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E063N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E063N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T155015_VV_EU020M_E063N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E066N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E066N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E066N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E066N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E063N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E063N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E063N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E063N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E063N000T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E063N000T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E063N000T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154950_VV_EU020M_E063N000T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E066N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E066N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E066N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E066N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E066N000T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E066N000T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E066N000T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E066N000T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E063N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E063N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E063N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E063N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E063N000T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E063N000T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E063N000T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154925_VV_EU020M_E063N000T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154900_VV_EU020M_E066N000T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154900_VV_EU020M_E066N000T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154900_VV_EU020M_E066N000T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T154900_VV_EU020M_E066N000T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150206_VV_EU020M_E072N009T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150206_VV_EU020M_E072N009T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150206_VV_EU020M_E072N009T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150206_VV_EU020M_E072N009T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150141_VV_EU020M_E075N009T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150141_VV_EU020M_E075N009T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150141_VV_EU020M_E075N009T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150141_VV_EU020M_E075N009T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150141_VV_EU020M_E072N009T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150141_VV_EU020M_E072N009T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150141_VV_EU020M_E072N009T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150141_VV_EU020M_E072N009T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E075N009T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E075N009T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E075N009T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E075N009T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E075N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E075N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E075N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E075N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E072N009T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E072N009T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E072N009T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E072N009T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E072N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E072N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E072N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150112_VV_EU020M_E072N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150021_VV_EU020M_E075N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150021_VV_EU020M_E075N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150021_VV_EU020M_E075N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150021_VV_EU020M_E075N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150021_VV_EU020M_E075N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150021_VV_EU020M_E075N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150021_VV_EU020M_E075N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T150021_VV_EU020M_E075N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145956_VV_AF020M_E078N090T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145956_VV_AF020M_E078N090T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145956_VV_AF020M_E078N090T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145956_VV_AF020M_E078N090T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145956_VV_AF020M_E075N090T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145956_VV_AF020M_E075N090T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145956_VV_AF020M_E075N090T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145956_VV_AF020M_E075N090T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145931_VV_AF020M_E078N090T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145931_VV_AF020M_E078N090T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145931_VV_AF020M_E078N090T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145931_VV_AF020M_E078N090T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145931_VV_AF020M_E075N090T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145931_VV_AF020M_E075N090T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145931_VV_AF020M_E075N090T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T145931_VV_AF020M_E075N090T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035829_VV_EU020M_E063N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035829_VV_EU020M_E063N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035829_VV_EU020M_E063N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035829_VV_EU020M_E063N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035829_VV_EU020M_E063N000T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035829_VV_EU020M_E063N000T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035829_VV_EU020M_E063N000T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035829_VV_EU020M_E063N000T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035804_VV_EU020M_E066N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035804_VV_EU020M_E066N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035804_VV_EU020M_E066N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035804_VV_EU020M_E066N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035804_VV_EU020M_E063N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035804_VV_EU020M_E063N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035804_VV_EU020M_E063N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035804_VV_EU020M_E063N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E066N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E066N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E066N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E066N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E066N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E066N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E066N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E066N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E063N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E063N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E063N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E063N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E063N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E063N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E063N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035739_VV_EU020M_E063N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E066N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E066N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E066N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E066N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E063N009T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E063N009T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E063N009T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E063N009T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E063N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E063N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E063N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T035714_VV_EU020M_E063N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T030951_VV_AF020M_E075N090T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T030951_VV_AF020M_E075N090T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T030951_VV_AF020M_E075N090T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T030951_VV_AF020M_E075N090T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T030926_VV_AF020M_E075N090T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T030926_VV_AF020M_E075N090T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T030926_VV_AF020M_E075N090T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/AF020M/2026/09/26/DLR_FLOOD_20260926T030926_VV_AF020M_E075N090T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E075N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E075N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E075N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E075N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E075N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E075N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E075N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E075N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E072N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E072N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E072N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E072N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E072N003T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E072N003T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E072N003T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030901_VV_EU020M_E072N003T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030836_VV_EU020M_E075N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030836_VV_EU020M_E075N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030836_VV_EU020M_E075N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030836_VV_EU020M_E075N006T3.XML: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030836_VV_EU020M_E072N006T3.aux: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030836_VV_EU020M_E072N006T3.AUX: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030836_VV_EU020M_E072N006T3.xml: 403\n",
      "WARNING:rasterio._env:CPLE_AppDefined in HTTP response code on https://data.eodc.eu/collections/GFM_NRT_INTERIM/flood_extent/EU020M/2026/09/26/DLR_FLOOD_20260926T030836_VV_EU020M_E072N006T3.XML: 403\n",
      "18:50:47 INFO [Doğrulama] CEMS GFM Sentinel-1 taşkın yayılımı (EODC): ok 50 sahne, S1 52 hücre, CSI 0.00\n",
      "INFO:trflood:[Doğrulama] CEMS GFM Sentinel-1 taşkın yayılımı (EODC): ok 50 sahne, S1 52 hücre, CSI 0.00\n",
      "18:50:47 INFO [Kontrol düzlemi] profil default, hedef PennyLane default.qubit: ok tavan 20 kübit; VQC 6, QAE 12, QSVM 12, QLSTM 6, genlik 15/15\n",
      "INFO:trflood:[Kontrol düzlemi] profil default, hedef PennyLane default.qubit: ok tavan 20 kübit; VQC 6, QAE 12, QSVM 12, QLSTM 6, genlik 15/15\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "▶ 7/8 Kuantum: kontrol düzlemi, VQC tahmin, QUBO tahsis, portföy (QAE · QSVM · QLSTM)\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "18:50:56 INFO [QML] PennyLane VQC (default.qubit): ok test RMSE 1.8\n",
      "INFO:trflood:[QML] PennyLane VQC (default.qubit): ok test RMSE 1.8\n",
      "18:50:56 WARNING [Kuantum portföyü] QAE · QSVM · QLSTM: hata zero-size array to reduction operation minimum which has no identity\n",
      "WARNING:trflood:[Kuantum portföyü] QAE · QSVM · QLSTM: hata zero-size array to reduction operation minimum which has no identity\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "▶ 8/8 Etki, EDA, ekranlar, rapor\n"
     ]
    },
    {
     "output_type": "error",
     "ename": "ValueError",
     "evalue": "cannot convert float NaN to integer",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[0;32m/tmp/ipykernel_520/843254105.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mIPython\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisplay\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mdisplay\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mImage\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mMarkdown\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpandas\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mres\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mte\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_pipeline\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcfg\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mprogress\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mlambda\u001b[0m \u001b[0mm\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"▶\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mm\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      5\u001b[0m \u001b[0mhy\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mimp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mres\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"hydro\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mres\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"impact\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      6\u001b[0m display(Markdown(f\"## {res['title']}\\n**Uyarı seviyesi:** {res['warning'][1]} (MGM MeteoUyarı: {res['mgm_alert'][0]})  \\n\"\n",
      "\u001b[0;32m/content/trflood_engine.py\u001b[0m in \u001b[0;36mrun_pipeline\u001b[0;34m(cfg, progress)\u001b[0m\n\u001b[1;32m   2593\u001b[0m         \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mqubo_team_allocation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrisk_skoru\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mteams\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msolver\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcfg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mqubo_solver\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mseed\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcfg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mseed\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   2594\u001b[0m         \u001b[0mres\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"qubo\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mteams\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mallocation\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0milce\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtolist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minfo\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2595\u001b[0;31m     \u001b[0mres\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"notifications\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mbuild_notifications\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mres\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   2596\u001b[0m     \u001b[0;31m# ---- E · EDA\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   2597\u001b[0m     \u001b[0mres\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"eda\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m/content/trflood_engine.py\u001b[0m in \u001b[0;36mbuild_notifications\u001b[0;34m(res)\u001b[0m\n\u001b[1;32m   1963\u001b[0m     \u001b[0mmsgs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkanal\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"WhatsApp Business / Telegram\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhedef\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"Muhtarlar, mahalle afet gönüllüleri\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmetin\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34mf\"⚠️ {top}: {lvl_txt}. Riskli sokaklar için harita paketteki 'derinlik_haritasi.png'. Tahliye noktaları: okul/cami bahçeleri (yüksek kot).\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1964\u001b[0m     msgs.append(dict(kanal=\"API (JSON) — belediye/kurum entegrasyonu\", hedef=\"Webhook\", metin=json.dumps(dict(engine=ENGINE_NAME, version=ENGINE_VERSION, level=lvl, il=cfg.il, districts=(d.head(5).ilce.tolist() if d is not None else []),\n\u001b[0;32m-> 1965\u001b[0;31m                      exposed_pop=round(imp['exposed_pop']), max_depth_m=round(imp['max_depth'], 2), peak_m3s=round(res['hydro'].get('peak_m3s', 0)), generated=res['generated']), ensure_ascii=False)))\n\u001b[0m\u001b[1;32m   1966\u001b[0m     \u001b[0;32mreturn\u001b[0m \u001b[0mmsgs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1967\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mValueError\u001b[0m: cannot convert float NaN to integer"
     ]
    }
   ],
   "source": "#@title 4 · Uçtan uca çalıştır (≈1–4 dk) { display-mode: \"form\" }\nfrom IPython.display import display, Image, Markdown\nimport pandas as pd\nres = te.run_pipeline(cfg, progress=lambda m: print(\"▶\", m))\nhy, imp = res[\"hydro\"], res[\"impact\"]\ndisplay(Markdown(f\"## {res['title']}\\n**Uyarı seviyesi:** {res['warning'][1]} (MGM MeteoUyarı: {res['mgm_alert'][0]})  \\n\"\n                 f\"**Yağış girdisi:** {float(res['rain']['rain'].sum()):.0f} mm — {res['rain']['source']}  \\n\"\n                 f\"**Havza:** {hy['catchment']['area_km2']:.0f} km², tc {hy['catchment']['tc_hours']:.1f} s, CN {hy['cn']:.0f} (HSG {hy['hsg']})  \\n\"\n                 f\"**Tepe debi:** {hy['peak_m3s']:,.0f} m³/s ({hy['best_model']})\" + (f\" · nehir seviyesi {hy['stage_m']:.2f} m\" if hy.get('stage_m') else \"\") + \"  \\n\"\n                 f\"**Etkilenen nüfus (>{imp['exposure_depth_m']} m):** {imp['exposed_pop']:,.0f} (+{imp['exposed_pop_shallow']:,.0f} sığ) · alan {imp['exposed_area_km2']:.1f} km² · maks derinlik {imp['max_depth']:.1f} m  \\n\"\n                 f\"**Tahmini hasar:** {te._fmt_try(imp['loss_try'])} · PML90 {te._fmt_try(imp.get('pml90_try'))}  \\n\"\n                 f\"**Model seçimi:** {hy['selection_note']}\"))\ndisplay(hy[\"metrics\"].round(3))\nif imp.get(\"districts\") is not None: display(imp[\"districts\"].round(2).head(12))\nif res.get(\"qml\"): display(pd.DataFrame(res[\"qml\"][\"scores\"]).T.round(3)); print(\"QML:\", res[\"qml\"][\"info\"])\ndisplay(Markdown(\"### Kontrol düzlemi (H) — kübit bütçesi ve cihaz uygunluğu\"))\ndisplay(pd.DataFrame([res[\"sizes\"]]).T.rename(columns={0: \"değer\"}))\ndisplay(pd.DataFrame(res[\"device_fit\"]).T[[\"qubits_ok\",\"depth_ok\",\"fidelity_est\",\"fits\",\"reason\"]])\nif res.get(\"hydraulics\"):\n    h = res[\"hydraulics\"]; display(Markdown(f\"### Hidrolik (D) — 2B sığ su çözücüsü: {h['grid_shape']} ızgara, {h['steps']} adım, {h['runtime_s']:.0f} s · kapı: {'GEÇİLDİ' if h['gate']['passed'] else 'GEÇİLMEDİ'}\")); display(h[\"gate\"][\"checks\"])\nif res.get(\"portfolio\"):\n    display(Markdown(\"### Kuantum portföyü (G) — her aday klasik şampiyonuyla\"))\n    for k, v in res[\"portfolio\"].items():\n        if \"scores\" in v: display(pd.DataFrame(v[\"scores\"]).T.round(3))\n        else: print(k, {kk: (round(vv, 4) if isinstance(vv, float) else vv) for kk, vv in v.items() if kk in (\"p_qae\",\"se_qae\",\"p_mc\",\"se_mc\",\"n_qubits\",\"n_oracle\",\"eşik_aciklama\")})\nif res.get(\"eda\"):\n    e = res[\"eda\"]; display(Markdown(f\"### Veri analizi (E) — Moran I {e['spatial']['moran_I']:.2f} (p {e['spatial']['moran_p']:.2f}), sıcak nokta {e['spatial']['hot_cells']}\"))\n    display(e[\"inventory\"]); display(e[\"fits\"].round(3)); display(e[\"tests\"]); display(e[\"outliers\"]); display(e[\"cell_correlations\"])\ndisplay(Markdown(f\"### Zaman defteri (L) — kuantum payı %{res['ledger_summary']['quantum_share']*100:.0f}, {res['ledger_summary']['circuits']} devre\"))\ndisplay(res[\"ledger\"].frame()); display(res[\"qpu_projection\"])\ndisplay(res[\"provenance\"][[\"katman\",\"kaynak\",\"durum\",\"detay\"]])\nfor k in [\"hidrograf\",\"model_skor\",\"derinlik_nehir\",\"derinlik_pluvyal\",\"derinlik_birlesik\",\"asilma\",\"asilma_pluvyal\",\"dogrulama\",\"qml\",\"ilce\"]:\n    if k in res[\"figures\"]: display(Image(filename=str(res[\"figures\"][k]), width=820))\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "538db28c",
   "metadata": {
    "id": "538db28c"
   },
   "outputs": [],
   "source": "#@title 5 · Ekranlar — acil durum bilgi paylaşımı · sigorta · bildirim merkezi { display-mode: \"form\" }\nfrom IPython.display import HTML, display\ndisplay(HTML(te.screen_emergency(res)))\ndisplay(HTML(te.screen_insurance(res)))\ndisplay(HTML(te.screen_notifications(res)))\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19f18d9b",
   "metadata": {
    "id": "19f18d9b"
   },
   "outputs": [],
   "source": "#@title 6 · İnteraktif harita (folium) { display-mode: \"form\" }\nimport numpy as np, folium, matplotlib, matplotlib.pyplot as plt\nfrom matplotlib import colors\ns, w, n, e = cfg.bbox\ndepth = res[\"depth\"]; vmax = max(float(np.nanpercentile(depth, 99.5)), 0.5)\nrgba = plt.get_cmap(\"Blues\")(np.clip(depth / vmax, 0, 1)); rgba[..., 3] = np.where(depth > 0.05, 0.85, 0.0)\nm = folium.Map(location=[(s+n)/2, (w+e)/2], zoom_start=11, tiles=\"OpenStreetMap\")\nfolium.raster_layers.ImageOverlay(image=(rgba*255).astype(\"uint8\"), bounds=[[s, w], [n, e]], opacity=0.9, name=\"Su derinliği (m)\").add_to(m)\nif res.get(\"mc_riv\") is not None:\n    pr = res[\"mc_riv\"][\"exceed_prob\"]; rg = plt.get_cmap(\"YlOrRd\")(np.clip(pr, 0, 1)); rg[..., 3] = np.where(pr > 0.05, 0.7, 0.0)\n    folium.raster_layers.ImageOverlay(image=(rg*255).astype(\"uint8\"), bounds=[[s, w], [n, e]], opacity=0.8, name=\"Aşılma olasılığı (>0,3 m)\", show=False).add_to(m)\nfor ft in te.GeoBoundariesClient().features_in_bbox(cfg.bbox, \"ADM2\"):\n    fs, fw, fn, fe = ft[\"bbox\"]; d = res[\"impact\"].get(\"districts\")\n    row = d[d.ilce == ft[\"name\"]] if d is not None else None\n    txt = f\"{ft['name']}: {row.etkilenen_nufus.iloc[0]:,.0f} kişi, maks {row.maks_derinlik_m.iloc[0]:.1f} m\" if row is not None and len(row) else ft[\"name\"]\n    folium.GeoJson(ft[\"geometry\"], name=ft[\"name\"], style_function=lambda x: dict(color=\"#555\", weight=1, fill=False), tooltip=txt).add_to(m)\nfolium.LayerControl().add_to(m)\nfolium.Rectangle([[s, w], [n, e]], color=\"#2b6cb0\", weight=1, fill=False).add_to(m)\nm\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79674a62",
   "metadata": {
    "id": "79674a62"
   },
   "outputs": [],
   "source": "#@title 7 · Çift-şehir POC — Bozkurt 2021 (nehir) + İstanbul 2023 (kentsel) { display-mode: \"form\" }\n#@markdown Malezya Shah Alam / Kota Bharu çiftinin Türkiye karşılığı. Her ikisi ardışık çalıştırılır ve karşılaştırma tablosu üretilir.\nrun_poc = False  #@param {type:\"boolean\"}\nimport copy, pandas as pd\npoc_results = {}\nif run_poc:\n    for key in te.DEFAULT_POC_PAIR:\n        c = copy.deepcopy(cfg); c.mode, c.scope, c.event_key, c.flood_type, c.bbox, c.start, c.end = \"historical\", \"event\", key, \"auto\", None, None, None\n        poc_results[key] = te.run_pipeline(c, progress=lambda m, k=key: print(f\"[{k}] {m}\"))\n    rows = []\n    for k, r in poc_results.items():\n        rows.append(dict(olay=r[\"title\"], tip=te.EVENTS[k][\"tip\"], yagis_mm=round(float(r[\"rain\"][\"rain\"].sum())), tepe_m3s=round(r[\"hydro\"][\"peak_m3s\"]),\n                         model=r[\"hydro\"][\"best_model\"], seviye_m=round(r[\"hydro\"].get(\"stage_m\", 0), 2), alan_km2=round(r[\"impact\"][\"exposed_area_km2\"], 1),\n                         nufus=round(r[\"impact\"][\"exposed_pop\"]), hasar=te._fmt_try(r[\"impact\"][\"loss_try\"]), uyari=r[\"warning\"][1],\n                         qml_rmse=round(r[\"qml\"][\"scores\"][\"QML-VQC\"][\"RMSE\"], 2) if r.get(\"qml\") else None, paket=r[\"bundle\"].name))\n    display(pd.DataFrame(rows))\nelse:\n    print(\"run_poc kutusunu işaretleyip hücreyi çalıştırın.\")\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "80b51ea3",
   "metadata": {
    "id": "80b51ea3"
   },
   "outputs": [],
   "source": "#@title 8 · Ulusal tarama — 81 il risk endeksi (≈3–5 dk) { display-mode: \"form\" }\n#@markdown Tahmin yağışı (Open-Meteo, 3 gün) + GloFAS debi anomalisi + MGM MeteoUyarı → il bazlı risk endeksi; en riskli il için detay çalıştırma (isteğe bağlı).\nrun_screening = False  #@param {type:\"boolean\"}\nscreening_days = 3  #@param {type:\"integer\"}\ndetail_top_k = 1  #@param {type:\"integer\"}\nif run_screening:\n    import matplotlib.pyplot as plt\n    scr = te.national_screening(days=screening_days, progress=lambda m: print(\"  \", m))\n    display(scr.head(20).round(2))\n    fig, ax = plt.subplots(figsize=(10, 5.5))\n    sc = ax.scatter(scr.lon, scr.lat, c=scr.risk_endeksi, s=40 + 60*scr.yagis_mm.fillna(0)/max(scr.yagis_mm.max(), 1), cmap=\"YlOrRd\", edgecolor=\"k\", linewidth=0.3)\n    for _, r in scr.head(8).iterrows(): ax.annotate(r.il, (r.lon, r.lat), fontsize=8, xytext=(3, 3), textcoords=\"offset points\")\n    ax.set_xlim(25.5, 45); ax.set_ylim(35.5, 42.5); ax.set_title(f\"Ulusal taşkın risk taraması — {screening_days} gün\"); plt.colorbar(sc, label=\"risk endeksi\"); plt.show()\n    scr.to_csv(\"ulusal_tarama.csv\", index=False, encoding=\"utf-8-sig\")\n    for il_name in scr.head(detail_top_k).il:\n        import copy\n        c = copy.deepcopy(cfg); c.mode, c.scope, c.il, c.bbox, c.flood_type = \"forecast\", \"il\", il_name, None, \"both\"\n        c.half_deg = 0.12; c.resolve()\n        print(f\"\\n=== Detay: {il_name} ===\")\n        r = te.run_pipeline(c, progress=lambda m: print(\"▶\", m))\n        print(il_name, \"→\", r[\"warning\"][1], \"| etkilenen nüfus\", f\"{r['impact']['exposed_pop']:,.0f}\", \"| paket\", r[\"bundle\"].name)\nelse:\n    print(\"run_screening kutusunu işaretleyip hücreyi çalıştırın.\")\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b7a17552",
   "metadata": {
    "id": "b7a17552"
   },
   "outputs": [],
   "source": "#@title 9 · DSİ AGİ gözlem verisi ile değerlendirme (isteğe bağlı) { display-mode: \"form\" }\n#@markdown CSV sütunları: `tarih` (ISO) ve `debi_m3s`. DSİ Akım Gözlem Yıllığı'ndan çıkarılmış günlük seri yüklenirse modeller GloFAS yerine gözlemle puanlanır.\nupload_observed = False  #@param {type:\"boolean\"}\nif upload_observed:\n    from google.colab import files\n    up = files.upload()\n    for name in up:\n        obs = te.load_observed_discharge(name)\n        met = te.evaluate_models(res[\"hydro\"][\"sims\"], obs)\n        display(met.round(3)); print(\"Gözleme göre en iyi model:\", met.iloc[0][\"model\"])\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1762a457",
   "metadata": {
    "id": "1762a457"
   },
   "outputs": [],
   "source": "#@title 10 · Rapor paketini indir / Drive'a kopyala { display-mode: \"form\" }\ncopy_to_drive = False  #@param {type:\"boolean\"}\ndrive_folder = \"TRFlood/raporlar\"  #@param {type:\"string\"}\nfrom google.colab import files\nprint(\"Paket:\", res[\"bundle\"], \"|\", round(res[\"bundle\"].stat().st_size/1e6, 1), \"MB\")\nprint(\"Raporlar:\", [p.name for p in res[\"reports\"]])\nif copy_to_drive:\n    from google.colab import drive; import shutil, os\n    drive.mount(\"/content/drive\", force_remount=False)\n    dst = f\"/content/drive/MyDrive/{drive_folder}\"; os.makedirs(dst, exist_ok=True)\n    shutil.copy(res[\"bundle\"], dst); print(\"Drive'a kopyalandı:\", dst)\nfiles.download(str(res[\"bundle\"]))\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "230e7c88",
   "metadata": {
    "id": "230e7c88"
   },
   "outputs": [],
   "source": "#@title 11 · Periyodik çalıştırma (oturum içi) { display-mode: \"form\" }\n#@markdown Colab oturumu açıkken `run_frequency` ayarına göre döngü. Üretimde Cloud Run / Airflow / cron + bu modül (`python -c \"import trflood_engine as te; te.run_pipeline(te.RunConfig(mode='nowcast', scope='il', il='Rize'))\"`) önerilir.\nstart_loop = False  #@param {type:\"boolean\"}\niterations = 3  #@param {type:\"integer\"}\nif start_loop:\n    import time, copy\n    every_h = {\"hourly\": 1, \"daily\": 24, \"weekly\": 168}[cfg.run_frequency]\n    for i in range(iterations):\n        c = copy.deepcopy(cfg); c.mode = \"nowcast\" if cfg.mode == \"historical\" else cfg.mode; c.start = c.end = None; c.resolve()\n        r = te.run_pipeline(c, progress=lambda m: None)\n        print(time.strftime(\"%Y-%m-%d %H:%M\"), \"→\", r[\"title\"], \"|\", r[\"warning\"][1], \"| nüfus\", f\"{r['impact']['exposed_pop']:,.0f}\", \"|\", r[\"bundle\"].name)\n        if i < iterations - 1: time.sleep(every_h * 3600)\n"
  },
  {
   "cell_type": "markdown",
   "id": "9bc5eb0b",
   "metadata": {
    "id": "9bc5eb0b"
   },
   "source": "## Sınırlılıklar ve yol haritası\n\n**Bilinen sınırlılıklar**\n- ERA5/Open-Meteo yağışı konvektif ekstremleri ciddi ölçüde düşük gösterir (Bozkurt 2021: ERA5 ~90 mm vs MGM 451,7 mm). Referans olaylarda MGM istasyon toplamı/süresi ile **olay hiyetografı** yerleştirilir; canlı modda MGM AWOS 24 s toplamları ve MeteoUyarı seviyesi ile birlikte değerlendirin.\n- **GloFAS 5 km** küçük ve dik havzaları (Ezine Çayı gibi) çözemez; motor bu durumu tespit edip DSİ uygulamasına uygun **SCS-CN + sentetik birim hidrograf** yöntemine döner. Kalibrasyon için **DSİ AGİ** verisi (hücre 9) şarttır.\n- HAND + sentetik anahtar eğrisi ve 2B hücresel otomat **tarama** araçlarıdır; kanalizasyon/kültür kapasitesi tek bir parametre (`sewer_capacity_mm_h`) ile temsil edilir. Karar düzeyi çalışmalar için HEC-RAS 2D / LISFLOOD-FP gerekir.\n- Copernicus 30 m DEM kentsel mikro-topografyayı çözemez → **HGM SYM12** (kamu kurumları/üniversiteler) veya belediye LiDAR ile değiştirilmelidir.\n- Hasar eğrileri (JRC/Huizinga 2017) ve birim maliyetler **TL bazında kalibre edilmemiştir**; DASK taşkını kapsamadığından sigortalı pay varsayımdır.\n- Sentinel-1 GFM doğrulaması dar ve dik vadilerde (radar gölge/örtme) sınırlıdır; düşük CSI tek başına model hatası değildir.\n\n**Sonraki adımlar (BVK ürün yolu)**\n1. Erişim başvuruları: CDS/EWDS, Earthdata, CDSE, Google Flood API, EFAS ortaklığı, MEVBİS; resmî yazı: DSİ AGİ, HGM SYM12, SYGM taşkın haritaları.\n2. Kalibrasyon: 3–5 referans olayda DSİ gözlemi + GFM yayılımı ile CN, Manning n, şebeke kapasitesi ve QML hiper-parametrelerinin ayarı.\n3. Donanım: IBM/Wukong'da 12–20 kübit VQC çıkarımı ve D-Wave'de gerçek QUBO tahsisi (hücre 3 → `quantum_backend`, `qubo_solver`).\n4. Ürünleşme: modülün servis olarak (FastAPI + zamanlayıcı) sunulması, MGM MeteoUyarı ile tetiklenen otomatik nowcast, AFAD/belediye webhook entegrasyonu.\n"
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}