#!/usr/bin/env python3
"""
MicroLink.Review — PDF REFERENCE IMPLEMENTATION (layout contract)

This file produced the canonical NPS Mansfield review PDF. It is the layout
contract for every review PDF.

FROZEN (do not change): page size and margins, palette constants, font
registration, helper functions (bg, header, footer, kick, point, wrap, para,
tag, rulel, tiercol, risklist), the nine-page structure, chart construction
methods, spacing rhythms.

REPLACE PER REVIEW (data only): the strings, numbers, table rows, verdicts,
scores, flags, questions, tier entries, actions, and chart values, all of
which are inline in the page sections below. Regenerate this script with the
new review's data in place of the NPS data; keep everything else identical.

REQUIREMENTS: reportlab, fonts in ./fonts/ (shipped with this pack: Newsreader,
Inter, JetBrains Mono static instances, OFL). Register all six weights exactly
as below. NEVER substitute a browser print, headless page.pdf(), or LibreOffice
conversion for this build.

VERIFY BEFORE DELIVERY: render with pdftoppm and view every page; confirm all
used font weights are embedded TrueType subsets via pypdf.
"""
OUTPUT_PATH = "/mnt/user-data/outputs/MicroLink_Review_NPS_Mansfield_Jul2026.pdf"
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

for name, path in [("News","fonts/Newsreader-Regular.ttf"),("NewsMed","fonts/Newsreader-Medium.ttf"),
                   ("Inter","fonts/Inter-Regular.ttf"),("InterSB","fonts/Inter-SemiBold.ttf"),
                   ("Mono","fonts/JBMono-Regular.ttf"),("MonoMed","fonts/JBMono-Medium.ttf")]:
    pdfmetrics.registerFont(TTFont(name, path))

PAPER=HexColor("#F2EBDD");PAPER2=HexColor("#EDE5D5");PAPER3=HexColor("#E4DAC6")
INK=HexColor("#0A0A0A");INK2=HexColor("#2A2A2A");INK3=HexColor("#6E6A60");INK4=HexColor("#9A9388")
WARM=HexColor("#B07347");WARM2=HexColor("#8A5733");STEEL2=HexColor("#5A6675");STOP=HexColor("#A24B3C");GO=HexColor("#5E7D5A")
RULE=HexColor("#C9C0A8");RULES=HexColor("#D7CFB8")

W,H=A4  # 595 x 842
ML,MR,MT,MB=46,46,52,46
CW=W-ML-MR

c=canvas.Canvas(OUTPUT_PATH,pagesize=A4)  # OUTPUT_PATH set per review at top of file
c.setTitle("MicroLink Offer Review · NPS Mansfield Power Plan · Jul 2026")
c.setAuthor("MicroLink Data Centers")

def bg():
    c.setFillColor(PAPER);c.rect(0,0,W,H,fill=1,stroke=0)

def header(section):
    c.setFillColor(INK2);c.setFont("InterSB",9)
    c.drawString(ML,H-30,"MicroLink");w=c.stringWidth("MicroLink","InterSB",9)
    c.setFillColor(INK3);c.setFont("Inter",9);c.drawString(ML+w,H-30,".Review · NPS Mansfield Power Plan")
    c.setFillColor(INK3);c.setFont("Mono",6.5)
    c.drawRightString(W-MR,H-30,section.upper())
    c.setStrokeColor(INK);c.setLineWidth(1.1);c.line(ML,H-38,W-MR,H-38)

def footer(pg):
    c.setStrokeColor(RULE);c.setLineWidth(0.5);c.line(ML,34,W-MR,34)
    c.setFillColor(INK3);c.setFont("Mono",6)
    c.drawString(ML,24,"MICROLINK.REVIEW · V1.0 · 08 JUL 2026 · RESEARCH RUN 08 JUL 2026, STALE AFTER OCT 2026")
    c.drawRightString(W-MR,24,f"{pg:02d}")

def kick(y,txt,accent=None):
    c.setFont("MonoMed",6.5)
    if accent:
        c.setFillColor(WARM);c.drawString(ML,y,accent.upper())
        w=c.stringWidth(accent.upper(),"MonoMed",6.5)
        c.setFillColor(INK3);c.setFont("Mono",6.5);c.drawString(ML+w,y," · "+txt.upper())
    else:
        c.setFillColor(INK3);c.drawString(ML,y,txt.upper())

def point(y,lines,size=19,color=INK,font="News"):
    c.setFillColor(color)
    for i,l in enumerate(lines):
        c.setFont(font,size);c.drawString(ML,y-i*(size*1.16),l)
    return y-(len(lines)-1)*(size*1.16)

def wrap(txt,font,size,maxw):
    words=txt.split();lines=[];cur=""
    for w_ in words:
        t=(cur+" "+w_).strip()
        if c.stringWidth(t,font,size)<=maxw: cur=t
        else: lines.append(cur);cur=w_
    if cur: lines.append(cur)
    return lines

def para(x,y,txt,font="Inter",size=8.4,leading=12.2,color=INK2,maxw=None,bolds=()):
    maxw=maxw or CW
    c.setFillColor(color)
    for l in wrap(txt,font,size,maxw):
        c.setFont(font,size);c.drawString(x,y,l);y-=leading
    return y

def tag(x,y,label,col,w=None):
    c.setFont("Mono",5.6)
    tw=(w or c.stringWidth(label.upper(),"Mono",5.6)+10)
    c.setStrokeColor(col);c.setLineWidth(0.7);c.setFillColor(PAPER)
    c.roundRect(x,y-3.4,tw,10.4,2,fill=0,stroke=1)
    c.setFillColor(col);c.drawCentredString(x+tw/2,y-0.4,label.upper())
    return tw

