#!/usr/bin/env python3
"""Make standalone scientific SVGs from frozen results, without repeating tests."""
from pathlib import Path
import argparse,html,json,math
LABELS={'primary_70_30':'Primary:70% wind /30% solar','sensitivity_50_50':'Sensitivity:50% wind /50% solar','sensitivity_90_10':'Sensitivity:90% wind /10% solar','sensitivity_remove_DST_dates':'Sensitivity:remove both DST-transition dates','sensitivity_nonnegative_generation':'Sensitivity:clip negative renewable values only'}
COLORS=['#a15d34','#0072b2','#009e73','#8054a4','#b86e00']
def esc(s):return html.escape(str(s))
def nice_step(maximum):
    raw=maximum/5;p=10**math.floor(math.log10(raw));return next(x*p for x in [1,2,5,10] if x*p>=raw)
def start(width,height,title,description):
    return [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width} {height}" role="img" aria-labelledby="title desc"><title id="title">{esc(title)}</title><desc id="desc">{esc(description)}</desc><rect width="{width}" height="{height}" fill="white"/><style>text{{font-family:Arial,sans-serif;fill:#252a27;font-size:13px}}.grid{{stroke:#dedede;stroke-width:1}}.axis{{stroke:#333;stroke-width:1.1}}</style>']
def capacity_panel(parts,rows,offset,height,ymax,heading):
    left,right,top,bottom=100,935,offset+48,offset+height-45
    x=lambda year:left+(year-2015)/9*(right-left);y=lambda value:bottom-value/ymax*(bottom-top)
    parts.append(f'<text x="100" y="{offset+21}" font-size="17" font-weight="bold">{esc(heading)}</text>')
    step=nice_step(ymax)
    for n in range(int(ymax/step)+1):
        value=n*step;yy=y(value)
        parts.extend([f'<line class="grid" x1="{left}" y1="{yy:.2f}" x2="{right}" y2="{yy:.2f}"/>',f'<text x="{left-10}" y="{yy+4:.2f}" text-anchor="end">{value:,.0f}</text>'])
    parts.extend([f'<line class="axis" x1="{left}" y1="{top}" x2="{left}" y2="{bottom}"/>',f'<line class="axis" x1="{left}" y1="{bottom}" x2="{right}" y2="{bottom}"/>'])
    for r in rows:
        xx=x(r['year']);lo=y(r['permutation_q025_nominal_GWh_equivalent']);hi=y(r['permutation_q975_nominal_GWh_equivalent']);median=y(r['permutation_median_nominal_GWh_equivalent']);original=y(r['original_nominal_GWh_equivalent'])
        parts.extend([f'<path d="M{xx:.2f},{lo:.2f}V{hi:.2f}M{xx-5:.2f},{lo:.2f}H{xx+5:.2f}M{xx-5:.2f},{hi:.2f}H{xx+5:.2f}" stroke="#0072b2" fill="none" stroke-width="2"/>',f'<rect x="{xx-4:.2f}" y="{median-4:.2f}" width="8" height="8" fill="#0072b2"/>',f'<circle cx="{xx:.2f}" cy="{original:.2f}" r="4.5" fill="#a15d34"/>',f'<text x="{xx:.2f}" y="{bottom+21}" text-anchor="middle">{r["year"]}</text>'])
    center=(top+bottom)/2
    parts.append(f'<text x="23" y="{center}" text-anchor="middle" transform="rotate(-90 23 {center})">Nominal GWh-equivalent</text>')
