Initial Cariflex project

- 40 FlexMeasures assets (10 PV, 10 Bat, 10 Chg, 10 EV)
- Geolocated on Martinique
- Documentation: architecture, deployment, concepts
- Standards: Flex Ready, S2, OpenADR, EPEX SPOT
- R&D tools: HAMLET, OPLEM, lemlab
- Map patch: Mapbox -> OpenStreetMap
This commit is contained in:
Eric F
2026-06-07 22:19:29 -04:00
commit ffc08d0629
18 changed files with 1229 additions and 0 deletions

22
scripts/check_auth.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
"""Cariflex - Create assets directly via FlexMeasures API using JWT token."""
import requests, json, uuid, sys
HOST = "https://flexmeasures.digitribe.fr"
API_VERSION = "v3_0"
# Get the admin user from DB and generate a password hash
import subprocess
result = subprocess.run([
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
"-t", "-c", "SELECT password FROM fm_user WHERE email='admin@digitribe.fr';"
], capture_output=True, text=True)
print(f"Admin password hash: {result.stdout.strip()[:50]}...")
# Try to get auth token
# First, check what auth endpoints exist
for endpoint in [f"/api/{API_VERSION}/requestAuthToken", f"/api/{API_VERSION}/auth/token", f"/api/{API_VERSION}/login"]:
r = requests.post(f"{HOST}{endpoint}",
json={"email": "admin@digitribe.fr", "password": "Digitribe972"},
verify=False, timeout=10)
print(f"{endpoint}: {r.status_code} - {r.text[:200]}")