VC={"high":( "HIGH-MARKET",WARM),"at":("AT MARKET",GO),"off":("OFF-MARKET",STOP),"und":("UNDEFINED",STEEL2)}

def rulel(y,w=CW,col=RULES,lw=0.5,x=None):
    c.setStrokeColor(col);c.setLineWidth(lw);c.line(x or ML,y,(x or ML)+w,y)

# ============ PAGE 1 · COVER ============
bg()
c.setFillColor(INK2);c.setFont("InterSB",11);c.drawString(ML,H-64,"MicroLink")
w=c.stringWidth("MicroLink","InterSB",11)
c.setFillColor(INK3);c.setFont("Inter",11);c.drawString(ML+w,H-64,".Review")
c.setStrokeColor(INK);c.setLineWidth(1.2);c.line(ML,H-76,W-MR,H-76)

y=H-118
kick(y,"Offer Review · received Jul 2026 · opening proposal · review v1.0 · 08 Jul 2026","Mansfield Energy Acres")
y-=32
c.setFillColor(INK);c.setFont("News",27);c.drawString(ML,y,"New Power Solutions")
c.setFillColor(INK3);c.setFont("News",16);c.drawString(ML,y-24,"(Energy Capital Partners)")
y-=48
c.setFillColor(INK2);c.setFont("Inter",9.5)
c.drawString(ML,y,"Power Plan Proposal for Energy Acres Mansfield, Ohio · 900 MW BTM + 100 MW Grid")
y-=26
y=para(ML,y,"NPS proposes 900 MW of behind-the-meter gas generation at Mansfield under a 20 year PPA, first power Q4 2028. The design is sound and the speed is real. The price sits high against every benchmark, three terms are off-market, five material terms are undefined, and the structure asks MicroLink to backstop the equipment orders thirteen months before NPS reaches its own final investment decision.",size=8.8,leading=13.2,maxw=CW-40)

# stats band
y-=16
rulel(y,col=RULE);y-=34
stats=[("42/100","WEIGHTED SCORE · RED-FLAGGED",STOP),("$116.65","ALL-IN PER MWH · PRIME POWER",WARM),
       ("$228M","PEAK PRE-FID EXPOSURE",STOP),("Q4 2028","FIRST POWER · 217 MW",STEEL2)]
sw=CW/4
for i,(v,l,col) in enumerate(stats):
    cx=ML+sw*i+sw/2
    c.setFillColor(col);c.setFont("News",21);c.drawCentredString(cx,y,v)
    c.setFillColor(INK3);c.setFont("Mono",5.6);c.drawCentredString(cx,y-14,l)
y-=26
rulel(y,col=RULE)

# verdicts panel
y-=26
c.setFillColor(PAPER2);c.setStrokeColor(RULE);c.setLineWidth(0.8)
ph=300
c.roundRect(ML,y-ph,CW,ph,5,fill=1,stroke=1)
py=y-26
c.setFillColor(INK2);c.setFont("MonoMed",10);c.drawString(ML+22,py,"The verdicts")
c.setFillColor(INK3);c.setFont("Mono",7);c.drawString(ML+22,py-12,"Eight terms, one line each")
py-=26
verd=[("Capacity charge $47.00/kW-mo","high"),("All-in $116.65/MWh","high"),("Capex ~$2,580/kW installed","high"),
      ("Fuel pass-through $3.55/MMBtu","at"),("Reserve billing 250 of 300 MW","off"),("20 year flat term","off"),
      ("CRA: we backstop $195M pre-FID","off"),("Grid transition revenue share","und")]
rulel(py,CW-44,RULE,0.6,ML+22);py-=20
for t,v in verd:
    c.setFillColor(INK2);c.setFont("Mono",7.6);c.drawString(ML+22,py,t)
    lab,col=VC[v];tag(ML+CW-114,py,lab,col,w=86)
    py-=10;rulel(py,CW-44,RULES,0.4,ML+22);py-=18
footer(1);c.showPage()

# ============ PAGE 2 · 01 SUMMARY ============
bg();header("01 · Offer summary")
y=H-70
kick(y,"one page maximum","01 · Offer summary");y-=26
y=point(y,["The real ask arrives next month, not in 2028."],size=17);y-=24

fields=[
 ("Counterparty","New Power Solutions LLC. Signing entity not confirmed in deck.",True),
 ("Sponsor / parent","Energy Capital Partners (~USD 87B AUM, exclusive BTM partner). Not guaranteed: nothing in the offer obligates ECP to stand behind NPS.",True),
 ("Offer scope","900 MW BTM reciprocating gas in three 300 MW blocks (82 Jenbachers Block 1; 24 Bergen B36:45V20 each Blocks 2 and 3), 107 MW of 2 hour BESS per block, plus 100 MW FirstEnergy grid power Q4 2028. First BTM power 217 MW Q4 2028, full 1 GW Q1 2030. Optional 100 MW mobile fleet bridge.",False),
 ("Headline price","USD 47.00/kW-month on 250 MW contract capacity, 20 year term. All-in prime power USD 116.65/MWh (capacity 67.77 + fuel pass-through 30.63 at USD 3.55/MMBtu + VOM 18.25). Gas lateral (~USD 30M) excluded, adds ~USD 2 to 3/MWh.",False),
 ("Term","20 years flat. No extension options, step-downs, or exit pricing stated.",False),
 ("Total contract value","~USD 2.7B capacity charges Block 1 alone over term; ~USD 8.5B at full 750 MW contract capacity. Before fuel.",False),
 ("Decision deadline","None stated. De facto: August 2026, when the CRA schedule puts the first USD 7M Jenbacher deposit.",False),
 ("What they actually want","MicroLink or a backstop entity funds ~USD 195M of pre-FID equipment commitments plus USD 33M of LCs (lateral 30M, Rover FT 3M), thirteen months before NPS reaches its own FID (est. Aug 2027).",True),
 ("BATNA","FirstEnergy grid-only path (100 MW Q4 2028 claimed, unverified); no competing BTM proposal at term sheet stage for Mansfield; mobile/bridge generation as interim. Field requires Nick's confirmation of current counterparty pipeline before gate finalises.",False),
 ("Status","Opening proposal. Pricing and structure read as an opening position inside a negotiable band.",False),
]
rulel(y,col=RULE);y-=14
LBLW=108
for label,val,flag in fields:
    c.setFillColor(INK);c.setFont("InterSB",8);c.drawString(ML,y,label)
    ny=para(ML+LBLW+10,y,val,size=7.8,leading=11,maxw=CW-LBLW-10,color=(STOP if flag and label in ("What they actually want",) else INK2))
    if flag and label!="What they actually want":
        pass
    y=ny-4;rulel(y+2,col=RULES);y-=12
