Mock MdW v0.9.15 – Guide
Ce que c'est
Simulateur du Middleware MA (MdW) pour développer et tester l'Application de Management (AppMNG / Streem) et valider la chaîne complète AppMNG → MdW → MK6.
Conforme au CI AppMNG↔MdW v1.0.0 (20/02/2026) et au CI GW↔MK6 v1.0.0 pour la translation vers les contrôleurs.
Deux modes de fonctionnement
| Mode | Comportement |
|---|---|
| mock (défaut) | Valide la requête, stocke le planning, traduit et exécute automatiquement vers le(s) mock MK6, retourne le résultat E2E |
| recette | Proxy transparent vers le vrai MdW Operametrix, récupère les logs SIEM via GET /health?include_logs |
Démarrage rapide
pip install fastapi uvicorn httpx markdown cryptography
# HTTP (défaut — identique aux versions précédentes)
python mock_mdw.py --port 9000
# HTTPS avec cert auto-signé auto-généré
python mock_mdw.py --port 9000 --ssl --ssl-host 20.19.89.53
# HTTPS avec certificats fournis
python mock_mdw.py --port 9000 --ssl --ssl-cert cert.pem --ssl-key key.pem
# Ancien mode uvicorn direct (HTTP uniquement)
uvicorn mock_mdw:app --port 9000 --reload
Ouvrir http://localhost:9000 (ou https://...) dans un navigateur.
markdownest requis pour le rendu HTML des pages/et/scenarios. Prérequis pour le mode mock : un mock MK6 doit tourner sur l'URL configurée via/mock/configouDEFAULT_CONFIG.json. Sans mock MK6, les commandes sont validées et stockées mais l'exécution MK6 échoue silencieusement. HTTPS : avec--ssl, un certificat auto-signé est généré automatiquement. Les navigateurs afficheront un avertissement à accepter. Pour curl, utiliser-k.
Pages web intégrées
| URL | Contenu |
|---|---|
/ |
Guide fonctionnel (rendu de MOCK_MDW_GUIDE.md) |
/scenarios |
Scénarios curl (rendu de MOCK_MDW_SCENARIOS.md) |
/simulation |
Page de simulation interactive |
/docs |
Swagger UI interactif (OpenAPI) |
/api/v1/health |
Health check JSON |
Architecture
┌──────────────────────────────────────────────────────┐
│ Mock MdW (:8080) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ API métier │ │ Validation │ │ Translation │ │
│ │ CI AppMNG↔MW │→│ 400→404→503 │→│ MK6 vocab │ │
│ │ /commands │ │ →422→409 │ │ httpx async │ │
│ └─────────────┘ └──────────────┘ └──────┬──────┘ │
│ │ │
│ ┌─────────────┐ ┌──────────────┐ ↓ │
│ │ Mock control │ │ Simulation │ ┌─────────────┐ │
│ │ /mock/* │ │ /simulation │ │ → Mock MK6 │ │
│ └─────────────┘ └──────────────┘ │ (:8081) │ │
│ └─────────────┘ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ SIEM logs │ │ Planning │ │
│ │ 500 entries │ │ in-memory │ │
│ └─────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────┘
API métier — CI AppMNG↔MdW v1.0.0
POST /api/v1/commands
Envoie une commande de consigne pour un PRM donné.
Code retour : 200 (pas 201 — CI §4.1).
{
"prm_id": "50048589698480",
"order_id": "ORD-2026-001",
"source": "MA",
"start_time": "2026-03-01T14:00:00Z",
"preset": false,
"commands": [
{
"command_type": "psetpoint",
"value_kw": 5000.0,
"end_time": "2026-03-01T18:00:00Z",
"preset": false
}
]
}
Réponse succès (200) :
{
"status": "accepted",
"order_id": "ORD-2026-001",
"prm_id": "50048589698480",
"segments_count": 1,
"request_id": "uuid-auto-or-X-Request-ID",
"accepted_at": "2026-03-01T10:00:00Z"
}
Validations — ordre séquentiel DEC-047
| Étape | HTTP | error_code | status | Condition |
|---|---|---|---|---|
| 1 | 400 | INVALID_JSON, MISSING_FIELD, EMPTY_COMMANDS | "error" |
Structure JSON invalide |
| 2 | 404 | PRM_NOT_FOUND | "error" |
prm_id absent de la table |
| 3 | 503 | PRM_UNAVAILABLE | "rejected" |
PRM en défaut ou supervision > 20 min |
| 4 | 422 | UNKNOWN_COMMAND_TYPE, VALUE_EXCEEDS_PMAX, INVALID_TIMELINE, MISSING_VALUE_KW | "error" |
Sémantique invalide |
| 5 | 409 | DUPLICATE_ORDER_ID | "error" |
order_id déjà accepté (rejeté = retryable) |
Court-circuit strict : si l'étape 3 échoue (503), les étapes 4 et 5 ne sont pas évaluées.
Discrimination status (CI §5.1) :
"error"pour 4xx,"rejected"uniquement pour 503. La réponse 503 inclutprm_idetavailable_prm(avecpmax_kwpar PRM — DEC-037).
GET /api/v1/health
Sans auth. Retourne toujours 3 champs :
{"status": "healthy", "timestamp": "2026-03-01T10:00:00Z", "version": "1.0.0"}
Avec ?include_logs=true : ajoute recent_logs (array SIEM).
GET /api/v1/status (CI §4.2)
Supervision par PRM. Retourne pour chaque PRM : disponibilité, faute, dernière supervision, consigne active, données opérationnelles simulées.
Filtrable : ?prm_id=X&prm_id=Y.
{
"request_id": "...", "timestamp": "...",
"prm_count": 12, "available_count": 12, "faulted_count": 0,
"prm_statuses": [
{
"prm_id": "50048589698480", "alias": "ROU1 PDL1",
"raccordement_id": "ROU1.1", "mk6_id": "MK6_ROU1.1",
"pmax_kw": 8340.0, "available": true, "faulted": false,
"last_seen": "2026-03-01T10:00:00+00:00",
"has_active_command": true,
"active_command": {
"order_id": "ORD-001", "source": "MA",
"command_type": "psetpoint", "value_kw": 5000.0,
"start_time": "...", "end_time": "...", "preset": false
},
"operational_data": {
"p_available_kw": 8340.0, "pmax_kw": 8340.0,
"p_metered_1min_kw": 4900.0, "p_metered_10min_kw": 4950.0
}
}
]
}
Données opérationnelles simulées : p_metered s'adapte à la consigne active (cap à value_kw pour psetpoint, 0 pour stop, fluctuation libre sinon).
GET /api/v1/planning (CI §4.3)
Planning consolidé par PRM avec timeline de segments.
Filtrable : ?prm_id=X&prm_id=Y.
{
"request_id": "...", "timestamp": "...",
"planning": [
{
"prm_id": "50048589698480",
"timeline": [
{
"order_id": "TAO_12345", "source": "MA",
"command_type": "psetpoint", "value_kw": 5000.0,
"start_time": "2026-03-01T14:00:00Z",
"end_time": "2026-03-01T18:00:00Z", "preset": false
}
]
}
]
}
Note production : en production (Ignition), ces endpoints sont prévus en R3. Le mock les implémente pour permettre à Streem de tester l'intégration dès maintenant.
Endpoints mock (pilotage)
| Méthode | Endpoint | Usage |
|---|---|---|
| POST | /mock/reset | Purge complète (planning, logs, order_ids, faults) + re-provision ROUVAI |
| GET/POST | /mock/config | Voir/modifier la config (mk6_url, raccordements, PRM) |
| POST | /mock/prm | Créer ou modifier un PRM (raccordement auto-créé) |
| DELETE | /mock/prm/{id} | Supprimer un PRM + nettoyage |
| POST | /mock/prm/{id}/fault | Mettre un PRM en faute (→ 503) |
| DELETE | /mock/prm/{id}/fault | Lever la faute |
| GET/POST | /mock/time | Voir/figer le temps simulé ({"now": "2026-03-01T10:00:00Z"}) |
| POST | /mock/time/release | Libérer l'horloge → retour au temps réel |
| GET/POST | /mock/mode | Voir/changer le mode (mock/recette) |
| GET | /mock/state | État complet : planning, logs, config |
| GET/POST | /mock/error-config | Voir/modifier les scénarios d'injection d'erreur |
| POST | /mock/error-config/reset | Désactiver tous les scénarios d'erreur |
Injection d'erreurs
Erreurs MdW (via /mock/error-config)
Le panneau d'injection permet d'activer/désactiver 6 scénarios d'erreur sur le MdW :
| Scénario | Effet |
|---|---|
mdw_force_http_500 |
POST /commands → 500 Internal Server Error |
mdw_force_http_429 |
POST /commands → 429 Rate Limit |
mdw_force_timeout |
POST /commands → timeout 15s puis 504 |
mdw_force_invalid_json_response |
POST /commands → body corrompu non-JSON |
mdw_force_degraded_health |
GET /health → status="degraded" |
mdw_force_health_503 |
GET /health → 503 (en cours d'initialisation) |
Les erreurs s'injectent via l'API ou via le panneau ⚡ de la page simulation.
Erreurs MK6 (via le panneau simulation)
Le panneau ⚡ Injection erreurs expose l'arbre complet des MK6 provisionnés avec des contrôles à deux niveaux de granularité :
Par MK6 (raccordement) :
| Contrôle | Endpoint MK6 appelé | Effet |
|---|---|---|
| Réseau → offline | POST /mock/mk6/{id}/offline |
Toutes les API du MK6 → 503 |
| Réseau → unreachable | POST /mock/mk6/{id}/unreachable |
Toutes les API du MK6 → timeout 120s |
| Réseau → online | POST /mock/mk6/{id}/online |
Retour au fonctionnement normal |
| Auth Bearer ON | POST /mock/mk6/{id}/auth |
API rejetée sans token (401) |
Par site (sycode) :
| Contrôle | Endpoint MK6 appelé | Effet |
|---|---|---|
| Fault (6 types) | POST /mock/mk6/{id}/fault/{sycode} |
Site en défaut, state auto=0 |
| Clear fault | DELETE /mock/mk6/{id}/fault/{sycode} |
Rétablit le site, state retour à 2 |
| State=0 (unreachable) | POST /mock/mk6/{id}/site-state/{sycode} |
Forçage manuel du state |
| State=1 (not controllable) | idem | Forçage manuel du state |
| State=2 (nominal) | idem | Retour à la normale |
Types de fault disponibles : comm_loss, hardware, overtemp, grid_fault, sensor_fail, emergency_stop.
Règles d'isolation : - Chaque MK6 est indépendant (offline sur MK6_A n'affecte pas MK6_B) - Chaque site est indépendant (fault sur A1 n'affecte pas A2) - Un fault auto-set state=0 ; un clear auto-reset state=2 - Un state override manuel est possible même avec un fault actif - L'offline prime sur le fault (API bloquée au niveau réseau avant d'atteindre le site)
Boutons globaux : - ⟳ Refresh MK6 : recharge l'état complet - ✕ Reset MK6 errors : remet tout online, clear tous les faults, state=2, auth off - Le badge comptabilise séparément "N MdW + M MK6"
File de requêtes — export/import
La file de requêtes (section 📋 File de la page simulation) supporte l'export et l'import par fichier JSON :
⬇ Export : télécharge un fichier queue_YYYY-MM-DDTHH-MM-SS.json contenant le tableau JSON de la file. Inclut les commandes POST et les requêtes GET.
⬆ Import : charge un fichier .json contenant un tableau. Chaque élément est filtré :
- Accepté si prm_id est présent (commande POST)
- Accepté si _method === "GET" et _endpoint est présent (requête GET)
- Ignoré sinon (compteur affiché)
L'import ajoute à la file existante (pas de remplacement). Pour remplacer, vider d'abord avec ✕ Vider.
Format du fichier :
[
{
"prm_id": "50048589698480",
"order_id": "ORD-001",
"source": "MA",
"start_time": "2026-03-01T14:00:00Z",
"preset": false,
"commands": [
{ "command_type": "psetpoint", "value_kw": 5000.0, "end_time": "2026-03-01T18:00:00Z", "preset": false }
]
},
{
"_method": "GET",
"_endpoint": "/api/v1/status?prm_id=50048589698480",
"_label": "GET status PDL1"
}
]
Astuce : exporter une file pour la sauvegarder, ou préparer des scénarios manuellement dans un éditeur JSON pour les rejouer à volonté.
Consolidation — DEC-080
Écrasement brut par PRM : chaque nouvelle commande acceptée pour un PRM remplace intégralement la précédente. Pas d'interval tree, pas de merge partiel.
T1: POST /commands prm=PDL1 order=A → psetpoint 5000 kW ← planning PDL1 = A
T2: POST /commands prm=PDL1 order=B → stop ← planning PDL1 = B (A effacé)
Les PRM sont indépendants : une commande sur PDL1 ne touche pas PDL10.
Translation MK6 — DEC-108/117/120
En mode mock, chaque commande acceptée est traduite et envoyée au mock MK6 :
| Champ CI MdW | Champ MK6 | Règle |
|---|---|---|
| command_type: "psetpoint" | set: "Psetpoint" | P majuscule |
| command_type: "stop" | set: "stop" | Identique |
| command_type: "clear" | DELETE /planning/{id} | Pas de POST |
| preset: true | preset: "auto" | Boolean → string |
| preset: false | preset: "none" | Boolean → string |
| prm_id | site: sycode | Lookup table PRM→sycode |
Multi-MK6 URL (v0.9.9)
Par défaut, tous les MK6 sont adressés via mk6_url (racine de la config, typiquement http://localhost:8081). Pour tester avec plusieurs processus Mock MK6 sur des ports différents, chaque raccordement peut spécifier son propre mk6_url :
{
"mk6_url": "http://localhost:8081",
"raccordements": [
{
"raccordement_id": "ROU1.1",
"mk6_id": "MK6_ROU1.1",
"mk6_url": "http://localhost:8082",
"prm_list": [...]
},
{
"raccordement_id": "ROU2",
"mk6_id": "MK6_ROU2",
"prm_list": [...]
}
]
}
Dans cet exemple, MK6_ROU1.1 sera adressé sur le port 8082, et MK6_ROU2 sur le port par défaut 8081. Si aucun raccordement ne spécifie mk6_url, le comportement est identique aux versions précédentes.
L'endpoint GET /mock/config retourne mk6_urls: {"MK6_ROU1.1": "http://localhost:8082"} pour visualiser les overrides actifs.
Jeu de données — ROUVAI
Auto-provisionné au démarrage. 12 PRM répartis sur 5 raccordements / 5 MK6 :
| Raccordement | MK6 | PRM (sycode) | Pmax |
|---|---|---|---|
| ROU1.1 | MK6_ROU1.1 | PDL1 (8FV898VMwWX0), PDL2 (8FV898VPwW20) | 8340 kW |
| ROU1.2 | MK6_ROU1.2 | PDL3 (8FV899VJw7Q0) | 8340 kW |
| ROU1.3 | MK6_ROU1.3 | PDL6bis (8FV8C98VwC40) | 11120 kW |
| ROU2 | MK6_ROU2 | PDL10 (8FV8CCX2w7X0), PDL5 (8FV8C9MWwHH0) | 11120 kW |
| VAI | MK6_VAI | PDL4 (8FV897G6wGQ0), PDL1 (8FV89688wQV0) | 8340 kW |
Décisions implémentées
| ID | Résumé |
|---|---|
| DEC-045 | start_time dans le passé accepté |
| DEC-047 | Validation séquentielle court-circuit 400→404→503→422→409 |
| DEC-080 | Consolidation écrasement brut |
| DEC-089 | ROUVAI 5 MK6 distincts |
| DEC-108/117 | Clear = DELETE /planning (pas de null/absence) |
| DEC-112/113 | Preset boolean deux niveaux (commande + segment) |
| DEC-120 | Translation preset true→"auto", false→"none" |
Changelog
v0.9.15 (27/02/2026)
| Changement | Impact |
|---|---|
| Support HTTPS | --ssl active TLS avec auto-génération cert auto-signé. --ssl-cert/--ssl-key pour certs fournis. CORS middleware activé. verify=False pour appels MK6. |
| GET /mock/tls | Nouvel endpoint état TLS (ssl_enabled, protocol, cert/key paths) |
| CLI argparse | --host, --port, --ssl, --ssl-cert, --ssl-key, --ssl-host, --reload |
| Tests unifiés | test_mock_mdw.py : 146 tests (fonctionnel + SSL + CORS + CLI) |
v0.9.9 (26/02/2026)
| Changement | Impact |
|---|---|
| Multi-MK6 URL backend | RaccordementConfig.mk6_url optionnel. state.get_mk6_url(mk6_id) avec fallback. build_mk6_actions() résout par mk6_id. |
| Multi-MK6 URL frontend | getMk6UrlFor(mk6Id) + getDistinctMk6Urls(). Init, reset, refresh, erreurs, arbre itèrent sur toutes les URLs distinctes. Table overrides dans le panneau config. |
| mk6_urls dans endpoints | /mock/config, /mock/state, /simulation/api/config exposent mk6_urls: {} |
| Réorganisation sidebar | Scénario + File + Exécuter en haut. Config/MK6/Erreurs repliés. Hiérarchie visuelle CSS. |
| Toast + scrollIntoView | Feedback ajout file + scroll auto résultats |
| Chaînage horaire | Auto-remplissage start/end après ajout à la file (+30min) |
| Persistance file | sessionStorage sauve/restaure la file |
| Raccourcis clavier | Ctrl+Enter (ajouter), Ctrl+Shift+Enter (exécuter) |
| Liens MK6 dynamiques | Navbar + lien serveur Python utilisent state.mk6_url |
| Refactoring | getMk6Url() centralisé, suppression code mort, responsive CSS |
v0.9.8 (25/02/2026)
| Changement | Impact |
|---|---|
| Panneau injection erreurs MK6 dans simulation.html | Arbre dépliable MK6→sites avec contrôles inline : réseau (online/offline/unreachable), auth Bearer, fault par site (6 types), state forcing (0/1/2). Appels directs aux endpoints natifs du mock MK6 |
| Export/import file de requêtes | Boutons ⬇ Export (JSON timestampé) et ⬆ Import (fichier .json, filtre POST+GET, ajout à la file existante) |
| Badge erreurs unifié MdW+MK6 | Le badge ⚡ comptabilise séparément les erreurs MdW et MK6 (ex: "2 MdW + 3 MK6") |
| Reset MK6 errors | Bouton dédié : remet tous les MK6 online, clear faults, state=2, auth off |
| Refactoring simulation.html | Suppression de 110 lignes de code dupliqué (fonctions scenario builder/execute déclarées deux fois). 1845→1722 lignes, 54 fonctions uniques, 0 doublons |
| Auto-refresh arbre MK6 | L'arbre MK6 se recharge toutes les 60s et après chaque action |
v0.9.7 (25/02/2026)
| Changement | Impact |
|---|---|
GET /api/v1/status fonctionnel (CI §4.2) |
Supervision par PRM avec consigne active, données opérationnelles simulées (p_metered adapté) |
GET /api/v1/planning fonctionnel (CI §4.3) |
Planning consolidé avec timeline, filtre multi-PRM |
| Panneau injection erreurs MdW | 6 scénarios activables (500, 429, timeout, body corrompu, health degraded/503) |
Config externalisée DEFAULT_CONFIG.json |
Rechargeable via /mock/reset, fallback vers builtin ROUVAI |
v0.9.6 (21/02/2026)
| Changement | Impact |
|---|---|
GET / → rendu HTML de MOCK_MDW_GUIDE.md |
Style identique au Mock MK6 |
GET /scenarios |
Scénarios rendus en HTML depuis MOCK_MDW_SCENARIOS.md |
| Navigation inter-pages | Guide / Scénarios / Swagger / Simulation / Supervision MK6 |
DEFAULT_CONFIG.json externalisé |
Changer de site sans modifier le code |
| CORS middleware activé | Appels cross-origin depuis simulation.html |
POST /mock/time/release |
Libère l'horloge simulée → retour au temps réel |
_skip_mk6 dans /simulation/api/execute |
Mode Manuel MK6 |
| Charte graphique dark unifiée | Cohérence visuelle avec Mock MK6 |
v0.1 → v0.9 (16–20/02/2026)
| Changement | Impact |
|---|---|
| Validation séquentielle DEC-047 | Court-circuit strict 400→404→503→422→409 |
| Consolidation écrasement brut DEC-080 | Dernière commande reçue par PRM prévaut |
| Preset deux niveaux DEC-112/113 | Boolean au niveau commande ET segment |
| Anti-replay order_id (409) | Commande rejetée = retryable |
| Translation MK6 complète | psetpoint→Psetpoint, preset→auto/none, clear→DELETE |
| Page simulation HTML intégrée | Assistant scénario, file de requêtes, résultats E2E |
| Auto-provisionnement ROUVAI | 12 PRM, 5 raccordements, 5 MK6 au démarrage |
| SIEM logs circulaire | 500 entrées, accessible via /health?include_logs |