MdW v0.9.15

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.

markdown est 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/config ou DEFAULT_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 inclut prm_id et available_prm (avec pmax_kw par 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