13
scripts/gen_pw.py Normal file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env python3
"""Generate bcrypt hash for FlexMeasures admin password."""
import bcrypt
password = b"Digitribe972"
# Generate salt and hash
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password, salt)
print(f"Hash: {hashed.decode()}")
# Verify
check = bcrypt.checkpw(password, hashed)
print(f"Verify: {check}")

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Cariflex - Initialise les actifs FlexMeasures dans la base de donnees."""
import subprocess
def run_sql(query):
result = subprocess.run([
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
"-t", "-c", query
], capture_output=True, text=True)
return result.stdout.strip()
# 1. Create asset types
print("=== Creating asset types ===")
asset_types = [
("Panneau Photovoltaique", "Installation de production d'energie solaire"),
("Batterie de Stockage", "Systeme de stockage d'energie par batterie"),
("Borne de Recharge VE", "Station de recharge pour vehicules electriques"),
("Vehicule Electrique", "Vehicule electrique avec capacite vehicle-to-grid"),
]
for name, desc in asset_types:
run_sql("INSERT INTO generic_asset_type (name, description) SELECT '{}', '{}' WHERE NOT EXISTS (SELECT 1 FROM generic_asset_type WHERE name = '{}');".format(name, desc, name))
types = run_sql("SELECT id, name FROM generic_asset_type;")
print(types)
# Get type IDs from DB
type_map = {}
for line in types.split('\n'):
parts = [p.strip() for p in line.split('|')]
if len(parts) == 2:
type_map[parts[1]] = int(parts[0])
pv_type = type_map.get('Panneau Photovoltaique', 1)
bat_type = type_map.get('Batterie de Stockage', 2)
chg_type = type_map.get('Borne de Recharge VE', 3)
ev_type = type_map.get('Vehicule Electrique', 4)
print("Type IDs: PV={}, Bat={}, Charger={}, EV={}".format(pv_type, bat_type, chg_type, ev_type))
# 2. Create 10 assets of each type
print("\n=== Creating assets ===")
base_lat = 14.6091
base_lon = -61.2155
configs = [
('PV', pv_type, 'kW', 'capacity', 5),
('Bat', bat_type, 'kWh', 'capacity', 100),
('Chg', chg_type, 'kW', 'max_power', 22),
('EV', ev_type, 'kWh', 'capacity', 75),
]
for prefix, type_id, unit, attr_key, attr_val in configs:
for i in range(1, 11):
name = "{}_{:02d}".format(prefix, i)
lat = base_lat + (i * 0.01)
lon = base_lon + (i * 0.01)
sensor = "{}_{:02d}_power".format(prefix.lower(), i)
# Create asset
run_sql("""INSERT INTO generic_asset (name, latitude, longitude, generic_asset_type_id, account_id, attributes, flex_context, flex_model, sensors_to_show, sensors_to_show_as_kpis)
SELECT '{}', {}, {}, {}, 1, '{{"{}": {}}}'::jsonb, '{{}}'::jsonb, '{{}}'::jsonb, '[]'::jsonb, '[]'::jsonb
WHERE NOT EXISTS (SELECT 1 FROM generic_asset WHERE name = '{}');""".format(name, lat, lon, type_id, attr_key, attr_val, name))
# Get asset ID
aid = run_sql("SELECT id FROM generic_asset WHERE name = '{}';".format(name))
if aid:
# Create sensor
run_sql("""INSERT INTO sensor (name, unit, timezone, event_resolution, knowledge_horizon_fnc, knowledge_horizon_par, generic_asset_id, attributes)
SELECT '{}', '{}', 'America/Martinique', '00:05:00', 'x_days_ago_at_y_oclock', '{{"x": 1, "y": 13, "z": "America/Martinique"}}'::json, {}, '{{}}'::jsonb
WHERE NOT EXISTS (SELECT 1 FROM sensor WHERE name = '{}');""".format(sensor, unit, aid, sensor))
print(" {} (ID:{}) -> sensor {}".format(name, aid, sensor))
else:
print(" {} already exists".format(name))
print("\n=== Summary ===")
for prefix, type_id, unit, _, _ in configs:
n_assets = run_sql("SELECT COUNT(*) FROM generic_asset WHERE generic_asset_type_id = {};".format(type_id))
n_sensors = run_sql("SELECT COUNT(*) FROM sensor WHERE generic_asset_id IN (SELECT id FROM generic_asset WHERE generic_asset_type_id = {});".format(type_id))
print(" {}: {} assets, {} sensors".format(prefix, n_assets, n_sensors))

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Re-localise les assets Cariflex sur la Martinique."""
import subprocess, random
def run_sql(query):
subprocess.run([
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
"-t", "-c", query
], capture_output=True, text=True)
# Martinique bounding box (approximate)
# Lat: 14.38 to 14.87, Lon: -61.25 to -60.82
# But we need to avoid the sea - use known land areas
# Realistic zones on Martinique (lat, lon) - major towns and industrial zones
ZONES = [
# Fort-de-France area (capital, dense urban)
(14.6091, -61.2155), # Fort-de-France center
(14.6150, -61.2080), # Fort-de-France north
(14.6050, -61.2200), # Fort-de-France south
(14.6200, -61.2000), # Fort-de-France east
(14.5950, -61.2100), # Fort-de-France west
# Le Lamentin (airport, industrial zone)
(14.6100, -61.0100), # Le Lamentin center
(14.6150, -61.0050), # Airport area
(14.6050, -61.0200), # Industrial zone
# Sainte-Marie (north-east)
(14.7800, -60.9800), # Sainte-Marie town
(14.7900, -60.9700), # North coast
# Le Robert (north-east coast)
(14.6800, -60.9400), # Le Robert town
# Le François (south-east coast)
(14.6600, -60.9000), # Le François town
# Le Vauclin (south-east tip)
(14.5500, -60.8400), # Le Vauclin
# Le Marin (south)
(14.4700, -60.8700), # Le Marin town
# Sainte-Anne (south coast)
(14.4400, -60.8500), # Sainte-Anne
# Le Diamant (south-west)
(14.4800, -61.0200), # Le Diamant
# Les Anses-d'Arlet (south-west coast)
(14.5000, -61.0800), # Les Anses-d'Arlet
# Sainte-Luce (south-east)
(14.4600, -60.9300), # Sainte-Luce
# Le Lorrain (north)
(14.7200, -60.9300), # Le Lorrain
]
random.seed(42) # Reproducible
# Shuffle zones and pick 10 for each asset type (with some overlap for co-location)
random.shuffle(ZONES)
# Get all assets ordered by type and name
result = subprocess.run([
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
"-t", "-c", """
SELECT g.id, g.name, gt.name as type_name
FROM generic_asset g
JOIN generic_asset_type gt ON g.generic_asset_type_id = gt.id
ORDER BY gt.id, g.name;
"""
], capture_output=True, text=True)
assets = []
for line in result.stdout.strip().split('\n'):
parts = [p.strip() for p in line.split('|')]
if len(parts) == 3:
assets.append((int(parts[0]), parts[1], parts[2]))
# Assign random locations per type (4 types, 10 assets each)
type_groups = {}
for aid, name, atype in assets:
if atype not in type_groups:
type_groups[atype] = []
type_groups[atype].append((aid, name))
zone_idx = 0
for atype, group in type_groups.items():
# Pick 10 random zones for this type (zones can repeat slightly for density)
type_zones = [ZONES[i % len(ZONES)] for i in range(zone_idx, zone_idx + 10)]
random.shuffle(type_zones)
zone_idx += 10
for i, (aid, name) in enumerate(group):
lat, lon = type_zones[i]
# Add small random jitter (±0.005 degrees ≈ 500m)
lat += random.uniform(-0.005, 0.005)
lon += random.uniform(-0.005, 0.005)
run_sql("UPDATE generic_asset SET latitude = {}, longitude = {} WHERE id = {};".format(
round(lat, 6), round(lon, 6), aid))
print(" {} ({}) -> {:.4f}, {:.4f}".format(name, atype[:3], lat, lon))
print("\nDone!")

