#!/usr/bin/env python3
"""Render allowlisted aggregate figures; requires matplotlib 3.11.2.

Run: python render_figures.py --data figures.json --out figures.
Inputs: the explicit sanitized figures.json download. No private sources or model execution.
"""
from pathlib import Path
import json, os, argparse
parser=argparse.ArgumentParser(description='Render Vezryn Research sanitized aggregate figures; no model execution.')
parser.add_argument('--data',type=Path,default=Path('figures.json'))
parser.add_argument('--out',type=Path,default=Path('figures'))
args=parser.parse_args()
args.out.mkdir(parents=True,exist_ok=True)
os.environ.setdefault('MPLCONFIGDIR',str(Path.cwd()/'.cache'/'matplotlib'))
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
DATA=json.loads(args.data.read_text())['figures']
BG, PANEL, TEXT, MUTED, GOLD, BLUE, EDGE = '#151719', '#1e2225', '#f1ede4', '#c2c5c5', '#d8bb7f', '#82b4ca', '#434a50'
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':13,'text.color':TEXT,'axes.labelcolor':TEXT,'xtick.color':MUTED,'ytick.color':MUTED,'axes.edgecolor':EDGE,'axes.facecolor':BG,'figure.facecolor':BG,'svg.fonttype':'none','svg.hashsalt':'vezryn-research-figures-v1','savefig.facecolor':BG})
def base(fid):
 m=DATA[fid]; fig=plt.figure(figsize=(m['width']/100,m['height']/100),dpi=100)
 fig.text(.04,.94, fid.upper()+'  /  '+m['title'],fontsize=19,weight='bold',va='top')
 source = ', '.join(m['sourceIds']) if len(m['sourceIds']) < 8 else m['sourceIds'][0]+'–'+m['sourceIds'][-1]+' (bounded subset; see caption)'
 fig.text(.04,.028,'Sources: '+source+'  ·  Vezryn Research / 0.1',fontsize=11,color=MUTED)
 return fig,m['data']
def axes(fig,bounds):
 ax=fig.add_axes(bounds); ax.spines[['top','right']].set_visible(False); ax.grid(axis='y',color=EDGE,alpha=.35); ax.set_axisbelow(True); return ax
def txt(ax,x,y,text,**kwargs): ax.text(x,y,text,**kwargs)
def box(ax,x,y,w,h,title,detail):
 ax.add_patch(Rectangle((x,y),w,h,facecolor=PANEL,edgecolor=EDGE,lw=1))
 txt(ax,x+.02,y+h-.02,title,fontsize=14,weight='bold',va='top')
 txt(ax,x+.02,y+.023,detail,fontsize=12,color=MUTED,va='bottom')
def f1(fig,d):
 ax=fig.add_axes([.04,.07,.92,.81]);ax.axis('off')
 columns=[.01,.11,.28,.65]
 for x,s in zip(columns,['ID','FINAL RECORD','CHANGED VARIABLE','BOUNDED VERDICT']):txt(ax,x,.99,s,fontsize=11,color=GOLD,va='top')
 for i,row in enumerate(d['rows']):
  y=.94-i*.049
  ax.axhline(y-.021,color=EDGE,lw=.5)
  for j,v in enumerate(row):txt(ax,columns[j],y,v,fontsize=12,va='center',color=TEXT if j!=1 else MUTED)
def f2(fig,d):
 ax=axes(fig,[.36,.29,.56,.48]);ax.set_xlim(215,251);ax.set_ylim(-.6,1.6);ax.set_yticks([1,0],[r[0] for r in d['rows']]);ax.set_xlabel('Benchmark score / 300')
 for i,(_,q,c) in enumerate(d['rows']):
  y=1-i;ax.plot([c,q],[y,y],color=EDGE,lw=3);ax.scatter([q,c],[y,y],c=[GOLD,BLUE],s=110)
  txt(ax,q,y+.18,f'{q} incumbent',ha='center',fontsize=12);txt(ax,c,y-.25,f'{c} candidate',ha='center',fontsize=12)
 fig.text(.04,.12,'Earlier historical incumbent: 242/300 · accounting history only; unchanged behavior',fontsize=13,color=MUTED)
def f3(fig,d):
 a=np.array(d['rows']); ax=axes(fig,[.09,.29,.36,.50]);ax.scatter(a[:,0],a[:,1],s=70,c=GOLD);ax.set_xlabel('LoRA-factor interpolation fraction α');ax.set_ylabel('Factor displacement L2');ax.set_ylim(0,.0038)
 for x,y in a[:,:2]:txt(ax,x,y+.00015,f'{y:.6f}',ha='left' if x==a[0,0] else 'center',fontsize=11)
 ax=axes(fig,[.57,.29,.35,.50]);xs=np.arange(5);fmt=[d['baseline']['format']]+a[:,2].tolist();fab=[d['baseline']['fabrication']]+a[:,3].tolist()
 ax.scatter(xs,fmt,c=GOLD,s=65,label='Format failures');ax.scatter(xs,fab,c=BLUE,s=65,marker='s',label='Fabrication')
 ax.set_xticks(xs,['Q1','0.125','0.25','0.375','0.5']);ax.set_ylabel('Failures / 18');ax.set_ylim(0,9);ax.set_yticks(range(0,10,2));ax.legend(frameon=False,fontsize=11,loc='upper left')
 fig.text(.09,.11,'Discrete measured points only · no fitted interpolation curve',fontsize=12,color=MUTED)
