#!/usr/bin/env python3 """Generate Cariflex architecture diagrams v2 (with OCPI and GIREVE) as PDF.""" import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch import numpy as np fig, ax = plt.subplots(1, 1, figsize=(22, 16)) ax.set_xlim(0, 22) ax.set_ylim(0, 16) ax.axis('off') fig.patch.set_facecolor('#f8f9fa') def draw_box(ax, x, y, w, h, text, color='#2196F3', textcolor='white', fontsize=8, alpha=1.0, border='white'): box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.15", facecolor=color, edgecolor=border, linewidth=1.5, alpha=alpha) ax.add_patch(box) ax.text(x + w/2, y + h/2, text, ha='center', va='center', fontsize=fontsize, color=textcolor, fontweight='bold', wrap=True, multialignment='center') def draw_arrow(ax, x1, y1, x2, y2, color='#666', label='', lw=1.2): ax.annotate('', xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle='->', color=color, lw=lw)) if label: mx, my = (x1+x2)/2, (y1+y2)/2 ax.text(mx+0.1, my+0.1, label, fontsize=6, color=color, style='italic') # Title ax.text(11, 15.5, 'CARIFLEX EMS - Architecture d\'Intégration v2', ha='center', fontsize=18, fontweight='bold', color='#1565C0') ax.text(11, 15.1, 'avec OCPI et GIREVE', ha='center', fontsize=12, color='#666') # ═══════════════════════════════════════════════════════════════════ # EXTERNAL LAYER # ═══════════════════════════════════════════════════════════════════ ax.text(0.3, 14.3, 'EXTERNAL', fontsize=9, fontweight='bold', color='#888', style='italic') draw_box(ax, 0.3, 13.3, 4.5, 0.8, 'EPEX SPOT\nDay-ahead / Localflex', '#FF9800', fontsize=8) draw_box(ax, 5.3, 13.3, 4.5, 0.8, 'ENTSO-E\nPrices / CO2', '#FF9800', fontsize=8) draw_box(ax, 10.3, 13.3, 4.5, 0.8, 'Weather API\nSolcast / Météo', '#FF9800', fontsize=8) draw_box(ax, 15.3, 13.3, 6, 0.8, 'DSO / TSO\nOpenADR / S2', '#FF9800', fontsize=8) # Arrows to interop/integration draw_arrow(ax, 2.5, 13.3, 3, 12.5, '#FF9800', 'prices') draw_arrow(ax, 7.5, 13.3, 8, 12.5, '#FF9800', 'CO2') draw_arrow(ax, 12.5, 13.3, 10, 12.5, '#FF9800', 'weather') draw_arrow(ax, 18.3, 13.3, 17, 12.5, '#FF9800', 'flex') # ═══════════════════════════════════════════════════════════════════ # INTEROPERABILITY LAYER (NEW) # ═══════════════════════════════════════════════════════════════════ ax.text(0.3, 12.7, 'INTEROPERABILITY', fontsize=9, fontweight='bold', color='#888', style='italic') draw_box(ax, 0.3, 11.5, 9, 1, 'OCPI Gateway (eMSP/CPO)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n• Tarifs • Sessions • CDRs • Roaming (in/out) • Commands', '#E91E63', fontsize=7.5) draw_box(ax, 10, 11.5, 9, 1, 'GIREVE Connector\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n• Session data • Roaming hub • Billing • PNIRE • CDRs', '#795548', fontsize=7.5) # Arrows from external draw_arrow(ax, 5.3, 11.5, 5.3, 10.8, '#E91E63', 'roaming') draw_arrow(ax, 14.5, 11.5, 12, 10.8, '#795548', 'sessions') # ═══════════════════════════════════════════════════════════════════ # INTEGRATION LAYER # ═══════════════════════════════════════════════════════════════════ ax.text(0.3, 11.0, 'INTEGRATION', fontsize=9, fontweight='bold', color='#888', style='italic') # FlexMeasures EMS (central) draw_box(ax, 2, 9.2, 16, 1.5, '', '#2196F3', alpha=0.15) ax.text(10, 10.4, 'FLEXMEASURES EMS', ha='center', fontsize=11, fontweight='bold', color='#1565C0') draw_box(ax, 2.3, 9.4, 3.5, 0.9, 'INGESTION\nSensors / Assets\nBeliefs / OCPI CDRs', '#2196F3', fontsize=7) draw_box(ax, 6.2, 9.4, 3.5, 0.9, 'FORECASTING\nPV / Load\nPrices / CO2', '#2196F3', fontsize=7) draw_box(ax, 10.1, 9.4, 3.5, 0.9, 'SCHEDULING\nBatteries / EVs\nGrid services', '#2196F3', fontsize=7) draw_box(ax, 14, 9.4, 3.7, 0.9, 'REPORTING\nAssets / Sched\nForecasts / CDRs', '#2196F3', fontsize=7) # Sub-components draw_box(ax, 0.3, 8.2, 5, 0.8, 'CITRINEOS (CSMS)\nOCPP 2.0.1 • Charge Points • Transactions', '#4CAF50', fontsize=7.5) draw_box(ax, 5.8, 8.2, 5, 0.8, 'EVEREST (Middleware)\nISO 15118 • Smart Charging • V2G', '#FF9800', fontsize=7.5) draw_box(ax, 11.3, 8.2, 5, 0.8, 'OPENLEADR (VEN)\nOpenADR 2.0b • DSO • Flexibility', '#9C27B0', fontsize=7.5) # Arrows to FM draw_arrow(ax, 2.8, 8.2, 4, 9.2, '#4CAF50', 'REST') draw_arrow(ax, 8.3, 8.2, 8, 9.2, '#FF9800', 'OCPP') draw_arrow(ax, 13.8, 8.2, 12, 9.2, '#9C27B0', 'REST') # ═══════════════════════════════════════════════════════════════════ # DEVICES LAYER # ═══════════════════════════════════════════════════════════════════ ax.text(0.3, 7.5, 'DEVICES', fontsize=9, fontweight='bold', color='#888', style='italic') draw_box(ax, 0.3, 6.3, 4.5, 1, '10 PV Panels\n5kWc each\nModbus TCP → FM', '#FFC107', textcolor='#333', fontsize=8) draw_box(ax, 5.3, 6.3, 4.5, 1, '10 Batteries\n100kWh / 50kW\nModbus TCP → FM', '#FFC107', textcolor='#333', fontsize=8) draw_box(ax, 10.3, 6.3, 4.5, 1, '10 EV Chargers\n22kW OCPP 2.0.1\n→ CitrineOS', '#FFC107', textcolor='#333', fontsize=8) draw_box(ax, 15.3, 6.3, 5.5, 1, '10 EVs\n75kWh V2G\nISO 15118 → EVerest', '#FFC107', textcolor='#333', fontsize=8) # ═══════════════════════════════════════════════════════════════════ # MARKET LAYER (R&D) # ═══════════════════════════════════════════════════════════════════ ax.text(0.3, 5.6, 'MARKET (R&D)', fontsize=9, fontweight='bold', color='#888', style='italic') draw_box(ax, 0.3, 4.4, 6.5, 1, 'GRID SINGULARITY\ngsy-e • P2P Trading • Market Clearing\nPrice discovery → FM schedules', '#00BCD4', fontsize=7.5) draw_box(ax, 7.3, 4.4, 6.5, 1, 'OPLEM\nLocal Market • Prosumer Opt.\nBattery/EV agents → FM', '#00BCD4', fontsize=7.5) draw_box(ax, 14.3, 4.4, 6.5, 1, 'HAMLET\nMulti-agent Simulation\nStrategy validation → FM', '#00BCD4', fontsize=7.5) # ═══════════════════════════════════════════════════════════════════ # VISUALIZATION LAYER # ═══════════════════════════════════════════════════════════════════ ax.text(0.3, 3.7, 'VISUALIZATION', fontsize=9, fontweight='bold', color='#888', style='italic') draw_box(ax, 0.3, 2.5, 6, 1, 'GRAFANA (Port 3001)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n4 Timeseries + 1 Table\nPostgreSQL-FlexMeasures DS', '#E91E63', fontsize=7.5) draw_box(ax, 6.8, 2.5, 6, 1, 'METABASE\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSQL Analytics • Reporting\nBilling • Data exploration', '#E91E63', fontsize=7.5) draw_box(ax, 13.3, 2.5, 7, 1, 'CARIFLEX UI (Port 5000)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nAsset Map • Schedules\nForecasts • OCPI Sessions', '#E91E63', fontsize=7.5) # ═══════════════════════════════════════════════════════════════════ # NETWORK LEGEND # ═══════════════════════════════════════════════════════════════════ ax.text(0.3, 1.8, 'PROTOCOLS:', fontsize=8, fontweight='bold', color='#666') protocols = [ ('OCPP 2.0.1', '#4CAF50'), ('ISO 15118', '#FF9800'), ('OCPI 2.2', '#E91E63'), ('OpenADR', '#9C27B0'), ('Modbus', '#FFC107'), ('REST API', '#2196F3'), ('GIREVE', '#795548'), ] for i, (name, color) in enumerate(protocols): x = 0.3 + i * 2.8 rect = plt.Rectangle((x, 1.1), 0.3, 0.4, facecolor=color, edgecolor='white') ax.add_patch(rect) ax.text(x + 0.5, 1.3, name, fontsize=7, color='#333') # Footer ax.text(11, 0.5, 'Cariflex - Caribbean Flexibility Platform - 2026', ha='center', fontsize=10, fontweight='bold', color='#1565C0', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8)) plt.tight_layout() plt.savefig('/home/eric/cariflex/docs/architecture_cariflex_v2.pdf', dpi=150, bbox_inches='tight') plt.savefig('/home/eric/cariflex/docs/architecture_cariflex_v2.png', dpi=150, bbox_inches='tight') print("✅ Architecture v2 diagrams generated") print(" - PDF: /home/eric/cariflex/docs/architecture_cariflex_v2.pdf") print(" - PNG: /home/eric/cariflex/docs/architecture_cariflex_v2.png")