"""Forward projection of the fleet crossover for PLRN-031.

The per-turn crossover is past (the hook chain's median exceeds the measured
classifier latencies). What has a date is the fleet crossover: the point where
control costs the mean governed agent more time than judgment, i.e. where the
mean judgment time per turn falls below the per-action control cost,

    Jbar(t) = (1 - f(t)) * Js + f(t) * Jf(t),   control dominates when Jbar < C.

Scenario (stated in section 5 of the note, conservative by construction):

- f(t): fast-turn fraction rises logistically from 0.90 at the note's date
  (2026-10-14) to 0.99 one year later.
- Jf(t): classifier latency halves annually from 50 ms (a slower cadence than
  the two orders of magnitude in seven months actually observed).
- Js: the slow call is held at 60 s (the note's working median), so the
  projection does not depend on frontier calls getting faster.

C: hook-chain medians from plrn031_chain_model.py (115 ms in region, 450 ms
public SaaS); 12 us for an in-path decision.

Running it prints the schedule as JSON and writes plrn031-projection.svg
(Figure 4) next to this script.
"""

import json
import math
import os
import sys

MONTHS = ["", "January", "February", "March", "April", "May", "June", "July",
          "August", "September", "October", "November", "December"]

NOTE_DATE = 2026 + (9.5 + 13 / 30.0) / 12.0  # 2026-10-14 as a decimal year
JS = 60_000.0   # ms, slow turn (held fixed)
JF0 = 50.0      # ms, fast-turn judgment at the note's date
F0, F1 = 0.90, 0.99
C_SAAS, C_REGION, C_INPATH = 450.0, 115.0, 0.012  # ms

def logistic(f_start, f_year1):
    """Logistic parameters with f(0) = f_start and f(1) = f_year1."""
    a = math.log(f_start / (1 - f_start))
    return a, math.log(f_year1 / (1 - f_year1)) - a


A, B = logistic(F0, F1)


def f(t, a=A, b=B):
    return 1.0 / (1.0 + math.exp(-(a + b * t)))


def jf(t):
    return JF0 * 2.0 ** (-t)


def jbar(t, js=JS, a=A, b=B):
    return (1 - f(t, a, b)) * js + f(t, a, b) * jf(t)


def crossing(c, js=JS, a=A, b=B):
    """First t (years from the note's date) where jbar < c; log-linear refine."""
    prev = 0.0
    for m in range(1, 4 * 12 * 10 + 1):
        t = m / 120.0
        if jbar(t, js, a, b) < c:
            f0, f1v = math.log(jbar(prev, js, a, b)), math.log(jbar(t, js, a, b))
            return prev + (t - prev) * (math.log(c) - f0) / (f1v - f0)
        prev = t
    return None


def date_label(t):
    d = NOTE_DATE + t
    return f"{MONTHS[int((d % 1) * 12) + 1]} {int(d)}"


def fstar(js, c):
    """Fast-turn fraction at which the mean agent becomes control-dominated."""
    return (js - c) / (js - jf(0))


# --- numbers for the note -------------------------------------------------

out = {
    "scenario": {"f": "logistic 0.90 -> 0.99 in year 1 (leading cohort)",
                 "Jf": "50 ms, halving annually",
                 "Js": "60 s, fixed", "note_date": "2026-10-14"},
    "jbar_ms": {d: round(jbar(t), 1)
                for d, t in [("note_date", 0.0), ("+1y", 1.0), ("+2y", 2.0),
                             ("+4y", 4.0)]},
    "crossings": {},
    "fstar_percent": {},
    "kept_percent": {},
}

for name, c in [("public_saas", C_SAAS), ("in_region", C_REGION)]:
    tc = crossing(c)
    out["crossings"][name] = {"t_years": round(tc, 2), "date": date_label(tc)}

# The median fleet walks the same path to the same threshold from a lower
# start: 50% of turns fast at the note's date, 90% one year later (the same
# nine-point cadence as the leading cohort's 90% -> 99%).
A_MED, B_MED = logistic(0.50, 0.90)
out["median_cohort"] = {
    "scenario": {"f": "logistic 0.50 -> 0.90 in year 1", "Jf": "as above", "Js": "as noted"},
    "public_saas": {"date": date_label(crossing(C_SAAS, a=A_MED, b=B_MED))},
    "in_region": {"date": date_label(crossing(C_REGION, a=A_MED, b=B_MED))},
    "public_saas_js_10s": {"date": date_label(crossing(C_SAAS, js=10_000.0, a=A_MED, b=B_MED))},
    "in_region_js_10s": {"date": date_label(crossing(C_REGION, js=10_000.0, a=A_MED, b=B_MED))},
}