def f4(fig,d):
 labels=d['labels'];lookup={}
 for a,b,g,u in d['pairs']:lookup[(a,b)]=(g,u);lookup[(b,a)]=(g,u)
 for k,title in enumerate(['Gradient cosine','Fresh Adam update cosine']):
  ax=fig.add_axes([.08+k*.47,.17,.37,.61]);ax.set_xlim(-.5,3.5);ax.set_ylim(3.5,-.5);ax.set_xticks(range(4),labels);ax.set_yticks(range(4),labels);ax.set_title(title,fontsize=15,pad=15);ax.set_aspect('equal');ax.tick_params(length=0)
  for i,a in enumerate(labels):
   for j,b in enumerate(labels):
    v=lookup.get((a,b));color=PANEL if v is None else plt.matplotlib.colors.to_hex((.17+v[k]*.29,.19+v[k]*.24,.20+v[k]*.13))
    ax.add_patch(Rectangle((j-.5,i-.5),1,1,facecolor=color,edgecolor=BG,lw=3))
    if v:txt(ax,j,i,f'{v[k]:.3f}',ha='center',va='center',fontsize=11)
 fig.text(.08,.095,'M1/M2: masked DPO · SL: chosen-span supervision · Rounded display; exact data linked',fontsize=12,color=MUTED)
def f5(fig,d):
 for i,p in enumerate(d['panels']):
  ax=axes(fig,[.07+i*.31,.34,.25,.43]);x=np.arange(len(p['radii']));ax.bar(x-.15,p['predicted'],.3,color=GOLD,label='Predicted '+p['prediction'].lower());ax.bar(x+.15,p['observed'],.3,color=BLUE,label='Observed improve')
  ax.set_ylim(0,14);ax.set_yticks([0,4,8,12]);ax.set_xticks(x,[f'{r:.6f}' for r in p['radii']],rotation=25,ha='right',fontsize=11);ax.set_xlabel('Displacement radius',fontsize=12);ax.set_title(p['id'],fontsize=15)
  for j,(a,b) in enumerate(zip(p['predicted'],p['observed'])):txt(ax,j-.15,a+.25,str(a),ha='center',fontsize=12);txt(ax,j+.15,b+.25,str(b),ha='center',fontsize=12)
  ax.legend(frameon=True,facecolor=PANEL,edgecolor=EDGE,framealpha=1,fontsize=9,loc='lower left');
  if i==0:ax.set_ylabel('Chosen spans / 12')
 fig.text(.07,.1,'0065: nonworsening forecast ≠ positive improvement measurement · own-zero, teacher-forced NLL',fontsize=12,color=MUTED)
def f6(fig,d):
 ax=fig.add_axes([.04,.08,.92,.79]);ax.axis('off')
 txt(ax,.01,.96,'MATCHED INTERVENTIONS — separate contrasts, no claimed causal arrows',fontsize=12,color=GOLD)
 for i,(id,title,detail) in enumerate(d['nodes']):
  y=.79-i*.145;box(ax,.01,y,.97,.125,id+'  /  '+title,detail)
 txt(ax,.03,.015,d['native'],fontsize=13,color=BLUE)
def f7(fig,d):
 a=d['amplitudes'];x=np.arange(3)
 for i,(key,title,ylim) in enumerate([('medianAbsoluteInteractionNats','Median |interaction| (nats)',(0,.06)),('normalizedInteractionScores','Normalized interaction score',(0,1)),('spanEntryReversalRates','Span-entry reversal rate',(0,.4))]):
  ax=axes(fig,[.08+i*.31,.45,.24,.33]);vals=d[key];ax.scatter(x,vals,s=65,c=GOLD);ax.set_title(title,fontsize=12,pad=12);ax.set_xticks(x,[f'{r:.6g}' for r in a],rotation=20,ha='right',fontsize=10);ax.set_ylim(*ylim)
  for j,v in enumerate(vals):txt(ax,j,v+ylim[1]*.05,f'{v:.9f}',ha='center',fontsize=10)
 fig.text(.08,.28,'Sign agreement / 12 spans',fontsize=14,weight='bold');fig.text(.08,.22,'V1: linear 7 · quadratic 6     V2: linear 6 · quadratic 7',fontsize=13)
 e=d['v2Error'];fig.text(.08,.155,f"V2 MAE: linear {e['linearMAE']:.10f} · quadratic {e['quadraticMAE']:.9f} nats",fontsize=12,color=MUTED)
 fig.text(.08,.105,f"V2 RMSE: linear {e['linearRMSE']:.9f} · quadratic {e['quadraticRMSE']:.8f} nats",fontsize=12,color=MUTED)
def f8(fig,d):
 ax=fig.add_axes([.04,.07,.92,.81]);ax.axis('off');ax.set_xlim(0,1);ax.set_ylim(0,1);txt(ax,.01,.98,d['question'],fontsize=16,weight='bold',va='top')
 ax.plot([.025,.025],[.04,.91],color=EDGE,lw=2)
 for i,(id,hypothesis,verdict) in enumerate(d['branches']):
  y=.86-i*.109;ax.plot([.025,.07],[y,y],color=EDGE,lw=2);txt(ax,.085,y+.026,id+'  /  '+hypothesis,fontsize=13,weight='bold',va='center');txt(ax,.085,y-.022,verdict,fontsize=12,color=GOLD if 'false' in verdict else MUTED,va='center')
for fid,renderer in [('f1',f1),('f2',f2),('f3',f3),('f4',f4),('f5',f5),('f6',f6),('f7',f7),('f8',f8)]:
 fig,d=base(fid);renderer(fig,d)
 fig.savefig(args.out / f'{fid}.svg',metadata={'Date':None,'Creator':'Vezryn Research aggregate figure generator','Title':DATA[fid]['title'],'Description':DATA[fid]['alt']})
 svg_path = args.out / f"{fid}.svg"
 svg_path.write_text("\n".join(line.rstrip() for line in svg_path.read_text().splitlines()) + "\n")
 plt.close(fig)
print('Rendered f1–f8 from public/data/figures.json')
