diff --git a/scripts/create_sim_assets.py b/scripts/create_sim_assets.py new file mode 100644 index 0000000..d2d0d8f --- /dev/null +++ b/scripts/create_sim_assets.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Cariflex - Crée les sensors météo et prix DSO pour la simulation.""" + +import json +import requests +import re +import warnings +warnings.filterwarnings("ignore") +from datetime import datetime, timezone, timedelta + +FM_HOST = "https://cariflex.digitribe.fr" +CREDS_FILE = "/tmp/fm_creds.json" + +with open(CREDS_FILE) as f: + creds = json.load(f) + +session = requests.Session() +session.verify = False + +# Login +r = session.get(f"{FM_HOST}/login") +match = re.search(r']*csrf_token[^>]*value="([^"]+)"', r.text) +csrf = match.group(1) +r = session.post(f"{FM_HOST}/login", data={ + "email": creds["email"], "password": creds["password"], + "csrf_token": csrf, "remember": "y" +}, allow_redirects=True) +print(f"Login: {'OK' if 'dashboard' in r.url else 'FAILED'}") + +# Step 1: Check existing asset types +r = session.get(f"{FM_HOST}/api/v3_0/generic_asset_types") +print(f"Asset types: HTTP {r.status_code}") +types = {} +if r.status_code == 200: + for t in r.json(): + types[t["name"]] = t["id"] + print(f" {t['id']}: {t['name']}") + +# Step 2: Check existing assets +r = session.get(f"{FM_HOST}/api/v3_0/assets") +print(f"\nAssets: HTTP {r.status_code}") +assets = {} +if r.status_code == 200: + for a in r.json()[:10]: + assets[a["name"]] = a["id"] + print(f" {a['id']}: {a['name']} ({a.get('generic_asset_type', {}).get('name', '?')})") + +# Step 3: Create assets for weather and pricing +# Use existing asset types or create generic ones +print("\n=== Création des assets ===") + +new_assets = [ + {"name": "Météo Martinique", "type_id": 1, "lat": 14.6091, "lon": -61.2155}, + {"name": "Marché Énergie", "type_id": 1, "lat": 14.6091, "lon": -61.2155}, +] + +for asset in new_assets: + r = session.post(f"{FM_HOST}/api/v3_0/assets", json={ + "name": asset["name"], + "generic_asset_type_id": asset["type_id"], + "latitude": asset["lat"], + "longitude": asset["lon"], + }) + print(f" Asset '{asset['name']}': HTTP {r.status_code}") + if r.status_code == 201: + aid = r.json().get("id") + print(f" ID: {aid}") + assets[asset["name"]] = aid + else: + try: + err = r.json() + print(f" Error: {err.get('message', r.text[:200])}") + except: + print(f" Error: {r.text[:200]}") + +print(f"\n=== Assets existants ===") +for name, aid in assets.items(): + print(f" {aid}: {name}") diff --git a/scripts/create_simulation_assets.py b/scripts/create_simulation_assets.py new file mode 100644 index 0000000..fa88d37 --- /dev/null +++ b/scripts/create_simulation_assets.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Cariflex - Crée les assets et sensors pour la simulation météo et prix DSO.""" + +import json +import requests +import re +import warnings +warnings.filterwarnings("ignore") +from datetime import datetime, timezone, timedelta + +FM_HOST = "https://cariflex.digitribe.fr" +CREDS_FILE = "/tmp/fm_creds.json" + +with open(CREDS_FILE) as f: + creds = json.load(f) + +session = requests.Session() +session.verify = False + +# Login +r = session.get(f"{FM_HOST}/login") +match = re.search(r']*csrf_token[^>]*value="([^"]+)"', r.text) +csrf = match.group(1) +r = session.post(f"{FM_HOST}/login", data={ + "email": creds["email"], "password": creds["password"], + "csrf_token": csrf, "remember": "y" +}, allow_redirects=True) +print(f"Login: {'OK' if 'dashboard' in r.url else 'FAILED'}") + +# Get auth token for API +token = r.cookies.get("session", "") +headers = {"Authorization": f"Bearer {token}"} if token else {} + +def fm_post(path, data=None, json_data=None): + url = f"{FM_HOST}/api/v3_0{path}" + r = session.post(url, data=data, json=json_data) + return r + +def fm_get(path): + url = f"{FM_HOST}/api/v3_0{path}" + r = session.get(url) + return r + +# ======================================== +# 1. Créer les assets météo +# ======================================== +print("\n=== Création des assets météo ===") + +meteo_assets = [ + { + "name": "Météo Martinique", + "asset_type_name": "Weather Station", + "latitude": 14.6091, + "longitude": -61.2155, + "sensors": [ + {"name": "irradiance", "unit": "W/m²"}, + {"name": "temperature", "unit": "°C"}, + {"name": "wind_speed", "unit": "m/s"}, + {"name": "humidity", "unit": "%"}, + ] + }, + { + "name": "Prix DSO", + "asset_type_name": "Market", + "latitude": 14.6091, + "longitude": -61.2155, + "sensors": [ + {"name": "consumption_price", "unit": "EUR/MWh"}, + {"name": "production_price", "unit": "EUR/MWh"}, + ] + } +] + +for asset in meteo_assets: + r = fm_post("/assets", json_data={ + "name": asset["name"], + "latitude": asset["latitude"], + "longitude": asset["longitude"], + }) + print(f" Asset '{asset['name']}': HTTP {r.status_code}") + + if r.status_code in [200, 201]: + try: + resp = r.json() + asset_id = resp.get("id") + print(f" ID: {asset_id}") + except: + print(f" Response: {r.text[:200]}") + else: + print(f" Error: {r.text[:200]}") + +print("\n=== Simulation setup complete ===") diff --git a/scripts/inject_final.py b/scripts/inject_final.py new file mode 100644 index 0000000..9ad5945 --- /dev/null +++ b/scripts/inject_final.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""Cariflex - Injecte les données météo réelles et prix DSO dans FM via le CLI.""" + +import subprocess +import json +import random +import math +from datetime import datetime, timezone, timedelta +import pytz +import requests + +MARTINIQUE_TZ = pytz.timezone("America/Martinique") + +# Sensor IDs (from previous creation) +SENSORS = { + "irradiance": 81, + "temperature": 82, + "wind_speed": 83, + "consumption_price": 84, + "production_price": 85, +} + +def run_fm_cli(args): + """Run FM CLI inside the container.""" + cmd = ["docker", "exec", "flexmeasures-server", "bash", "-c", + f"cd /app && .venv/bin/flexmeasures {' '.join(args)}"] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + return result.stdout.strip(), result.stderr.strip(), result.returncode + +def post_sensor_data(sensor_id, values, unit, start, duration="PT1H"): + """Post sensor data via FM API.""" + import re + + # Login first + session = requests.Session() + session.verify = False + r = session.get("https://cariflex.digitribe.fr/login") + match = re.search(r']*csrf_token[^>]*value="([^"]+)"', r.text) + if not match: + return False + csrf = match.group(1) + r = session.post("https://cariflex.digitribe.fr/login", data={ + "email": "admin@digitribe.fr", "password": "Digitribe972", + "csrf_token": csrf, "remember": "y" + }, allow_redirects=True) + + if "dashboard" not in r.url: + return False + + # Post data - use proper ISO format with timezone + r = session.post( + f"https://cariflex.digitribe.fr/api/v3_0/sensors/{sensor_id}/data", + json={ + "values": values, + "start": start, + "duration": duration, + "unit": unit + }, + timeout=30 + ) + return r.status_code in [200, 201, 202] + +# ======================================== +# 1. Données météo réelles via API Open-Meteo +# ======================================== +print("=== Récupération des données météo ===") + +LAT, LON = 14.6091, -61.2155 +today = datetime.now(MARTINIQUE_TZ).strftime("%Y-%m-%d") + +resp = requests.get( + f"https://api.open-meteo.com/v1/forecast?" + f"latitude={LAT}&longitude={LON}" + f"&hourly=shortwave_radiation,temperature_2m,wind_speed_10m" + f"&timezone=America/Martinique" + f"&start_date={today}&end_date={today}", + timeout=15 +) + +if resp.status_code == 200: + wdata = resp.json() + times = wdata["hourly"]["time"] + + for sensor_name, (key, unit) in { + "irradiance": ("shortwave_radiation", "W/m²"), + "temperature": ("temperature_2m", "°C"), + "wind_speed": ("wind_speed_10m", "m/s") + }.items(): + sensor_id = SENSORS[sensor_name] + values = wdata["hourly"][key] + posted = 0 + + for i, t in enumerate(times): + # Parse datetime and format properly + dt = datetime.fromisoformat(t) + dt_utc = dt.astimezone(timezone.utc) + start_str = dt_utc.strftime("%Y-%m-%dT%H:%M:%S+00:00") + val = round(max(0, values[i]), 1) + + if post_sensor_data(sensor_id, [val], unit, start_str): + posted += 1 + + print(f" '{sensor_name}': {posted}/{len(times)} posted") +else: + print(f" Erreur API: {resp.status_code}") + +# ======================================== +# 2. Prix DSO simulés (24h) +# ======================================== +print("\n=== Injection des prix DSO ===") + +now_utc = datetime.now(timezone.utc) + +for sensor_name, sensor_id in [("consumption_price", SENSORS["consumption_price"]), + ("production_price", SENSORS["production_price"])]: + posted = 0 + for h in range(24): + dt = now_utc - timedelta(hours=23-h) + hour_of_day = dt.hour + + if 6 <= hour_of_day <= 22: + price = round(random.uniform(80, 150), 2) if "consumption" in sensor_name else round(random.uniform(60, 120), 2) + else: + price = round(random.uniform(40, 80), 2) if "consumption" in sensor_name else round(random.uniform(30, 60), 2) + + start_str = dt.strftime("%Y-%m-%dT%H:%M:%S+00:00") + if post_sensor_data(sensor_id, [price], "EUR/MWh", start_str): + posted += 1 + + print(f" '{sensor_name}': {posted}/24 posted") + +print("\n=== Terminé ===") diff --git a/scripts/inject_weather.py b/scripts/inject_weather.py new file mode 100644 index 0000000..a6a3669 --- /dev/null +++ b/scripts/inject_weather.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""Cariflex - Corrige l'injection des données météo.""" + +import json +import requests +import re +import warnings +import random +from datetime import datetime, timezone, timedelta +import pytz + +warnings.filterwarnings("ignore") + +FM_HOST = "https://cariflex.digitribe.fr" +CREDS_FILE = "/tmp/fm_creds.json" +MARTINIQUE_TZ = pytz.timezone("America/Martinique") + +with open(CREDS_FILE) as f: + creds = json.load(f) + +session = requests.Session() +session.verify = False + +# Login +r = session.get(f"{FM_HOST}/login") +match = re.search(r']*csrf_token[^>]*value="([^"]+)"', r.text) +csrf = match.group(1) +r = session.post(f"{FM_HOST}/login", data={ + "email": creds["email"], "password": creds["password"], + "csrf_token": csrf, "remember": "y" +}, allow_redirects=True) +print(f"Login: {'OK' if 'dashboard' in r.url else 'FAILED'}") + +# Sensor IDs (from previous creation) +sensors = { + "irradiance": 81, + "temperature": 82, + "wind_speed": 83, + "consumption_price": 84, + "production_price": 85, +} + +# Inject weather data +print("\n=== Données météo réelles ===") +LAT, LON = 14.6091, -61.2155 +today = datetime.now(MARTINIQUE_TZ).strftime("%Y-%m-%d") + +resp = requests.get( + f"https://api.open-meteo.com/v1/forecast?" + f"latitude={LAT}&longitude={LON}" + f"&hourly=shortwave_radiation,temperature_2m,wind_speed_10m" + f"&timezone=America/Martinique" + f"&start_date={today}&end_date={today}", + timeout=15 +) + +if resp.status_code == 200: + wdata = resp.json() + times = wdata["hourly"]["time"] + + for sensor_name, (key, unit) in { + "irradiance": ("shortwave_radiation", "W/m²"), + "temperature": ("temperature_2m", "°C"), + "wind_speed": ("wind_speed_10m", "m/s") + }.items(): + sensor_id = sensors[sensor_name] + values = wdata["hourly"][key] + posted = 0 + + for i, t in enumerate(times): + dt = datetime.fromisoformat(t) + val = round(max(0, values[i]), 1) + + try: + r = session.post( + f"{FM_HOST}/api/v3_0/sensors/{sensor_id}/data", + json={ + "values": [val], + "start": dt.isoformat(), + "duration": "PT1H", + "unit": unit + }, + timeout=30 + ) + if r.status_code in [200, 201, 202]: + posted += 1 + else: + if posted == 0: + print(f" Error response: {r.text[:100]}") + except Exception as e: + print(f" Exception: {e}") + + print(f" '{sensor_name}': {posted}/{len(times)} posted") +else: + print(f" API error: {resp.status_code}") + +print("\n=== Terminé ===") diff --git a/scripts/inject_weather_prices.py b/scripts/inject_weather_prices.py new file mode 100644 index 0000000..dc4e8b3 --- /dev/null +++ b/scripts/inject_weather_prices.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Cariflex - Injecte les données météo réelles et prix DSO simulés.""" + +import json +import requests +import re +import warnings +import random +from datetime import datetime, timezone, timedelta +import pytz + +warnings.filterwarnings("ignore") + +FM_HOST = "https://cariflex.digitribe.fr" +CREDS_FILE = "/tmp/fm_creds.json" +MARTINIQUE_TZ = pytz.timezone("America/Martinique") + +with open(CREDS_FILE) as f: + creds = json.load(f) + +session = requests.Session() +session.verify = False + +# Login +r = session.get(f"{FM_HOST}/login") +match = re.search(r']*csrf_token[^>]*value="([^"]+)"', r.text) +csrf = match.group(1) +r = session.post(f"{FM_HOST}/login", data={ + "email": creds["email"], "password": creds["password"], + "csrf_token": csrf, "remember": "y" +}, allow_redirects=True) +print(f"Login: {'OK' if 'dashboard' in r.url else 'FAILED'}") + +# Get existing sensors +r = session.get(f"{FM_HOST}/api/v3_0/sensors") +sensors = {} +if r.status_code == 200: + for s in r.json(): + sensors[s["name"]] = s["id"] + print(f" Sensor: {s['id']} - {s['name']} (asset {s.get('generic_asset_id', '?')})") + +# ======================================== +# 1. Injecter les prévisions météo réelles +# ======================================== +print("\n=== Injection des données météo ===") + +LAT, LON = 14.6091, -61.2155 +now_local = datetime.now(MARTINIQUE_TZ) +today = now_local.strftime("%Y-%m-%d") + +url = ( + f"https://api.open-meteo.com/v1/forecast?" + f"latitude={LAT}&longitude={LON}" + f"&hourly=shortwave_radiation,temperature_2m,wind_speed_10m" + f"&timezone=America/Martinique" + f"&start_date={today}&end_date={today}" +) + +try: + resp = requests.get(url, timeout=15) + if resp.status_code == 200: + wdata = resp.json() + hourly = wdata["hourly"] + times = hourly["time"] + irradiance = hourly["shortwave_radiation"] + temperature = hourly["temperature_2m"] + wind_speed = hourly["wind_speed_10m"] + + print(f" Données météo récupérées: {len(times)} heures") + + # Map sensor names to data + sensor_data_map = { + "irradiance": (irradiance, "W/m²"), + "temperature": (temperature, "°C"), + "wind_speed": (wind_speed, "m/s"), + } + + for sensor_name, (data, unit) in sensor_data_map.items(): + if sensor_name not in sensors: + print(f" Sensor '{sensor_name}' non trouvé, ignoré") + continue + + sensor_id = sensors[sensor_name] + posted = 0 + + for i, t in enumerate(times): + dt = datetime.fromisoformat(t) + value = max(0, data[i]) if data[i] is not None else 0 + + r = session.post( + f"{FM_HOST}/api/v3_0/sensors/{sensor_id}/data", + json={ + "values": [round(value, 1)], + "start": dt.isoformat(), + "duration": "PT1H", + "unit": unit + }, + timeout=30 + ) + if r.status_code in [200, 201, 202]: + posted += 1 + + print(f" Sensor '{sensor_name}' (ID {sensor_id}): {posted}/{len(times)} values posted") + else: + print(f" Erreur API météo: HTTP {resp.status_code}") +except Exception as e: + print(f" Erreur récupération météo: {e}") + +# ======================================== +# 2. Injecter les prix DSO simulés +# ======================================== +print("\n=== Injection des prix DSO ===") + +if "consumption_price" in sensors and "production_price" in sensors: + consumption_price_id = sensors["consumption_price"] + production_price_id = sensors["production_price"] + + now_utc = datetime.now(timezone.utc) + posted = 0 + + for hour in range(24): + dt = now_utc - timedelta(hours=23-hour) + hour_of_day = dt.hour + + # Price pattern: peak during day, lower at night + if 6 <= hour_of_day <= 22: + consumption_price = round(random.uniform(80, 150), 2) + production_price = round(random.uniform(60, 120), 2) + else: + consumption_price = round(random.uniform(40, 80), 2) + production_price = round(random.uniform(30, 60), 2) + + # Post consumption price + r = session.post( + f"{FM_HOST}/api/v3_0/sensors/{consumption_price_id}/data", + json={ + "values": [consumption_price], + "start": dt.isoformat(), + "duration": "PT1H", + "unit": "EUR/MWh" + }, + timeout=30 + ) + if r.status_code in [200, 201, 202]: + posted += 1 + + # Post production price + r = session.post( + f"{FM_HOST}/api/v3_0/sensors/{production_price_id}/data", + json={ + "values": [production_price], + "start": dt.isoformat(), + "duration": "PT1H", + "unit": "EUR/MWh" + }, + timeout=30 + ) + if r.status_code in [200, 201, 202]: + posted += 1 + + print(f" Prix DSO injectés: {posted} values") +else: + print(" Sensors de prix non trouvés") + +print("\n=== Injection terminée ===") diff --git a/scripts/simulation_dashboard.py b/scripts/simulation_dashboard.py new file mode 100644 index 0000000..17dd564 --- /dev/null +++ b/scripts/simulation_dashboard.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 +"""Cariflex - Script optimal de simulation avec page web intégrée.""" + +import json +import requests +import re +import warnings +import random +import math +from datetime import datetime, timezone, timedelta +from flask import Flask, render_template_string, request, jsonify +import pytz + +warnings.filterwarnings("ignore") + +FM_HOST = "https://cariflex.digitribe.fr" +CREDS_FILE = "/tmp/fm_creds.json" +MARTINIQUE_TZ = pytz.timezone("America/Martinique") + +# ======================================== +# Web App +# ======================================== +app = Flask(__name__) + +DASHBOARD_HTML = """ + + +
+