""" Governance Topology: The Tristable Basin — Thesis Architecture Diagram Post-Audit Revision — 12/12 Research Programs Complete """ import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch, FancyArrowPatch import numpy as np # ── CONFIGURATION ────────────────────────────────────────────────── DPI = 200 FIG_W, FIG_H = 24, 22 # inches # Colors NAVY = '#1a2332' NAVY_LIGHT = '#2d3748' BLUE = '#2b6cb0' BLUE_LIGHT = '#ebf4ff' PURPLE = '#553c9a' PURPLE_LIGHT = '#f3e8ff' RED = '#c53030' RED_LIGHT = '#fff5f5' RED_DARK = '#742a2a' GREEN = '#276749' GREEN_LIGHT = '#f0fff4' GREEN_MED = '#38a169' AMBER = '#975a16' AMBER_LIGHT = '#fefcbf' ORANGE = '#dd6b20' YELLOW = '#d69e2e' GRAY = '#4a5568' GRAY_LIGHT = '#718096' GRAY_LIGHTER = '#a0aec0' BG = '#f7f8fa' WHITE = '#ffffff' TIER1_BG = '#edf2f7' TIER2_BG = '#f0f4f8' TIER3_BG = '#fafbfc' SIDE_BG = '#f0f4f8' BORDER = '#cbd5e0' DIVIDER = '#e2e8f0' fig, ax = plt.subplots(1, 1, figsize=(FIG_W, FIG_H), facecolor=BG) ax.set_xlim(0, 100) ax.set_ylim(0, 100) ax.set_aspect('equal') ax.axis('off') def box(x, y, w, h, fc=WHITE, ec=BORDER, lw=1.5, radius=0.3, zorder=2): p = FancyBboxPatch((x, y), w, h, boxstyle=f"round,pad={radius}", facecolor=fc, edgecolor=ec, linewidth=lw, zorder=zorder, transform=ax.transData) ax.add_patch(p) return p def header_box(x, y, w, h_header, h_total, header_color=BLUE, body_color=WHITE, ec=None): """Draw a box with colored header""" if ec is None: ec = header_color box(x, y, w, h_total, fc=body_color, ec=ec, lw=2) box(x, y + h_total - h_header, w, h_header, fc=header_color, ec=header_color, lw=0) def txt(x, y, text, size=8, color=GRAY, weight='normal', ha='center', va='center', family='sans-serif', zorder=5): ax.text(x, y, text, fontsize=size, color=color, weight=weight, ha=ha, va=va, fontfamily=family, zorder=zorder, clip_on=False) def arrow(x1, y1, x2, y2, color=GRAY, lw=1.5, style='->', zorder=3): ax.annotate('', xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle=style, color=color, lw=lw), zorder=zorder) def hline(x1, x2, y, color=DIVIDER, lw=1, ls='-', zorder=3): ax.plot([x1, x2], [y, y], color=color, lw=lw, ls=ls, zorder=zorder) # ═══════════════════════════════════════════════════════════════════ # TITLE BAR # ═══════════════════════════════════════════════════════════════════ box(0, 96.5, 100, 3.5, fc=NAVY, ec=NAVY, lw=0, radius=0) txt(38, 98.8, 'GOVERNANCE TOPOLOGY — THE TRISTABLE BASIN', size=9, color=GRAY_LIGHTER, weight='bold', ha='center') txt(38, 97.5, 'Thesis Architecture — Post-Audit Revision', size=16, color=WHITE, weight='bold', ha='center') txt(82, 97.5, '12/12 RPs Complete', size=10, color=GREEN_MED, weight='bold', ha='center') # ═══════════════════════════════════════════════════════════════════ # TIER 1: THEORETICAL FRAMEWORK # ═══════════════════════════════════════════════════════════════════ T1_TOP = 75 T1_H = 20 T1_W = 74 # Tier background box(0.5, T1_TOP, T1_W, T1_H, fc=TIER1_BG, ec=BORDER, lw=1) box(0.5, T1_TOP + T1_H - 2.2, T1_W, 2.2, fc=NAVY, ec=NAVY, lw=0, radius=0) txt(T1_W/2 + 0.5, T1_TOP + T1_H - 1.1, 'TIER 1 — THEORETICAL FRAMEWORK', size=9, color=WHITE, weight='bold') # ── Box 1: Ternary Phase Space ── B1_X, B1_Y, B1_W, B1_H = 2, T1_TOP + 1.5, 23, 16 header_box(B1_X, B1_Y, B1_W, 2, B1_H) txt(B1_X + B1_W/2, B1_Y + B1_H - 1, 'TERNARY PHASE SPACE', size=8.5, color=WHITE, weight='bold') txt(B1_X + B1_W/2, B1_Y + B1_H - 4, 'L + T + C = 100', size=16, color=NAVY, weight='bold') hline(B1_X + 1, B1_X + B1_W - 1, B1_Y + B1_H - 5.2) # L ax.add_patch(plt.Circle((B1_X + 2, B1_Y + B1_H - 6.5), 0.5, color=BLUE, zorder=4)) txt(B1_X + 3.2, B1_Y + B1_H - 6.5, 'L', size=9, color=NAVY, weight='bold', ha='left') txt(B1_X + 4.5, B1_Y + B1_H - 6.5, 'Liberty', size=8, color=GRAY, ha='left') txt(B1_X + 2, B1_Y + B1_H - 7.8, 'Distributed power with', size=7, color=GRAY_LIGHT, ha='left') txt(B1_X + 2, B1_Y + B1_H - 8.7, 'institutional constraints', size=7, color=GRAY_LIGHT, ha='left') # T ax.add_patch(plt.Circle((B1_X + 13, B1_Y + B1_H - 6.5), 0.5, color=RED, zorder=4)) txt(B1_X + 14.2, B1_Y + B1_H - 6.5, 'T', size=9, color=NAVY, weight='bold', ha='left') txt(B1_X + 15.5, B1_Y + B1_H - 6.5, 'Tyranny', size=8, color=GRAY, ha='left') txt(B1_X + 13, B1_Y + B1_H - 7.8, 'Residual: T = 100 − L − C', size=7, color=GRAY_LIGHT, ha='left') txt(B1_X + 13, B1_Y + B1_H - 8.7, '76% determined by L (R²=0.76)', size=7, color=GRAY_LIGHT, ha='left') # C ax.add_patch(plt.Circle((B1_X + 2, B1_Y + B1_H - 10.2), 0.5, color=YELLOW, zorder=4)) txt(B1_X + 3.2, B1_Y + B1_H - 10.2, 'C', size=9, color=NAVY, weight='bold', ha='left') txt(B1_X + 4.5, B1_Y + B1_H - 10.2, 'Chaos', size=8, color=GRAY, ha='left') txt(B1_X + 2, B1_Y + B1_H - 11.5, 'Fragmented power, CV=0.62', size=7, color=GRAY_LIGHT, ha='left') txt(B1_X + 2, B1_Y + B1_H - 12.4, '24% independent variation', size=7, color=GRAY_LIGHT, ha='left') # Footer box(B1_X + 1, B1_Y + 0.5, B1_W - 2, 1.8, fc=BLUE_LIGHT, ec='#bee3f8', lw=1) txt(B1_X + B1_W/2, B1_Y + 1.4, 'FH (L) + FSI (C) · Constraint: 0 violations', size=6.5, color=BLUE, weight='bold') # ── Box 2: Tristable Dynamics ── B2_X, B2_Y, B2_W, B2_H = 26.5, T1_TOP + 1.5, 23, 16 header_box(B2_X, B2_Y, B2_W, 2, B2_H) txt(B2_X + B2_W/2, B2_Y + B2_H - 1, 'TRISTABLE DYNAMICS', size=8.5, color=WHITE, weight='bold') txt(B2_X + B2_W/2, B2_Y + B2_H - 3.5, 'Three+ attractor basins', size=8, color=GRAY, weight='bold') txt(B2_X + B2_W/2, B2_Y + B2_H - 4.5, '(GMM K=4, ΔBIC=206)', size=7, color=GRAY_LIGHT) # Potential well curve — hand-tuned for clear 3-well structure well_x = np.linspace(0, 1, 500) # Three clear wells at x=0.08, x=0.35, x=0.88 # Two barriers at x=0.22, x=0.62 well_y = np.zeros_like(well_x) for xi in range(len(well_x)): x = well_x[xi] # Tyranny well (deep) w1 = -4.0 * np.exp(-((x - 0.08)**2) / 0.006) # Hybrid dip (shallow) w2 = -1.5 * np.exp(-((x - 0.38)**2) / 0.012) # Democracy well (medium) w3 = -3.0 * np.exp(-((x - 0.88)**2) / 0.008) # Rising edges edge = 0.5 * x # gentle upward slope well_y[xi] = w1 + w2 + w3 + edge + 4.5 well_y = well_y - well_y.min() well_y = well_y / well_y.max() * 6.5 plot_x = B2_X + 1.5 + well_x * (B2_W - 3) plot_y = B2_Y + B2_H - 5.5 - well_y ax.plot(plot_x, plot_y, color=NAVY, lw=2.5, zorder=4) # Balls in wells — positioned at actual well minima ball_configs = [ (0.08, RED, 140, -4.0), # Tyranny: deepest (0.38, YELLOW, 100, -1.5), # Hybrid: shallow (0.88, GREEN_MED, 120, -3.0) # Democracy: medium ] for bp, bc, bs, _ in ball_configs: bx = B2_X + 1.5 + bp * (B2_W - 3) # Find actual y at this x position idx = int(bp * 499) by = plot_y[idx] ax.scatter([bx], [by + 0.35], s=bs, color=bc, zorder=5, edgecolors='white', linewidths=1.5) # Barrier marker at L≈52 (position ~0.62 on the x-axis) barrier_x = B2_X + 1.5 + 0.62 * (B2_W - 3) barrier_idx = int(0.62 * 499) barrier_top = plot_y[barrier_idx] ax.plot([barrier_x, barrier_x], [barrier_top + 0.5, barrier_top - 1.8], color=RED, lw=1.5, ls='--', zorder=4) txt(barrier_x, barrier_top - 2.5, 'Barrier L≈52', size=6.5, color=RED, weight='bold') # Basin labels txt(B2_X + 4, B2_Y + 1.5, 'Tyranny', size=8, color=RED, weight='bold') txt(B2_X + 4, B2_Y + 0.5, 'L≈7 (31%)', size=6.5, color=GRAY_LIGHT) txt(B2_X + B2_W/2, B2_Y + 1.5, 'Hybrid', size=8, color=AMBER, weight='bold') txt(B2_X + B2_W/2, B2_Y + 0.5, 'L≈21-55 (60%)', size=6.5, color=GRAY_LIGHT) txt(B2_X + B2_W - 4, B2_Y + 1.5, 'Democracy', size=8, color=GREEN_MED, weight='bold') txt(B2_X + B2_W - 4, B2_Y + 0.5, 'L≈90 (9%)', size=6.5, color=GRAY_LIGHT) # ── Box 3: 8-Stage Ladder ── B3_X, B3_Y, B3_W, B3_H = 51, T1_TOP + 1.5, 23, 16 header_box(B3_X, B3_Y, B3_W, 2, B3_H) txt(B3_X + B3_W/2, B3_Y + B3_H - 1, '8-STAGE LADDER', size=8.5, color=WHITE, weight='bold') stage_colors = ['#276749', '#38a169', '#48bb78', '#68d391', '#f6e05e', '#ed8936', '#e53e3e', '#c53030', '#742a2a'] stage_labels = [ ('S1', 'Consolidated Democracy', '85-100'), ('S2', 'Early Warning', '80-84'), ('S3', 'Democratic Erosion', '70-79'), ('S4', 'Hybrid', '60-69'), None, # Event horizon marker ('S5', 'Competitive Auth.', '50-59'), ('S6', 'Authoritarian', '35-49'), ('S7', 'Closed Autocracy', '25-34'), ('S8', 'Totalitarian', '0-24'), ] bar_y = B3_Y + B3_H - 3.5 widths = [19, 17, 15, 13, 0, 11, 9, 7, 5] ci = 0 for i, item in enumerate(stage_labels): if item is None: # Event horizon line ax.plot([B3_X + 1, B3_X + B3_W - 1], [bar_y - 0.2, bar_y - 0.2], color=RED, lw=2, ls='--', zorder=4) txt(B3_X + B3_W - 2, bar_y - 0.8, 'EVENT HORIZON', size=5.5, color=RED, weight='bold', ha='right') txt(B3_X + B3_W - 2, bar_y - 1.5, 'L≈52-55', size=5.5, color=RED, weight='bold', ha='right') bar_y -= 1.8 continue s_id, s_name, s_range = item w = widths[i] text_color = WHITE if ci in [0, 1, 5, 6, 7, 8] else NAVY box(B3_X + 2, bar_y - 1.1, w, 1.2, fc=stage_colors[ci], ec=stage_colors[ci], lw=0) txt(B3_X + 2.5, bar_y - 0.5, f'{s_id}: {s_name} ({s_range})', size=5.8, color=text_color, weight='bold', ha='left') bar_y -= 1.4 ci += 1 # Footer for Box 3 txt(B3_X + B3_W/2, B3_Y + 2.2, 'AR(1) beats stages by ΔAIC > 300', size=6.5, color=GRAY_LIGHT) txt(B3_X + B3_W/2, B3_Y + 1.0, 'Stages: descriptive only, not predictive', size=6.5, color=AMBER, weight='bold') # ── Arrows from Tier 1 to Tier 2 ── arrow(B1_X + B1_W/2, T1_TOP + 1, B1_X + B1_W/2, T1_TOP - 1.5, color=GRAY) arrow(B2_X + B2_W/2, T1_TOP + 1, B2_X + B2_W/2, T1_TOP - 1.5, color=GRAY) arrow(B3_X + B3_W/2, T1_TOP + 1, B3_X + B3_W/2, T1_TOP - 1.5, color=GRAY) # ═══════════════════════════════════════════════════════════════════ # CROSS-CUTTING EVENT HORIZON LINE # ═══════════════════════════════════════════════════════════════════ EH_Y = T1_TOP - 0.5 ax.plot([0.5, T1_W + 0.5], [EH_Y, EH_Y], color=RED, lw=2.5, ls=(0, (8, 4)), zorder=6) box(22, EH_Y - 1.2, 32, 2.4, fc=RED_LIGHT, ec=RED, lw=1.5) txt(38, EH_Y, 'EVENT HORIZON (L ≈ 52-55) · ΔBIC = 233', size=9, color=RED, weight='bold') # ═══════════════════════════════════════════════════════════════════ # TIER 2: EMPIRICAL ENGINE # ═══════════════════════════════════════════════════════════════════ T2_TOP = 54 T2_H = 18 T2_W = T1_W box(0.5, T2_TOP, T2_W, T2_H, fc=TIER2_BG, ec=BORDER, lw=1) box(0.5, T2_TOP + T2_H - 2.2, T2_W, 2.2, fc=NAVY_LIGHT, ec=NAVY_LIGHT, lw=0, radius=0) txt(T2_W/2 + 0.5, T2_TOP + T2_H - 1.1, 'TIER 2 — EMPIRICAL ENGINE', size=9, color=WHITE, weight='bold') # Central hub HUB_X, HUB_Y, HUB_W, HUB_H = 22, T2_TOP + 8, 32, 5 box(HUB_X, HUB_Y, HUB_W, HUB_H, fc=NAVY, ec=NAVY, lw=0) txt(HUB_X + HUB_W/2, HUB_Y + HUB_H - 1.5, 'Master Dataset', size=11, color=WHITE, weight='bold') txt(HUB_X + HUB_W/2, HUB_Y + HUB_H - 3.2, '91 countries · 225 years · 1,656 observations', size=8, color=GRAY_LIGHTER) # Branch boxes # Left: Transition + Panel FE BR_W, BR_H = 17, 4.5 bx1 = 2 by1 = T2_TOP + 8.5 box(bx1, by1, BR_W, BR_H, fc=WHITE, ec=PURPLE, lw=2) txt(bx1 + BR_W/2, by1 + BR_H - 1.2, 'Transition + Panel FE', size=8, color=PURPLE, weight='bold') txt(bx1 + BR_W/2, by1 + BR_H - 2.7, 'Country FE 63%; Year FE 30%', size=6.5, color=GRAY_LIGHT) txt(bx1 + BR_W/2, by1 + BR_H - 3.7, 'High-peak ρ=0.91 (persistent)', size=6.5, color=GRAY_LIGHT) arrow(HUB_X, HUB_Y + HUB_H/2, bx1 + BR_W, by1 + BR_H/2, color=GRAY) # Right: MC + Chow/CUSUM bx2 = T2_W - BR_W - 1 by2 = T2_TOP + 8.5 box(bx2, by2, BR_W, BR_H, fc=WHITE, ec=PURPLE, lw=2) txt(bx2 + BR_W/2, by2 + BR_H - 1.2, 'MC + Chow / CUSUM', size=8, color=PURPLE, weight='bold') txt(bx2 + BR_W/2, by2 + BR_H - 2.7, 'Break at 2000 (F=21.2)', size=6.5, color=GRAY_LIGHT) txt(bx2 + BR_W/2, by2 + BR_H - 3.7, 'CUSUM: 35% unstable', size=6.5, color=GRAY_LIGHT) arrow(HUB_X + HUB_W, HUB_Y + HUB_H/2, bx2, by2 + BR_H/2, color=GRAY) # Down-left: HCI + L-T-C Decomposition bx3 = 7 by3 = T2_TOP + 1.5 BR_W2 = 19 box(bx3, by3, BR_W2, BR_H, fc=WHITE, ec=PURPLE, lw=2) txt(bx3 + BR_W2/2, by3 + BR_H - 1.2, 'HCI + L-T-C Decomposition', size=8, color=PURPLE, weight='bold') txt(bx3 + BR_W2/2, by3 + BR_H - 2.7, 'Decline = 85% tyranny-driven', size=6.5, color=GRAY_LIGHT) txt(bx3 + BR_W2/2, by3 + BR_H - 3.7, 'T-recovery: 22% vs C-recovery: 48%', size=6.5, color=GRAY_LIGHT) arrow(HUB_X + HUB_W*0.3, HUB_Y, bx3 + BR_W2*0.7, by3 + BR_H, color=GRAY) # Down-right: Risk Scenarios bx4 = T2_W - BR_W2 - 6 by4 = T2_TOP + 1.5 box(bx4, by4, BR_W2, BR_H, fc=WHITE, ec=PURPLE, lw=2) txt(bx4 + BR_W2/2, by4 + BR_H - 1.2, 'Risk Scenarios', size=8, color=PURPLE, weight='bold') txt(bx4 + BR_W2/2, by4 + BR_H - 2.7, 'Post-2006: P(L<50|15yr)=69%', size=6.5, color=GRAY_LIGHT) txt(bx4 + BR_W2/2, by4 + BR_H - 3.7, 'Contagion β=−15.3; Reserve: −1,671bp', size=6.5, color=GRAY_LIGHT) arrow(HUB_X + HUB_W*0.7, HUB_Y, bx4 + BR_W2*0.3, by4 + BR_H, color=GRAY) # Tier 2 to Tier 3 arrow arrow(38, T2_TOP + 1, 38, T2_TOP - 2, color=GRAY) # ═══════════════════════════════════════════════════════════════════ # TIER 3: FIVE CORE CLAIMS # ═══════════════════════════════════════════════════════════════════ T3_TOP = 6 T3_H = 46 T3_W = T1_W box(0.5, T3_TOP, T3_W, T3_H, fc=TIER3_BG, ec=BORDER, lw=1) box(0.5, T3_TOP + T3_H - 2.2, T3_W, 2.2, fc=GRAY, ec=GRAY, lw=0, radius=0) txt(T3_W/2 + 0.5, T3_TOP + T3_H - 1.1, 'TIER 3 — FIVE CORE CLAIMS (POST-AUDIT)', size=9, color=WHITE, weight='bold') # Each claim column CL_W = 14 CL_H = 42 CL_GAP = 0.5 CL_START_X = 1.5 claims = [ { 'title': '1. TOPOLOGY', 'subtitle': ['Tristable system with', 'asymmetric attractor wells'], 'evidence_label': 'EVIDENCE', 'evidence': ['GMM K=4 best (ΔBIC=206)', 'Tyranny well 17.5× deeper', 'Random walk rejected', '(KS p=6.3×10⁻²¹⁶)'], 'verdict_color': AMBER_LIGHT, 'verdict_text_color': AMBER, 'verdict': 'UPGRADED → Tristable', 'bottom_label': 'KEY NUMBERS', 'bottom': ['Stationary: 66% at L>80', 'Markov: 96.3% L>80 stays', '80.9% L<20 stays'], 'bottom_color': PURPLE, }, { 'title': '2. EVENT HORIZON', 'subtitle': ['L≈52-55 threshold real', '— 3 methods converge'], 'evidence_label': 'EVIDENCE', 'evidence': ['Structural break ΔBIC=233', 'Bootstrap: 3.0% recovery', 'Recovery cliff at L=45-55', '(3.8% → 26.7% → 58.6%)'], 'verdict_color': AMBER_LIGHT, 'verdict_text_color': AMBER, 'verdict': 'CONFIRMED at L≈52-55', 'bottom_label': 'ERA EFFECT', 'bottom': ['Post-1995 recovery: 9.1%', 'Pre-1972 recovery: 65%', 'Modern autocracies stickier'], 'bottom_color': RED, }, { 'title': '3. VELOCITY', 'subtitle': ['US decline unprecedented', 'at any measurement window'], 'evidence_label': 'EVIDENCE', 'evidence': ['10yr: #5 of 359 (top 1.4%)', 'Peers: WWII + coups only', '15yr: −3.07/yr', '2yr: −18/yr (thesis claim)'], 'verdict_color': AMBER_LIGHT, 'verdict_text_color': AMBER, 'verdict': 'PARTIAL — Window-dep.', 'bottom_label': 'WEAKNESS', 'bottom': ['L=48 is author-scored', 'Cross-index mean: L=58', 'Credible range: 57-84'], 'bottom_color': AMBER, }, { 'title': '4. DECOUPLING', 'subtitle': ['HCI ≠ Liberty', '(Great Decoupling)'], 'evidence_label': 'EVIDENCE', 'evidence': ['r: 0.80 → 0.60 (p=0.011)', '39 capable autocracies', 'Survives bootstrap, panel,', 'weighting tests (ρ=0.99)'], 'verdict_color': GREEN_LIGHT, 'verdict_text_color': GREEN, 'verdict': 'CONFIRMED', 'bottom_label': 'KEY FINDING', 'bottom': ['70% genuine decorrelation', '30% compositional shift', 'MENA drives global pattern'], 'bottom_color': GREEN_MED, }, { 'title': '5. MISPRICING', 'subtitle': ['Reserve currency masks', 'governance risk (reframed)'], 'evidence_label': 'EVIDENCE', 'evidence': ['Slope −0.35, R²=0.37 ✓', 'Reserve premium: 1,671bp', 'US residual w/ reserve: 0bp', 'Yield lag: 3-8 years'], 'verdict_color': RED_LIGHT, 'verdict_text_color': RED, 'verdict': 'REFRAMED — Not gap', 'bottom_label': 'NEW FRAMING', 'bottom': ['1,671bp reserve premium', 'at stake if governance', 'erodes reserve status'], 'bottom_color': RED, }, ] for i, cl in enumerate(claims): cx = CL_START_X + i * (CL_W + CL_GAP) cy = T3_TOP + 1 # Column box box(cx, cy, CL_W, CL_H, fc=WHITE, ec=DIVIDER, lw=1.5) # Header box(cx, cy + CL_H - 2, CL_W, 2, fc=BLUE, ec=BLUE, lw=0) txt(cx + CL_W/2, cy + CL_H - 1, cl['title'], size=7.5, color=WHITE, weight='bold') # Subtitle for j, line in enumerate(cl['subtitle']): txt(cx + CL_W/2, cy + CL_H - 3.5 - j*1.0, line, size=7.5, color=NAVY, weight='bold') # Divider hline(cx + 1, cx + CL_W - 1, cy + CL_H - 6) # Evidence section txt(cx + 1.5, cy + CL_H - 6.8, cl['evidence_label'], size=6, color=GRAY, weight='bold', ha='left') for j, line in enumerate(cl['evidence']): txt(cx + 1.5, cy + CL_H - 8 - j*1.1, line, size=6.5, color=GRAY_LIGHT, ha='left') # Divider ev_bottom = cy + CL_H - 8 - len(cl['evidence'])*1.1 - 0.5 hline(cx + 1, cx + CL_W - 1, ev_bottom) # Verdict txt(cx + 1.5, ev_bottom - 0.8, 'AUDIT VERDICT', size=6, color=GRAY, weight='bold', ha='left') box(cx + 1, ev_bottom - 2.8, CL_W - 2, 1.6, fc=cl['verdict_color'], ec=cl['verdict_color'], lw=0) txt(cx + CL_W/2, ev_bottom - 2, cl['verdict'], size=7, color=cl['verdict_text_color'], weight='bold') # Divider hline(cx + 1, cx + CL_W - 1, ev_bottom - 3.5) # Bottom section txt(cx + 1.5, ev_bottom - 4.3, cl['bottom_label'], size=6, color=GRAY, weight='bold', ha='left') for j, line in enumerate(cl['bottom']): txt(cx + 1.5, ev_bottom - 5.5 - j*1.1, line, size=6.5, color=cl['bottom_color'], ha='left') # ═══════════════════════════════════════════════════════════════════ # SIDE PANEL: APPLICATION DOMAINS + AUDIT # ═══════════════════════════════════════════════════════════════════ SP_X = 76 SP_W = 23.5 SP_Y = 6 SP_H = 90 box(SP_X, SP_Y, SP_W, SP_H, fc=SIDE_BG, ec=BORDER, lw=1) box(SP_X, SP_Y + SP_H - 2.2, SP_W, 2.2, fc=PURPLE, ec=PURPLE, lw=0, radius=0) txt(SP_X + SP_W/2, SP_Y + SP_H - 1.1, 'APPLICATION DOMAINS', size=9, color=WHITE, weight='bold') # App Box 1: US Case Study ab_y = SP_Y + SP_H - 3.5 AB_W = SP_W - 2 AB_H = 9 box(SP_X + 1, ab_y - AB_H, AB_W, AB_H, fc=WHITE, ec=PURPLE, lw=1.5) box(SP_X + 1, ab_y - 2, AB_W, 2, fc=PURPLE, ec=PURPLE, lw=0) txt(SP_X + SP_W/2, ab_y - 1, 'US CASE STUDY', size=7.5, color=WHITE, weight='bold') txt(SP_X + 2, ab_y - 3.2, 'L = 94 → 48 (1-of-29 outlier)', size=8, color=NAVY, weight='bold', ha='left') txt(SP_X + 2, ab_y - 4.3, '7.9σ event · 0.7th percentile path', size=6.5, color=GRAY_LIGHT, ha='left') hline(SP_X + 2, SP_X + SP_W - 2, ab_y - 5) txt(SP_X + 2, ab_y - 5.8, 'Only mature democracy to fall', size=6.5, color=RED, weight='bold', ha='left') txt(SP_X + 2, ab_y - 6.8, 'below L=70 after qualifying', size=6.5, color=RED, weight='bold', ha='left') txt(SP_X + 2, ab_y - 7.8, 'P(recovery L>70|20yr) = 72-76%', size=6.5, color=AMBER, weight='bold', ha='left') # App Box 2: Global Maps ab2_y = ab_y - AB_H - 1 AB_H2 = 8 box(SP_X + 1, ab2_y - AB_H2, AB_W, AB_H2, fc=WHITE, ec=PURPLE, lw=1.5) box(SP_X + 1, ab2_y - 2, AB_W, 2, fc=PURPLE, ec=PURPLE, lw=0) txt(SP_X + SP_W/2, ab2_y - 1, 'GLOBAL MAPS', size=7.5, color=WHITE, weight='bold') txt(SP_X + 2, ab2_y - 3.2, '72/91 countries declining', size=8, color=NAVY, weight='bold', ha='left') txt(SP_X + 2, ab2_y - 4.3, 'Chow break at 2000 (F=21.2)', size=6.5, color=GRAY_LIGHT, ha='left') hline(SP_X + 2, SP_X + SP_W - 2, ab2_y - 5) txt(SP_X + 2, ab2_y - 5.8, 'FREE: ΔL +6.45→−0.04 post-2006', size=6.5, color=GREEN_MED, weight='bold', ha='left') txt(SP_X + 2, ab2_y - 6.8, 'Contagion: β=−15.3 when >50% decline', size=6.5, color=AMBER, weight='bold', ha='left') # App Box 3: Sovereign Credit ab3_y = ab2_y - AB_H2 - 1 box(SP_X + 1, ab3_y - AB_H2, AB_W, AB_H2, fc=WHITE, ec=PURPLE, lw=1.5) box(SP_X + 1, ab3_y - 2, AB_W, 2, fc=PURPLE, ec=PURPLE, lw=0) txt(SP_X + SP_W/2, ab3_y - 1, 'SOVEREIGN CREDIT', size=7.5, color=WHITE, weight='bold') txt(SP_X + 2, ab3_y - 3.2, 'Y = 33.05 − 0.35×L (R²=0.37)', size=8, color=NAVY, weight='bold', ha='left') txt(SP_X + 2, ab3_y - 4.3, 'Reserve currency premium: 1,671bp', size=6.5, color=GRAY_LIGHT, ha='left') hline(SP_X + 2, SP_X + SP_W - 2, ab3_y - 5) txt(SP_X + 2, ab3_y - 5.8, '650bp "gap" = reserve premium', size=6.5, color=RED, weight='bold', ha='left') txt(SP_X + 2, ab3_y - 6.8, 'True risk: loss of reserve status', size=6.5, color=AMBER, weight='bold', ha='left') # App Box 4: Great Decoupling ab4_y = ab3_y - AB_H2 - 1 box(SP_X + 1, ab4_y - AB_H2, AB_W, AB_H2, fc=WHITE, ec=PURPLE, lw=1.5) box(SP_X + 1, ab4_y - 2, AB_W, 2, fc=PURPLE, ec=PURPLE, lw=0) txt(SP_X + SP_W/2, ab4_y - 1, 'THE GREAT DECOUPLING', size=7.5, color=WHITE, weight='bold') txt(SP_X + 2, ab4_y - 3.2, 'Capability ≠ Freedom', size=8, color=NAVY, weight='bold', ha='left') txt(SP_X + 2, ab4_y - 4.3, '39 capable autocracies · r: 0.80→0.60', size=6.5, color=GRAY_LIGHT, ha='left') hline(SP_X + 2, SP_X + SP_W - 2, ab4_y - 5) txt(SP_X + 2, ab4_y - 5.8, 'STRONGEST thesis claim', size=6.5, color=GREEN_MED, weight='bold', ha='left') txt(SP_X + 2, ab4_y - 6.8, 'Survives all robustness tests (p=0.011)', size=6.5, color=GREEN_MED, weight='bold', ha='left') # ─── AUDIT VERDICT ─── av_y = ab4_y - AB_H2 - 1.5 AV_H = av_y - SP_Y + 0.5 box(SP_X + 1, SP_Y + 0.5, AB_W, AV_H, fc=WHITE, ec=NAVY, lw=2) box(SP_X + 1, av_y - 2, AB_W, 2, fc=NAVY, ec=NAVY, lw=0) txt(SP_X + SP_W/2, av_y - 1, 'AUDIT VERDICT', size=8, color=WHITE, weight='bold') # Direction vy = av_y - 3.5 box(SP_X + 2, vy - 3, AB_W - 2, 3.2, fc=GREEN_LIGHT, ec='#c6f6d5', lw=1) txt(SP_X + SP_W/2, vy - 0.5, 'DIRECTION', size=7, color=GREEN, weight='bold') txt(SP_X + SP_W/2, vy - 1.7, 'CORRECT', size=13, color=GREEN_MED, weight='bold') txt(SP_X + SP_W/2, vy - 2.6, 'US + global erosion validated', size=6, color=GRAY_LIGHT) # Magnitude vy2 = vy - 3.8 box(SP_X + 2, vy2 - 3, AB_W - 2, 3.2, fc=RED_LIGHT, ec='#fed7d7', lw=1) txt(SP_X + SP_W/2, vy2 - 0.5, 'MAGNITUDE', size=7, color=RED_DARK, weight='bold') txt(SP_X + SP_W/2, vy2 - 1.7, 'OVERSTATED ~10×', size=13, color=RED, weight='bold') txt(SP_X + SP_W/2, vy2 - 2.6, 'σ inflated 2-7×; P(tyranny) 62%→0%', size=6, color=GRAY_LIGHT) # Recalibrated vy3 = vy2 - 3.8 box(SP_X + 2, vy3 - 3, AB_W - 2, 3.2, fc='#fffff0', ec=AMBER_LIGHT, lw=1) txt(SP_X + SP_W/2, vy3 - 0.5, 'RECALIBRATED', size=7, color=AMBER, weight='bold') txt(SP_X + SP_W/2, vy3 - 1.7, 'SERIOUS EROSION', size=11, color=AMBER, weight='bold') txt(SP_X + SP_W/2, vy3 - 2.6, 'Not collapse. Vigilance required.', size=6, color=AMBER) # New Discovery vy4 = vy3 - 3.8 box(SP_X + 2, vy4 - 2.5, AB_W - 2, 2.7, fc=BLUE_LIGHT, ec='#bee3f8', lw=1) txt(SP_X + SP_W/2, vy4 - 0.5, 'NEW DISCOVERY', size=7, color=BLUE, weight='bold') txt(SP_X + SP_W/2, vy4 - 1.7, 'TRISTABLE SYSTEM', size=11, color=BLUE, weight='bold') # Key stats hline(SP_X + 2, SP_X + SP_W - 2, vy4 - 3.3) txt(SP_X + SP_W/2, vy4 - 4, 'KEY AUDIT STATISTICS', size=7, color=NAVY, weight='bold') stats = [ ('Research programs:', '12 / 12 ✓', GREEN_MED), ('Claims confirmed:', '1 (Decoupling)', GREEN_MED), ('Partially confirmed:', '3', AMBER), ('Overturned:', '1 (Mispricing)', RED), ('Total observations:', '1,656', NAVY), ] sy = vy4 - 5 for label, val, vc in stats: txt(SP_X + 2, sy, label, size=6, color=GRAY_LIGHT, ha='left') txt(SP_X + SP_W - 2, sy, val, size=7, color=vc, weight='bold', ha='right') sy -= 1.1 hline(SP_X + 2, SP_X + SP_W - 2, sy + 0.3) txt(SP_X + SP_W/2, sy - 0.5, 'STRONGEST SURVIVING CLAIMS', size=6.5, color=NAVY, weight='bold') surviving = [ ('1. Great Decoupling (r: 0.80→0.60)', GREEN_MED), ('2. Event Horizon L≈52-55 (ΔBIC=233)', GREEN_MED), ('3. US 1-of-29 outlier (7.9σ event)', GREEN_MED), ('4. Decline tyranny-driven (84.6%)', GREEN_MED), ('5. Structural break formal (F=21.2)', GREEN_MED), ('6. Contagion real (β=−15.3)', GREEN_MED), ('7. Country FE: 63% of variance', AMBER), ('8. Post-1995 recovery collapse (9.1%)', RED), ] sy2 = sy - 1.5 for text, color in surviving: txt(SP_X + 2, sy2, text, size=5.8, color=color, ha='left') sy2 -= 0.95 # ═══════════════════════════════════════════════════════════════════ # BOTTOM: METHODOLOGY BAR # ═══════════════════════════════════════════════════════════════════ box(0.5, 2, T3_W, 3, fc=TIER1_BG, ec=BORDER, lw=1) txt(T3_W/2 + 0.5, 4, 'METHODOLOGY', size=7, color=GRAY, weight='bold') txt(T3_W/2 + 0.5, 2.8, '12 RPs · AR(1) · GMM · Bootstrap · KDE Potential · Markov · Monte Carlo · Chow/CUSUM/Bai-Perron · Panel FE · Contagion · L-T-C Decomp. · 91 countries · 1,656 obs.', size=6.5, color=GRAY_LIGHT) # ═══════════════════════════════════════════════════════════════════ # FOOTER BAR # ═══════════════════════════════════════════════════════════════════ box(0, 0, 100, 2, fc=NAVY, ec=NAVY, lw=0, radius=0) txt(50, 1, 'Governance Topology: The Tristable Basin · Post-Audit Revision · 12 Research Programs Complete · 2026-02-09', size=7.5, color=GRAY_LIGHTER) # ── Dashed flow lines from Tier 2 hub to each claim ── for i in range(5): cx = CL_START_X + i * (CL_W + CL_GAP) + CL_W/2 ax.plot([cx, HUB_X + HUB_W/2], [T3_TOP + CL_H + 1, T2_TOP + 1], color=BORDER, lw=0.8, ls=':', zorder=1) # ── SAVE ── base = '/Users/nickgogerty/Downloads/Political topology/00-THESIS-ARCHITECTURE-DIAGRAM' # High-res PNG at 300 DPI png_path = base + '.png' fig.savefig(png_path, dpi=300, bbox_inches='tight', facecolor=BG, pad_inches=0.3) print(f"PNG saved: {png_path}") print(f" Size: {FIG_W}x{FIG_H} inches at 300 DPI = {FIG_W*300}x{FIG_H*300} pixels") # SVG (vector, infinite resolution) svg_path = base + '-generated.svg' fig.savefig(svg_path, format='svg', bbox_inches='tight', facecolor=BG, pad_inches=0.3) print(f"SVG saved: {svg_path}") plt.close() import os print(f" PNG size: {os.path.getsize(png_path)/1024/1024:.1f} MB") print(f" SVG size: {os.path.getsize(svg_path)/1024/1024:.1f} MB")