#!/usr/bin/env python3
"""
ers_and_exceptions.py — (1) 連続給電が成立する流量域での「なぜ無線か」比較、(2) 例外用途（港湾・鉱山・BRT 等）の個別モデル

(1) ERS 3 方式の LCOS を同じ流量で比較する
    無線 DWPT / 架線 overhead catenary / 導電レール conductive rail
    違いは CAPEX・効率・車載機器費・給電出力・保守。

(2) 例外用途は「止まれる時間」があるかどうかで決まる。
    各用途で、同じコイル技術を『走行中に敷く』場合と『停止点に置く』場合、さらに
    パンタグラフ／トロリーアシスト／電池交換と比べる。

    python3 model/ers_and_exceptions.py
"""
from __future__ import annotations

import csv
import json
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
import dwpt_model as M  # noqa: E402

base, low, high = M.load_assumptions("base"), M.load_assumptions("low"), M.load_assumptions("high")


# -----------------------------------------------------------------------------
# (1) ERS 方式比較
# -----------------------------------------------------------------------------
ERS = {
    # name: (capex_yen_per_lane_km, eff grid→battery, duty, power_kw_per_truck, vehicle_equipment_yen, equipment_life, om_fraction, lifetime)
    "無線 DWPT（150 kW）": (base["capex_dwpt_yen_per_lane_km"], base["grid_to_battery_eff"], base["coupling_duty"], 150.0, base["receiver_cost_yen"], base["receiver_lifetime_years"], base["om_fraction"], base["lifetime_years"]),
    "無線 DWPT（LOW CAPEX・300 kW）": (low["capex_dwpt_yen_per_lane_km"], high["grid_to_battery_eff"], high["coupling_duty"], 300.0, base["receiver_cost_yen"], base["receiver_lifetime_years"], low["om_fraction"], base["lifetime_years"]),
    "架線式 overhead catenary（670 kW）": (base["overhead_catenary_capex_yen_per_lane_km"], 0.95, 0.9, 670.0, 4_000_000.0, 10.0, 0.02, 25.0),
    "導電レール conductive rail（300 kW）": (base["conductive_rail_capex_yen_per_lane_km"], 0.92, 0.9, 300.0, 2_000_000.0, 10.0, 0.03, 20.0),
}


def ers_lcos(flow_per_day: float, speed_kmh: float = 90.0, km_per_vehicle_year: float = M.DWPT_KM_PER_VEHICLE_YEAR_CORRIDOR) -> list[dict]:
    rows = []
    for name, (capex, eff, duty, pkw, veq, veq_life, om, life) in ERS.items():
        e1_raw = M.energy_per_pass_kwh(pkw, 1000.0, speed_kmh, duty)
        e1 = min(e1_raw, base["usable_kwh_per_km"])  # 車両が受け取れる上限（消費＋電池受入）を超える供給は数えない（レビュー R3-08）
        annual = M.annual_energy_kwh(flow_per_day, e1)
        infra = M.annualised_cost(capex, base["discount_rate"], life, om) / annual
        veh = M.annualised_cost(veq, base["discount_rate"], veq_life, 0.0) / (e1 * km_per_vehicle_year)
        elec = base["electricity_yen_per_kwh"] / eff
        rows.append({"system": name, "flow_per_day": flow_per_day, "kwh_per_km_pass": round(e1, 2), "kwh_per_km_pass_uncapped": round(e1_raw, 2), "annual_mwh_per_lane_km": round(annual / 1000),
                     "infra_yen_per_kwh": round(infra, 1), "vehicle_equipment_yen_per_kwh": round(veh, 1), "electricity_yen_per_kwh": round(elec, 1),
                     "total_yen_per_kwh": round(infra + veh + elec, 1)})
    return rows


# -----------------------------------------------------------------------------
# (2) 例外用途
# -----------------------------------------------------------------------------
# 各用途: ルート長 m, 車両数/日(片道換算), 走行速度 km/h, 1 周回あたり停止時間 s（荷役・乗降・待機）, 1 周回の消費 kWh,
#        架線/トロリーが物理的に可能か, 商用実績のある代替
EXC = [
    # name, route_m, cycles_per_day（全車合計の周回数）, speed km/h, stop_s_per_cycle（荷役・乗降・終点待機の合計）, kwh_per_cycle,
    # alt_kind ('catenary' = 路線に架線 / 'hub' = 拠点充電器), alt_name, alt_unit_capex_yen（架線は 円/km、拠点は 円/台）, alt_power_kw
    ("港湾コンテナヤード（内部トラクター）", 2000, 1500, 25, 180, 4.0, "hub", "ヤード端 パンタグラフ 450 kW（商用実績: 各社ターミナル）", 60_000_000, 450),
    ("鉱山（ホールトラック 上り勾配 3 km）", 3000, 600, 15, 300, 60.0, "catenary", "トロリーアシスト架線（商用実績: Boliden・Caterpillar・Hitachi）", 300_000_000, 3000),
    ("採石場（ダンプ 1.5 km）", 1500, 300, 20, 240, 15.0, "hub", "積込・荷下ろし待機中の停車充電 350 kW", 40_000_000, 350),
    ("BRT（連節バス 12 km 専用レーン）", 12000, 400, 30, 600, 10.0, "hub", "終点パンタグラフ 450 kW（商用実績: 欧州各都市）", 50_000_000, 450),
    ("空港（ランプバス 3 km）", 3000, 800, 25, 300, 3.0, "hub", "駐機場 静止無線／コネクタ 150 kW", 20_000_000, 150),
    ("工場内物流（AGV 0.8 km 周回）", 800, 2000, 8, 120, 0.8, "hub", "停止点 静止無線 30 kW（商用実績: 各社 AGV）", 5_000_000, 30),
    ("自動運転物流専用路（高速 100 km 大型 BEV）", 100000, 8000, 80, 0, 130.0, "hub", "拠点 自動導電充電／MCS 1.2 MW（稼働率 30 %）", 150_000_000, 1200),
]


