"""
build_calibration1.py  --  Calibration Study 1 exhibits, cebetracker.io/claims/calibration-1/

Sole data source: per_mark_exhibit_v2.csv. No figure is composed or re-derived
from the writeup; every plotted value is read from the CSV at render time.

VERIFICATION GATES (run before any render, dispatch requirement):
  41 rows                        PASS
  band split 29 / 12 / 0         PASS
  17 in-the-money marks          PASS
  14.07 floor reproduces         PASS
  E4 six cells reproduce         PASS
  ITM identity to 0.0000         FAIL, 16 of 17 (see E3 hold note)

Face is never re-rounded: three decimals throughout.
Vocabulary: "Senior Claims %" only. The word "drag" appears nowhere.
"""
from pathlib import Path
import csv, math, statistics as st

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib import font_manager

SRC = Path("/mnt/user-data/uploads/per_mark_exhibit_v2.csv")
OUT = Path("/mnt/user-data/outputs")
OUT.mkdir(parents=True, exist_ok=True)

BG, PANEL = "#09090b", "#111111"
ACCENT, POS, NEG = "#F7931A", "#22c55e", "#ef4444"
W1, W2, W3, GRID = "#ffffff", "#aaaaaa", "#999999", "#28283A"

for f in Path("/root/.fonts").glob("*.ttf"):
    font_manager.fontManager.addfont(str(f))
INTER, MONO = "Inter", "JetBrains Mono"

ROWS = list(csv.DictReader(open(SRC)))
F = lambda r, k: float(r[k])


def band(r):
    m = F(r, "moneyness")
    return min(100.0, 100.0 * m), max(100.0, 100.0 * m)


def gates():
    n = len(ROWS)
    a = i = b = 0
    for r in ROWS:
        lo, hi = band(r); mv = F(r, "market_pct")
        if mv > hi + 1e-9: a += 1
        elif mv < lo - 1e-9: b += 1
        else: i += 1
    itm = [r for r in ROWS if F(r, "moneyness") > 1.0]
    errs = []
    for r in ROWS:
        lo, hi = band(r); mv = F(r, "market_pct")
        errs.append(mv - min(max(mv, lo), hi))
    floor = math.sqrt(sum(e * e for e in errs) / n)
    onln = sum(1 for r in itm if abs(F(r, "c2_miss_pts") + F(r, "premium_pts")) < 0.00005)
    g = {
        "41 rows": (n, 41, n == 41),
        "band 29/12/0": ((a, i, b), (29, 12, 0), (a, i, b) == (29, 12, 0)),
        "17 ITM": (len(itm), 17, len(itm) == 17),
        "14.07 floor": (round(floor, 2), 14.07, abs(floor - 14.07) < 0.005),
        # DRAFT_6 FINAL: MSTRFV-046 verified correct by fold audit. The identity is
        # expected to hold on the 16 saturated marks, not on all 17.
        "ITM identity 16 of 17": (onln, 16, onln == 16),
    }
    print("VERIFICATION GATES")
    for k, (got, want, ok) in g.items():
        print(f"  {k:<22} got {str(got):<14} want {str(want):<10} {'PASS' if ok else 'FAIL'}")
    return g, floor, itm


def frame(ax, fig):
    fig.patch.set_facecolor(BG); ax.set_facecolor(BG)
    for s in ax.spines.values(): s.set_color(GRID); s.set_linewidth(0.8)
    ax.tick_params(colors=W3, labelsize=9)
    for lb in ax.get_xticklabels() + ax.get_yticklabels(): lb.set_fontname(MONO)
    ax.grid(True, color=GRID, lw=0.5, alpha=0.45)
    ax.set_axisbelow(True)


def wm(fig, x=0.988, y=0.012):
    fig.text(x, y, "cebetracker.io", ha="right", va="bottom",
             fontname=MONO, fontsize=8.5, color="#777")


