"""
Projekt Rente – Rechenmodell „Drei Schlösser“ (2026–2070)
=========================================================
Jährliches Makro-Modell in realen Euro (Kaufkraft 2026). Bausteine:

  Demografie  → Äquivalenz-Rentner / Äquivalenz-Beitragszahler
  GRV         → Rentenwert-Formel (Lohn × Beitragssatzfaktor × Nachhaltigkeitsfaktor),
                Haltelinie, Beitragssatz aus der Rücklagen-Regel (§ 158 SGB VI),
                Bundesmittel (§ 213 SGB VI, vereinfacht)
  Reform      → Kopplung Rentenalter, Rente 63, Haltelinie/α, neue Versicherte,
                Erwerbsquote, Deutschlandkonto (Kohortensimulation), Beamte
  Vergleich   → fiskalischer Saldo öffentliche Hand (Reform − Basis)

Alle Parameter: annahmen.py. Nur Standardbibliothek.
"""
import copy
import math

import annahmen as A


# ---------------------------------------------------------------------------
# Hilfen
# ---------------------------------------------------------------------------
def params(**overrides):
    """Alle GROSSGESCHRIEBENEN Namen aus annahmen.py als dict (+ Overrides)."""
    p = {k: copy.deepcopy(v) for k, v in vars(A).items() if k.isupper()}
    p.update(copy.deepcopy(A.KALIB_START))
    for k, v in overrides.items():
        p[k] = v
    return p


def interp(xs, ys, x):
    if x <= xs[0]:
        return ys[0]
    if x >= xs[-1]:
        return ys[-1]
    for i in range(1, len(xs)):
        if x <= xs[i]:
            f = (x - xs[i - 1]) / (xs[i] - xs[i - 1])
            return ys[i - 1] + f * (ys[i] - ys[i - 1])


def interp_dict(d, x):
    ks = sorted(d)
    return interp(ks, [d[k] for k in ks], x)


def stufe(d, t):
    """Stufenfunktion: Wert des letzten Schlüssels ≤ t (sonst 0)."""
    v = 0.0
    for k in sorted(d):
        if t >= k:
            v = d[k]
    return v


def demo(p, t):
    d = p["DEMO"][p["DEMO_VARIANTE"] if "DEMO_VARIANTE" in p else p["DEMO_STANDARD"]]
    return (interp(p["DEMO_JAHRE"], d["p2066"], t), interp(p["DEMO_JAHRE"], d["p67"], t))


def e65(p, t):
    return p["E65_2026"] + p["E65_ZUWACHS"] * (t - 2026)


def lebenserwartung_mit(p, t, alter):
    # [S] fernere LE sinkt um ≈ 0,85 Jahre je Altersjahr über 65
    return max(8.0, e65(p, t) - 0.85 * (alter - 65))


def kopplung_delta(p, t, reform):
    """Anhebung der Regelaltersgrenze über 67 (Jahre) im Kalenderjahr t."""
    if not reform or t < p["KOPPLUNG_AB"]:
        return 0.0
    return p["KOPPLUNG_ANTEIL"] * p["E65_ZUWACHS"] * (t - (p["KOPPLUNG_AB"] - 1))


def lohn(p, t):
    return p["DURCHSCHNITTSENTGELT_2026"] * (1 + p["REALLOHN"]) ** (t - 2026)


def konto_satz(p, t):
    kp = p.get("KONTO_PFAD")
    if kp:
        if t in kp:
            return kp[t]
        # Fix 3.1 (Gutachten 13.10.2026): Nach dem Simulationsende (2090) gilt die Konto-Regel weiter.
        # Früher fiel konto_einzel hier auf die alte Stufe KONTO_SATZ (3 % ab 2035) zurück. Fortgeschrieben
        # wird der letzte Wert der Regel (2090: 2 %, GRV-Satz liegt auf der 20-%-Grenze) [S].
        if t > max(kp):
            return kp[max(kp)]
    return stufe(p["KONTO_SATZ"], t)


# ---------------------------------------------------------------------------
# Runde 5: Geburten / Fertilitätsvarianten
# ---------------------------------------------------------------------------
def geburten_basis(p, t):
    var = p.get("DEMO_VARIANTE", p["DEMO_STANDARD"])
    return interp(p["DEMO_JAHRE"], p["GEBURTEN_REIHE"][var], t)


def fertilitaet(p, reform=True):
    """Geburten je Jahr (Mio) und Zusatzpersonen ggü. G2. Rückgabe: dict t → (geb, extra_u20, extra_2066)."""
    T0, T1 = 1960, p["ENDE"]
    base = {t: (geburten_basis(p, t) if t >= 2024 else 0.68) for t in range(T0, T1 + 1)}
    geb = dict(base)
    ziel = p.get("FERT_ZIEL") if reform else None
    if ziel:
        c = {}
        for t in range(T0, T1 + 1):
            if t < 2028:
                c[t] = 1.0
                continue
            g2 = interp_dict(p["FERT_TFR_G2"], t)
            g2_28 = interp_dict(p["FERT_TFR_G2"], 2028)
            zj = p["FERT_ZIELJAHR"]
            tfr = g2_28 + (ziel - g2_28) * min(1.0, (t - 2028) / (zj - 2028))
            f = max(tfr, g2) / g2
            mut = sum(c.get(s, 1.0) for s in range(t - 36, t - 25)) / 11   # Müttergeneration (Alter 26–36)
            c[t] = f * mut
            geb[t] = base[t] * c[t]
    extra = {t: geb[t] - base[t] for t in geb}
    out = {}
    for t in range(p["START"], T1 + 1):
        u20 = sum(extra.get(s, 0.0) for s in range(t - 19, t + 1))
        e2066 = 0.98 * sum(extra.get(s, 0.0) for s in range(t - 66, t - 19))
        out[t] = (geb[t], u20, e2066)
    return out


def steuer_rente(p, t):
    return interp([2026, 2058], [p["STEUER_RENTE_2026"], p["STEUER_RENTE_2058"]], t)


