"""
make_figures.py — every SVG published with the A-block, generated from the same data
the estimator uses. Written 2026-08-23 for the same reason money_analysis.py was:
a figure hand-drawn from remembered numbers is a prose citation with extra steps.

Run: python3 scripts/make_figures.py   ->  writes web/fig_step_not_slope.svg
"""
import csv, math, os, sys
import numpy as np
from scipy import stats as sps

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import money_analysis as M  # noqa: E402

# GRID is #5A6B85, not the site's --border #1B2D49. #1B2D49 measures 1.18:1 on the elevated
# ground and 1.31:1 on the page ground, which fails WCAG 2.1 AA 1.4.11 (3:1 for graphical
# objects needed to understand the content). #5A6B85 is 3.02:1 / 3.35:1 and passes.
BG, EL, TXT, MUT, DIM, ACC, HON, GRID = (
    "#0A1628", "#112036", "#F5F2EC", "#C9C4B8", "#8A95A5", "#E06B6B", "#C8A24A", "#5A6B85")

W, H = 900, 470
L, R, T, B = 62, 24, 26, 74          # margins
XMAX, YMAX = 46.0, 40.0


def sx(t): return L + (t / XMAX) * (W - L - R)
def sy(v): return (H - B) - (v / YMAX) * (H - B - T)