footer(2);c.showPage()

# ============ PAGE 3 · 02 ANATOMY ============
bg();header("02 · Deal anatomy")
y=H-70
kick(y,"five layers, always","02 · Deal anatomy");y-=26
y=point(y,["Conventional structure. The risk, the cash,","and the exits are where it turns."],size=17);y-=22

c.setFillColor(INK4);c.setFont("MonoMed",6.5);c.drawString(ML,y,"2.1 · SCOPE AND TECHNICAL");y-=13
y=para(ML,y,"Design is credible: 34.5 kV distribution to dodge HV transformer lead times, cross-tied blocks, N+4 on the Bergens, 8,641 BTU/kWh HHV blended Block 1 and 7,731 on the Bergens, 3 to 4 nines at plant and 5 at rack. Block 1 single-line, load flow, short-circuit, and RAM studies offered: request immediately. Permitting straight to PSD for the full 900 MW is the right playbook; 12 month PSD plus OPSB is aggressive. Ohio precedent at smaller scale: PowerConneX 120 and 216 MW OPSB-approved, Meta/Williams Socrates in construction. No heat recovery interface in the design (tracked separately).",size=7.8,leading=11)
y-=16

c.setFillColor(INK4);c.setFont("MonoMed",6.5);c.drawString(ML,y,"2.2 · PRICE MECHANICS");y-=15
rows=[("Capacity charge","47.00 USD/kW-mo on 250 MW · ~USD 11.3M/month","Unconditional: economically 100% take-or-pay"),
      ("Fuel + FT","Pass-through at cost · 3.55/MMBtu plan, ~30.63/MWh","Commodity risk ours, 20 years"),
      ("VOM","Pass-through on metered delivery · 18.25/MWh","At cost"),
      ("Lateral","Excluded from headline · ~USD 30M per Dth or into capex","Adds ~2 to 3/MWh, treatment undefined"),
      ("Indexation","PPA priced to final capitalized cost (774M ref)","Bands mentioned, no widths, no cap")]
rulel(y,col=RULE);y-=12
for n,a,b in rows:
    c.setFillColor(INK);c.setFont("InterSB",7.6);c.drawString(ML,y,n)
    c.setFillColor(INK2);c.setFont("Inter",7.3);c.drawString(ML+92,y,a)
    c.setFillColor(INK3);c.setFont("Inter",7.3);c.drawString(ML+300,y,b)
    y-=8;rulel(y+2,col=RULES);y-=11

y-=8
c.setFillColor(INK4);c.setFont("MonoMed",6.5);c.drawString(ML,y,"2.3 · RISK ALLOCATION");y-=12
bh=150
c.setFillColor(PAPER2);c.setStrokeColor(RULE);c.setLineWidth(0.8)
c.roundRect(ML,y-bh,CW,bh,4,fill=1,stroke=1)
c.setStrokeColor(RULE);c.line(ML+CW/2,y-bh,ML+CW/2,y)
def risklist(x,ytop,title,col,items):
    c.setFillColor(col);c.setFont("MonoMed",6.3);c.drawString(x,ytop,title.upper())
    yy=ytop-14
    for b,rest in items:
        c.setFillColor(col);c.setFont("Inter",7.2);c.drawString(x,yy,"·")
        c.setFillColor(INK);c.setFont("InterSB",7.2);c.drawString(x+8,yy,b)
        bw=c.stringWidth(b,"InterSB",7.2)
        for l in wrap(rest,"Inter",7.0,CW/2-30-8):
            if l is rest: pass
        # simple: bold then wrapped rest below
        yy2=yy
        first=True
        for l in wrap(rest,"Inter",7.0,CW/2-38):
            if first:
                c.setFillColor(INK3);c.setFont("Inter",7.0)
                c.drawString(x+8+bw+3,yy2,l) if c.stringWidth(l,"Inter",7.0)<=CW/2-38-bw-3 else None
                if c.stringWidth(l,"Inter",7.0)>CW/2-38-bw-3:
                    yy2-=9.5;c.drawString(x+8,yy2,l)
                first=False
            else:
                yy2-=9.5;c.setFillColor(INK3);c.setFont("Inter",7.0);c.drawString(x+8,yy2,l)
        yy=yy2-12
risklist(ML+16,y-18,"Risks we carry",STOP,[
 ("Fuel and commodity,","full pass-through, 20 years (market-normal)"),
 ("Demand and ramp,","unconditional charge from COD (not normal)"),
 ("Equipment residual,","195M of gensets under the CRA, unsecured"),
 ("Overrun,","shared via indexation, bands undefined"),
 ("Counterparty,","no sponsor guarantee, no remedies stated")])