# ---------------------------------------------------------------------------
# Deutschlandkonto – Einzelperson (Standardrentner, Beispielhaushalte)
# ---------------------------------------------------------------------------
def rendite_jahr(p, t, jahre_bis_rente, rendite_aktien, crash):
    if jahre_bis_rente >= p["LZ_GLEITJAHRE"]:
        q = p["LZ_AKTIEN_START"]
    else:
        q = p["LZ_AKTIEN_ENDE"] + (p["LZ_AKTIEN_START"] - p["LZ_AKTIEN_ENDE"]) * max(0, jahre_bis_rente) / p["LZ_GLEITJAHRE"]
    r_akt = -p["CRASH_HOEHE"] if (crash and t == p["CRASH_JAHR"]) else rendite_aktien
    return q * r_akt + (1 - q) * p["RENDITE_ANLEIHEN"] - p["KOSTEN"]


def rentenalter_kohorte(p, geburtsjahr, reform=True):
    """Ganzzahliges Rentenalter (Regelaltersgrenze inkl. Kopplung) für einen Jahrgang."""
    t67 = geburtsjahr + 67
    return 67 + int(round(kopplung_delta(p, t67, reform)))


def annuitaet(kapital, zins, jahre):
    if jahre <= 0:
        return kapital
    if zins == 0:
        return kapital / jahre
    return kapital * zins / (1 - (1 + zins) ** (-jahre))


def konto_einzel(p, geburtsjahr, lohn_rel=1.0, kinder_alter=(), rendite=None, crash=False, start_alter=20,
                 start_kapital=0.0):
    """Kontorente (real €/Jahr) eines durchgehend Beschäftigten mit konstanter relativer
    Lohnposition lohn_rel (1 = Durchschnittsentgelt). Rückgabe: dict."""
    r_akt = p["RENDITE_WACHSTUM"] if rendite is None else rendite
    ra = rentenalter_kohorte(p, geburtsjahr)
    t_rente = geburtsjahr + ra
    B = 0.0
    eigen = 0.0
    if start_kapital:
        B = start_kapital
        for alter in range(0, start_alter):
            B *= 1 + rendite_jahr(p, geburtsjahr + alter, ra - alter, r_akt, crash)
    kj = 6 if p.get("KP_AKTIV") else p["KINDER_JAHRE"]
    for alter in range(start_alter, ra):
        t = geburtsjahr + alter
        satz = konto_satz(p, t)
        beitrag = satz * lohn(p, t) * lohn_rel
        if any(0 <= alter - k < kj for k in kinder_alter):
            beitrag += satz * lohn(p, t)          # Kindergutschrift: voller Beitrag eines Ø-Verdieners
        r = rendite_jahr(p, t, ra - alter, r_akt, crash)
        B = B * (1 + r) + beitrag * (1 + r / 2)
        eigen += beitrag
    n = round(lebenserwartung_mit(p, t_rente, ra))
    rente_jahr = annuitaet(B, p["RENDITE_AUSZAHLUNG"], n)
    return {"rentenalter": ra, "rentenjahr": t_rente, "kapital": B, "rente_jahr": rente_jahr,
            "rente_monat": rente_jahr / 12, "bezugsjahre": n}


