"""
Amanah — five-year portfolio model, three scenarios.

DISCIPLINE (this is the whole point of the file)
------------------------------------------------
Every input below is one of:
  SOURCED  — a published figure with a citation id in ../_research/CITATIONS.json
  DECISION — a number the founders choose (a salary, a fee, a target). Not a claim
             about the world. Labelled as a decision so nobody reads it as research.
  DERIVED  — computed here from SOURCED and DECISION inputs by the code below.
  MEASURE  — cannot honestly be known before the first contracts run. Modelled as a
             range and flagged on the slide as something to measure, not assume.

No input is a "class typical". If it is not sourced and not a decision, it is a
MEASURE and the deck says so.

MECHANICS
---------
A murabaha is a sale, not a loan: Amanah buys the equipment and resells it at a
disclosed markup payable in instalments. Economically the profit accrues on the
declining unpaid balance, so the portfolio is modelled as a monthly cohort engine —
originate each month, amortise each cohort over its term, accrue profit on the
outstanding balance. That is arithmetic, not an assumption.
"""
import json, pathlib

# ── SOURCED anchors (ids refer to CITATIONS.json) ────────────────────────────
SOURCED = {
    "prime_rate":            (0.0675, "C9",  "US bank prime loan rate, 9 Sep 2026"),
    "sofr":                  (0.0362, "C10", "SOFR, 10 Sep 2026"),
    "bbb_corp_yield":        (0.0586, "C11", "ICE BofA BBB US corporate effective yield, 10 Sep 2026"),
    "elfa_small_ticket_co":  (0.0072, "C14", "ELFA small-ticket charge-off rate, June 2026"),
    "elfa_industry_co":      (0.0054, "C14", "ELFA industry-wide charge-off rate, June 2026"),
    "bank_ci_chargeoff":     (0.0058, "C13", "Fed net charge-off rate, business loans, Q2 2026"),
    "usury_ceiling":         (0.25,   "C3",  "Michigan criminal-usury ceiling, MCL 438.41"),
    "sales_tax":             (0.06,   "C5",  "Michigan sales tax rate, MCL 205.52(1)"),
    "ucc_file_fee":          (15.0,   "C19", "UCC-1 filing fee, MCL 440.9525(1)"),
    "ucc_search_fee":        (6.0,    "C19", "UCC search fee, MCL 440.9525(2)"),
}