risklist(ML+CW/2+16,y-18,"Risks they carry",STEEL2,[
 ("Permitting,","PSD/OPSB strategy plus mobile fleet (credible)"),
 ("O&M,","post-COD"),
 ("Schedule,","nominally: no COD LDs stated, so unremedied"),
 ("Post-FID capital,","all costs after FID borne by NPS")])
y-=bh+14
y=para(ML,y,"A risk the offer does not mention sits with us by default. Exit terms are absent: no termination rights, buyout, or remedy structures. Equipment fallback waterfall is weak; liquidation lands on us in a thin, opaquely priced secondary market for new-model 60 Hz units. Peak commitment USD 228M including LC faces; reimbursement trigger (their FID, est. Aug 2027) partly within their control; lateral cancellation ramps USD 2M at signing to USD 39M at month 18. The 20yr x ~USD 141M/yr capacity obligation is imputed debt under S&P methodology, visible to Mansfield lenders and the USD 40M round.",size=7.6,leading=10.8)
footer(3);c.showPage()

# ============ PAGE 4 · 03 SCORING ============
bg();header("03 · Scoring matrix")
y=H-70
kick(y,"anchors applied · evidence page 5","03 · Scoring matrix");y-=26
y=point(y,["42 out of 100, and the number is not the point:","two automatic red flags block the gate."],size=17);y-=20

c.setFillColor(PAPER2);c.setStrokeColor(STOP);c.setLineWidth(0)
c.rect(ML,y-30,CW,34,fill=1,stroke=0)
c.setFillColor(STOP);c.rect(ML,y-30,2.6,34,fill=1,stroke=0)
c.setFillColor(INK)
para(ML+12,y-6,"Red flag status: BLOCKED. Pre-FID Exposure scores 1 and Flexibility scores 1. This offer cannot pass the gate without the flagged terms renegotiated or explicitly accepted in writing at founder level.",size=7.6,leading=10.5,maxw=CW-24)
y-=48

hdr=["CRITERION","WT","SCORE","WTD","JUSTIFICATION"]
xs=[ML,ML+128,ML+152,ML+184,ML+214]
c.setFillColor(INK4);c.setFont("Mono",5.8)
for hx,ht in zip(xs,hdr):c.drawString(hx,y,ht)
y-=6;rulel(y,col=RULE);y-=13
scores=[
 ("Price vs market","20","2","8","High-market on all three priced anchors: capacity charge, all-in USD/MWh, capex per kW",False),
 ("Risk allocation","20","2","8","Demand, residual, and credit risk all ours; fuel pass-through the only market-normal allocation",False),
 ("Counterparty credibility","15","2","6","No delivered BTM DC project; sponsor named, not obligated (anchor caps at 2)",False),
 ("Schedule confidence","10","3","6","Ohio precedent at smaller scale; mobile fleet real insurance; permitting aggressive; FirstEnergy unverified",False),
 ("Flexibility","10","1","2","Flat 250 MW from COD, 20 years, no ramp matching, no priced exit · RED FLAG",True),
 ("Pre-FID exposure","10","1","2","USD 195M + 33M LCs, unsecured, trigger partly theirs, weak recovery · RED FLAG",True),
 ("Strategic fit","10","4","8","Only credible Q4 2028 path; sidesteps PUCO tariff; 345 kV pathway real optionality",False),
 ("Contract completeness","5","2","2","Revenue share, indexation bands, remedies, signing entity all undefined",False)]
for n,wt,s,wtd,j,flag in scores:
    c.setFillColor(INK);c.setFont("InterSB",7.4);c.drawString(xs[0],y,n)
    c.setFillColor(INK2);c.setFont("Mono",7.2)
    c.drawString(xs[1],y,wt)
    c.setFillColor(STOP if flag else INK2);c.setFont("MonoMed",7.2);c.drawString(xs[2],y,s)
    c.setFillColor(INK2);c.setFont("Mono",7.2);c.drawString(xs[3],y,wtd)
    ny=para(xs[4],y,j,size=6.9,leading=9.2,maxw=CW-(xs[4]-ML),color=(STOP if flag else INK3))
    y=min(ny,y-9)-3;rulel(y+2,col=RULES);y-=12
c.setFillColor(PAPER3);c.rect(ML,y-6,CW,18,fill=1,stroke=0)
c.setFillColor(WARM2);c.setFont("InterSB",7.8);c.drawString(xs[0],y-1,"Total")
c.setFont("MonoMed",7.6);c.drawString(xs[1],y-1,"100")
c.drawString(xs[3],y-1,"42 / 100")
c.setFillColor(STOP);c.drawString(xs[4],y-1,"RED-FLAGGED · GATE BLOCKED")
y-=34

# scorecard chart
c.setFillColor(INK4);c.setFont("MonoMed",6.5);c.drawString(ML,y,"THE SCORECARD · SCORE OF 5 PER CRITERION");y-=16
bars=[("Price",2,WARM),("Risk",2,WARM),("Counterparty",2,WARM),("Schedule",3,STEEL2),
      ("Flexibility",1,STOP),("Exposure",1,STOP),("Fit",4,GO),("Completeness",2,WARM)]
bx=ML+110;bw_full=CW-130
for n,s,col in bars:
    c.setFillColor(STOP if col==STOP else INK2);c.setFont("Mono",6.8);c.drawRightString(bx-8,y-1,f"{n} · {s}")
    c.setFillColor(PAPER3);c.roundRect(bx,y-4,bw_full,7,3,fill=1,stroke=0)
    c.setFillColor(col);c.roundRect(bx,y-4,bw_full*s/5,7,3,fill=1,stroke=0)
    y-=16