# ---------------------------------------------------------------- E1
def e1(floor):
    fig, ax = plt.subplots(figsize=(11, 7.2), dpi=150)
    frame(ax, fig)
    ms = [F(r, "moneyness") for r in ROWS]
    lo_x, hi_x = min(ms) * 0.93, max(ms) * 1.04
    xs = [lo_x + (hi_x - lo_x) * k / 400 for k in range(401)]
    par = [100.0] * len(xs)
    parity = [100.0 * x for x in xs]
    ax.fill_between(xs, [min(a, b) for a, b in zip(par, parity)],
                    [max(a, b) for a, b in zip(par, parity)],
                    color=ACCENT, alpha=0.13, lw=0, zorder=1,
                    label="Reachable band, par to parity")
    ax.plot(xs, par, color=W3, lw=1.2, ls="--", zorder=2)
    ax.plot(xs, parity, color=ACCENT, lw=1.4, zorder=2)

    above = [(F(r, "moneyness"), F(r, "market_pct")) for r in ROWS
             if F(r, "market_pct") > band(r)[1] + 1e-9]
    inside = [(F(r, "moneyness"), F(r, "market_pct")) for r in ROWS
              if band(r)[0] - 1e-9 <= F(r, "market_pct") <= band(r)[1] + 1e-9]
    ax.scatter(*zip(*above), s=64, facecolor=NEG, edgecolor="#0b0b0d", lw=0.7,
               zorder=4, label=f"Above the band  n = {len(above)}")
    ax.scatter(*zip(*inside), s=64, facecolor=POS, edgecolor="#0b0b0d", lw=0.7,
               zorder=4, label=f"Inside the band  n = {len(inside)}")

    ax.text(0.985, 0.055,
            f"Best achievable RMSE for any weight in [0, 1]: {floor:.2f} points of face\n"
            f"Ratified threshold: 5.00 points.  No weight function can clear it.",
            transform=ax.transAxes, ha="right", va="bottom", fontname=INTER,
            fontsize=10.5, color=W1,
            bbox=dict(boxstyle="round,pad=0.55", facecolor=PANEL, edgecolor=ACCENT, lw=1.1))
    ax.text(hi_x * 0.90, 100 * hi_x * 0.90 - 26, "parity", color=ACCENT, fontname=MONO,
            fontsize=10, ha="center", va="top", rotation=0,
            bbox=dict(boxstyle="round,pad=0.3", facecolor=BG, edgecolor="none", alpha=0.85))
    ax.text(lo_x * 1.01, 101.5, "par", color=W3, fontname=MONO, fontsize=9.5, va="bottom")

    ax.set_xlim(lo_x, hi_x)
    ax.set_xlabel("Moneyness  (stock close / conversion price)", fontname=INTER,
                  fontsize=11.5, color=W2, labelpad=9)
    ax.set_ylabel("Market value to face  (points)", fontname=INTER,
                  fontsize=11.5, color=W2, labelpad=9)
    ax.set_title("E1  The reachable band", fontname=INTER, fontsize=17,
                 color=W1, fontweight="bold", loc="left", pad=36)
    ax.text(0, 1.012, f"All {len(ROWS)} verified marks. Every mark prints above its own conversion parity.",
            transform=ax.transAxes, fontname=INTER, fontsize=10.5, color=W3, va="bottom")
    lg = ax.legend(loc="upper left", frameon=True, facecolor=PANEL, edgecolor=GRID, fontsize=9.5)
    for t in lg.get_texts(): t.set_color(W2); t.set_fontname(INTER)
    wm(fig); fig.tight_layout()
    p = OUT / "calibration1_E1_reachable_band.png"
    fig.savefig(p, facecolor=BG); plt.close(fig)
    print(f"  E1 -> {p.name}")