tc3 = crossing(C_SAAS, js=180_000.0)
out["saas_crossing_if_js_triples"] = {"date": date_label(tc3),
                                      "delay_months": round((tc3 - out["crossings"]["public_saas"]["t_years"]) * 12)}

# Sensitivity of the dates to the path: the same leading cohort taking two
# years rather than one to go from 90% to 99%, and classifier latency frozen
# at 50 ms. Only the path moves the dates by more than a quarter.
out["path_sensitivity"] = {
    "routing_90_to_99_over_two_years": {
        n: date_label(crossing(c, a=A, b=B / 2.0))
        for n, c in [("public_saas", C_SAAS), ("in_region", C_REGION)]},
}
_jf_halving = jf
jf = lambda t: JF0  # noqa: E731
out["path_sensitivity"]["jf_frozen_50ms"] = {
    n: date_label(crossing(c)) for n, c in [("public_saas", C_SAAS), ("in_region", C_REGION)]}
jf = _jf_halving

for js in [60_000, 10_000, 3_000, 1_000, 500]:
    out["fstar_percent"][f"Js={js / 1000:g}s"] = {
        "public_saas": round(100 * fstar(js, C_SAAS), 1),
        "in_region": round(100 * fstar(js, C_REGION), 1),
    }

for name, c in [("in_region", C_REGION), ("public_saas", C_SAAS)]:
    out["kept_percent"][name] = {
        f"Oct {2026 + k}": round(100 * jf(k) / (jf(k) + c), 1) for k in range(4)}

json.dump(out, sys.stdout, indent=2)
print()

# --- Figure 4 -------------------------------------------------------------

W, H = 1180, 690
X0, X1, TMAX = 230.0, 820.0, 4.0
Y0, Y1 = 40.0, 600.0
LOG_HI, DECADES = 5.0, 7  # top of axis is 100 s, bottom 10 us

INK, ORANGE, BLUE, GRAY, GRID = "#16161D", "#C2410C", "#0077B6", "#787B82", "#E4E6EA"


def px(t):
    return X0 + (t / TMAX) * (X1 - X0)


def py(ms):
    return Y0 + (LOG_HI - math.log10(ms)) / DECADES * (Y1 - Y0)


pts = [(m / 12.0, jbar(m / 12.0)) for m in range(0, 49)]
curve = f"M {px(0):.1f} {py(pts[0][1]):.1f} " + " ".join(
    f"L {px(t):.1f} {py(v):.1f}" for t, v in pts[1:])

tc_saas, tc_reg = crossing(C_SAAS), crossing(C_REGION)
wedge = (f"M {px(tc_saas):.1f} {py(C_SAAS):.1f} "
         + " ".join(f"L {px(t):.1f} {py(v):.1f}" for t, v in pts if t >= tc_saas)
         + f" L {X1:.1f} {py(C_SAAS):.1f} Z")

s = []
s.append(f"<svg xmlns='http://www.w3.org/2000/svg' font-family=\"'Red Hat Mono', ui-monospace, Menlo, monospace\" "
         f"viewBox='0 0 {W} {H}' role='img' aria-labelledby='fig4-title fig4-desc'>"
         f"<rect width='100%' height='100%' fill='#FAFAFA'/>")
s.append("<title id='fig4-title'>The fleet crossover, projected</title>")
s.append("<desc id='fig4-desc'>Log-scale projection of mean judgment time per turn from October 2026 to "
         "October 2030, falling from about six seconds to a few milliseconds. Two horizontal lines mark the "
         "modeled hook-chain control costs of 450 milliseconds (public SaaS) and 115 milliseconds (in region). "
         "The curve crosses the first at the end of 2027 and the second in mid-2028; below each line, control "
         "costs the mean agent more time than judgment. An in-path decision of 12 microseconds sits far below.</desc>")

gridlines = "".join(f"<line x1='170' y1='{Y0 + k * (Y1 - Y0) / DECADES:.1f}' x2='{X1:.1f}' y2='{Y0 + k * (Y1 - Y0) / DECADES:.1f}'/>"
                    for k in range(DECADES + 1))