footer(4);c.showPage()

# ============ PAGE 5 · 04 BENCHMARKS ============
bg();header("04 · Benchmark evidence")
y=H-70
kick(y,"deep research run 08 Jul 2026 · stale after Oct 2026","04 · Benchmarks");y-=26
y=point(y,["Every priced term against the evidenced range.","Two-source rule satisfied on all scored terms."],size=17);y-=22

bench=[
 ("Capacity charge","47.00 USD/kW-mo, 20yr","Mid-40s justifiable at current equipment inflation (GE Vernova guidance; Melius via CNBC; SemiAnalysis). PJM 26/27 cleared ~10/kW-mo, floor reference only","high"),
 ("All-in prime power","116.65 USD/MWh","Recip LCOE 80 to 100 (Thunder Said); Bloom PPA ~99; new CCGT 45 to 100 (BCG/Lazard). Speed and firmness premium real but unsized","high"),
 ("Installed capex","~2,580 USD/kW incl BESS","Recips 1,700 to 2,000/kW (SemiAnalysis); BESS explains part. Implied ~22% gross yield, low-to-mid teens levered IRR to them","high"),
 ("Fuel pass-through","At cost, 3.55/MMBtu","Offtaker bears commodity under pass-through as standard; IPP hedging rare","at"),
 ("Reserve billing","Pay 250 of 300 MW","Comparables bill firm delivered capacity or share reserve value; we fund the asset, they keep the optionality","off"),
 ("Term structure","20 years flat","Offtaker trend 10 to 12yr + extensions; S&P imputes risk-factored NPV as debt","off"),
 ("Minimum take vs ramp","Unconditional from COD","AEP Ohio DC tariff (PUCO Jul 2025): 85% minimum, 4yr ramp, 12yr horizon. Harder than the regulated benchmark on both axes","off"),
 ("CRA backstop","We fund ~195M pre-FID","OEM deposits 20 to 25% nonrefundable are real; offtaker backstops bespoke; secondary market for new-model 60 Hz units thin","off"),
 ("Grid revenue share","\u201cSharing market income\u201d","Market norm is a locked percentage; nothing benchmarkable because no number offered","und"),
 ("Mobile fleet bridge","8.3M mob + 6.8M/mo per 100 MW","~68/kW-mo before fuel, ~45% above permanent plant. Insurance, not a plan","high")]
xs2=[ML,ML+96,ML+196,ML+CW-84]
c.setFillColor(INK4);c.setFont("Mono",5.8)
for hx,ht in zip(xs2,["TERM","THEIR NUMBER","MARKET RANGE AND SOURCES","VERDICT"]):c.drawString(hx,y,ht)
y-=6;rulel(y,col=RULE);y-=13
for n,t,m,v in bench:
    c.setFillColor(INK);c.setFont("InterSB",7.2);c.drawString(xs2[0],y,n)
    yy=para(xs2[1],y,t,size=6.8,leading=9,maxw=94,color=INK2)
    ny=para(xs2[2],y,m,size=6.8,leading=9,maxw=xs2[3]-xs2[2]-12,color=INK3)
    lab,col=VC[v];tag(xs2[3],y,lab,col,w=80)
    y=min(ny,yy)-4;rulel(y+2,col=RULES);y-=12
y-=4
c.setFillColor(PAPER2);c.rect(ML,y-26,CW,30,fill=1,stroke=0)
c.setFillColor(STEEL2);c.rect(ML,y-26,2.6,30,fill=1,stroke=0)
para(ML+12,y-4,"Source caveat: nearly all BTM data center pricing is private. Ranges are triangulated from OEM disclosures, earnings calls, LCOE studies, PUCO filings, and analyst notes, not disclosed contract terms. Indicative bands, dated 08 Jul 2026.",size=7.2,leading=10,maxw=CW-24,color=INK2)
footer(5);c.showPage()

# ============ PAGE 6 · 05 FLAGS ============
bg();header("05 · Red flags and undefined terms")
y=H-70
kick(y,"two lists, one goes back to them","05 · Flags");y-=26
y=point(y,["Three flags, five questions.","The questions go back with the response."],size=17);y-=22

c.setFillColor(STOP);c.setFont("MonoMed",6.5);c.drawString(ML,y,"5.1 · RED FLAGS");y-=15
flags=[("CRA residual risk","USD 195M of new-model 60 Hz gensets in a thin secondary market; unsecured; reimbursement trigger (their FID) partly within their control; roughly five times the company round","Cap the exposure; first-priority security over slots and equipment; reimbursement at FID with interest; defined waterfall; or ECP warehouses"),
 ("Unconditional capacity charge","\u201cNo minimum purchase\u201d plus an unconditional USD 11.3M/month charge is a 100% take-or-pay for 20 years, harder than the AEP Ohio 85%/12yr regulated benchmark","Contract capacity phased to contracted tenant load with a defined ramp schedule"),
 ("Uncapped indexation","Bands and collars mentioned with no widths and no cap: converts fixed price into cost-plus in disguise, in a market inflating 10 to 20 points per quarter","Tight deadband, hard cap, open-book EPC/OEM pricing, most-favoured pricing")]