def main():
    ap=argparse.ArgumentParser();ap.add_argument('--directory',type=Path,default=Path(__file__).resolve().parent);args=ap.parse_args();directory=args.directory;rows=json.loads((directory/'results/year-scenario-results.json').read_text());figures=directory/'figures';figures.mkdir(exist_ok=True);scenarios=list(LABELS)
    primary=[r for r in rows if r['scenario']=='primary_70_30'];max_primary=max(max(r['original_nominal_GWh_equivalent'],r['permutation_q975_nominal_GWh_equivalent']) for r in primary);step=nice_step(max_primary);ymax=math.ceil(max_primary/step)*step
    p=start(1000,475,'Chronology and ideal storage accounting:primary scenario','Original capacity versus median and central95%reference from999within-month day orders per nominal profile. Not physical French capacity or confidence intervals.');capacity_panel(p,primary,10,390,ymax,LABELS['primary_70_30']);p.extend(['<circle cx="110" cy="420" r="4.5" fill="#a15d34"/><text x="123" y="425">Original source-label order</text><rect x="400" y="416" width="8" height="8" fill="#0072b2"/><text x="416" y="425">Permutation median + central95% reference range</text>','<text x="100" y="455">Nominal48-slot/day accounting model; no physical UTC completeness or real-system sizing claim.</text></svg>']);(figures/'capacity-primary.svg').write_text('\n'.join(p)+'\n')
    maximum=max(max(r['original_nominal_GWh_equivalent'],r['permutation_q975_nominal_GWh_equivalent']) for r in rows);step=nice_step(maximum);ymax=math.ceil(maximum/step)*step;p=start(1000,1570,'All predeclared storage-accounting scenarios','All50year-scenario results; axes share a common scale; source chronology points and conditional permutation reference ranges.');
    for i,sid in enumerate(scenarios):capacity_panel(p,[r for r in rows if r['scenario']==sid],i*290+10,280,ymax,LABELS[sid])
    p.extend(['<circle cx="110" cy="1495" r="4.5" fill="#a15d34"/><text x="123" y="1500">Original source-label order</text><rect x="400" y="1491" width="8" height="8" fill="#0072b2"/><text x="416" y="1500">Permutation median + central95% reference range</text>','<text x="100" y="1535">Reference ranges are conditional day-order distributions, not confidence intervals or physical capacity estimates.</text></svg>']);(figures/'capacity-all-scenarios.svg').write_text('\n'.join(p)+'\n')
    # Dimensionless ratios expose the90/10 exception rather than selecting only positive cases.
    p=start(1000,650,'Chronology/reference ratios across all scenarios','Every year and all five prespecified scenarios. The90%wind scenario in2016is below one; no universal direction is claimed.');left,right,top,bottom=100,935,65,440;x=lambda year:left+(year-2015)/9*(right-left);low=.9;high=1.5;y=lambda v:bottom-(v-low)/(high-low)*(bottom-top)
    for tick in [.9,1,1.1,1.2,1.3,1.4,1.5]:p.extend([f'<line x1="{left}" y1="{y(tick):.2f}" x2="{right}" y2="{y(tick):.2f}" stroke="{("#555" if tick==1 else "#ddd")}" stroke-dasharray="{("6 4" if tick==1 else "none")}"/>',f'<text x="88" y="{y(tick)+4:.2f}" text-anchor="end">{tick:.1f}</text>'])
    for i,sid in enumerate(scenarios):
        group=[r for r in rows if r['scenario']==sid];points=' '.join(f'{x(r["year"]):.2f},{y(r["original_to_permutation_median_ratio"]):.2f}' for r in group);p.append(f'<polyline points="{points}" fill="none" stroke="{COLORS[i]}" stroke-width="1.7"/>')
        for r in group:p.append(f'<circle cx="{x(r["year"]):.2f}" cy="{y(r["original_to_permutation_median_ratio"]):.2f}" r="3.5" fill="{COLORS[i]}"/>')
        yy=495+i*25;p.extend([f'<line x1="105" y1="{yy}" x2="125" y2="{yy}" stroke="{COLORS[i]}" stroke-width="2"/>',f'<text x="137" y="{yy+4}">{esc(LABELS[sid])}</text>'])
    for year in range(2015,2025):p.append(f'<text x="{x(year):.2f}" y="464" text-anchor="middle">{year}</text>')
    p.append('<text x="24" y="260" text-anchor="middle" transform="rotate(-90 24 260)">Original / permutation median capacity</text><text x="100" y="637">Nominal reported-profile model. Ratios do not establish actual French storage needs.</text></svg>');(figures/'ratios-all-scenarios.svg').write_text('\n'.join(p)+'\n')
    # Exact four-slot control, no data fitting.
    controls=json.loads((directory/'results/deterministic-control.json').read_text());p=start(850,440,'Same residual distribution, different storage excursion','Persistent residuals1,1,-1,-1need1MWh; alternating1,-1,1,-1need0.5MWhunder the declared half-hour model.');left,right,top,bottom=95,795,60,325;x=lambda i:left+i/4*(right-left);y=lambda v:bottom-v/1.1*(bottom-top)
    for value in [0,.5,1]:p.extend([f'<line class="grid" x1="{left}" y1="{y(value):.2f}" x2="{right}" y2="{y(value):.2f}"/>',f'<text x="82" y="{y(value)+4:.2f}" text-anchor="end">{value:g}</text>'])
    for i,c in enumerate(controls):
        points=' '.join(f'{x(j):.2f},{y(v):.2f}' for j,v in enumerate(c['cumulative_nominal_MWh']));p.append(f'<polyline points="{points}" fill="none" stroke="{COLORS[i]}" stroke-width="2.5"/>');p.append(f'<text x="{100+i*370}" y="385">{esc(c["label"])}:capacity{c["capacity_MWh"]:g}MWh</text>')
    for i in range(5):p.append(f'<text x="{x(i):.2f}" y="350" text-anchor="middle">{i}</text>')
    p.append('<text x="440" y="370" text-anchor="middle">Elapsed half-hour model slots</text><text x="24" y="195" text-anchor="middle" transform="rotate(-90 24 195)">Cumulative deficit (model MWh)</text><text x="95" y="420">Exact toy control:same zero mean,variance1and peak1. Established accounting,not a new theorem.</text></svg>');(figures/'deterministic-control.svg').write_text('\n'.join(p)+'\n')
    print('Created4standalone scientific SVGs from existing computed results.')
if __name__=='__main__':main()