s.append(f"<g stroke='{GRID}' stroke-width='1.5'>{gridlines}</g>")

labels = ["100 s", "10 s", "1 s", "100 ms", "10 ms", "1 ms", "100 µs", "10 µs"]
ytext = "".join(f"<text x='154' y='{Y0 + k * (Y1 - Y0) / DECADES + 6:.1f}'>{labels[k]}</text>" for k in range(DECADES + 1))
s.append(f"<g font-size='18' text-anchor='end' fill='{GRAY}'>{ytext}</g>")
s.append(f"<text x='84' y='16' font-size='15' letter-spacing='2.2' fill='{GRAY}'>MEAN JUDGMENT PER TURN · LOG SCALE · PROJECTED</text>")

ylines = "".join(f"<line x1='{px(y):.1f}' y1='{Y0}' x2='{px(y):.1f}' y2='{Y1}'/>" for y in range(5))
s.append(f"<g stroke='{INK}' stroke-opacity='.3' stroke-width='2'>{ylines}</g>")
xtext = "".join(f"<text x='{px(y):.1f}' y='640'>OCT {2026 + y}</text>" for y in range(5))
s.append(f"<g font-size='19' letter-spacing='3' text-anchor='middle' fill='{INK}'>{xtext}</g>")

s.append(f"<path d='{wedge}' fill='{ORANGE}' fill-opacity='.16'/>")
s.append(f"<path d='{curve}' fill='none' stroke='{INK}' stroke-width='4'/>")

for c, w, op in [(C_SAAS, 4, 1.0), (C_REGION, 2.5, 0.55)]:
    s.append(f"<line x1='{X0}' y1='{py(c):.1f}' x2='{X1}' y2='{py(c):.1f}' stroke='{ORANGE}' "
             f"stroke-width='{w}' stroke-opacity='{op}'/>")

for tc, dy, lab in [(tc_saas, -12, "END OF 2027"), (tc_reg, 30, "MID-2028")]:
    c = C_SAAS if tc == tc_saas else C_REGION
    s.append(f"<circle cx='{px(tc):.1f}' cy='{py(c):.1f}' r='8' fill='{ORANGE}'/>"
             f"<circle cx='{px(tc):.1f}' cy='{py(c):.1f}' r='13' fill='none' stroke='{ORANGE}' stroke-width='3'/>"
             f"<text x='{px(tc) + 20:.1f}' y='{py(c) + dy:.1f}' font-size='15' letter-spacing='1.8' fill='{ORANGE}'>{lab}</text>")

s.append(f"<line x1='{X0}' y1='{py(C_INPATH):.1f}' x2='{X1}' y2='{py(C_INPATH):.1f}' stroke='{BLUE}' stroke-width='3'/>")

s.append(f"<g font-size='20'>"
         f"<text x='250' y='90' font-weight='500' fill='{INK}'>Mean judgment per turn</text>"
         f"<text x='250' y='114' font-size='17' fill='{GRAY}'>≈6 s today → 625 ms Oct 2027 → 68 ms Oct 2028</text>"
         f"<text x='848' y='{py(C_SAAS) - 10:.1f}' font-weight='500' fill='{ORANGE}'>Public-SaaS chain</text>"
         f"<text x='848' y='{py(C_SAAS) + 14:.1f}' font-size='17' fill='{GRAY}'>450 ms median · §3</text>"
         f"<text x='848' y='{py(C_REGION) - 10:.1f}' font-weight='500' fill='{ORANGE}'>In-region chain</text>"
         f"<text x='848' y='{py(C_REGION) + 14:.1f}' font-size='17' fill='{GRAY}'>115 ms median · §3</text>"
         f"<text x='848' y='{py(C_INPATH) - 28:.1f}' font-weight='500' fill='{BLUE}'>In-path decision · 12 µs</text>"
         f"<text x='848' y='{py(C_INPATH):.1f}' font-size='17' fill='{GRAY}'>no crossover at any date</text>"
         f"</g>")
s.append("</svg>")

svg_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "plrn031-projection.svg")
with open(svg_path, "w") as fh:
    fh.write("\n".join(s))
print(f"wrote {svg_path}", file=sys.stderr)