def build():
    rows = M.load()
    inc = [d for d in rows if d["tenure"] > 0]
    non = [d for d in rows if d["tenure"] == 0]
    x = np.array([d["tenure"] for d in inc]); y = np.array([d["corp_pct"] for d in inc])
    slope, icept, r, p, se = sps.linregress(x, y)
    mi, mn = float(np.mean(y)), float(np.mean([d["corp_pct"] for d in non]))
    o = []
    a = o.append
    a(f'<svg viewBox="0 0 {W} {H}" style="min-width:680px" role="img" aria-labelledby="stt" aria-describedby="std" xmlns="http://www.w3.org/2000/svg">')
        # A background rect so the standalone file is dark-on-dark everywhere it opens
    # (round three: opened outside the page it rendered grey on the viewer's white).
    # class="bg" lets the pages' print CSS flip it to white.
    a(f'<rect class="bg" x="-20" y="-20" width="{W+40}" height="{H+40}" fill="{BG}"/>')
    a('<title id="stt">Corporate and trade PAC share against years in Congress</title>')
    climb = slope * (x.max() - x.min())
    a(f'<desc id="std">Scatter plot of {len(rows)} battleground House candidates. The horizontal axis '
      f'is years of congressional service, 0 to 45. The vertical axis is the share of campaign receipts '
      f'coming from corporate and trade PACs, 0 to 40 percent. {len(non)} candidates who have never '
      f'served sit in a flat band at zero years, averaging {mn:.2f} percent. {len(inc)} candidates with '
      f'congressional service spread across the rest of the chart, averaging {mi:.2f} percent, with an '
      f'upward trend of correlation {r:.2f}. The gap between the two group averages is '
      f'{mi - mn:.2f} points; the fitted line climbs {climb:.2f} points across the observed range; the '
      f'points scatter widely around it. A two-term regression in the text finds both a step at arrival '
      f'and a small, uncertain per-year slope, with the step much the larger.</desc>')

    # grid + axes
    for v in (0, 10, 20, 30, 40):
        a(f'<line x1="{L}" y1="{sy(v):.1f}" x2="{W-R}" y2="{sy(v):.1f}" stroke="{GRID}" stroke-width="1"/>')
        a(f'<text x="{L-10}" y="{sy(v)+4:.1f}" text-anchor="end" font-family="ui-monospace,monospace" '
          f'font-size="11" fill="{DIM}">{v}%</text>')
    for t in (0, 10, 20, 30, 40):
        a(f'<text x="{sx(t):.1f}" y="{H-B+22:.0f}" text-anchor="middle" '
          f'font-family="ui-monospace,monospace" font-size="11" fill="{DIM}">{t}</text>')
    a(f'<line x1="{L}" y1="{T}" x2="{L}" y2="{H-B}" stroke="{GRID}" stroke-width="1"/>')

    # the two group means — this is the finding
    a(f'<line x1="{sx(0)-6:.1f}" y1="{sy(mn):.1f}" x2="{sx(3.2):.1f}" y2="{sy(mn):.1f}" '
      f'stroke="{MUT}" stroke-width="2.5"/>')
    a(f'<line x1="{sx(1.5):.1f}" y1="{sy(mn):.1f}" x2="{sx(1.5):.1f}" y2="{sy(mi):.1f}" '
      f'stroke="{HON}" stroke-width="2" stroke-dasharray="4 4"/>')
    a(f'<line x1="{sx(0)-6:.1f}" y1="{sy(mi):.1f}" x2="{sx(3.2):.1f}" y2="{sy(mi):.1f}" '
      f'stroke="{HON}" stroke-width="2.5"/>')
    a(f'<text x="{sx(4.2):.1f}" y="{sy(mi)-14:.1f}" font-family="Inter,system-ui,sans-serif" '
      f'font-size="13" fill="{HON}">the step: {mn:.2f}% &#8594; {mi:.2f}%, never served vs served</text>')

    # fitted line through incumbents only
    x0, x1 = 1.5, 44.5
    a(f'<line x1="{sx(x0):.1f}" y1="{sy(icept+slope*x0):.1f}" x2="{sx(x1):.1f}" '
      f'y2="{sy(icept+slope*x1):.1f}" stroke="{MUT}" stroke-width="2" stroke-dasharray="7 5" opacity=".8"/>')
    a(f'<text x="{sx(30):.1f}" y="{sy(icept+slope*30)-12:.1f}" font-family="Inter,system-ui,sans-serif" '
      f'font-size="13" fill="{MUT}">the slope: r = {r:.2f} among those who have served</text>')

    # points — non-incumbents first so incumbents draw on top
    for d in non:
        # opacity .55 composited #8A95A5 down to #505C6D = 2.67:1 on the page ground,
        # failing 1.4.11 for 64 marks that are half the finding. .75 gives 3.94:1.
        a(f'<circle cx="{sx(0):.1f}" cy="{sy(d["corp_pct"]):.1f}" r="3.5" fill="{DIM}" opacity=".75">'
          f'<title>{d["name"]} ({d["district"]}) — never served, {d["corp_pct"]:.2f}%</title></circle>')
    for d in inc:
        a(f'<circle cx="{sx(d["tenure"]):.1f}" cy="{sy(d["corp_pct"]):.1f}" r="5" fill="{HON}" '
          f'fill-opacity=".85" stroke="{BG}" stroke-width="1">'
          f'<title>{d["name"]} ({d["district"]}) — {d["tenure"]:.1f} years, {d["corp_pct"]:.2f}%</title></circle>')

    # leverage, named not removed
    kap = max(inc, key=lambda d: d["tenure"])
    a(f'<circle cx="{sx(kap["tenure"]):.1f}" cy="{sy(kap["corp_pct"]):.1f}" r="10" fill="none" '
      f'stroke="{ACC}" stroke-width="2"/>')
    a(f'<text x="{sx(kap["tenure"])-14:.1f}" y="{sy(kap["corp_pct"])-18:.1f}" text-anchor="end" '
      f'font-family="Inter,system-ui,sans-serif" font-size="13" fill="{ACC}">'
      f'{kap["name"]}, {kap["tenure"]:.0f} yrs &#8212; kept in</text>')

    a(f'<text x="{sx(0)-14:.1f}" y="{H-B+46:.0f}" font-family="ui-monospace,monospace" font-size="11" '
      f'fill="{MUT}" letter-spacing="1.2">NEVER SERVED</text>')
    a(f'<text x="{(L+W-R)/2:.0f}" y="{H-14:.0f}" text-anchor="middle" font-family="ui-monospace,monospace" '
      f'font-size="11" fill="{MUT}" letter-spacing="1.2">YEARS IN CONGRESS</text>')
    a(f'<text x="18" y="{(T+H-B)/2:.0f}" text-anchor="middle" font-family="ui-monospace,monospace" '
      f'font-size="11" fill="{MUT}" letter-spacing="1.2" '
      f'transform="rotate(-90 18 {(T+H-B)/2:.0f})">CORPORATE / TRADE PAC SHARE</text>')
    a('</svg>')
    return "\n".join(o), dict(r=r, mi=mi, mn=mn, n_inc=len(inc), n_non=len(non), slope=slope)


if __name__ == "__main__":
    svg, meta = build()
    # writes into ../web when run from the repo, and beside itself when run from the
    # published bundle, so the reproduction instructions work as written
    web = os.path.join(HERE, "..", "web")
    out = os.path.join(web if os.path.isdir(web) else HERE, "fig_step_not_slope.svg")
    open(out, "w", encoding="utf-8").write(svg)
    print("wrote", os.path.normpath(out), len(svg), "bytes")
    print(meta)