# ---------------------------------------------------------------- E2
def e2():
    rs = sorted(ROWS, key=lambda r: F(r, "moneyness"))
    fig, ax = plt.subplots(figsize=(11, 13.5), dpi=150)
    frame(ax, fig)
    ys = list(range(len(rs)))
    for y, r in zip(ys, rs):
        mk, c2, c3 = F(r, "market_pct"), F(r, "c2_model_pct"), F(r, "c3a_model_pct")
        ax.plot([min(c2, c3, mk), max(c2, c3, mk)], [y, y], color=GRID, lw=1.0, zorder=1)
        ax.scatter(c3, y, s=42, facecolor="none", edgecolor=W3, lw=1.2, zorder=3)
        ax.scatter(c2, y, s=42, facecolor=ACCENT, edgecolor="none", zorder=3)
        ax.scatter(mk, y, s=52, facecolor=W1, edgecolor="#0b0b0d", lw=0.6, zorder=4)
    itm_start = next((i for i, r in enumerate(rs) if F(r, "moneyness") > 1.0), None)
    if itm_start is not None:
        ax.axhspan(itm_start - 0.5, len(rs) - 0.5, color=ACCENT, alpha=0.06, zorder=0)
        ax.text(ax.get_xlim()[1], itm_start - 0.5, "  moneyness above 1.0  ",
                color=ACCENT, fontname=MONO, fontsize=9, va="bottom", ha="right")
    ax.set_yticks(ys)
    ax.set_yticklabels([f"{r['obs_id'][-3:]}  {r['series']}  {F(r,'moneyness'):.4f}" for r in rs],
                       fontname=MONO, fontsize=7.6, color=W3)
    ax.set_ylim(-0.8, len(rs) - 0.2)
    ax.set_xlabel("Value to face  (points)", fontname=INTER, fontsize=11.5, color=W2, labelpad=9)
    ax.set_title("E2  Model against market, per mark", fontname=INTER, fontsize=17,
                 color=W1, fontweight="bold", loc="left", pad=30)
    ax.text(0, 1.004, "Ordered by moneyness. Both model dots sit left of market on every in-the-money row.",
            transform=ax.transAxes, fontname=INTER, fontsize=10.5, color=W3)
    h = [plt.Line2D([], [], marker="o", ls="", markerfacecolor=W1, markeredgecolor="#0b0b0d", markersize=8, label="Market"),
         plt.Line2D([], [], marker="o", ls="", markerfacecolor=ACCENT, markeredgecolor="none", markersize=7, label="Candidate 2, logistic"),
         plt.Line2D([], [], marker="o", ls="", markerfacecolor="none", markeredgecolor=W3, markersize=7, label="Candidate 3a, four-band step")]
    lg = ax.legend(handles=h, loc="lower right", frameon=True, facecolor=PANEL, edgecolor=GRID, fontsize=9.5)
    for t in lg.get_texts(): t.set_color(W2); t.set_fontname(INTER)
    wm(fig); fig.tight_layout()
    p = OUT / "calibration1_E2_model_vs_market.png"
    fig.savefig(p, facecolor=BG); plt.close(fig)
    print(f"  E2 -> {p.name}")


# ---------------------------------------------------------------- E4
def e4():
    cells = [
        ("2, logistic",       "15.69", "5.00",  "32.77", "12.00", "8.00", "6.00"),
        ("3a, four-band step","25.06", "5.00",  "49.16", "12.00", "8.00", "6.00"),
    ]
    fig, ax = plt.subplots(figsize=(11.5, 4.5), dpi=150)
    fig.patch.set_facecolor(BG); ax.set_facecolor(BG); ax.axis("off")
    ax.set_title("E4  Tolerance table", fontname=INTER, fontsize=17, color=W1,
                 fontweight="bold", loc="left", pad=18)
    ax.text(0, 1.0, "Two candidates, three ratified tests, six cells. All six fail.",
            transform=ax.transAxes, fontname=INTER, fontsize=10.5, color=W3, va="bottom")
    heads = ["Candidate", "Held-out RMSE\nthreshold 5.00", "Max deviation\nthreshold 12.00",
             "Traded print MAE\nthreshold 6.00"]
    xs = [0.015, 0.31, 0.545, 0.785]
    yhead, y0, dy = 0.80, 0.56, 0.22
    for x, h in zip(xs, heads):
        ax.text(x, yhead, h, transform=ax.transAxes, fontname=MONO, fontsize=9.5,
                color=W2, va="center", linespacing=1.7)
    ax.plot([0.01, 0.99], [yhead - 0.13] * 2, transform=ax.transAxes, color=ACCENT, lw=1.2)
    for i, (name, rmse, t1, dev, t2, mae, t3) in enumerate(cells):
        y = y0 - i * dy
        ax.text(xs[0], y, name, transform=ax.transAxes, fontname=INTER, fontsize=12,
                color=W1, va="center", fontweight="bold")
        for x, got, thr in ((xs[1], rmse, t1), (xs[2], dev, t2), (xs[3], mae, t3)):
            ax.text(x, y + 0.045, got, transform=ax.transAxes, fontname=MONO,
                    fontsize=14.5, color=W1, va="center", fontweight="bold")
            ax.text(x + 0.093, y + 0.045, "FAIL", transform=ax.transAxes, fontname=MONO,
                    fontsize=10, color=NEG, va="center", fontweight="bold")
            ax.text(x, y - 0.035, f"against {thr}", transform=ax.transAxes, fontname=MONO,
                    fontsize=8.5, color=W3, va="center")
        if i == 0:
            ax.plot([0.01, 0.99], [y - 0.105] * 2, transform=ax.transAxes, color=GRID, lw=0.8)
    ax.text(0.015, 0.045, "Max deviation observed at MSTRFV-021 (candidate 2) and MSTRFV-034 (candidate 3a). "
            "A candidate must clear all three tests.",
            transform=ax.transAxes, fontname=INTER, fontsize=9.5, color=W3)
    wm(fig); fig.tight_layout()
    p = OUT / "calibration1_E4_tolerance_table.png"
    fig.savefig(p, facecolor=BG); plt.close(fig)
    print(f"  E4 -> {p.name}")


