from pathlib import Path
import json,html
ROOT=Path(__file__).resolve().parent.parent
def render_svg(spec):
parts=['<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="1120" viewBox="0 0 1600 1120">','<defs><marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0 0 L8 4 L0 8 Z" fill="#527084"/></marker></defs>','<rect width="1600" height="1120" fill="#FAFBF8"/>']
def rect(x,y,w,h,fill,stroke='none',rx=14):parts.append(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{rx}" fill="{fill}" stroke="{stroke}"/>')
def text(x,y,t,size=23,fill='#18384D',weight='400',anchor='start'):
parts.append(f'<text x="{x}" y="{y}" font-family="DejaVu Sans,Arial,sans-serif" font-size="{size}" font-weight="{weight}" fill="{fill}" text-anchor="{anchor}">{html.escape(t)}</text>')
def arrow(x1,y1,x2,y2):parts.append(f'<path d="M{x1} {y1} L{x2} {y2}" stroke="#527084" stroke-width="3" fill="none" marker-end="url(#arrow)"/>')
def box(x,y,w,h,title,lines):
rect(x,y,w,h,'#EAF1F4','#CFDDE4');text(x+w/2,y+34,title,25,weight='600',anchor='middle')
for i,t in enumerate(lines):text(x+w/2,y+66+27*i,t,20,anchor='middle')
text(60,68,spec['title'],39,weight='600')
text(60,108,'Source structures • declared operations • retained information',23,fill='#526977')
rect(50,145,725,885,'#FFFFFF','#DCE5E6');rect(805,145,745,885,'#FFFFFF','#DCE5E6')
text(85,192,'SP: one local source mechanism',28,weight='600')
text(840,192,'Companion constructions',28,weight='600')
box(105,225,615,94,'Ordered begetting rows',['Nine source rows + primary Noah'])
arrow(412,319,412,365);text(445,347,'accumulate',18,fill='#526977')
box(105,377,615,94,'Regular birth field',['Complete source births, with count bindings'])
arrow(412,471,412,532);text(445,503,'fixed Flood relation',18,fill='#526977')
box(105,544,615,94,'Flood capacities',['Birth − Flood start + 1'])
arrow(412,638,412,701);text(445,663,'minimum with',18,fill='#526977');text(445,690,'baseline life ledger',18,fill='#526977')
box(105,713,615,108,'Capped lifespan ledger',['All nine lives reconstructed','Three source rows shortened'])
arrow(330,821,248,874);arrow(495,821,578,874)
text(140,855,'accumulate',17,fill='#526977');text(590,855,'round counts',17,fill='#526977')
box(100,886,290,105,'Cumulative field',['Ordered changes','from local reductions'])
box(435,886,290,105,'Rounded counts',['Same cap rule','resolved before rounding'])
descriptions=[
('Rounded inverse','Original components → one reversal','Register and carry branch'),
('Calendar Keys','Measured spans → exact conversion','Units, selected parts and pivot'),
('Covenant / clutch','Two source paths → derived shift','Shared lifespans; additional joins'),
('Lists and NT','Ordered counts or slots → fields','Category, metric and source order'),
('Toledot','Occurrences → source sections','Multiplicity and symmetry tests')]
for i,(title,line1,line2) in enumerate(descriptions):
y=230+i*150
rect(840,y,675,128,'#F0F4ED','#DAE4D6');text(865,y+36,title,27,weight='600');text(865,y+72,line1,22);text(865,y+103,line2,20,fill='#526977')
text(60,1070,'Shared operations connect these families; they do not make one mandatory pipeline.',23,weight='600')
text(60,1102,'A total can agree while internal boundaries, source roles or admissible grids differ. • C932–C1131',19,fill='#526977')
parts.append('</svg>');return '\n'.join(parts)+'\n'
if __name__=='__main__':
spec=json.loads((ROOT/'model/family_map_spec.json').read_text())
(ROOT/'deliverables/490d_Chronological_Family_Map_C1131_Final.svg').write_text(render_svg(spec))
Evidence
render map v2.py
Edition and provenance
render_map_v2.py
SHA-256 363a862e630d1fb5064ab49f96ec9f6baac7149d275cf22ff90adca70551dc57
C480–C1634/Research_Cycles/C0932_C1131/evidence/render_map_v2.py