xs3=[ML,ML+112,ML+322]
c.setFillColor(INK4);c.setFont("Mono",5.8)
for hx,ht in zip(xs3,["TERM","WHY IT IS DANGEROUS","WHAT CLEARS IT"]):c.drawString(hx,y,ht)
y-=6;rulel(y,col=RULE);y-=13
for n,d,cl in flags:
    c.setFillColor(INK);c.setFont("InterSB",7.4)
    for i,l in enumerate(wrap(n,"InterSB",7.4,104)):c.drawString(xs3[0],y-i*9.5,l)
    a=para(xs3[1],y,d,size=6.9,leading=9.3,maxw=xs3[2]-xs3[1]-12,color=INK2)
    b=para(xs3[2],y,cl,size=6.9,leading=9.3,maxw=ML+CW-xs3[2],color=INK3)
    y=min(a,b)-4;rulel(y+2,col=RULES);y-=13

y-=8
c.setFillColor(WARM);c.setFont("MonoMed",6.5);c.drawString(ML,y,"5.2 · UNDEFINED TERMS · SEND AS QUESTIONS");y-=15
qs=[("Grid transition share","What locked percentage of merchant, capacity market, and ancillary revenue flows to the offtaker once grid-connected, and how do capacity charges step down?"),
 ("Indexation mechanics","What deadband and collar apply to pricing indexed to final capitalized cost, and is there a hard cap with open-book substantiation?"),
 ("Remedies","What are the COD liquidated damages, the guaranteed availability level, the capacity payment abatement schedule, and outage step-in rights?"),
 ("Signing entity and guarantee","Which legal entity signs the PPA and CRA, and will Energy Capital Partners guarantee its obligations?"),
 ("Lateral treatment","Is the ~USD 30M lateral recovered per Dth or added to indexed capex, and what does each route add to the all-in USD/MWh?")]
rulel(y,col=RULE);y-=13
for n,q in qs:
    c.setFillColor(INK);c.setFont("InterSB",7.4)
    for i,l in enumerate(wrap(n,"InterSB",7.4,104)):c.drawString(ML,y-i*9.5,l)
    ny=para(ML+112,y,q,size=6.9,leading=9.3,maxw=CW-112,color=INK2)
    y=ny-4;rulel(y+2,col=RULES);y-=13
footer(6);c.showPage()

# ============ PAGE 7 · 06 NEGOTIATION ============
bg();header("06 · Negotiation map")
y=H-70
kick(y,"this is the brief, no separate document","06 · Negotiation");y-=26
y=point(y,["Their ask is an opening position inside a","negotiable band, about 6% wide on price alone."],size=17);y-=24

def tiercol(x,w,ytop,title,sub,col,items,lw=2):
    c.setStrokeColor(col);c.setLineWidth(lw);c.line(x,ytop,x+w,ytop)
    yy=ytop-14
    c.setFillColor(col);c.setFont("MonoMed",6.2);c.drawString(x,yy,title.upper());yy-=11
    c.setFillColor(INK);c.setFont("InterSB",8.2);c.drawString(x,yy,sub);yy-=14
    for head,body in items:
        c.setStrokeColor(RULES);c.setLineWidth(0.4);c.line(x,yy+7,x+w,yy+7)
        c.setFillColor(INK);c.setFont("InterSB",7.0);c.drawString(x,yy-2,head);yy-=12
        for l in wrap(body,"Inter",6.6,w):
            c.setFillColor(INK3);c.setFont("Inter",6.6);c.drawString(x,yy,l);yy-=8.6
        yy-=7
    return yy

colw=(CW-40)/3
tiercol(ML,colw,y,"Tier 1 · Must move","We do not sign as offered",STOP,[
 ("CRA protection package","Theirs: we fund 195M unsecured, reimbursed at their FID. Target: capped, secured against slots/equipment, interest, defined waterfall, or ECP warehouses. Walk-away: no unsecured backstop above the founder threshold."),
 ("Contract capacity ramp","Theirs: flat 250 MW from COD. Target: phased to contracted tenant load. Walk-away: no 100% take-or-pay from day one."),
 ("Indexation cap","Theirs: bands mentioned, undefined. Target: tight deadband, hard cap, open book, MFN. Walk-away: no uncapped indexation.")],3)
tiercol(ML+colw+20,colw,y,"Tier 2 · Should move","Meaningful value, winnable",WARM,[
 ("Capacity charge","Target 44 USD/kW-month or below against open-book capitalized cost, or equivalent value via structure."),
 ("Term","10 to 12 year initial with extensions, or a grid-arrival step-down/conversion."),
 ("Reserve billing","Pay firm delivered capacity only, or share the 50 MW reserve's merchant and expansion value."),
 ("Grid revenue share","A locked percentage in the PPA, not language."),
 ("Remedies","COD LDs, availability guarantee with abatement, step-in. Non-optional but expected to be conceded.")])
tiercol(ML+2*(colw+20),colw,y,"Tier 3 · Trades","Priced before offered",STEEL2,[
 ("Longer term","Trade for a lower capacity charge; they price term length directly, so this is their favourite currency."),
 ("Bounded Mansfield exclusivity","Trade for the CRA risk shift; scope, geography, and time limited."),
 ("Committed Block 2/3 rights","Trade for ramp phasing; volume certainty against our timing flexibility.")])

# comparison chart bottom
cy=210
c.setFillColor(INK4);c.setFont("MonoMed",6.5);c.drawString(ML,cy,"THE COMPARISON · VS THE OHIO REGULATED BENCHMARK");cy-=8
c.setFillColor(INK3);c.setFont("Mono",6);c.drawString(ML,cy,"the single most usable negotiation exhibit: their private ask against Ohio's own public standard (AEP DC tariff, PUCO Jul 2025)");cy-=20
bx=ML+150;bwf=CW-210
pairs=[("Minimum take","AEP Ohio tariff",0.85,"85%",STEEL2),("","NPS offer",1.0,"100%",STOP),
       ("Committed horizon","AEP Ohio tariff",0.60,"12 yr",STEEL2),("","NPS offer",1.0,"20 yr",STOP)]