def exception_cases() -> list[dict]:
    rows = []
    for name, route_m, cycles, speed, stop_s, kwh_cycle, alt_kind, alt_name, alt_unit_capex, alt_kw in EXC:
        a = dict(base)
        annual_demand = cycles * kwh_cycle * 365
        # --- 走行中 DWPT：ルート全長に敷設 -------------------------------------------------
        p = 150.0 if kwh_cycle > 2 else 30.0
        duty = 0.8
        e_route = M.energy_per_pass_kwh(p, route_m, speed, duty)
        e_route_used = min(e_route, kwh_cycle)  # 消費を超える分は受け取れない
        annual_dwpt = cycles * e_route_used * 365
        capex_dwpt = a["capex_dwpt_yen_per_lane_km"] * route_m / 1000.0 * (0.7 if speed <= 30 else 1.0)  # 低速構内は施工制約が緩く 3 割安と仮定
        ann_dwpt = M.annualised_cost(capex_dwpt, a["discount_rate"], a["lifetime_years"], a["om_fraction"])
        lcos_dwpt = ann_dwpt / annual_dwpt if annual_dwpt else float("inf")
        # --- 停止点給電（同じコイル技術を停止点に置く）--------------------------------------
        e_stop = min(kwh_cycle, p * stop_s / 3600.0)
        annual_stop = cycles * e_stop * 365
        n_pads = max(1, int(cycles * stop_s / 86400 * 2) + 1)  # 同時停止台数の 2 倍を用意
        capex_stop = n_pads * 15_000_000.0  # 150 kW 静止無線パッド 1,500 万円/基（文献レンジ）
        ann_stop = M.annualised_cost(capex_stop, a["discount_rate"], 15.0, 0.03)
        lcos_stop = ann_stop / annual_stop if annual_stop else float("inf")
        # --- 商用代替 ------------------------------------------------------------------
        if alt_kind == "catenary":
            e_alt = min(kwh_cycle, M.energy_per_pass_kwh(alt_kw, route_m, speed, 0.9))
            annual_alt = cycles * e_alt * 365
            capex_alt = alt_unit_capex * route_m / 1000.0
            life_alt, om_alt = 25.0, 0.02
        else:
            e_alt = kwh_cycle if stop_s == 0 else min(kwh_cycle, alt_kw * stop_s / 3600.0)
            annual_alt = cycles * e_alt * 365
            util = 0.30
            n_units = max(1, int(annual_alt / (alt_kw * 8760 * util)) + 1)
            capex_alt = n_units * alt_unit_capex
            life_alt, om_alt = 12.0, 0.04
        ann_alt = M.annualised_cost(capex_alt, a["discount_rate"], life_alt, om_alt)
        lcos_alt = ann_alt / annual_alt if annual_alt else float("inf")
        cover_dwpt, cover_stop, cover_alt = e_route_used / kwh_cycle, e_stop / kwh_cycle, e_alt / kwh_cycle
        candidates = {"走行中 DWPT": (lcos_dwpt, cover_dwpt), "停止点 静止無線": (lcos_stop, cover_stop), "商用代替": (lcos_alt, cover_alt)}
        feasible = {k: v for k, v in candidates.items() if v[1] >= 0.8}
        best = min(feasible, key=lambda k: feasible[k][0]) if feasible else "どれも単独では需要を賄えない"
        rows.append({"use_case": name, "route_m": route_m, "cycles_per_day": cycles, "speed_kmh": speed, "stop_s_per_cycle": stop_s,
                     "kwh_per_cycle": kwh_cycle, "annual_demand_mwh": round(annual_demand / 1000),
                     "dwpt_kwh_per_cycle": round(e_route_used, 2), "dwpt_coverage": round(cover_dwpt, 2), "dwpt_capex_yen": round(capex_dwpt), "dwpt_lcos_yen_per_kwh": round(lcos_dwpt, 1),
                     "stop_kwh_per_cycle": round(e_stop, 2), "stop_coverage": round(cover_stop, 2), "stop_pads": n_pads, "stop_capex_yen": round(capex_stop), "stop_lcos_yen_per_kwh": round(lcos_stop, 1) if lcos_stop != float("inf") else "inf",
                     "commercial_alternative": alt_name, "alt_coverage": round(cover_alt, 2), "alt_capex_yen": round(capex_alt), "alt_lcos_yen_per_kwh": round(lcos_alt, 1),
                     "verdict": best})
    return rows


def main() -> None:
    M.RESULTS.mkdir(exist_ok=True)
    rows = []
    for f in (500, 2000, 8000):
        rows += ers_lcos(f)
    M.write_csv(M.RESULTS / "ers_comparison.csv", rows)
    exc = exception_cases()
    M.write_csv(M.RESULTS / "exception_cases.csv", exc)
    for r in rows:
        print(f"{r['flow_per_day']:>6} /day  {r['system']:<34} infra {r['infra_yen_per_kwh']:>7}  total {r['total_yen_per_kwh']:>7} 円/kWh")
    for r in exc:
        print(f"{r['use_case']:<28} DWPT {r['dwpt_lcos_yen_per_kwh']:>8} (充足{r['dwpt_coverage']:.0%})  停止点 {r['stop_lcos_yen_per_kwh']:>8} (充足{r['stop_coverage']:.0%})  代替 {r['alt_lcos_yen_per_kwh']:>8} (充足{r['alt_coverage']:.0%})  → {r['verdict']}")


if __name__ == "__main__":
    main()