# ── Scenarios ────────────────────────────────────────────────────────────────
# Each entry carries its class so the deck can print it beside the number.
SCENARIOS = {
 "pessimistic": {
   "label": "Pessimistic",
   "capital_y1":        (1_500_000, "DECISION", "seed equity actually raised in year one"),
   "capital_added":     ([0, 0, 500_000, 0, 500_000], "DECISION", "further equity by year"),
   "profit_rate":       (0.095, "MEASURE", "price the market will bear, below plan"),
   "avg_ticket":        (60_000, "MEASURE", "deals land at the small end"),
   "term_months":       (36, "DECISION", "standard contract term"),
   "deals_per_month":   ([1, 2, 3, 4, 4], "MEASURE", "origination pace by year"),
   "utilisation":       (0.60, "MEASURE", "share of capital sitting in earning contracts"),
   "net_loss_rate":     (0.030, "MEASURE", "annual net loss on average earning assets — 4.2x the ELFA small-ticket rate"),
   "opex_y1":           (395_000, "DECISION", "year-one operating budget"),
   "opex_growth":       (0.08, "DECISION", "annual operating cost growth"),
   "origination_cost":  (900, "DECISION", "direct cost to close one contract"),
   "prepay_rate":       (0.06, "MEASURE", "annual share of balances settled early"),
   "doc_fee":           (750, "DECISION", "documentation fee per contract — must be actual cost recovery, not a disguised return"),
   "leverage":          (0.0, "DECISION", "no funding facility — all equity"),
   "cost_of_funding":   (0.075, "MEASURE", "profit rate payable on a Shariah-compliant funding facility"),
 },
 "base": {
   "label": "Base",
   "capital_y1":        (2_500_000, "DECISION", "seed equity actually raised in year one"),
   "capital_added":     ([0, 1_500_000, 2_000_000, 2_000_000, 2_000_000], "DECISION", "further equity by year"),
   "profit_rate":       (0.115, "MEASURE", "prime + 4.75 points; ceiling is 25% by statute"),
   "avg_ticket":        (95_000, "MEASURE", "midpoint of the stated 25k-250k band, weighted low"),
   "term_months":       (36, "DECISION", "standard contract term"),
   "deals_per_month":   ([2, 4, 7, 10, 13], "MEASURE", "origination pace by year"),
   "utilisation":       (0.80, "MEASURE", "share of capital sitting in earning contracts"),
   "net_loss_rate":     (0.014, "MEASURE", "annual net loss — about 2x the ELFA small-ticket rate, for a new book"),
   "opex_y1":           (395_000, "DECISION", "year-one operating budget"),
   "opex_growth":       (0.12, "DECISION", "annual operating cost growth"),
   "origination_cost":  (750, "DECISION", "direct cost to close one contract"),
   "prepay_rate":       (0.08, "MEASURE", "annual share of balances settled early"),
   "doc_fee":           (750, "DECISION", "documentation fee per contract — must be actual cost recovery, not a disguised return"),
   "leverage":          (1.0, "DECISION", "funding facility at 1x equity — from YEAR THREE, not year two (R03)"),
   "cost_of_funding":   (0.081, "MEASURE", "SOFR 3.62% + 450bp. R03: a first-time book cannot borrow at prime + 25bp"),
 },
 "optimistic": {
   "label": "Optimistic",
   "capital_y1":        (4_000_000, "DECISION", "seed equity actually raised in year one"),
   "capital_added":     ([0, 3_000_000, 4_000_000, 5_000_000, 6_000_000], "DECISION", "further equity by year"),
   "profit_rate":       (0.125, "MEASURE", "prime + 5.75 points; ceiling is 25% by statute"),
   "avg_ticket":        (120_000, "MEASURE", "deals land toward the upper half of the band"),
   "term_months":       (36, "DECISION", "standard contract term"),
   "deals_per_month":   ([3, 7, 12, 17, 22], "MEASURE", "origination pace by year"),
   "utilisation":       (0.90, "MEASURE", "share of capital sitting in earning contracts"),
   "net_loss_rate":     (0.008, "MEASURE", "annual net loss — close to the ELFA small-ticket rate"),
   "opex_y1":           (395_000, "DECISION", "year-one operating budget"),
   "opex_growth":       (0.15, "DECISION", "annual operating cost growth"),
   "origination_cost":  (650, "DECISION", "direct cost to close one contract"),
   "prepay_rate":       (0.10, "MEASURE", "annual share of balances settled early"),
   "doc_fee":           (750, "DECISION", "documentation fee per contract — must be actual cost recovery, not a disguised return"),
   "leverage":          (2.0, "DECISION", "facility at 2x equity from year three"),
   "cost_of_funding":   (0.071, "MEASURE", "SOFR 3.62% + 350bp — the best a seasoned book might reach"),
 },
}

def v(x):
    """unwrap (value, class, note) tuples"""
    return x[0] if isinstance(x, tuple) else x

