#!/usr/bin/env python3 """Generate Governance Topology thesis architecture diagram using matplotlib.""" import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch import numpy as np def draw_rounded_box(ax, x, y, w, h, text, facecolor, edgecolor='#333333', fontsize=8, fontweight='normal', textcolor='white', alpha=0.95, linewidth=1.2): """Draw a rounded rectangle with centered text.""" box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.008", facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, alpha=alpha, zorder=2) ax.add_patch(box) ax.text(x + w/2, y + h/2, text, ha='center', va='center', fontsize=fontsize, fontweight=fontweight, color=textcolor, zorder=3, wrap=True, fontfamily='sans-serif') def draw_layer_header(ax, x, y, w, h, text, color, fontsize=11): """Draw a layer header bar.""" box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.005", facecolor=color, edgecolor='none', linewidth=0, alpha=1.0, zorder=2) ax.add_patch(box) ax.text(x + w/2, y + h/2, text, ha='center', va='center', fontsize=fontsize, fontweight='bold', color='white', zorder=3, fontfamily='sans-serif') def draw_arrow(ax, x1, y1, x2, y2, color='#666666'): """Draw a connecting arrow between layers.""" ax.annotate('', xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle='->', color=color, lw=1.8, connectionstyle='arc3,rad=0'), zorder=1) def main(): fig, ax = plt.subplots(1, 1, figsize=(16, 22), dpi=200) ax.set_xlim(0, 1) ax.set_ylim(0, 1) ax.axis('off') fig.patch.set_facecolor('white') # Title ax.text(0.5, 0.975, 'Governance Topology: The Tristable Basin', ha='center', va='center', fontsize=22, fontweight='bold', color='#1a1a2e', fontfamily='sans-serif') ax.text(0.5, 0.958, 'Thesis Architecture', ha='center', va='center', fontsize=14, fontweight='normal', color='#555555', fontfamily='sans-serif') # Score badge badge = FancyBboxPatch((0.30, 0.935), 0.40, 0.018, boxstyle="round,pad=0.004", facecolor='#2ecc71', edgecolor='#27ae60', linewidth=1.5, zorder=2) ax.add_patch(badge) ax.text(0.5, 0.944, 'Peer Review: 5.7 \u2192 6.2 \u2192 7.3 \u2192 8.3 / 10 | 25/25 Fixes Complete', ha='center', va='center', fontsize=9, fontweight='bold', color='white', zorder=3, fontfamily='sans-serif') # Layer definitions layers = [ { 'name': 'LAYER 1: THEORETICAL FRAMEWORK (8.5/10)', 'color': '#1a3a5c', # dark blue 'box_color': '#2c5282', 'y_top': 0.895, 'boxes': [ ('Tristable Basin\nModel\nL + T + C = 100', 0.06), ('AR(1) Persistence\n\u03b2 = 0.956\n[0.941, 0.971]', 0.275), ('Literature\n12 Scholars Engaged\nExtends Levitsky & Way', 0.49), ('Potential Function\nV(L) = \u2212log p(L)\nTriple-well BIC-best', 0.705), ] }, { 'name': 'LAYER 2: EMPIRICAL FOUNDATION (8.5/10)', 'color': '#1a6b5a', # teal 'box_color': '#2d8e7e', 'y_top': 0.755, 'boxes': [ ('GMM K=5\nBIC-optimal\nOOS Accuracy 90.8%', 0.06), ('Survival Analysis\n579 spells\nDem \u2248 Tyr (p=0.70)', 0.275), ('IRT Validated\nOrdinal\u2192Interval\nInnocuous', 0.49), ('Time-Spacing\n\u03b2=0.9949/yr\nPost-1972 Robust', 0.705), ] }, { 'name': 'LAYER 3: US CASE STUDY (8.0/10)', 'color': '#2d6a2e', # green 'box_color': '#3d8b3e', 'y_top': 0.615, 'boxes': [ ('Current Position\nPTI: L = 48\nFH: 83 | V-Dem: ~68', 0.10), ('OECD Monte Carlo\n4 Calibrations\nDrift > Volatility', 0.375), ('Resilience Cases\nPoland, SK, Brazil\nSelection Bias Fixed', 0.65), ] }, { 'name': 'LAYER 4: SOVEREIGN CREDIT & FINANCIAL (8.5/10)', 'color': '#c0620a', # orange 'box_color': '#d4841a', 'y_top': 0.475, 'boxes': [ ('Yield Model\nY = 33.05 \u2212 0.35\u00d7L\nR\u00b2 = 0.79 (multivar)', 0.10), ('IV Causality\nLagged-L F=396\n\u0394\u03b2 = 3.6% vs OLS', 0.375), ('Reserve Currency\nPremium\n\u22122,080 bp', 0.65), ] }, { 'name': 'LAYER 5: GLOBAL MAPS & GOVERNANCE (7.5/10)', 'color': '#5b3a8c', # purple 'box_color': '#7b52a8', 'y_top': 0.335, 'boxes': [ ('15 Thematic Maps\n91 Countries\nAll Regions', 0.10), ('Scenario Weights\nFour Roads E[L]=31.2\nSix Frames E[L]=46.6', 0.375), ('Sample Analysis\nEurope 1.36\u00d7 over-rep\nExpansion \u2192 stronger', 0.65), ] }, { 'name': 'LAYER 6: AUDIT & SYNTHESIS (8.5/10)', 'color': '#4a4a4a', # gray 'box_color': '#666666', 'y_top': 0.195, 'boxes': [ ('Uncertainty Audit\n53 Claims Reviewed\n31-Param Master Table', 0.10), ('Grand Synthesis\n9/10 Quality\nCanonical Params', 0.375), ('Peer Review\n25/25 Fixes \u2713\nA:5 B:10 C:10', 0.65), ] }, ] header_h = 0.022 box_h = 0.065 box_w = 0.19 box_w_3 = 0.22 # wider boxes for 3-column layers gap_header_box = 0.008 for i, layer in enumerate(layers): y_top = layer['y_top'] n_boxes = len(layer['boxes']) bw = box_w if n_boxes == 4 else box_w_3 # Layer header draw_layer_header(ax, 0.04, y_top, 0.88, header_h, layer['name'], layer['color']) # Content boxes box_y = y_top - gap_header_box - box_h for text, bx in layer['boxes']: draw_rounded_box(ax, bx, box_y, bw, box_h, text, facecolor=layer['box_color'], fontsize=7.8, textcolor='white') # Connecting arrows between layers if i < len(layers) - 1: arrow_y_start = box_y next_y = layers[i+1]['y_top'] + header_h ax.annotate('', xy=(0.48, next_y + 0.002), xytext=(0.48, arrow_y_start - 0.002), arrowprops=dict(arrowstyle='->', color='#aaaaaa', lw=2.0, connectionstyle='arc3,rad=0'), zorder=1) # Right sidebar - Data scope sidebar_x = 0.935 sidebar_y = 0.12 sidebar_h = 0.82 sidebar_w = 0.003 # Vertical line ax.plot([sidebar_x, sidebar_x], [sidebar_y, sidebar_y + sidebar_h], color='#cccccc', linewidth=2, zorder=1) # Data annotations along sidebar data_items = [ (0.85, '91', 'Countries'), (0.71, '225', 'Years'), (0.57, '1,656', 'Observations'), (0.43, '173', 'Documents'), (0.29, '25', 'Fixes Applied'), ] for dy, num, label in data_items: ax.text(sidebar_x + 0.005, dy + 0.015, num, ha='left', va='center', fontsize=14, fontweight='bold', color='#1a3a5c', fontfamily='sans-serif') ax.text(sidebar_x + 0.005, dy - 0.005, label, ha='left', va='center', fontsize=8, color='#888888', fontfamily='sans-serif') # small tick mark ax.plot([sidebar_x - 0.005, sidebar_x + 0.003], [dy + 0.005, dy + 0.005], color='#cccccc', linewidth=1.5, zorder=1) # Sidebar header ax.text(sidebar_x + 0.005, sidebar_y + sidebar_h + 0.015, 'DATA', ha='left', va='center', fontsize=10, fontweight='bold', color='#1a3a5c', fontfamily='sans-serif') ax.text(sidebar_x + 0.005, sidebar_y + sidebar_h + 0.001, 'SCOPE', ha='left', va='center', fontsize=8, color='#888888', fontfamily='sans-serif') # Key findings callout box at bottom findings_y = 0.04 findings_h = 0.065 findings_box = FancyBboxPatch((0.04, findings_y), 0.88, findings_h, boxstyle="round,pad=0.008", facecolor='#f8f9fa', edgecolor='#dee2e6', linewidth=1.5, zorder=2) ax.add_patch(findings_box) ax.text(0.48, findings_y + findings_h - 0.012, 'KEY FINDINGS', ha='center', va='center', fontsize=9, fontweight='bold', color='#1a3a5c', fontfamily='sans-serif', zorder=3) findings = [ '\u2022 Triple-well potential confirmed (BIC-best, \u0394BIC=202) | IV causality verified (F=396, \u0394\u03b2=3.6%)', '\u2022 OOS zone accuracy 90.8% [86-94%] exceeds 78% claim | Ordinal\u2192interval assumption innocuous', '\u2022 Liberty-yield R\u00b2=0.79 | OECD Monte Carlo: drift matters more than volatility', '\u2022 12 scholars engaged | 4 resilience cases | 53 uncertainty claims audited | All 25 fixes complete', ] for j, finding in enumerate(findings): ax.text(0.07, findings_y + findings_h - 0.022 - j * 0.012, finding, ha='left', va='center', fontsize=7, color='#333333', fontfamily='sans-serif', zorder=3) # Footer ax.text(0.5, 0.015, 'N. Gogerty | Governance Topology: The Tristable Basin | 2026', ha='center', va='center', fontsize=7, color='#999999', fontfamily='sans-serif', style='italic') plt.tight_layout(pad=0.5) output_path = "/Users/nickgogerty/Downloads/Political topology/00-THESIS-ARCHITECTURE-DIAGRAM.png" fig.savefig(output_path, dpi=200, bbox_inches='tight', facecolor='white', edgecolor='none') print(f"Saved to: {output_path}") plt.close() if __name__ == '__main__': main()