101
scripts/relocate_land.py Normal file
View File

@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Relocate all assets to known land locations on Martinique."""
import subprocess
def run_sql(query):
subprocess.run([
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
"-c", query
], capture_output=True, text=True)
# Real locations on Martinique (verified land positions)
# Format: (lat, lon, name)
LOCATIONS = [
# Fort-de-France area (center)
(14.6091, -61.2155, "Fort-de-France"),
(14.6150, -61.2080, "Fort-de-France Nord"),
(14.6050, -61.2200, "Fort-de-France Sud"),
(14.6200, -61.2000, "Fort-de-France Est"),
(14.5950, -61.2100, "Fort-de-France Ouest"),
# Le Lamentin (west, industrial)
(14.6100, -61.0100, "Le Lamentin"),
(14.6150, -61.0050, "Aeroport Lamentin"),
(14.6050, -61.0200, "Lamentin Zone Industrielle"),
(14.6080, -61.0150, "Lamentin Centre"),
# Schoelcher (north-west)
(14.6200, -61.1000, "Schoelcher"),
(14.6250, -61.0900, "Schoelcher Nord"),
# Saint-Joseph (south)
(14.4800, -61.0400, "Saint-Joseph"),
(14.4900, -61.0350, "Saint-Joseph Nord"),
(14.4700, -61.0450, "Saint-Joseph Sud"),
# Le Marin (south-east)
(14.4700, -60.8700, "Le Marin"),
(14.4650, -60.8800, "Le Marin Ouest"),
# Sainte-Anne (south-east coast)
(14.4400, -60.8500, "Sainte-Anne"),
(14.4350, -60.8600, "Sainte-Anne Nord"),
# Le Vauclin (south-east tip)
(14.5500, -60.8400, "Le Vauclin"),
(14.5450, -60.8500, "Le Vauclin Nord"),
# Le Diamant (south-west)
(14.4800, -61.0200, "Le Diamant"),
(14.4850, -61.0150, "Le Diamant Nord"),
# Les Trois-Ilets (south-west)
(14.5000, -61.0800, "Les Trois-Ilets"),
(14.4950, -61.0900, "Les Trois-Ilets Est"),
# Rivière-Salée (south)
(14.4900, -60.9700, "Riviere-Salee"),
(14.4850, -60.9650, "Riviere-Salee Nord"),
# Le François (east coast)
(14.6600, -60.9000, "Le Francois"),
(14.6650, -60.8950, "Le Francois Nord"),
# Sainte-Marie (north-east)
(14.7800, -60.9800, "Sainte-Marie"),
(14.7850, -60.9750, "Sainte-Marie Nord"),
# Le Lorrain (north)
(14.7200, -60.9300, "Le Lorrain"),
(14.7250, -60.9250, "Le Lorrain Sud"),
# Le Robert (east)
(14.6800, -60.9400, "Le Robert"),
(14.6850, -60.9350, "Le Robert Nord"),
# Basse-Pointe (north-west)
(14.7200, -61.1200, "Basse-Pointe"),
# Grand'Rivière (north-west tip)
(14.7500, -61.1500, "Grand-Riviere"),
# Macouba (north-west)
(14.7000, -61.1300, "Macouba"),
# Ajoupa-Bouillon (north)
(14.7500, -61.1000, "Ajoupa-Bouillon"),
# Morne-Rouge (north)
(14.7000, -61.1000, "Morne-Rouge"),
# L'Ajoupa (north-west)
(14.7300, -61.1100, "L'Ajoupa"),
# Bellefontaine (north)
(14.7300, -61.1500, "Bellefontaine"),
]
# Get all assets
result = subprocess.run([
"docker", "exec", "flexmeasures-db", "psql", "-U", "flexmeasures", "-d", "flexmeasures",
"-t", "-c", """
SELECT g.id, g.name, gt.name as type_name
FROM generic_asset g
JOIN generic_asset_type gt ON g.generic_asset_type_id = gt.id
ORDER BY gt.id, g.name;
"""
], capture_output=True, text=True)
assets = []
for line in result.stdout.strip().split('\n'):
parts = [p.strip() for p in line.split('|')]
if len(parts) == 3:
assets.append((int(parts[0]), parts[1], parts[2]))
# Assign locations (spread across types)
for i, (aid, name, atype) in enumerate(assets):
lat, lon, loc_name = LOCATIONS[i % len(LOCATIONS)]
run_sql("UPDATE generic_asset SET latitude = {}, longitude = {} WHERE id = {};".format(lat, lon, aid))
print(" {} -> {:.4f}, {:.4f} ({})".format(name, lat, lon, loc_name))
print("\n✅ All assets relocated to verified land locations on Martinique")

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Cariflex - Create FlexMeasures assets via API client."""
import time
from flexmeasures_client import FlexMeasuresClient
# Connect to FlexMeasures
client = FlexMeasuresClient(
email="admin@digitribe.fr",
password="Digitribe972",
host="flexmeasures.digitribe.fr",
ssl=True,
request_timeout=60.0
)
print("✅ Connected to FlexMeasures")
# Get access token
token = client.access_token
print(f"🔑 Token: {token[:20]}...")
# Check existing assets
try:
assets = client.get_assets()
print(f"📦 Existing assets: {len(assets)}")
for a in assets[:5]:
print(f" - {a}")
except Exception as e:
print(f"Error getting assets: {e}")

43
scripts/test_fm_api.py Normal file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Test FlexMeasures connection and create Cariflex assets via API."""
import asyncio
from flexmeasures_client import FlexMeasuresClient
async def main():
# Connect
client = FlexMeasuresClient(
email="admin@digitribe.fr",
password="Digitribe972",
host="flexmeasures.digitribe.fr",
ssl=True,
request_timeout=60.0
)
print("Connected. Token:", client.access_token[:20] if client.access_token else "None")
# Get user info
try:
user = await client.get_user()
print("User:", user)
except Exception as e:
print(f"User error: {e}")
# Get assets
try:
assets = await client.get_assets()
print(f"Assets: {len(assets)}")
for a in assets[:5]:
print(f" - {a}")
except Exception as e:
print(f"Assets error: {e}")
# Get sensors
try:
sensors = await client.get_sensors()
print(f"Sensors: {len(sensors)}")
except Exception as e:
print(f"Sensors error: {e}")
await client.close()
asyncio.run(main())