# ---------------------------------------------------------------------------
# Hauptsimulation
# ---------------------------------------------------------------------------
def simuliere(p, reform=False, alpha=None, rendite=None, crash=False, mit_konto=None):
    """Ein Szenario 2026–2070. Rückgabe: dict Jahr → dict Größen."""
    alpha_ref = p["ALPHA_REFORM"] if alpha is None else alpha
    if reform:
        p.pop("KONTO_PFAD", None)
    mit_konto = reform if mit_konto is None else mit_konto
    r_akt = p["RENDITE_WACHSTUM"] if rendite is None else rendite
    w0 = lohn(p, 2026)
    p2066_0, p67_0 = demo(p, 2026)
    b0 = p["BEITRAGSSATZ_2026"]

    # Startwerte 2026 (Kalibrierung auf Bundesmittel / Ausgaben / Defizit)
    L0 = (p["AUSGABEN_2026"] - p["BUNDESMITTEL_2026"] - p["DEFIZIT_2026"]) / b0
    SR0 = p["AKTUELLER_RENTENWERT_2026"] * 12 * p["STANDARD_EP"]           # €/Jahr Standardrente
    R_eq0 = p["AUSGABEN_2026"] * p["ANTEIL_RENTEN_AN_AUSGABEN"] * 1000 / SR0  # Mio Äquivalenzrentner
    E0 = p2066_0
    k0 = p["NIVEAU_2026"] / (SR0 / w0 * (1 - p["SV_ANTEIL_RENTNER"]) / (1 - p["SV_ANTEIL_AN"]))
    bm_rest0 = p["BUNDESMITTEL_2026"] - p["BUNDESMITTEL_ALLG_2026"]

    res = {}
    ep_pipeline = {}           # Jahr → Mio EP, die in diesem Jahr als Rente gezahlt werden
    kohorten = {}              # Geburtsjahr → dict(B=Anspar, R=Rentenreserve, A=Annuität, n=Restjahre)
    rw = rw_s = p["AKTUELLER_RENTENWERT_2026"]
    reserve = p["RUECKLAGE_ENDE_2025"]
    hist = {}
    fert = fertilitaet(p, reform)
    zz = p.get("ZUGANG_ZUSCHLAG") or {}

    for t in range(p["START"], p["ENDE"] + 1):
        w = lohn(p, t)
        P2066, P67 = demo(p, t)
        if reform and p.get("H_MIG_GEWICHT", 0):
            d3 = p["DEMO"]["V3_migration_hoch"]
            a3 = interp(p["DEMO_JAHRE"], d3["p2066"], t) - P2066
            b3 = interp(p["DEMO_JAHRE"], d3["p67"], t) - P67
            P2066 += p["H_MIG_GEWICHT"] * p["H_MIG_EFFIZIENZ"] * a3
            P67 += p["H_MIG_GEWICHT"] * b3
        geb_t, fert_u20, fert_2066 = fert[t]
        P2066 += fert_2066
        trend = (1 + p["q_trend"]) ** (min(t, 2039) - 2026)
        E = P2066 * trend
        L_base = L0 * (w / w0) * (E / E0)

        # ---------- Reform: zusätzliche Erwerbstätige / weniger Rentner ----------
        J = interp_dict(p["JAHRGANG_AN_GRENZE"], t)
        d_k = kopplung_delta(p, t, reform)
        kop_rentner = d_k * J * p["KOPPLUNG_RENTE_VERMIEDEN"]
        kop_work = d_k * J * p["KOPPLUNG_ARBEITEN"]
        if reform and t >= p["R63_AB"]:
            f = min(1.0, (t - p["R63_AB"] + 1) / p["R63_EINPHASEN_JAHRE"])
            skal = J / 1.25
            r63_rentner = p["R63_RENTNER_WENIGER"] * f * skal
            r63_work = p["R63_ARBEITSKRAEFTE"] * f * skal
        else:
            r63_rentner = r63_work = 0.0
        gw_p = alo = japan = 0.0
        if reform and p.get("GW_AKTIV"):
            r63_rentner *= 1 - p["GW_R63_VERLUST"]
            r63_work *= 1 - p["GW_R63_VERLUST"]
            verl = kop_work * p["GW_KOPPLUNG_VERLUST"]
            kop_work -= verl
            kop_rentner -= verl
            gw_p = p["GW_PERSONEN_JG"] * min(10, max(0, t - 2031))
        if reform:
            alo = d_k * J * p["KOPPLUNG_ALO"]
            japan = p["JAPAN_VZAE"] * min(1.0, max(0.0, (t - p["JAPAN_AB"] + 1) / (p["JAPAN_VOLL"] - p["JAPAN_AB"] + 1)))
        if reform:
            eq = p["EQ_PLUS_2040"] * min(1.0, max(0.0, (t - 2026) / (2040 - 2026)))
            jahre_s = t - p["SELBST_AB"] + 1
            selbst = p["SELBST_ZUGANG"] * min(max(0, jahre_s), p["SELBST_BEITRAGSJAHRE"])
            abg = p["ABG_ANZAHL"] if t >= p["ABG_AB"] else 0.0
            fr = min(1.0, max(0.0, (t - 2027) / (2040 - 2027)))
            eqf = p["H_EQ_FRAUEN_2040"] * fr
            eqa = p["H_EQ_AELTERE_2040"] * fr
            # angestellte Lehrkräfte: Bestand wächst ab 2028 um Neueinstellungen × Anteil, 34 Dienstjahre
            jl = max(0, min(t - p["BEAMTENFONDS_AB"] + 1, p["BEAMTE_DIENSTJAHRE"]))
            lehrer = p["BEAMTE_NEU_JE_JAHR"] * p["H_LEHRER_ANTEIL"] * jl
        else:
            eq = selbst = abg = eqf = eqa = lehrer = 0.0
        eqh = eqf * p["H_EQ_FRAUEN_ENTGELT"] + eqa * p["H_EQ_AELTERE_ENTGELT"]
        jap_l = japan * p["JAPAN_ENTGELT"]
        gw_l = gw_p * p["GW_TEILZEIT"]
        eq_extra = (eq * p["EQ_ENTGELT"] + kop_work * p["KOPPLUNG_ENTGELT"] + r63_work + eqh
                    + selbst * p["SELBST_ENTGELT"] + abg * p["ABG_ENTGELT"] + lehrer * p["H_LEHRER_ENTGELT"]
                    + jap_l * p["JAPAN_BEITRAG_FAKTOR"] + gw_l)
        lohn_jobs = (eq * p["EQ_ENTGELT"] + kop_work * p["KOPPLUNG_ENTGELT"] + r63_work + eqh + jap_l + gw_l) * w / 1000
        L = L_base + eq_extra * w / 1000
        # Runde 6 „Alle in eine Kasse“: GRV-versicherte (neue/gewechselte) Beamte – gilt in Basis UND Reform,
        # sobald BIG_PFAD gesetzt ist (Pfad aus big.py, Mengen in Mio Ø-Entgelt-Äquivalenten bzw. Mio EP)
        _big = (p.get("BIG_PFAD") or {}).get(t)
        L_big = _big["eq_aktiv"] * w / 1000 if _big else 0.0
        ep_big = _big["ep_bezug"] if _big else 0.0
        L += L_big

        # neue Anwartschaften (Mio EP) → später auszuzahlen (Lag, 20 Jahre Bezug)
        for menge, lag in ((eq * p["EQ_ENTGELT"], 20), (kop_work * p["KOPPLUNG_ENTGELT"], 1),
                           (r63_work, 1), (selbst * p["SELBST_ENTGELT"], 25), (abg * p["ABG_ENTGELT"], 10),
                           (eqf * p["H_EQ_FRAUEN_ENTGELT"], 20), (eqa * p["H_EQ_AELTERE_ENTGELT"], 3),
                           (lehrer * p["H_LEHRER_ENTGELT"], 30), (jap_l * p["JAPAN_BEITRAG_FAKTOR"], 3),
                           (gw_l, 3)):
            if menge > 0:
                for s in range(t + lag, t + lag + 20):
                    ep_pipeline[s] = ep_pipeline.get(s, 0.0) + menge
        ep_paid = ep_pipeline.get(t, 0.0)

        R_base = R_eq0 * (P67 / p67_0) ** p["theta"]
        R_eq = R_base - kop_rentner * p["KOPPLUNG_RENTE_AEQ"] - r63_rentner * p["R63_RENTE_AEQ"] + ep_paid / p["STANDARD_EP"] - gw_p * p["GW_EM_AEQ"]
        R_eq += ep_big / p["STANDARD_EP"]
        BZ_eq = L * 1000 / w
        RQ = R_eq / BZ_eq

        # ---------- Rentenwert ----------
        if t == 2026:
            g_base = g_ref = 1.0
        else:
            wg = w / lohn(p, t - 1)
            b1 = hist[t - 1]["b"]
            b2 = hist[t - 2]["b"] if t - 2 in hist else b1
            bsf = (100 - p["AVA"] - 100 * b1) / (100 - p["AVA"] - 100 * b2)
            if reform and p.get("BSF_MIT_KONTO"):
                # Runde 8 (Kommission, Empf. 14, S. 37): Beitrag zur Kapitalrente zählt im Beitragssatzfaktor mit
                k1, k2 = konto_satz(p, t - 1), konto_satz(p, t - 2)
                bsf_ref = (100 - p["AVA"] - 100 * (b1 + k1)) / (100 - p["AVA"] - 100 * (b2 + k2))
            else:
                bsf_ref = bsf
            rq1 = hist[t - 1]["RQ"]
            rq2 = hist[t - 2]["RQ"] if t - 2 in hist else rq1
            g_base = wg * bsf * (1 + p["ALPHA_GELTEND"] * (1 - rq1 / rq2))
            g_ref = wg * bsf_ref * (1 + alpha_ref * (1 - rq1 / rq2))
        b_prev = hist[t - 1]["b"] if t - 1 in hist else b0

        def niveau(rw_, b_):
            sv_an = p["SV_ANTEIL_AN"] + (b_ - b0) / 2
            return k0 * rw_ * 12 * p["STANDARD_EP"] / w * (1 - p["SV_ANTEIL_RENTNER"]) / (1 - sv_an)

        HL = p["H_HL_REFORM"] if (reform and p.get("H_HL_REFORM")) else p["HALTELINIE_BIS"]
        if t > 2026:
            rw_s_neu = rw_s * g_base               # Vergleichswert ohne Haltelinie
            if reform and p.get("HL_OHNE_RUECKSETZUNG") and t > HL:
                rw_s_neu = rw_s * g_ref            # Runde 8: Vergleichswert folgt der neuen Formel, Aufschlag bleibt (Bund erstattet weiter)
            if reform and t == HL + 1 and not p.get("HL_OHNE_RUECKSETZUNG"):
                rw_neu = rw_s * g_ref              # Basis 2032 = Vergleichswert (Advocatus R2, 1.3)
            elif reform and t > HL:
                rw_neu = rw * g_ref
            else:
                rw_neu = rw * g_base
            if t <= HL and niveau(rw_neu, b_prev) < p["NIVEAU_2026"]:
                rw_neu = rw_neu * p["NIVEAU_2026"] / niveau(rw_neu, b_prev)
            rw_neu = max(rw_neu, rw / (1 + p["INFLATION"]))   # Rentengarantie (nominal)
            rw, rw_s = rw_neu, rw_s_neu
        N = niveau(rw, b_prev)

        SR = rw * 12 * p["STANDARD_EP"]
        renten = R_eq * SR / 1000                         # Mrd
        # H1 Mischindex für laufende Renten: Ø Rückstand des Bestands ggü. Lohnformel
        misch = 1.0
        if reform and p.get("H_MISCH_AB") and t >= p["H_MISCH_AB"]:
            D = t - p["H_MISCH_AB"] + 1
            N_ = p["H_BEZUGSDAUER_GRV"]
            lag = sum(min(k, D) for k in range(N_)) / N_
            misch = (1 - p["H_MISCH_PREISANTEIL"] * p["REALLOHN"]) ** lag
        renten = renten * misch
        # H7 Hinterbliebene (Splitting)
        if reform and p.get("H_HINTERBL") and t > 2055:
            renten = renten * (1 - 0.015 * (t - 2055) / 15)
        ausgaben = renten / p["ANTEIL_RENTEN_AN_AUSGABEN"]
        # H2 Mütterrente III (in Basis enthalten, aus Bundesmitteln finanziert)
        mr3 = interp_dict(p["H_MR3_PFAD"], t) if (reform and p.get("H_MR3_STREICHEN")) else 0.0
        ausgaben -= mr3
        reha = p["GW_REHA_MRD"] if (reform and p.get("GW_AKTIV") and t >= 2030) else 0.0
        N_ = p["H_BEZUGSDAUER_GRV"]
        zg = renten / N_ * sum(zz.get(z, 0.0) for z in range(t - N_ + 1, t + 1)) if (reform and zz) else 0.0
        ausgaben += reha + zg
        if reform and t > HL and not p.get("HL_OHNE_RUECKSETZUNG"):
            erstattung = 0.0
        else:
            erstattung = max(0.0, ausgaben * (1 - rw_s / rw))

        # ---------- Beitragssatz (Rücklagenregel § 158) und Bundesmittel (§ 213, vereinfacht) ----------
        # allg. Bundeszuschuss: Vorjahr × Lohnentwicklung je Kopf × b_t / b_{t-1}
        # übrige Bundesmittel (zusätzl. BZ, Erhöhungsbetrag, KEZ-Beiträge): wachsen mit der Lohnsumme (Basis)
        # Haltelinien-Erstattung: Bund trägt Ausgaben-Mehrkosten ggü. Vergleichswert (Rentenpaket 2025)
        rest = bm_rest0 * (L_base / L0)
        vfl = p.get("H_VFL_MRD", 0.0) if (reform and t >= 2028) else 0.0
        c0 = rest + erstattung - mr3 + vfl + (zg if p.get("ZG_FINANZ", "bund") == "bund" else 0.0)
        if t == 2026:
            c1 = p["BUNDESMITTEL_ALLG_2026"] / b0
            b = b0
        else:
            b_last = hist[t - 1]["b"]
            c1 = hist[t - 1]["bz_allg"] * (w / lohn(p, t - 1)) / b_last
            b = b_last if t > p["BEITRAGSSATZ_FIX_BIS"] else b0
            if t > p["BEITRAGSSATZ_FIX_BIS"]:
                ziel_min = p["RUECKLAGE_MIN_MA"] * ausgaben / 12
                ziel_max = p["RUECKLAGE_MAX_MA"] * ausgaben / 12
                end = reserve + b * (L + c1) + c0 - ausgaben
                if end < ziel_min:
                    b = (ziel_min - reserve + ausgaben - c0) / (L + c1)
                elif end > ziel_max:
                    b = (ziel_max - reserve + ausgaben - c0) / (L + c1)
                b = round(b, 3)   # Beitragssatz auf 0,1 Pkt. gerundet (wie per Verordnung)
        bz_allg = c1 * b
        # H12 allg. Bundeszuschuss ab Jahr X nur noch mit der Lohnentwicklung je Kopf fortschreiben
        #     (Beitragssatzfaktor in § 213 Abs. 2 SGB VI ausgesetzt) – Beitragszahler tragen den Rest.
        if reform and p.get("H_BZ_NURLOHN") and t >= p["H_BZ_NURLOHN"]:
            bz_allg = hist[t - 1]["bz_allg"] * (w / lohn(p, t - 1))
            if t > p["BEITRAGSSATZ_FIX_BIS"]:
                ziel_min = p["RUECKLAGE_MIN_MA"] * ausgaben / 12
                ziel_max = p["RUECKLAGE_MAX_MA"] * ausgaben / 12
                end = reserve + b * L + bz_allg + c0 - ausgaben
                if end < ziel_min:
                    b = round((ziel_min - reserve + ausgaben - c0 - bz_allg) / L, 3)
                elif end > ziel_max:
                    b = round((ziel_max - reserve + ausgaben - c0 - bz_allg) / L, 3)
        bip = p["BIP_2026"] * (L_base / L0) * (1 + lohn_jobs / L_base)
        steuern_bund = p["STEUERN_BUND_2026"] * (L_base / L0) * (1 + lohn_jobs / L_base)
        ausgleich = 1.0
        # H13 Bundesmittel-Deckel (Runde 5): Bundesmittel ≤ Quote × Bundessteuern; Rest über Beitrag
        if reform and p.get("BM_DECKEL") and t >= p.get("BM_DECKEL_AB", 2030):
            q_deckel = p["BM_DECKEL"]
            # Optionaler Pfad (Runde 9, Mittelweg): [(Jahr, Quote), ...], linear interpoliert
            if p.get("BM_DECKEL_PFAD"):
                pf = p["BM_DECKEL_PFAD"]
                if t <= pf[0][0]:
                    q_deckel = pf[0][1]
                elif t >= pf[-1][0]:
                    q_deckel = pf[-1][1]
                else:
                    for (j0, q0), (j1, q1) in zip(pf, pf[1:]):
                        if j0 <= t <= j1:
                            q_deckel = q0 + (q1 - q0) * (t - j0) / (j1 - j0)
                            break
            bm_max = q_deckel * steuern_bund
            if bz_allg + c0 > bm_max:
                bz_allg = bm_max - c0
                ziel_min = p["RUECKLAGE_MIN_MA"] * ausgaben / 12
                ziel_max = p["RUECKLAGE_MAX_MA"] * ausgaben / 12
                end = reserve + b * L + bz_allg + c0 - ausgaben
                if end < ziel_min:
                    b = round((ziel_min - reserve + ausgaben - c0 - bz_allg) / L, 3)
                elif end > ziel_max:
                    b = round((ziel_max - reserve + ausgaben - c0 - bz_allg) / L, 3)
        # H14 Automatischer Ausgleich (Runde 5, Schweden-Logik): Liegt der Beitrag trotz Deckel über
        #     B_DECKEL, wird die Rentenanpassung (Bestand + Zugang) so gekürzt, dass B_DECKEL hält.
        b_deckel_t = p.get("B_DECKEL")
        if b_deckel_t and p.get("B_DECKEL_PFAD"):   # Runde 9: optionaler Pfad [(Jahr, Satz), ...], linear
            pf = p["B_DECKEL_PFAD"]
            if t <= pf[0][0]:
                b_deckel_t = pf[0][1]
            elif t >= pf[-1][0]:
                b_deckel_t = pf[-1][1]
            else:
                for (j0, q0), (j1, q1) in zip(pf, pf[1:]):
                    if j0 <= t <= j1:
                        b_deckel_t = q0 + (q1 - q0) * (t - j0) / (j1 - j0)
                        break
        if reform and b_deckel_t and t >= p.get("B_DECKEL_AB", 2028) and t > p["BEITRAGSSATZ_FIX_BIS"] and b > b_deckel_t + 1e-9:
            b = b_deckel_t
            rm = p["RUECKLAGE_MIN_MA"] / 12
            aus_max = (reserve + b * L + bz_allg + c0) / (1 + rm)
            ausgleich = min(1.0, aus_max / ausgaben)
            rw *= ausgleich
            renten *= ausgleich
            ausgaben *= ausgleich
            N = niveau(rw, b_prev)
        # H15 „Gaspedal“ (Gutachten 3.1, Schweden „balansering uppåt“): Liegt der GRV-Satz unter B_DECKEL und
        #     wurde die Rentenanpassung früher durch die Automatik gekürzt, wird die Kürzung zuerst zurückgegeben
        #     (höchstens bis der Satz wieder B_DECKEL erreicht), bevor das Konto aufgestockt wird (Teilumwidmung
        #     reagiert auf den Vorjahressatz und greift damit erst danach).
        if (reform and p.get("GASPEDAL") and p.get("B_DECKEL") and t > p["BEITRAGSSATZ_FIX_BIS"]
                and ausgleich == 1.0 and b < b_deckel_t - 1e-9):
            kum = 1.0
            for s_ in hist:
                kum *= hist[s_].get("ausgleich", 1.0)
            if kum < 1.0 - 1e-9:
                up = min(1.0 / kum, 1.0 + (b_deckel_t - b) * L / ausgaben)
                b = min(b_deckel_t, round(b + (up - 1.0) * ausgaben / L, 3))
                ausgleich = up
                rw *= up
                renten *= up
                ausgaben *= up
                N = niveau(rw, b_prev)
        bm = bz_allg + c0
        ein = b * L + bm
        reserve = reserve + ein - ausgaben

        hist[t] = dict(
            jahr=t, lohn=w, k0=k0, p2066=P2066, p67=P67, R_eq=R_eq, BZ_eq=BZ_eq, RQ=RQ,
            rw=rw, rw_s=rw_s, niveau=N, b=b, L=L, L_base=L_base, lohn_jobs=lohn_jobs,
            renten=renten, ausgaben=ausgaben, bundesmittel=bm, bz_allg=bz_allg,
            erstattung=erstattung, reserve=reserve, reserve_ma=reserve / (ausgaben / 12),
            bip=bip, steuern_bund=steuern_bund, bm_quote=bm / steuern_bund,
            rentenalter=67 + d_k, kop_rentner=kop_rentner, r63_rentner=r63_rentner,
            zusatz_erwerbstaetige=eq + kop_work + r63_work, selbst=selbst, abg=abg,
            selbst_beitraege=b * selbst * p["SELBST_ENTGELT"] * w / 1000,
            abg_beitraege=b * abg * p["ABG_ENTGELT"] * w / 1000,
            misch=misch, mr3=mr3, lehrer=lehrer, lehrer_lohnsumme=lehrer * p["H_LEHRER_ENTGELT"] * w / 1000,
            geburten=geb_t, fert_u20=fert_u20, fert_2066=fert_2066, gw_p=gw_p, alo=alo, japan=japan,
            reha=reha, zugangsgarantie=zg, ausgleich=ausgleich, L_big=L_big, ep_big=ep_big,
        )
        # Konto-Regel (Runde 5): 2 % fest, Aufstockung nur bei GRV-Satz < Schwelle (Vorjahr)
        if reform and p.get("KONTO_REGEL"):
            kp = p.setdefault("KONTO_PFAD", {})
            if t < 2028:
                kp[t] = 0.0
            elif t < 2030:
                kp[t] = 0.01
            elif t < 2035:
                kp[t] = 0.02
            else:
                luecke = max(0.0, p["KONTO_SCHWELLE"] - hist[t - 1]["b"])
                if p["KONTO_REGEL"] == "binaer":
                    kp[t] = 0.03 if luecke > 0 else 0.02
                else:
                    kp[t] = 0.02 + min(0.01, round(luecke, 3))

    # ---------- Deutschlandkonto (aggregiert, Kohorten) ----------
    if mit_konto:
        konto = konto_aggregat(p, hist, r_akt, crash)
        for t in hist:
            hist[t].update(konto[t])
    else:
        for t in hist:
            hist[t].update(konto_beitraege=0.0, konto_vermoegen=0.0, konto_auszahlung=0.0,
                           kindergutschrift=0.0, sozialausgleich=0.0, oed_ag=0.0, konto_satz=0.0, startkapital=0.0)
    return hist


