globalfindings/charts/generate_charts.py
2026-03-25 11:04:01 +00:00

706 lines
31 KiB
Python

#!/usr/bin/env python3
"""
TBOTE Project: Global Findings chart regeneration.
Editorial dark theme matching the site aesthetic.
No em dashes. No AI-looking styling.
"""
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.patheffects as pe
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
import numpy as np
import os
OUT = '/tmp/gf_images/output'
os.makedirs(OUT, exist_ok=True)
# ── Site palette ──
BG = '#0d0d0d'
BG_CARD = '#1a1a1a'
BG_PANEL = '#1e1e1e'
BORDER = '#2e2e2e'
RED = '#c22d2d'
RED_LT = '#e04545'
RED_DK = '#8b1a1a'
WHITE = '#f0f0f0'
TEXT = '#e0e0e0'
TEXT_DIM = '#999999'
TEXT_MUT = '#666666'
BLUE = '#4a90d9'
BLUE_DK = '#2d5a8a'
AMBER = '#d4943a'
GREEN = '#4a9a5a'
FONT = 'DejaVu Sans'
FONT_M = 'DejaVu Sans Mono'
def setup_style():
plt.rcParams.update({
'figure.facecolor': BG,
'axes.facecolor': BG,
'axes.edgecolor': BORDER,
'axes.labelcolor': TEXT_DIM,
'text.color': TEXT,
'xtick.color': TEXT_DIM,
'ytick.color': TEXT_DIM,
'grid.color': '#1f1f1f',
'grid.linewidth': 0.5,
'font.family': 'sans-serif',
'font.sans-serif': [FONT],
'axes.grid': False,
'figure.dpi': 150,
'savefig.dpi': 150,
'savefig.bbox': 'tight',
'savefig.pad_inches': 0.3,
'savefig.facecolor': BG,
})
setup_style()
# ═══════════════════════════════════════════════════════════════
# 1. GLOBAL AGE VERIFICATION LEGISLATION TIMELINE
# ═══════════════════════════════════════════════════════════════
def chart_global_av_timeline():
fig, ax = plt.subplots(figsize=(16, 9))
ax.set_facecolor(BG)
events = [
(2022.0, 1, 'EU Digital Services Act signed', RED, 'left'),
(2022.15, 2, 'Louisiana: first US state AV law', BLUE, 'left'),
(2022.3, 3, 'California AADC signed (AB 2273)', BLUE, 'left'),
(2022.5, 4, 'Brazil PL 2628 introduced', AMBER, 'left'),
(2022.65, 5, 'US Senate receives KOSA', BLUE, 'left'),
(2022.8, 6, 'UK Online Safety Bill introduced', RED, 'left'),
(2022.95, 7, 'EU Chat Control proposal', RED, 'left'),
(2023.3, 4, 'UK Online Safety Act enacted', RED, 'right'),
(2023.6, 6, 'Australia social media ban passed', GREEN, 'right'),
(2024.2, 2, 'France age verification mandate', RED, 'right'),
(2024.0, 5, 'IEEE 2089.1 AV standard published', TEXT_DIM, 'right'),
(2024.5, 7, 'Malaysia, Indonesia AV laws', GREEN, 'right'),
(2024.8, 1, 'UK VPN usage surges 1,400%', '#e8e855', 'right'),
(2025.1, 3, 'Display FARA filing (California)', AMBER, 'left'),
(2025.4, 6, 'UK device surveillance amendment', RED, 'right'),
(2025.6, 4, 'Brazil Digital ECA signed', AMBER, 'right'),
(2026.0, 2, 'Brazil Digital ECA enforcement', AMBER, 'right'),
(2026.1, 5, 'UK Ofcom AV enforcement begins', RED, 'right'),
]
# Background year bands
for yr in range(2022, 2027):
if yr % 2 == 0:
ax.axvspan(yr, yr+1, color='#111111', alpha=0.3)
# Inflection point marker
ax.axvline(x=2022, color=RED, linewidth=1.5, linestyle='-', alpha=0.6)
ax.text(2022.05, 7.7, '2022: INFLECTION POINT', fontsize=8, fontweight='bold',
color=RED, va='top', fontfamily=FONT)
# Plot events
for x, y, label, color, align in events:
ax.scatter(x, y, s=50, color=color, zorder=5, edgecolors='none')
ha = 'left' if align == 'left' else 'right'
offset = 0.06 if align == 'left' else -0.06
ax.text(x + offset, y, label, fontsize=7.5, color=TEXT,
va='center', ha=ha, fontfamily=FONT)
# Legend
legend_items = [
(RED, 'UK Legislation'),
(AMBER, 'Brazil Legislation'),
(BLUE, 'US Legislation'),
(GREEN, 'Other Jurisdictions'),
]
for i, (c, lbl) in enumerate(legend_items):
ax.scatter(2025.5, 8.0 - i*0.45, s=30, color=c, zorder=5)
ax.text(2025.58, 8.0 - i*0.45, lbl, fontsize=7, color=TEXT_DIM,
va='center', fontfamily=FONT)
ax.set_xlim(2021.7, 2026.5)
ax.set_ylim(0.2, 8.5)
ax.set_xlabel('Year', fontsize=10, labelpad=10)
ax.set_xticks([2022, 2023, 2024, 2025, 2026])
ax.set_yticks([])
ax.tick_params(axis='x', length=0, pad=8)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_color(BORDER)
fig.suptitle('Global Age Verification Legislation Timeline',
fontsize=16, fontweight='bold', color=WHITE, y=0.96, fontfamily=FONT)
ax.set_title('"The same organizations appear across every jurisdiction"',
fontsize=10, color=TEXT_DIM, style='italic', pad=15, fontfamily=FONT)
fig.savefig(f'{OUT}/chart_global_av_timeline.png')
plt.close()
print(' [1/10] chart_global_av_timeline.png')
# ═══════════════════════════════════════════════════════════════
# 2. THE STANDARDS CAPTURE LOOP (Kidron)
# ═══════════════════════════════════════════════════════════════
def chart_kidron_standards_capture():
fig, ax = plt.subplots(figsize=(10, 10))
ax.set_facecolor(BG)
ax.set_xlim(-2.2, 2.2)
ax.set_ylim(-2.5, 2.5)
ax.set_aspect('equal')
ax.axis('off')
fig.suptitle('The Standards Capture Loop',
fontsize=18, fontweight='bold', color=WHITE, y=0.94, fontfamily=FONT)
ax.set_title('One person connects legislation, standards, certification, and profit',
fontsize=10, color=TEXT_DIM, pad=20, fontfamily=FONT)
nodes = [
(0, 1.6, 'Baroness Kidron\nwrites legislation\n(UK AADC, OSA, CA AADC)', RED, AMBER),
(1.5, 0.5, 'Co-vice-chairs\nIEEE 2089.1\nAV Standard', RED, AMBER),
(1.5, -0.9, 'AVPA Executive Dir.\nchairs IEEE 2089.1\ncertification', RED, BLUE),
(0, -1.7, 'Yoti certified\nunder IEEE 2089.1\nRevenue: $47.9M', RED, GREEN),
(-1.5,-0.9, 'Meta pays Yoti\nfor Instagram\nage verification', RED, '#e8e855'),
(-1.5, 0.5, 'Legislation mandates\nage verification\n= creates market', RED, RED),
]
# Draw arrows between nodes (circular)
for i in range(len(nodes)):
x1, y1 = nodes[i][0], nodes[i][1]
x2, y2 = nodes[(i+1) % len(nodes)][0], nodes[(i+1) % len(nodes)][1]
dx = x2 - x1
dy = y2 - y1
dist = np.sqrt(dx**2 + dy**2)
# Shorten arrows
shrink = 0.42 / dist
sx, sy = x1 + dx * shrink, y1 + dy * shrink
ex, ey = x2 - dx * shrink, y2 - dy * shrink
ax.annotate('', xy=(ex, ey), xytext=(sx, sy),
arrowprops=dict(arrowstyle='->', color=TEXT_MUT,
lw=1.5, connectionstyle='arc3,rad=0.08'))
# Draw nodes
for x, y, label, ring_color, accent in nodes:
circle = plt.Circle((x, y), 0.4, fill=True, facecolor=BG_CARD,
edgecolor=ring_color, linewidth=2)
ax.add_patch(circle)
ax.text(x, y, label, fontsize=7, color=TEXT, ha='center', va='center',
fontfamily=FONT, linespacing=1.4)
fig.savefig(f'{OUT}/chart_kidron_standards_capture.png')
plt.close()
print(' [2/10] chart_kidron_standards_capture.png')
# ═══════════════════════════════════════════════════════════════
# 3. CROSS-BORDER FUNDING NETWORK
# ═══════════════════════════════════════════════════════════════
def chart_cross_border_funding():
fig, ax = plt.subplots(figsize=(14, 9))
ax.set_facecolor(BG)
ax.set_xlim(-0.5, 10.5)
ax.set_ylim(-0.5, 7)
ax.axis('off')
fig.suptitle('Cross-Border Funding Network',
fontsize=16, fontweight='bold', color=WHITE, y=0.96, fontfamily=FONT)
ax.set_title('The same funders appear in every jurisdiction',
fontsize=10, color=TEXT_DIM, pad=15, fontfamily=FONT)
def draw_box(x, y, w, h, label, border_color, bg=BG_CARD):
box = FancyBboxPatch((x - w/2, y - h/2), w, h,
boxstyle="round,pad=0.1", facecolor=bg,
edgecolor=border_color, linewidth=1.5)
ax.add_patch(box)
ax.text(x, y, label, fontsize=7.5, color=TEXT, ha='center', va='center',
fontfamily=FONT, linespacing=1.4)
# Tier labels
ax.text(-0.3, 6, 'FUNDERS', fontsize=7, color=TEXT_MUT, fontweight='bold',
rotation=90, va='center', fontfamily=FONT)
ax.text(-0.3, 3.5, 'ADVOCACY\nORGS', fontsize=7, color=TEXT_MUT, fontweight='bold',
rotation=90, va='center', ha='center', fontfamily=FONT)
ax.text(-0.3, 1, 'LEGISLATION', fontsize=7, color=TEXT_MUT, fontweight='bold',
rotation=90, va='center', fontfamily=FONT)
# Tier 1: Funders
funders = [
(1.5, 6, 'Oak\nFoundation'),
(3.5, 6, 'Omidyar\nNetwork'),
(5.5, 6, 'Luminate\n(Omidyar)'),
(7.5, 6, 'Imaginable\nFutures\n(Omidyar)'),
(9.5, 6, 'Rae/Sefaira\nFamily'),
]
for x, y, lbl in funders:
draw_box(x, y, 1.6, 0.7, lbl, AMBER)
# Tier 2: Advocacy
orgs = [
(1, 3.5, '5Rights\n(UK)'),
(3, 3.5, 'ECPAT'),
(5, 3.5, 'SaferNet\nBrasil'),
(7, 3.5, 'Common\nSense\nMedia (US)'),
(8.5, 3.5, 'Data Privacy\nBrasil'),
(10, 3.5, 'Instituto\nAlana (BR)'),
]
for x, y, lbl in orgs:
draw_box(x, y, 1.4, 0.7, lbl, RED)
# Tier 3: Legislation
laws = [
(1, 1, 'UK OSA\n+ AADC'),
(3, 1, 'EU DSA\n+ AI Act'),
(5.5, 1, 'Brazil\nDigital ECA'),
(7.5, 1, 'US KOSA\n+ COPPA 2.0'),
(9.5, 1, 'California\nAADC'),
]
for x, y, lbl in laws:
draw_box(x, y, 1.4, 0.6, lbl, GREEN)
# Draw connections (funder -> org -> legislation)
connections = [
# Oak Foundation connections
(1.5, 5.65, 1, 3.85), # Oak -> 5Rights
(1.5, 5.65, 3, 3.85), # Oak -> ECPAT
(1.5, 5.65, 5, 3.85), # Oak -> SaferNet
# Omidyar connections
(3.5, 5.65, 7, 3.85), # Omidyar -> Common Sense
(3.5, 5.65, 8.5, 3.85), # Omidyar -> Data Privacy BR
# Luminate connections
(5.5, 5.65, 1, 3.85), # Luminate -> 5Rights
(5.5, 5.65, 8.5, 3.85), # Luminate -> Data Privacy BR
# Imaginable Futures
(7.5, 5.65, 10, 3.85), # Imaginable -> Alana
# Rae/Sefaira
(9.5, 5.65, 1, 3.85), # Rae -> 5Rights
# Orgs -> Legislation
(1, 3.15, 1, 1.3), # 5Rights -> UK
(1, 3.15, 9.5, 1.3), # 5Rights -> CA AADC
(3, 3.15, 3, 1.3), # ECPAT -> EU
(5, 3.15, 5.5, 1.3), # SaferNet -> Brazil
(7, 3.15, 7.5, 1.3), # Common Sense -> US
(10, 3.15, 5.5, 1.3), # Alana -> Brazil
]
for x1, y1, x2, y2 in connections:
ax.plot([x1, x2], [y1, y2], color=TEXT_MUT, linewidth=0.7, alpha=0.5)
# Horizontal tier lines
ax.axhline(y=4.8, color=BORDER, linewidth=0.5, xmin=0.05, xmax=0.95)
ax.axhline(y=2.3, color=BORDER, linewidth=0.5, xmin=0.05, xmax=0.95)
fig.savefig(f'{OUT}/chart_cross_border_funding.png')
plt.close()
print(' [3/10] chart_cross_border_funding.png')
# ═══════════════════════════════════════════════════════════════
# 4. META DEPUTIES CRIMINAL RECORDS
# ═══════════════════════════════════════════════════════════════
def chart_meta_deputies_criminal():
fig, ax = plt.subplots(figsize=(12, 7))
names = ['Sostenes\nCavalcante', 'Gustavo\nGayer', 'Jadyel\nAlencar',
'Fernando\nMaximo', 'Gilvan da\nFederal']
fraud = [0.5, 0.1, 48.0, 30.0, 0]
seized = [0.47, 0.07, 0, 0, 0]
x = np.arange(len(names))
w = 0.55
bars1 = ax.bar(x, fraud, w, color=RED, label='Suspected Fraud (R$ Millions)', zorder=3)
bars2 = ax.bar(x, seized, w, bottom=fraud, color=RED_DK,
label='Cash Seized (R$ Millions)', zorder=3)
# Value labels
for i, (f, s) in enumerate(zip(fraud, seized)):
total = f + s
if total > 0:
ax.text(i, total + 0.8, f'R${f:.0f}M' if f >= 1 else f'R${f}M',
ha='center', va='bottom', fontsize=8.5, color=TEXT,
fontweight='bold', fontfamily=FONT)
if s > 0:
ax.text(i + 0.32, f + s/2, f'R${s}M',
ha='left', va='center', fontsize=7, color=TEXT_DIM,
fontfamily=FONT)
ax.set_xticks(x)
ax.set_xticklabels(names, fontsize=9, fontfamily=FONT)
ax.set_ylabel('R$ Millions', fontsize=10, fontfamily=FONT)
ax.set_ylim(0, 55)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color(BORDER)
ax.spines['bottom'].set_color(BORDER)
ax.tick_params(axis='x', length=0, pad=10)
ax.yaxis.grid(True, color='#1f1f1f', linewidth=0.5)
ax.set_axisbelow(True)
ax.legend(loc='upper right', fontsize=8, framealpha=0, labelcolor=TEXT_DIM)
fig.suptitle('Every Deputy Meta Contacted on PL 2628 Voting Day',
fontsize=14, fontweight='bold', color=WHITE, y=0.96, fontfamily=FONT)
ax.set_title('Is Under Criminal Investigation or Convicted',
fontsize=11, color=RED, pad=10, fontfamily=FONT)
fig.savefig(f'{OUT}/chart_meta_deputies_criminal.png')
plt.close()
print(' [4/10] chart_meta_deputies_criminal.png')
# ═══════════════════════════════════════════════════════════════
# 5. ALENCAR FINANCIAL PROFILE
# ═══════════════════════════════════════════════════════════════
def chart_alencar_financial():
fig, ax = plt.subplots(figsize=(12, 7))
categories = [
'Declared Assets',
'Suspected Money\nLaundering',
'COVID Fraud\n(MPF damages)',
'Unpaid\nChild Support',
]
values = [107.5, 48.0, 19.0, 0.036]
colors = [RED_DK, RED, RED, RED_LT]
y = np.arange(len(categories))
bars = ax.barh(y, values, height=0.55, color=colors, zorder=3)
for i, (v, bar) in enumerate(zip(values, bars)):
label = f'R${v:.1f}M' if v >= 1 else f'R${v*1000:.0f}K'
ax.text(v + 1.5, i, label, va='center', ha='left',
fontsize=10, fontweight='bold', color=TEXT, fontfamily=FONT)
ax.set_yticks(y)
ax.set_yticklabels(categories, fontsize=9.5, fontfamily=FONT)
ax.set_xlabel('R$ Millions', fontsize=10, fontfamily=FONT)
ax.set_xlim(0, 130)
ax.invert_yaxis()
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color(BORDER)
ax.spines['bottom'].set_color(BORDER)
ax.tick_params(axis='y', length=0, pad=10)
ax.xaxis.grid(True, color='#1f1f1f', linewidth=0.5)
ax.set_axisbelow(True)
fig.suptitle("Jadyel Alencar: Rapporteur of Brazil's Child Protection Law",
fontsize=14, fontweight='bold', color=WHITE, y=0.96, fontfamily=FONT)
ax.set_title('Financial Profile (R$ Millions)',
fontsize=11, color=TEXT_DIM, pad=10, fontfamily=FONT)
# Footer annotation
fig.text(0.5, 0.02,
'Criminal conviction for stolen medical supplies | 93 judicial processes | Prison order for R$6,306 child support',
ha='center', fontsize=7.5, color=TEXT_MUT, fontfamily=FONT)
fig.savefig(f'{OUT}/chart_alencar_financial.png')
plt.close()
print(' [5/10] chart_alencar_financial.png')
# ═══════════════════════════════════════════════════════════════
# 6. META REVOLVING DOOR: BRAZIL
# ═══════════════════════════════════════════════════════════════
def chart_meta_revolving_door_brazil():
fig, ax = plt.subplots(figsize=(12, 8))
ax.set_facecolor(BG)
ax.set_xlim(0, 10)
ax.set_ylim(0, 8)
ax.axis('off')
fig.suptitle("Meta's Revolving Door: Brazil",
fontsize=16, fontweight='bold', color=WHITE, y=0.95, fontfamily=FONT)
ax.set_title('19 government relations professionals. Two-thirds previously worked in government.',
fontsize=9.5, color=TEXT_DIM, pad=15, fontfamily=FONT)
def draw_panel(x, y, w, h, title, border_color):
box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.15",
facecolor=BG_CARD, edgecolor=border_color, linewidth=1.5)
ax.add_patch(box)
ax.text(x + w/2, y + h - 0.25, title, fontsize=10, fontweight='bold',
color=border_color, ha='center', va='top', fontfamily=FONT)
# Government panel (left)
draw_panel(0.5, 0.5, 3.2, 6.5, 'GOVERNMENT', BLUE)
# Meta panel (right)
draw_panel(6.3, 0.5, 3.2, 6.5, 'META', RED)
people = [
('Dario Durigan', 'Finance Minister\n(March 2026)', 'WhatsApp Dir.\n(2020-23)'),
('Yana Dumaresq', 'Deputy Minister\nof Economy\n(2019-21)', 'Meta Policy'),
('Murillo Laranjeira', 'Petri Consultancy\n(7 years)', 'Meta Sr. Dir.\nPublic Policy'),
('Tais Niffinegger', 'Anatel +\nPresidency', 'Meta'),
('Kaliana Kalache', 'Senate staff', 'Meta Dir.\n(promoted after\nBR law win)'),
]
for i, (name, gov_role, meta_role) in enumerate(people):
ypos = 6.0 - i * 1.15
# Government role
ax.text(1.2, ypos, gov_role, fontsize=7, color=TEXT_DIM, ha='left', va='center',
fontfamily=FONT, linespacing=1.3)
# Name (center)
ax.text(5, ypos, name, fontsize=9.5, fontweight='bold', color=WHITE,
ha='center', va='center', fontfamily=FONT,
bbox=dict(boxstyle='round,pad=0.3', facecolor=BG_PANEL,
edgecolor=BORDER, linewidth=1))
# Meta role
ax.text(8.8, ypos, meta_role, fontsize=7, color=TEXT_DIM, ha='right', va='center',
fontfamily=FONT, linespacing=1.3)
# Arrows
ax.annotate('', xy=(4.1, ypos), xytext=(3.5, ypos),
arrowprops=dict(arrowstyle='->', color=AMBER, lw=1.2))
ax.annotate('', xy=(5.9, ypos), xytext=(6.5, ypos),
arrowprops=dict(arrowstyle='<-', color=AMBER, lw=1.2))
fig.savefig(f'{OUT}/chart_meta_revolving_door_brazil.png')
plt.close()
print(' [6/10] chart_meta_revolving_door_brazil.png')
# ═══════════════════════════════════════════════════════════════
# 7. BRAZILIAN SECOM ADVERTISING SPEND
# ═══════════════════════════════════════════════════════════════
def chart_brazil_secom_meta_spend():
fig, ax = plt.subplots(figsize=(12, 7))
years = ['2023', '2024', '2025']
total = [47, 42, 129.6]
meta = [47 * 0.85, 42 * 0.82, 35.8]
x = np.arange(len(years))
w = 0.35
bars_total = ax.bar(x - w/2, total, w, color=RED_DK, label='Total Internet Ad Spend', zorder=3)
bars_meta = ax.bar(x + w/2, meta, w, color=RED, label='Meta (Facebook + Instagram)', zorder=3)
# Value labels
for i, (t, m) in enumerate(zip(total, meta)):
ax.text(i - w/2, t + 2, f'R${t:.0f}M' if t == int(t) else f'R${t:.1f}M',
ha='center', va='bottom', fontsize=9, fontweight='bold',
color=TEXT, fontfamily=FONT)
ax.text(i + w/2, m + 2, f'R${m:.1f}M',
ha='center', va='bottom', fontsize=9, fontweight='bold',
color=TEXT, fontfamily=FONT)
ax.set_xticks(x)
ax.set_xticklabels(years, fontsize=11, fontfamily=FONT)
ax.set_ylabel('Millions (R$)', fontsize=10, fontfamily=FONT)
ax.set_ylim(0, 150)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color(BORDER)
ax.spines['bottom'].set_color(BORDER)
ax.tick_params(axis='x', length=0, pad=10)
ax.yaxis.grid(True, color='#1f1f1f', linewidth=0.5)
ax.set_axisbelow(True)
ax.legend(loc='upper left', fontsize=8.5, framealpha=0, labelcolor=TEXT_DIM)
fig.suptitle('Brazilian Federal Government (SECOM)',
fontsize=14, fontweight='bold', color=WHITE, y=0.96, fontfamily=FONT)
ax.set_title('Internet Advertising Spend',
fontsize=11, color=TEXT_DIM, pad=10, fontfamily=FONT)
fig.text(0.5, 0.02,
'Source: Nucleo, Poder360 | A former Meta executive (Dario Durigan) became Finance Minister March 2026',
ha='center', fontsize=7.5, color=TEXT_MUT, fontfamily=FONT)
fig.savefig(f'{OUT}/chart_brazil_secom_meta_spend.png')
plt.close()
print(' [7/10] chart_brazil_secom_meta_spend.png')
# ═══════════════════════════════════════════════════════════════
# 8. CONSELHO DIGITAL: ZERO CAPITAL
# ═══════════════════════════════════════════════════════════════
def chart_conselho_digital_zero_capital():
fig, ax = plt.subplots(figsize=(12, 8))
companies = ['Google', 'Meta', 'Amazon', 'TikTok', 'Discord',
'Uber', 'Kwai', 'Hotmart']
mcap = [2100, 1500, 1900, 220, 15, 170, 30, 5]
colors_list = [RED, RED, RED, RED, BLUE_DK, BLUE_DK, BLUE_DK, BLUE_DK]
x = np.arange(len(companies))
bars = ax.bar(x, mcap, width=0.6, color=colors_list, zorder=3)
for i, (c, v) in enumerate(zip(companies, mcap)):
label = f'${v}B'
ax.text(i, v + 30, label, ha='center', va='bottom',
fontsize=9, fontweight='bold', color=TEXT, fontfamily=FONT)
ax.set_xticks(x)
ax.set_xticklabels(companies, fontsize=9, fontfamily=FONT, rotation=0)
ax.set_ylabel('Market Cap (USD Billions)', fontsize=10, fontfamily=FONT)
ax.set_ylim(0, 2400)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color(BORDER)
ax.spines['bottom'].set_color(BORDER)
ax.tick_params(axis='x', length=0, pad=10)
ax.yaxis.grid(True, color='#1f1f1f', linewidth=0.5)
ax.set_axisbelow(True)
# Callout box
callout_text = ('Single registered officer\n'
'R$0 declared capital\n'
'No public financial reports\n'
'No auditable records\n'
'Estatuto behind "restricted access"')
ax.text(6.5, 1500, callout_text, fontsize=7.5, color=TEXT,
fontfamily=FONT, linespacing=1.5, va='center',
bbox=dict(boxstyle='round,pad=0.5', facecolor=BG_CARD,
edgecolor=RED, linewidth=1.5))
fig.suptitle("Conselho Digital do Brasil: Members' Combined Market Cap",
fontsize=14, fontweight='bold', color=WHITE, y=0.96, fontfamily=FONT)
ax.set_title("vs. Entity's Declared Capital: R$0.00",
fontsize=11, color=RED, pad=10, fontfamily=FONT)
fig.savefig(f'{OUT}/chart_conselho_digital_zero_capital.png')
plt.close()
print(' [8/10] chart_conselho_digital_zero_capital.png')
# ═══════════════════════════════════════════════════════════════
# 9. SERPRO DATA ACCESS
# ═══════════════════════════════════════════════════════════════
def chart_serpro_data_access():
fig, ax = plt.subplots(figsize=(13, 7))
categories = [
'ABIN: 76M citizens\' driver data\n(Intercept Brasil, 2020)',
'PRF: 80M+ biometric database\npurchased off-books (2022)',
'Serpro employee: STF ministers\'\ntax records accessed (2026)',
'ANPD: Datavalid operates without\nfull LGPD legal basis',
'Digital ECA: All platform users\nmust be verified via Serpro',
]
values = [76, 80, 0, 85, 200]
labels_r = ['76M records', '80M records', 'Targeted access', '85M records', '200M records']
colors_list = [RED_DK, RED_DK, RED, RED, RED_LT]
y = np.arange(len(categories))
bars = ax.barh(y, values, height=0.55, color=colors_list, zorder=3)
# Special handling for zero-width bar
for i, (v, lbl) in enumerate(zip(values, labels_r)):
xpos = max(v, 5) + 3
ax.text(xpos, i, lbl, va='center', ha='left',
fontsize=9.5, fontweight='bold', color=TEXT, fontfamily=FONT)
ax.set_yticks(y)
ax.set_yticklabels(categories, fontsize=8.5, fontfamily=FONT)
ax.set_xlabel('Records Affected (Millions)', fontsize=10, fontfamily=FONT)
ax.set_xlim(0, 240)
ax.invert_yaxis()
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color(BORDER)
ax.spines['bottom'].set_color(BORDER)
ax.tick_params(axis='y', length=0, pad=10)
ax.xaxis.grid(True, color='#1f1f1f', linewidth=0.5)
ax.set_axisbelow(True)
fig.suptitle("Serpro: Brazil's Government Data Broker",
fontsize=14, fontweight='bold', color=WHITE, y=0.96, fontfamily=FONT)
ax.set_title('33 Billion Transactions/Year, Intelligence Agency Access',
fontsize=11, color=TEXT_DIM, pad=10, fontfamily=FONT)
fig.text(0.5, 0.02,
'Serpro\'s "sovereign cloud" runs on AWS Outpost (CIA partnership) and Google Distributed Cloud | US CLOUD Act applies',
ha='center', fontsize=7.5, color=TEXT_MUT, fontfamily=FONT)
fig.savefig(f'{OUT}/chart_serpro_data_access.png')
plt.close()
print(' [9/10] chart_serpro_data_access.png')
# ═══════════════════════════════════════════════════════════════
# 10. BARCLAYS / PALANTIR / META FINANCIAL LAYER
# ═══════════════════════════════════════════════════════════════
def chart_barclays_palantir_meta():
fig, ax = plt.subplots(figsize=(13, 9))
ax.set_facecolor(BG)
ax.set_xlim(0, 12)
ax.set_ylim(0, 9)
ax.axis('off')
fig.suptitle('The Financial Layer',
fontsize=16, fontweight='bold', color=WHITE, y=0.95, fontfamily=FONT)
ax.set_title('Barclays, Palantir, and Meta: Overlapping ownership, regulation, and contracts',
fontsize=9.5, color=TEXT_DIM, pad=15, fontfamily=FONT)
def box(x, y, label, border_color, w=1.8, h=0.6):
b = FancyBboxPatch((x - w/2, y - h/2), w, h,
boxstyle="round,pad=0.1", facecolor=BG_CARD,
edgecolor=border_color, linewidth=1.5)
ax.add_patch(b)
ax.text(x, y, label, fontsize=8, color=TEXT, ha='center', va='center',
fontfamily=FONT, linespacing=1.3)
return (x, y)
def arrow(x1, y1, x2, y2, label='', color=TEXT_MUT, style='-'):
ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle='->', color=color, lw=1,
linestyle=style))
if label:
mx, my = (x1+x2)/2, (y1+y2)/2
ax.text(mx, my + 0.15, label, fontsize=6.5, color=TEXT_MUT,
ha='center', va='bottom', fontfamily=FONT,
bbox=dict(boxstyle='round,pad=0.15', facecolor=BG,
edgecolor='none'))
# Top row
box(2.5, 7.5, 'Barclays', BLUE)
box(9.5, 7.5, 'Meta', RED)
# Middle row
box(2.5, 5.5, 'FCA\n(regulator)', BLUE)
box(6, 5.5, 'Peter Thiel', AMBER)
box(9.5, 5.5, 'Yoti', GREEN)
# Bottom row
box(4.5, 3, 'Palantir', AMBER)
box(7.5, 3, 'Founders\nFund', AMBER)
# Bottom
box(4.5, 1, 'Serpro\n(Brazil govt)', GREEN)
# Connections
arrow(2.5, 7.2, 2.5, 5.8, 'FCA regulates\nBarclays')
arrow(2.5, 7.2, 9.5, 7.8, '£1.4B in shares', AMBER, '-')
arrow(9.5, 7.2, 9.5, 5.8, 'Pays for Instagram\nage verification')
arrow(9.5, 7.2, 6, 5.8, 'Thiel: board\nsince 2005', TEXT_MUT, '--')
arrow(6, 5.2, 4.5, 3.3, 'Co-founder')
arrow(6, 5.2, 7.5, 3.3, 'Founder')
arrow(4.5, 5.2, 4.5, 3.3, 'Palantir processes\nFCA data') # FCA connection to Palantir (adjust coords)
arrow(7.5, 2.7, 4.5, 1.3, '$150M dividend')
arrow(4.5, 2.7, 4.5, 1.3, 'Palantir since 2022')
# Fix FCA to Palantir arrow
arrow(2.8, 5.2, 4.2, 3.3, 'Palantir processes\nFCA data')
fig.savefig(f'{OUT}/chart_barclays_palantir_meta.png')
plt.close()
print(' [10/10] chart_barclays_palantir_meta.png')
# ═══════════════════════════════════════════════════════════════
# GENERATE ALL
# ═══════════════════════════════════════════════════════════════
print('Generating charts...')
chart_global_av_timeline()
chart_kidron_standards_capture()
chart_cross_border_funding()
chart_meta_deputies_criminal()
chart_alencar_financial()
chart_meta_revolving_door_brazil()
chart_brazil_secom_meta_spend()
chart_conselho_digital_zero_capital()
chart_serpro_data_access()
chart_barclays_palantir_meta()
print('Done. Output in:', OUT)