from pathlib import Path
import csv, hashlib, json, math
from xml.sax.saxutils import escape
ROOT=Path(__file__).resolve().parent
OUT=Path(__file__).resolve().parent
SRC=ROOT/'point-and-aperture-response.csv'
rows=list(csv.DictReader(SRC.open(newline='',encoding='utf-8')))
a=['<svg xmlns="http://www.w3.org/2000/svg" width="288" height="1130" viewBox="0 0 288 1130" role="img" aria-labelledby="title desc">','<title id="title">Calculated optical response at 1 micrometre and 650 AU</title>','<desc id="desc">Two plots of the retained numerical CSV. Point response over 0 to 30 centimetres, then point and one metre aperture mean over 0 to 2 metres on a logarithmic vertical scale. These optical responses are not recovered planetary resolution.</desc>','<rect width="288" height="1130" fill="white"/>','<g font-family="Arial, sans-serif" font-size="18" fill="#142440">']
def text(x,y,s,size=18,anchor='start'):
 a.append(f'<text x="{x}" y="{y}" font-size="{size}" text-anchor="{anchor}">{escape(s)}</text>')
def line(x1,y1,x2,y2,color='#cbd5e1',dash=''):
 a.append(f'<path d="M{x1} {y1}L{x2} {y2}" fill="none" stroke="{color}" stroke-dasharray="{dash}"/>')
def curve(data,fn,col,dash=''):
 pts=' '.join(f'{x:.6f},{y:.6f}' for x,y in (fn(r) for r in data))
 a.append(f'<polyline points="{pts}" fill="none" stroke="{col}" stroke-width="1.6" stroke-dasharray="{dash}"/>')
text(16,29,'Calculated optical',20);text(16,54,'response',20);text(16,83,'1 µm wavelength · 650 AU')
text(16,124,'A point spreads into rings',19)
text(16,153,'Relative point response')
left,right=65,272;top,bottom=180,430
for v in [0,.5,1]:
 y=bottom-v*(bottom-top);line(left,y,right,y);text(left-9,y+6,str(v),anchor='end')
for v in [0,10,20,30]:
 x=left+(right-left)*v/30;line(x,top,x,bottom);text(x,459,str(v),anchor='middle')
small=[r for r in rows if float(r['radius_m'])<=.3]
curve(small,lambda r:(left+(right-left)*float(r['radius_m'])/.3,bottom-(bottom-top)*float(r['point_relative_response'])),'#9b288f')
x=left+(right-left)*4.91/30;line(x,top,x,bottom,'#186b8c','5 4')
text(144,490,'Offset (cm)',anchor='middle');text(16,523,'First zero ≈ 4.91 cm')
text(16,568,'A telescope averages',19);text(16,593,'many rings',19)
text(16,625,'Relative response · log scale')
top,bottom=720,990
line(18,654,45,654,'#9b288f');text(54,660,'Point sample')
line(18,683,45,683,'#186b8c','6 3');text(54,689,'1 m aperture mean')
for power,label in [(0,'1'),(-2,'0.01'),(-4,'0.0001')]:
 y=top+(math.log10(1.1)-power)/(math.log10(1.1)+5)*(bottom-top);line(left,y,right,y);text(left-6,y+5,label,16,anchor='end')
for v in [0,1,2]:
 x=left+(right-left)*v/2;line(x,top,x,bottom);text(x,1019,str(v),anchor='middle')
a.append(f'<svg x="{left}" y="{top}" width="{right-left}" height="{bottom-top}" viewBox="{left} {top} {right-left} {bottom-top}" overflow="hidden">')
large=[r for r in rows if float(r['radius_m'])<=2]
for key,col,dash in [('point_relative_response','#9b288f',''),('aperture_mean_relative_response','#186b8c','6 3')]:
 curve(large,lambda r:(left+(right-left)*float(r['radius_m'])/2,top+(math.log10(1.1)-math.log10(max(float(r[key]),1e-300)))/(math.log10(1.1)+5)*(bottom-top)),col,dash)
a.append('</svg>');text(144,1051,'Offset (m)',anchor='middle');text(16,1091,'Optical response, not');text(16,1117,'planetary resolution.')
a.append('</g></svg>')
f=OUT/'point-response-mobile.svg';f.write_text('\n'.join(a)+'\n',encoding='utf-8')
note={'source_csv':str(SRC.relative_to(ROOT)),'source_sha256':hashlib.sha256(SRC.read_bytes()).hexdigest(),'source_rows':len(rows),'first_panel_rows':len(small),'second_panel_rows_per_series':len(large),'svg_sha256':hashlib.sha256(f.read_bytes()).hexdigest(),'data_invariance':'Every selected CSV row retained without resampling or smoothing. Panel selections <=0.3m and <=2m and log limits 1e-5..1.1 match original reproduce.py. Radius-to-cm conversion is display only. First-zero 4.91cm annotation retained from existing plot, not recomputed from sampled data.','layout':'288px viewBox; all text >=16 units = >=16 CSSpx at288px width. Two stacked panels, legend outside plots, fewer log ticks.','scope':'Layout-only asset outside Site; no simulation rerun or scientific validation.'}
(OUT/'point-mobile-provenance.json').write_text(json.dumps(note,indent=2)+'\n',encoding='utf-8')