for grp,lab,frac,val,col in pairs:
    if grp:
        c.setFillColor(INK2);c.setFont("MonoMed",7);c.drawString(ML,cy,grp);cy-=13
    c.setFillColor(INK3);c.setFont("Mono",6.4);c.drawRightString(bx-8,cy-1,lab)
    c.setFillColor(PAPER3);c.roundRect(bx,cy-4,bwf,7,3,fill=1,stroke=0)
    c.setFillColor(col);c.roundRect(bx,cy-4,bwf*frac,7,3,fill=1,stroke=0)
    c.setFillColor(col);c.setFont("Mono",6.6);c.drawString(bx+bwf+8,cy-1,val)
    cy-=15
footer(7);c.showPage()

# ============ PAGE 8 · 07 GATE ============
bg();header("07 · Gate decision")
y=H-70
kick(y,"one outcome, owned actions","07 · Gate");y-=26
y=point(y,["Request a revised offer. The counter-argument","is real and it is about time, not terms."],size=17);y-=22

c.setFillColor(PAPER2);c.rect(ML,y-44,CW,48,fill=1,stroke=0)
c.setFillColor(WARM);c.rect(ML,y-44,2.6,48,fill=1,stroke=0)
para(ML+12,y-6,"Best argument against this recommendation: this is the only credible Q4 2028 power path at Mansfield. Genset slots are perishable, the CRA schedule is what holds the Q4 2028 date, and every quarter of delay costs more in tenant momentum and raise narrative than these terms cost in dollars. If the revised-offer round takes six months, the timeline the offer is selling may no longer exist.",size=7.4,leading=10.4,maxw=CW-24,color=INK)
y-=62

gates=[("Proceed to term sheet",GO,"Blocked: two red flags outstanding.",False),
       ("Proceed with conditions",WARM2,"Would replicate the Tier 1 list without leverage.",False),
       ("Request revised offer",STEEL2,"SELECTED. Attach 5.2 questions and Tier 1 positions. Signal seriousness on timeline.",True),
       ("Decline",STOP,"Strategic fit is real and the band is negotiable.",False)]
gw=(CW-30)/4
for i,(t,col,d,sel) in enumerate(gates):
    gx=ML+i*(gw+10)
    c.setFillColor(PAPER2);c.setStrokeColor(STEEL2 if sel else RULE);c.setLineWidth(1.2 if sel else 0.7)
    c.roundRect(gx,y-64,gw,64,4,fill=1,stroke=1)
    c.setStrokeColor(INK3);c.setLineWidth(1)
    if sel:
        c.setFillColor(STEEL2);c.rect(gx+10,y-18,8,8,fill=1,stroke=0)
        c.setFillColor(PAPER);c.setFont("InterSB",7);c.drawString(gx+11.3,y-16.8,"\u2713")
    else:
        c.setFillColor(PAPER);c.rect(gx+10,y-18,8,8,fill=0,stroke=1)
    c.setFillColor(col);c.setFont("InterSB",7.2)
    for j,l in enumerate(wrap(t,"InterSB",7.2,gw-34)):c.drawString(gx+24,y-16.5-j*9,l)
    yy=y-34
    for l in wrap(d,"Inter",6.2,gw-20):
        c.setFillColor(INK3);c.setFont("Inter",6.2);c.drawString(gx+10,yy,l);yy-=8
y-=82

c.setFillColor(INK4);c.setFont("MonoMed",6.5);c.drawString(ML,y,"ACTIONS");y-=14
acts=[("Send response: 5.2 questions + Tier 1 positions + timeline signal","Nick","Within 7 days (protects Aug 2026 slot economics)"),
 ("Request Block 1 single-line, load flow, short-circuit, RAM studies","Shane","With the response"),
 ("Verify FirstEnergy 100 MW Q4 2028 status (signed agreement? load study?)","Nick / Shane","Before revised offer lands"),
 ("Confirm BATNA: current competing power counterparty pipeline for Mansfield","Nick","Before gate re-run"),
 ("Financing counsel view on CRA + 20yr obligation debt-equivalence vs the raise","Deniz","Before any term sheet")]
rulel(y,col=RULE);y-=12
for a,o,d in acts:
    ny=para(ML,y,a,size=7.2,leading=9.6,maxw=282,color=INK2)
    c.setFillColor(INK);c.setFont("InterSB",7.2);c.drawString(ML+292,y,o)
    para(ML+360,y,d,size=6.8,leading=9.2,maxw=CW-360,color=INK3)
    y=ny-4;rulel(y+2,col=RULES);y-=12

y-=8
c.setFillColor(PAPER2);c.rect(ML,y-30,CW,34,fill=1,stroke=0)
c.setFillColor(STEEL2);c.rect(ML,y-30,2.6,34,fill=1,stroke=0)
para(ML+12,y-6,"Full founder review required. Exposure (USD 195M + 33M LCs) exceeds threshold X and term (20 years) exceeds threshold Y on any plausible setting of both. Thresholds remain [X = 5, Y = 10 suggested] pending confirmation.",size=7.4,leading=10.4,maxw=CW-24,color=INK)
footer(8);c.showPage()

# ============ PAGE 9 · 08 GRAPHICS ============
bg();header("08 · Standard graphics")
y=H-70
kick(y,"drawn from this review's data","08 · Graphics");y-=26
y=point(y,["The exposure curve, the heat map, the bands."],size=17);y-=24

