Files
cariflex/scripts/generate_architecture_v2.py

147 lines
9.9 KiB
Python

#!/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")