def run(s, years=5):
    ticket   = v(s["avg_ticket"]);   term = v(s["term_months"])
    rate     = v(s["profit_rate"]);  util = v(s["utilisation"])
    loss     = v(s["net_loss_rate"]); prepay = v(s["prepay_rate"])
    dpm      = v(s["deals_per_month"]); origcost = v(s["origination_cost"])
    doc_fee  = v(s["doc_fee"]); lev = v(s["leverage"]); cof = v(s["cost_of_funding"])
    capital  = v(s["capital_y1"]); added = v(s["capital_added"])
    opex     = v(s["opex_y1"]); opexg = v(s["opex_growth"])

    months = years * 12
    cohorts = []          # (remaining_balance, monthly_principal, months_left)
    out = [{"originations_n":0,"originations_$":0.0,"profit_income":0.0,"losses":0.0,
            "opex":0.0,"orig_cost":0.0,"avg_earning":0.0,"capital":0.0,
            "fee_income":0.0,"funding_cost":0.0,"avg_debt":0.0} for _ in range(years)]
    equity = capital
    monthly_rate = rate / 12.0
    monthly_loss = loss / 12.0
    monthly_prepay = prepay / 12.0

    for m in range(months):
        y = m // 12
        if m % 12 == 0:
            equity += added[y] if y < len(added) else 0
            out[y]["capital"] = equity
            opex_y = opex * ((1 + opexg) ** y)
        # capacity check — you cannot deploy capital you do not have
        outstanding = sum(c[0] for c in cohorts)
        # leverage from YEAR THREE. R03: nobody underwrites a warehouse line against a
        # book with less than two years of performance data.
        facility = equity * lev if y >= 2 else 0.0
        room = max(0.0, (equity + facility) * util - outstanding)
        want = dpm[y] if y < len(dpm) else dpm[-1]
        can  = int(min(want, room // ticket))
        if can > 0:
            cohorts.append([can * ticket, (can * ticket) / term, term])
            out[y]["originations_n"] += can
            out[y]["originations_$"] += can * ticket
            out[y]["orig_cost"]      += can * origcost
            out[y]["fee_income"]     += can * doc_fee
        # accrue on declining balances, amortise, apply loss and prepayment
        bal_start = sum(c[0] for c in cohorts)
        out[y]["profit_income"] += bal_start * monthly_rate
        out[y]["losses"]        += bal_start * monthly_loss
        out[y]["avg_earning"]   += bal_start / 12.0
        drawn = max(0.0, bal_start - equity * util)      # facility is drawn only above equity capacity
        drawn = min(drawn, facility)
        out[y]["funding_cost"]  += drawn * (cof / 12.0)
        out[y]["avg_debt"]      += drawn / 12.0
        alive = []
        for c in cohorts:
            c[0] -= c[1]                      # scheduled principal
            c[0] -= c[0] * monthly_prepay     # early settlement
            c[2] -= 1
            if c[2] > 0 and c[0] > 1:
                alive.append(c)
        cohorts = alive
        out[y]["opex"] = opex_y

    rows = []
    cum = 0.0
    for y in range(years):
        o = out[y]
        pretax = (o["profit_income"] + o["fee_income"] - o["losses"]
                  - o["opex"] - o["orig_cost"] - o["funding_cost"])
        cum += pretax
        rows.append({
            "year": y + 1,
            "capital": round(o["capital"]),
            "deals": o["originations_n"],
            "originated": round(o["originations_$"]),
            "avg_earning_assets": round(o["avg_earning"]),
            "profit_income": round(o["profit_income"]),
            "fee_income": round(o["fee_income"]),
            "funding_cost": round(o["funding_cost"]),
            "avg_debt": round(o["avg_debt"]),
            "credit_losses": round(o["losses"]),
            "opex": round(o["opex"]),
            "origination_cost": round(o["orig_cost"]),
            "pretax": round(pretax),
            "cumulative_pretax": round(cum),
            "return_on_equity": round(pretax / o["capital"], 4) if o["capital"] else 0.0,
            "yield_on_assets": round(o["profit_income"] / o["avg_earning"], 4) if o["avg_earning"] else 0.0,
        })
    return rows

def breakeven_month(s):
    """first month in which trailing-12 pretax turns positive — DERIVED, not claimed"""
    rows = run(s, years=5)
    for r in rows:
        if r["pretax"] > 0:
            return r["year"]
    return None

if __name__ == "__main__":
    result = {"sourced": {k: {"value": a, "citation": b, "note": c} for k, (a, b, c) in SOURCED.items()},
              "scenarios": {}}
    for key, s in SCENARIOS.items():
        rows = run(s)
        result["scenarios"][key] = {
            "label": s["label"],
            "inputs": {k: {"value": v(val), "class": val[1], "note": val[2]}
                       for k, val in s.items() if isinstance(val, tuple)},
            "years": rows,
            "first_profitable_year": breakeven_month(s),
            "five_year_pretax": rows[-1]["cumulative_pretax"],
        }
    pathlib.Path("model-output.json").write_text(json.dumps(result, indent=1))
    for k, sc in result["scenarios"].items():
        print(f"\n=== {sc['label']}  (first profitable year: {sc['first_profitable_year']})")
        print(f"{'yr':>3} {'capital':>11} {'deals':>6} {'originated':>12} {'avg assets':>12} {'profit':>10} {'losses':>9} {'opex':>9} {'pretax':>11} {'cum':>12} {'ROE':>7}")
        for r in sc["years"]:
            print(f"{r['year']:>3} {r['capital']:>11,} {r['deals']:>6} {r['originated']:>12,} "
                  f"{r['avg_earning_assets']:>12,} {r['profit_income']:>10,} {r['credit_losses']:>9,} "
                  f"{r['opex']:>9,} {r['pretax']:>11,} {r['cumulative_pretax']:>12,} {r['return_on_equity']*100:>6.1f}%")

# ── DERIVED diagnostics — the numbers that decide whether this is a business ──
def breakeven_earning_assets(s, extra_opex=0.0):
    """Earning assets required before the book pays for its own overhead.

    CORRECTED 2026-09-14 after council round R03. The first version was
    opex / (profit rate - loss rate), which omitted two real costs and
    understated the answer by about a third:

      * the funding cost on the share of assets funded by the facility
        rather than by equity, and
      * nothing about utilisation — but note the answer is expressed in
        EARNING ASSETS, so the utilisation drag belongs in the capital
        required to carry them, reported separately below.

    Two lineages (Google, Mistral) raised the funding omission independently.
    """
    y = v(s["profit_rate"]); l = v(s["net_loss_rate"]); o = v(s["opex_y1"]) + extra_opex
    lev = v(s["leverage"]); cof = v(s["cost_of_funding"])
    debt_share = lev / (1.0 + lev) if lev > 0 else 0.0     # of earning assets
    net_yield = y - l - debt_share * cof
    return o / net_yield if net_yield > 0 else None

def capital_required(s, extra_opex=0.0):
    """Capital needed to CARRY the break-even book: earning assets are only a
    fraction of capital deployed, and the facility supplies part of them."""
    ea = breakeven_earning_assets(s, extra_opex)
    if ea is None: return None
    lev = v(s["leverage"]); util = v(s["utilisation"])
    equity_share = 1.0 / (1.0 + lev) if lev > 0 else 1.0
    return ea * equity_share / util

def net_yield(s):
    y = v(s["profit_rate"]); l = v(s["net_loss_rate"])
    lev = v(s["leverage"]); cof = v(s["cost_of_funding"])
    debt_share = lev / (1.0 + lev) if lev > 0 else 0.0
    return y - l - debt_share * cof

def sensitivity(base_key="base", years=5):
    """One variable at a time — L-9: never move two and attribute to one."""
    import copy
    base = SCENARIOS[base_key]
    ref = run(base, years)[-1]["pretax"]
    moves = {
        "profit_rate":     [-0.02, +0.02],
        "net_loss_rate":   [+0.02, -0.006],
        "utilisation":     [-0.20, +0.10],
        "opex_y1":         [+100_000, -100_000],
        "avg_ticket":      [-30_000, +30_000],
        "cost_of_funding": [+0.03, -0.02],
        "leverage":        [-1.0, +1.0],
    }
    rows = []
    for k, deltas in moves.items():
        line = {"driver": k, "base_value": v(base[k]), "reference_pretax_y5": round(ref)}
        for i, d in enumerate(deltas):
            s2 = copy.deepcopy(base)
            val = v(s2[k]) + d
            if k == "leverage": val = max(0.0, val)
            s2[k] = (val, s2[k][1], s2[k][2])
            line[f"{'down' if i == 0 else 'up'}_value"] = round(val, 4)
            line[f"{'down' if i == 0 else 'up'}_pretax_y5"] = round(run(s2, years)[-1]["pretax"])
        rows.append(line)
    rows.sort(key=lambda r: -abs(r["up_pretax_y5"] - r["down_pretax_y5"]))
    return rows