# exposure curve
c.setFillColor(INK4);c.setFont("MonoMed",6.5);c.drawString(ML,y,"THE EXPOSURE CURVE · CUMULATIVE USD MILLIONS AT RISK UNDER THE CRA");y-=10
gy=y-190;gx=ML+26;gwd=CW-60;gh=170
c.setStrokeColor(INK3);c.setLineWidth(0.9)
c.line(gx,gy,gx+gwd,gy);c.line(gx,gy,gx,gy+gh)
vals=[("Aug 26",7),("Sep",7),("Oct",21),("Nov",31),("Dec",31),("Jan 27",57),("Feb",96),("Mar",111),("Apr",126),("May",165),("Jun",196),("Jul 27",228)]
mx=240
for g in (100,200):
    yy=gy+gh*g/mx
    c.setStrokeColor(RULES);c.setLineWidth(0.4);c.line(gx-3,yy,gx+gwd,yy)
    c.setFillColor(INK4);c.setFont("Mono",5.6);c.drawRightString(gx-6,yy-2,str(g))
bw2=gwd/len(vals)*0.68
for i,(lab,v) in enumerate(vals):
    bx2=gx+10+i*(gwd-40)/len(vals)
    hgt=gh*v/mx
    col=STOP if i==len(vals)-1 else WARM
    c.setFillColor(col);c.setFillAlpha(0.55+0.35*i/len(vals));c.rect(bx2,gy,bw2,hgt,fill=1,stroke=0);c.setFillAlpha(1)
    if i%2==0 or i==len(vals)-1:
        c.setFillColor(INK3);c.setFont("Mono",5.4);c.drawCentredString(bx2+bw2/2,gy-9,lab)
c.setFillColor(STOP);c.setFont("MonoMed",6.6);c.drawCentredString(gx+10+(len(vals)-1)*(gwd-40)/len(vals)+bw2/2,gy+gh*228/mx+8,"USD 228M peak")
fx=gx+gwd-38
c.setStrokeColor(STEEL2);c.setLineWidth(0.7);c.setDash(3,3);c.line(fx,gy,fx,gy+gh-8);c.setDash()
c.setFillColor(STEEL2);c.setFont("Mono",5.6);c.drawRightString(gx+gwd,gy+gh-2,"FID Aug 2027: NPS reimburses")
c.setFillColor(INK4);c.setFont("Mono",5.6);c.drawString(gx,gy-22,"all bars effectively nonrefundable: OEM deposits nonrefundable, lateral cancellation ramps 2M to 39M · includes 33M LC faces")
y=gy-42

# heat map + bands side by side
half=(CW-24)/2
c.setFillColor(INK4);c.setFont("MonoMed",6.5);c.drawString(ML,y,"THE HEAT MAP · RED OURS, STEEL THEIRS, WARM SHARED")
c.drawString(ML+half+24,y,"THE BANDS · THEIR NUMBER VS THE EVIDENCED RANGE")
y-=12
hy=y-158;hx=ML+18;hw=half-30;hh=140
c.setStrokeColor(INK3);c.setLineWidth(0.8);c.line(hx,hy,hx+hw,hy);c.line(hx,hy,hx,hy+hh)
c.setFillColor(STOP);c.setFillAlpha(0.09);c.rect(hx+hw*0.62,hy+hh*0.62,hw*0.38,hh*0.38,fill=1,stroke=0);c.setFillAlpha(1)
c.setFillColor(INK4);c.setFont("Mono",5.4);c.drawCentredString(hx+hw/2,hy-9,"probability \u2192")
c.saveState();c.translate(hx-8,hy+hh/2);c.rotate(90);c.setFont("Mono",5.4);c.drawCentredString(0,0,"impact \u2192");c.restoreState()
pts=[("CRA residual",0.45,0.92,STOP),("demand/ramp",0.80,0.82,STOP),("fuel price",0.66,0.42,STOP),("NPS performance",0.33,0.72,STOP),("capex overrun",0.76,0.56,WARM),("permitting slip",0.55,0.24,STEEL2),("schedule",0.72,0.14,STEEL2),("O&M",0.25,0.12,STEEL2)]
for lab,px,pyf,col in pts:
    cx2,cy2=hx+hw*px,hy+hh*pyf
    c.setFillColor(col);c.circle(cx2,cy2,3.4,fill=1,stroke=0)
    c.setFillColor(INK2);c.setFont("Mono",5.2);c.drawString(cx2+6,cy2-2,lab)

bx3=ML+half+24+4;bw3=half-70
bands=[("Capacity USD/kW-mo","38 to 46",0.93,"47.00",STOP),
       ("All-in USD/MWh","80 to 100 · Bloom ~99",1.04,"116.65",STOP),
       ("Capex USD/kW","1,700 to 2,000 + BESS",0.97,"2,580",WARM),
       ("Fuel USD/MMBtu","Utica planning range",0.42,"3.55",GO)]
by=y-24
for n,r,frac,val,col in bands:
    c.setFillColor(INK2);c.setFont("Mono",6.2);c.drawString(bx3,by,n)
    c.setFillColor(INK4);c.setFont("Mono",5.4);c.drawString(bx3,by-8,r)
    c.setFillColor(STEEL2);c.setFillAlpha(0.22);c.roundRect(bx3+118,by-6,bw3-118,9,4,fill=1,stroke=0);c.setFillAlpha(1)
    dotx=bx3+118+min(max((bw3-118)*frac,4),bw3-118+10)
    c.setFillColor(col);c.circle(dotx,by-1.5,3.6,fill=1,stroke=0)
    c.setFillColor(col);c.setFont("MonoMed",6);c.drawString(bx3+bw3+2,by-3,val)
    by-=36
footer(9);c.showPage()

c.save()
print("PDF written")