def konto_aggregat(p, hist, r_akt, crash):
    """Kohortenmodell: Beiträge gleich verteilt auf Alter 20 … Rentenalter−1,
    Lebenszyklus-Anlage, Verrentung bei Rentenalter (Annuität über fernere LE)."""
    L0 = hist[2026]["L"]
    out = {}
    anspar = {}      # Geburtsjahr → Kapital
    renten = {}      # Geburtsjahr → [Reserve, Annuität, Restjahre]
    for t in sorted(hist):
        satz = konto_satz(p, t)
        w = hist[t]["lohn"]
        konto_ls = p["KONTO_LOHNSUMME_2026"] * (hist[t]["L"] - hist[t].get("L_big", 0.0)) / L0   # Beamte sind in der Konto-Basis schon drin
        beitr_lohn = satz * konto_ls
        gebt = hist[t]["geburten"]
        kj = p["KP_GUTSCHRIFT_JAHRE"] if (p.get("KP_AKTIV") and t >= p["KP_AB"]) else p["KINDER_JAHRE"]
        kinder = gebt * kj * satz * w / 1000 if satz > 0 else 0.0
        _sk = p["KP_STARTKAPITAL"]
        _sk = stufe(_sk, t) if isinstance(_sk, dict) else _sk
        startk = gebt * _sk / 1000 if (p.get("KP_AKTIV") and t >= p["KP_AB"]) else 0.0
        sozial = satz / 2 * p["SOZIALAUSGLEICH_LOHNSUMME"] * w / lohn(p, 2026)
        oed = satz / 2 * p["OED_ANTEIL_LOHNSUMME"] * konto_ls
        # Verteilung auf Kohorten
        ra_t = 67 + int(round(kopplung_delta(p, t, True)))
        alter_aktiv = list(range(20, ra_t))
        je = beitr_lohn / len(alter_aktiv) if satz > 0 else 0.0
        for c in [c for c in anspar if 0 <= t - c < 20]:          # Kinder-Startkapital wächst mit
            ra_c = rentenalter_kohorte(p, c)
            anspar[c] *= 1 + rendite_jahr(p, t, ra_c - (t - c), r_akt, crash)
        if startk:
            anspar[t] = anspar.get(t, 0.0) + startk
        for alter in alter_aktiv:
            c = t - alter
            ra_c = rentenalter_kohorte(p, c)
            r = rendite_jahr(p, t, ra_c - alter, r_akt, crash)
            extra = kinder if alter == 31 else 0.0
            anspar[c] = anspar.get(c, 0.0) * (1 + r) + (je + extra) * (1 + r / 2)
        # Kohorten, die dieses Jahr in Rente gehen (Alter ≥ Rentenalter): verrenten
        for c in list(anspar):
            ra_c = rentenalter_kohorte(p, c)
            if t - c >= ra_c:
                n = round(lebenserwartung_mit(p, t, ra_c))
                kap = anspar.pop(c)
                renten[c] = [kap, annuitaet(kap, p["RENDITE_AUSZAHLUNG"], n), n]
        ausz = 0.0
        for c in list(renten):
            res_, ann, n = renten[c]
            if n <= 0:
                del renten[c]
                continue
            ausz += ann
            renten[c] = [res_ * (1 + p["RENDITE_AUSZAHLUNG"]) - ann, ann, n - 1]
        vermoegen = sum(anspar.values()) + sum(max(0.0, v[0]) for v in renten.values())
        out[t] = dict(konto_satz=satz, konto_beitraege=beitr_lohn + kinder + startk, konto_vermoegen=vermoegen,
                      konto_auszahlung=ausz, kindergutschrift=kinder, sozialausgleich=sozial, oed_ag=oed,
                      startkapital=startk)
    return out