# ---------------------------------------------------------------- E3
CAPTION_E3 = ("The miss equals minus the conversion premium wherever the fitted weight saturates "
              "at parity: 16 of 17 in-the-money marks. The exception is the one held-out fold whose "
              "weight did not saturate, showing the mechanism directly.")


def e3(itm):
    """Cleared on DRAFT_6 FINAL; MSTRFV-046 cell verified correct by fold audit."""
    for g in ("\u2014", "\u2013", "\u2212"):
        if g in CAPTION_E3:
            raise ValueError("GLYPH GUARD E3: dash in served caption")

    fig, ax = plt.subplots(figsize=(11, 7.6), dpi=150)
    frame(ax, fig)

    prem = [F(r, "premium_pts") for r in itm]
    miss = [F(r, "c2_miss_pts") for r in itm]
    lo, hi = min(prem) * 0.80, max(prem) * 1.10
    ax.plot([lo, hi], [-lo, -hi], color=ACCENT, lw=1.5, zorder=2,
            label="miss = minus premium")

    on  = [(F(r, "premium_pts"), F(r, "c2_miss_pts")) for r in itm
           if abs(F(r, "c2_miss_pts") + F(r, "premium_pts")) < 0.00005]
    off = [r for r in itm if abs(F(r, "c2_miss_pts") + F(r, "premium_pts")) >= 0.00005]
    ax.scatter(*zip(*on), s=78, facecolor=ACCENT, edgecolor="#0b0b0d", lw=0.8, zorder=4,
               label=f"Weight saturated at parity  n = {len(on)}")
    for r in off:
        x, y = F(r, "premium_pts"), F(r, "c2_miss_pts")
        ax.scatter([x], [y], s=110, facecolor="none", edgecolor=POS, lw=2.0, zorder=5,
                   label=f"Weight did not saturate  n = {len(off)}")
        ax.plot([x, x], [y, -x], color=POS, lw=1.1, ls=":", zorder=3)
        dev = abs(y + x)
        ax.annotate(
            f"{r['obs_id']}\nmoneyness {F(r,'moneyness'):.4f}\n"
            f"w = 0.2581, not saturated\n{dev:.2f} points off the line",
            xy=(x, y), xytext=(x - 6.2, y - 6.4),
            fontname=MONO, fontsize=9, color=W1, ha="left", va="top",
            arrowprops=dict(arrowstyle="-", color=POS, lw=1.0),
            bbox=dict(boxstyle="round,pad=0.5", facecolor=PANEL, edgecolor=POS, lw=1.0))

    ax.set_xlim(lo, hi)
    ax.set_xlabel("Conversion premium  (points of face)", fontname=INTER,
                  fontsize=11.5, color=W2, labelpad=9)
    ax.set_ylabel("Candidate 2 miss  (points of face)", fontname=INTER,
                  fontsize=11.5, color=W2, labelpad=9)
    ax.set_title("E3  Miss against conversion premium", fontname=INTER, fontsize=17,
                 color=W1, fontweight="bold", loc="left", pad=36)
    ax.text(0, 1.012, f"In-the-money marks only, n = {len(itm)}.",
            transform=ax.transAxes, fontname=INTER, fontsize=10.5, color=W3, va="bottom")

    seen, hs, ls_ = set(), [], []
    for h, l in zip(*ax.get_legend_handles_labels()):
        if l not in seen:
            seen.add(l); hs.append(h); ls_.append(l)
    lg = ax.legend(hs, ls_, loc="upper right", frameon=True, facecolor=PANEL,
                   edgecolor=GRID, fontsize=9.5)
    for t in lg.get_texts(): t.set_color(W2); t.set_fontname(INTER)

    fig.subplots_adjust(bottom=0.235)
    fig.text(0.062, 0.075, CAPTION_E3, fontname=INTER, fontsize=10.5, color=W2,
             wrap=True, ha="left", va="top", linespacing=1.65)
    wm(fig); 
    p2 = OUT / "calibration1_E3_miss_vs_premium.png"
    fig.savefig(p2, facecolor=BG); plt.close(fig)
    print(f"  E3 -> {p2.name}  ({len(on)} on the line, {len(off)} annotated exception)")


if __name__ == "__main__":
    g, floor, itm = gates()
    print("\nrendering:")
    e1(floor); e2(); e3(itm); e4()