# ---------------------------------------------------------------------------
# Vergleich Reform − Basis: fiskalischer Saldo öffentliche Hand
# ---------------------------------------------------------------------------
def beamtenfonds(p, t_max=None, reform=True):
    """Pflicht-Versorgungsfonds Länder: Zuführung je Neueinstellung ab 2028,
    Auszahlung der Pensionen dieser Jahrgänge aus dem Fonds (real, Mrd)."""
    fonds = 0.0
    out = {}
    t_max = t_max or p["ENDE"]
    for t in range(2026, t_max + 1):
        if t < p["BEAMTENFONDS_AB"]:
            out[t] = dict(zufuehrung=0.0, entlastung=0.0, fonds=0.0, lehrer_pens=0.0)
            continue
        jahrgaenge = range(p["BEAMTENFONDS_AB"], t + 1)
        # Runde 6: Jahrgänge nach BEAMTENFONDS_JG_BIS gehen (teilweise) in die GRV, nur REST_ANTEIL bleibt im Fonds
        jbis = p.get("BEAMTENFONDS_JG_BIS")
        gew = lambda j: 1.0 if (jbis is None or j <= jbis) else p.get("BEAMTENFONDS_REST_ANTEIL", 0.0)
        aktiv = sum(gew(j) for j in jahrgaenge if t - j < p["BEAMTE_DIENSTJAHRE"])
        pens = sum(gew(j) for j in jahrgaenge
                   if p["BEAMTE_DIENSTJAHRE"] <= t - j < p["BEAMTE_DIENSTJAHRE"] + p["BEAMTE_BEZUGSJAHRE"])
        bez = p["BEAMTE_BEZUEGE"] * (1 + p["REALLOHN"]) ** (t - 2026)
        neu = p["BEAMTE_NEU_JE_JAHR"] * (1 - p.get("H_LEHRER_ANTEIL", 0.0))
        satz = stufe(p["H_FONDS_SATZ"], t) if p.get("H_FONDS_SATZ") else p["ZUWEISUNGSSATZ"]
        zuf = aktiv * neu * bez * satz / 1000
        pension = pens * neu * bez * p["BEAMTE_PENSION_QUOTE"] / 1000
        aus_fonds = min(pension, fonds * (1 + p["FONDS_RENDITE_BEAMTE"]) + zuf)
        fonds = fonds * (1 + p["FONDS_RENDITE_BEAMTE"]) + zuf - aus_fonds
        # H5: Pensionen der angestellt statt verbeamtet eingestellten Lehrkräfte entfallen (Basis zahlt sie)
        pens_alle = sum(1 for j in jahrgaenge
                        if p["BEAMTE_DIENSTJAHRE"] <= t - j < p["BEAMTE_DIENSTJAHRE"] + p["BEAMTE_BEZUGSJAHRE"])
        lehrer_pens = pens_alle * p["BEAMTE_NEU_JE_JAHR"] * p.get("H_LEHRER_ANTEIL", 0.0) * bez * p["BEAMTE_PENSION_QUOTE"] / 1000
        out[t] = dict(zufuehrung=zuf, entlastung=aus_fonds, fonds=fonds, lehrer_pens=lehrer_pens)
    return out


def vergleich(p, basis, reform, steuer="vorgelagert"):
    """Jahresweise Saldo-Komponenten (Mrd € real 2026; + = Entlastung der öffentlichen Hand)."""
    bf = beamtenfonds(p)
    rows = {}
    kum = 0.0
    for t in sorted(basis):
        B, R = basis[t], reform[t]
        c = {}
        # 1 Bundesmittel an die GRV (inkl. Haltelinien-Erstattung)
        c["bundesmittel"] = B["bundesmittel"] - R["bundesmittel"]
        # 2 Einkommensteuer: niedrigere RV-Beiträge → höheres zu versteuerndes Einkommen
        c["steuer_beitragssatz"] = p["STEUER_BEITRAGSABZUG"] * (B["b"] - R["b"]) * B["L_base"]
        # 3 Steuer auf GRV-Renten (weniger Renten → weniger Steuer)
        c["steuer_renten"] = steuer_rente(p, t) * (R["renten"] - B["renten"])
        # 4 Steuern aus zusätzlicher Beschäftigung (Erwerbsquote, Rente 63, Kopplung)
        c["steuer_beschaeftigung"] = p["STEUER_ZUSATZJOBS"] * (R["lohn_jobs"] + max(0.0, R["L_base"] - B["L_base"]))
        # H5 angestellte Lehrkräfte: AG-Anteil RV + AV + Zusatzversorgung statt Fonds (Fonds-Effekt in beamtenfonds)
        c["lehrer_angestellt"] = -(R["b"] / 2 + p["H_AG_SV_SONST"] + p["H_ZUSATZVERSORGUNG"]
                                   + p["STEUER_BEITRAGSABZUG"] * (R["b"] / 2 + p["H_AG_SV_SONST"])) * R.get("lehrer_lohnsumme", 0.0)
        # 5 Selbstständige: Beiträge als Sonderausgaben abziehbar; Abgeordnete: AG-Hälfte
        c["selbst_abg"] = -p["STEUER_BEITRAGSABZUG"] * R["selbst_beitraege"] - 0.5 * R["abg_beitraege"]
        # 6 Beamtenversorgung wirkungsgleich (Niveau + Altersgrenze), ab 2032
        pens = interp_dict(p["PENSIONEN_PFAD"], t)
        if t >= p["PENSION_WIRKUNG_AB"]:
            if p.get("PENSION_GRV_FORMEL"):
                b0_ = reform[p["PENSION_WIRKUNG_AB"] - 1]
                ratio_n = (R["rw"] / b0_["rw"]) / (R["lohn"] / b0_["lohn"])
            else:
                ratio_n = R["rw"] / B["rw"]
            kop_anteil = R["kop_rentner"] * p["KOPPLUNG_RENTE_AEQ"] / B["R_eq"]
            c["pensionen"] = pens * (1 - ratio_n * R.get("misch", 1.0) * (1 - kop_anteil))
        else:
            c["pensionen"] = 0.0
        # 7 Pflicht-Versorgungsfonds Länder: Zuführung (−), später Entlastung (+)
        c["beamtenfonds"] = -bf[t]["zufuehrung"] + bf[t]["entlastung"]
        c["lehrer_angestellt"] += bf[t]["lehrer_pens"]
        # 8 Deutschlandkonto: Kindergutschrift, Sozialausgleich, Staat als Arbeitgeber
        c["kindergutschrift"] = -R["kindergutschrift"]
        c["sozialausgleich"] = -R["sozialausgleich"]
        c["oed_arbeitgeber"] = -R["oed_ag"]
        # Runde 5: Kinder-Paket, Finanzierung, Fertilität, Arbeitslosigkeit Älterer
        kp = p.get("KP_AKTIV") and t >= p["KP_AB"]
        c["kp_startkapital"] = -R.get("startkapital", 0.0)
        c["kp_praemie"] = -R["geburten"] * p["KP_PRAEMIE_JE_GEBURT"] / 1000 if kp else 0.0
        c["kp_kita_ivf_zins"] = -(p["KP_KITA"] * min(1.0, max(0.0, (t - 2026) / 6)) + p["KP_IVF"] + p["KP_ZINSAUSGLEICH"]) if kp else 0.0
        c["fert_kinderkosten"] = -R["fert_u20"] * p["FERT_KOSTEN_KIND"] / 1000
        if p.get("ERB_MODUS") == "diw" and t >= 2028:
            c["erbschaft"] = (p["ERB_DIW_LANG"] - p["ERB_DIW_UNTERNEHMEN"]) + p["ERB_DIW_UNTERNEHMEN"] * min(1.0, (t - 2027) / p["ERB_STUNDUNG_JAHRE"])
        elif p.get("ERB_MODUS") == "auftrag12" and t >= 2028:
            c["erbschaft"] = 12.0
        else:
            c["erbschaft"] = 0.0
        c["realsplitting"] = (p["REALSPLIT_END"] * min(1.0, max(0.0, (t - p["REALSPLIT_AB"] + 1) / (p["REALSPLIT_VOLL"] - p["REALSPLIT_AB"] + 1)))
                              if p.get("REALSPLIT_AKTIV") else 0.0)
        c["alo_aeltere"] = -R.get("alo", 0.0) * p["ALO_KOSTEN"] / 1000
        # 9 Steuerwirkung Konto
        if steuer == "nachgelagert":
            c["konto_steuer"] = (-p["STEUER_GRENZ_BEITRAG"] * (R["konto_beitraege"] - R["kindergutschrift"] - R.get("startkapital", 0.0))
                                 + p["STEUER_AUSZAHLUNG"] * R["konto_auszahlung"])
        else:
            c["konto_steuer"] = 0.0
        # 10 Grundsicherung im Alter (Bund): Δ Gesamtniveau des Rentenbestands
        gs = p["GRUNDSICHERUNG_2026"] * (R["p67"] / reform[2026]["p67"]) * (R["lohn"] / reform[2026]["lohn"])
        konto_add = R["konto_auszahlung"] / R["renten"] * R["niveau"] if R["renten"] else 0.0
        d_pkt = 100 * (R["niveau"] * R.get("misch", 1.0) + konto_add - B["niveau"])
        c["grundsicherung"] = gs * p["GRUNDSICHERUNG_ELAST"] * d_pkt
        saldo = sum(c.values())
        zins = kum * p["ZINS_REAL"]
        kum += saldo + zins
        c["saldo_ohne_zins"] = saldo
        c["zinseffekt"] = zins
        c["saldo_mit_zins"] = saldo + zins
        c["nominal_faktor"] = (1 + p["INFLATION"]) ** (t - 2026)
        # nachrichtlich: Entlastung der Beitragszahler (GRV), nicht im Saldo
        c["grv_beitragszahler_entlastung"] = (B["b"] - R["b"]) * B["L_base"]
        c["beamtenfonds_vermoegen"] = bf[t]["fonds"]
        rows[t] = c
    return rows


# ---------------------------------------------------------------------------
# Kalibrierung auf RVB 2025 (Beitragssatz 20,1 % in 2030, 21,2 % in 2039)
# ---------------------------------------------------------------------------
def kalibriere(p):
    ziele = p["KALIB_ZIELE"]

    def fehler(q, th):
        pp = dict(p, q_trend=q, theta=th)
        s = simuliere(pp, reform=False, mit_konto=False)
        return sum((s[j]["b"] - z) ** 2 for j, z in ziele.items())

    best = None
    for qi in range(-10, 31):
        q = qi * 0.0005
        for ti in range(25, 76):
            th = ti * 0.02
            f = fehler(q, th)
            if best is None or f < best[0]:
                best = (f, q, th)
    # Verfeinern
    f, q, th = best
    for _ in range(3):
        step_q, step_t = 0.0001, 0.004
        for dq in (-2, -1, 0, 1, 2):
            for dt in (-2, -1, 0, 1, 2):
                ff = fehler(q + dq * step_q, th + dt * step_t)
                if ff < f:
                    f, q2, t2 = ff, q + dq * step_q, th + dt * step_t
                    q, th = q2, t2
    return q, th, math.sqrt(f / len(ziele))
