Mock MK6 v2.2.0 – Scénarios de test
BASE=http://localhost:9001
Scénario 0 – Horloge simulée (v0.9.6)
BASE=http://localhost:9001
# Sans horloge figée : heure système utilisée (pas d'erreur 400)
curl $BASE/supervision/api/time
# → {"now": "2026-02-25T09:00:00Z", "source": "system"}
# Figer l'horloge
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
# → {"now": "2026-03-01T10:00:00+00:00"}
curl $BASE/supervision/api/time
# → {"now": "2026-03-01T10:00:00Z", "source": "mock"}
# Avancer de 30 minutes (depuis la supervision ou via API)
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:30:00Z"}'
# Libérer l'horloge → retour au temps réel (v0.9.6)
curl -X POST $BASE/mock/time/release
# → {"message": "Horloge liberee — temps reel actif", "now": null}
curl $BASE/supervision/api/time
# → {"now": "2026-02-25T09:00:12Z", "source": "system"}
Scénario 1 – Provisionnement ROUVAI (v0.9.6)
Convention : Le mk6_id correspond au MK6 physique. Chaque MK6 a un site maître (thissite) et d'éventuels sites esclaves (nearsite). Le raccordement est une notion MdW/GW qui n'existe pas au niveau MK6.
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
# MK6 ROU1.1 : PDL1 (thissite) + PDL2 (nearsite)
curl -X POST $BASE/mock/mk6/ROU1.1/provision -H "Content-Type: application/json" \
-d '{
"sites": [
{"sycode": "8FV898VMwWX0", "name": "ROU1 PDL1", "role": "thissite", "pmax_kw": 8340, "prm_id": "50048589698480"},
{"sycode": "8FV898VPwW20", "name": "ROU1 PDL2", "role": "nearsite", "pmax_kw": 8340, "prm_id": "50035999249967"}
]
}'
# → {"mk6_id": "ROU1.1", "sites": 2, "thissite": "8FV898VMwWX0", ...}
# MK6 ROU1.2 : PDL3 (thissite, seul)
curl -X POST $BASE/mock/mk6/ROU1.2/provision -H "Content-Type: application/json" \
-d '{
"sites": [
{"sycode": "8FV899VJw7Q0", "name": "ROU1 PDL3", "role": "thissite", "pmax_kw": 8340, "prm_id": "50028763359904"}
]
}'
# MK6 ROU1.3 : PDL6bis (thissite, seul)
curl -X POST $BASE/mock/mk6/ROU1.3/provision -H "Content-Type: application/json" \
-d '{
"sites": [
{"sycode": "8FV8C98VwC40", "name": "ROU1 PDL6bis", "role": "thissite", "pmax_kw": 11120, "prm_id": "50055536152901"}
]
}'
# MK6 ROU2 : PDL10 (thissite) + PDL8, PDL5, PDL9 (nearsite)
curl -X POST $BASE/mock/mk6/ROU2/provision -H "Content-Type: application/json" \
-d '{
"sites": [
{"sycode": "8FV8CCX2w7X0", "name": "ROU2 PDL10", "role": "thissite", "pmax_kw": 11120, "prm_id": "50048300262939"},
{"sycode": "8FV8C9MWwHJ0", "name": "ROU2 PDL8", "role": "nearsite", "pmax_kw": 11120, "prm_id": "50015159886785"},
{"sycode": "8FV8C9MWwHH0", "name": "ROU2 PDL5", "role": "nearsite", "pmax_kw": 11120, "prm_id": "50049023851893"},
{"sycode": "8FV8CCX2w7W0", "name": "ROU2 PDL9", "role": "nearsite", "pmax_kw": 11120, "prm_id": "50048155545173"}
]
}'
# MK6 VAI : PDL4 (thissite) + PDL3, PDL2, PDL1 (nearsite)
curl -X POST $BASE/mock/mk6/VAI/provision -H "Content-Type: application/json" \
-d '{
"sites": [
{"sycode": "8FV897G6wGQ0", "name": "VAI PDL4", "role": "thissite", "pmax_kw": 8340, "prm_id": "50048444980651"},
{"sycode": "8FV897G6wGQ1", "name": "VAI PDL3", "role": "nearsite", "pmax_kw": 11120, "prm_id": "50014581015503"},
{"sycode": "8FV89688wQW0", "name": "VAI PDL2", "role": "nearsite", "pmax_kw": 11120, "prm_id": "50048155545061"},
{"sycode": "8FV89688wQV0", "name": "VAI PDL1", "role": "nearsite", "pmax_kw": 8340, "prm_id": "50055391435158"}
]
}'
# Vérifier l'inventaire
curl $BASE/mock/mk6
# → 5 MK6 physiques avec sycodes, thissite, network
Scénario 2 – Monitoring v1.0.5 : 5 endpoints
# (suite du scénario 1)
# 1. GET /monitoring (base) — topologie + state (sur ROU2 : 4 sites)
curl $BASE/api/v1/mk6/ROU2/monitoring
# → {"thissite": {"8FV8CCX2w7X0": "ROU2 PDL10"},
# "nearsite": [{"8FV8C9MWwHJ0": "ROU2 PDL8"}, ...],
# "state": {"8FV8CCX2w7X0": 2, "8FV8C9MWwHJ0": 2, ...}}
# 2. GET /monitoring/sites — données complètes par site
curl $BASE/api/v1/mk6/ROU2/monitoring/sites
# → {"sites": {
# "8FV8CCX2w7X0": {"sycode": "...", "pmax_wf": 11120, "pavailable": ..., ...},
# "8FV8C9MWwHJ0": {"sycode": "...", "pmax_wf": 11120, ...},
# ...}}
# 3. GET /monitoring/sites/{sycode} — un site
curl $BASE/api/v1/mk6/ROU2/monitoring/sites/8FV8C9MWwHH0
# → {"sycode": "8FV8C9MWwHH0", "site_name": "ROU2 PDL5", "state": 2,
# "pmax_wf": 11120, "pavailable": ..., "active_request": null, ...}
# 4. GET /monitoring/planning — planning groupé par sycode
curl $BASE/api/v1/mk6/ROU2/monitoring/planning
# → {"sites": {"8FV8CCX2w7X0": {"sycode": "...", "site_name": "ROU2 PDL10", "planning": []}, ...}}
# 5. GET /monitoring/sites/{sycode} — site inconnu → 404
curl -v $BASE/api/v1/mk6/ROU2/monitoring/sites/FAKE
# → 404 {"error": "SITE_NOT_FOUND", "code": 404, ...}
# Monitoring ROU1.2 (mono-site : PDL3)
curl $BASE/api/v1/mk6/ROU1.2/monitoring
# → {"thissite": {"8FV899VJw7Q0": "ROU1 PDL3"}, "nearsite": [], "state": {"8FV899VJw7Q0": 2}}
# Monitoring ROU1.1 (2 sites : PDL1 + PDL2)
curl $BASE/api/v1/mk6/ROU1.1/monitoring
# → {"thissite": {"8FV898VMwWX0": "ROU1 PDL1"}, "nearsite": [{"8FV898VPwW20": "ROU1 PDL2"}], ...}
Scénario 3 – Consigne Psetpoint ciblée par site
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
curl -X POST $BASE/mock/mk6/ROU1.1/provision -H "Content-Type: application/json" \
-d '{"sites": [
{"sycode": "8FV898VMwWX0", "name": "ROU1 PDL1", "role": "thissite", "pmax_kw": 8340, "prm_id": "50048589698480"},
{"sycode": "8FV898VPwW20", "name": "ROU1 PDL2", "role": "nearsite", "pmax_kw": 8340, "prm_id": "50035999249967"}
]}'
# Consigne ciblée sur PDL2 nearsite (par sycode)
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "8FV898VPwW20", "psetpoint": 5000,
"start_time": "2026-03-01T12:00:00Z", "end_time": "2026-03-01T18:00:00Z"}'
# → 201 {"id": "REQ-001", "site": {"8FV898VPwW20": "ROU1 PDL2"}, "state": "scheduled", ...}
# Avancer à 14h
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T14:00:00Z"}'
# Monitoring par site — PDL2 impacté, PDL1 non impacté
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/8FV898VPwW20
# → psetpoint_recu: 5000, active_request: {"id": "REQ-001", ...}
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/8FV898VMwWX0
# → psetpoint_recu: 8340 (Pmax, pas de consigne), active_request: null
# Consigne ciblée par nom
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "stop", "site": "ROU1 PDL1",
"start_time": "2026-03-01T14:00:00Z", "end_time": "2026-03-01T16:00:00Z"}'
# → 201 {"site": {"8FV898VMwWX0": "ROU1 PDL1"}, ...}
Scénario 4 – Codes erreur v1.0.5
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
curl -X POST $BASE/mock/mk6/mk6-A/provision -H "Content-Type: application/json" \
-d '{"sites": [{"sycode": "SYC1", "name": "Site1", "role": "thissite", "pmax_kw": 8340}]}'
# 400 BAD_REQUEST — set invalide
curl -v -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "invalid"}'
# → 400 {"error": "BAD_REQUEST", "code": 400, "message": "set invalide: 'invalid'", "details": {...}}
# 441 INVALID_VALUE — psetpoint > pmax
curl -v -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 99999}'
# → 441 {"error": "INVALID_VALUE", "details": {"max": 8340, "site": "SYC1"}}
# 442 MISSING_FIELD — psetpoint absent
curl -v -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1"}'
# → 442 {"error": "MISSING_FIELD", "details": {"field": "psetpoint"}}
# 404 SITE_NOT_FOUND
curl -v -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "stop", "site": "NONEXISTENT"}'
# → 404 {"error": "SITE_NOT_FOUND"}
# 422 INVALID_TIME_RANGE — end <= start
curl -v -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 5000,
"start_time": "2026-03-01T18:00:00Z", "end_time": "2026-03-01T14:00:00Z"}'
# → 422 {"error": "INVALID_TIME_RANGE"}
# 503 SITE_NOT_ACCESSIBLE — site state=0
curl -X POST $BASE/mock/mk6/mk6-A/site-state/SYC1 -H "Content-Type: application/json" \
-d '{"state": 0}'
curl -v -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "stop", "site": "SYC1"}'
# → 503 {"error": "SITE_NOT_ACCESSIBLE"}
# 443 CONFIRMATION_REQUIRED — main_reset sans confirm
curl -v -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "main_reset"}'
# → 443 {"error": "CONFIRMATION_REQUIRED"}
# 409 REQUEST_LOCKED — PATCH consigne active
curl -X POST $BASE/mock/mk6/mk6-A/site-state/SYC1 -H "Content-Type: application/json" \
-d '{"state": 2}'
curl -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 5000}'
# → REQ-001 (active immédiatement car pas de start_time futur)
curl -v -X PATCH $BASE/api/v1/mk6/mk6-A/planning/REQ-001 -H "Content-Type: application/json" \
-d '{"psetpoint": 6000}'
# → 409 {"error": "REQUEST_LOCKED"}
# 405 REQUEST_NOT_FOUND
curl -v $BASE/api/v1/mk6/mk6-A/planning/REQ-999
# → 405 {"error": "REQUEST_NOT_FOUND"}
Scénario 5 – State par site (0/1/2)
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
curl -X POST $BASE/mock/mk6/ROU2/provision -H "Content-Type: application/json" \
-d '{"sites": [
{"sycode": "8FV8CCX2w7X0", "name": "ROU2 PDL10", "role": "thissite", "pmax_kw": 11120},
{"sycode": "8FV8C9MWwHJ0", "name": "ROU2 PDL8", "role": "nearsite", "pmax_kw": 11120},
{"sycode": "8FV8C9MWwHH0", "name": "ROU2 PDL5", "role": "nearsite", "pmax_kw": 11120},
{"sycode": "8FV8CCX2w7W0", "name": "ROU2 PDL9", "role": "nearsite", "pmax_kw": 11120}
]}'
# Tous state=2 (nominal)
curl $BASE/api/v1/mk6/ROU2/monitoring
# → state: {"8FV8CCX2w7X0": 2, "8FV8C9MWwHJ0": 2, "8FV8C9MWwHH0": 2, "8FV8CCX2w7W0": 2}
# Forcer PDL5 en state=1 (not controllable)
curl -X POST $BASE/mock/mk6/ROU2/site-state/8FV8C9MWwHH0 \
-H "Content-Type: application/json" -d '{"state": 1}'
# Mettre PDL8 en fault (→ state=0 automatique)
curl -X POST $BASE/mock/mk6/ROU2/fault/8FV8C9MWwHJ0
# Vérifier
curl $BASE/api/v1/mk6/ROU2/monitoring
# → state: {"8FV8CCX2w7X0": 2, "8FV8C9MWwHJ0": 0, "8FV8C9MWwHH0": 1, "8FV8CCX2w7W0": 2}
# Consigne vers PDL8 (state=0) → 503
curl -v -X POST $BASE/api/v1/mk6/ROU2/requests -H "Content-Type: application/json" \
-d '{"set": "stop", "site": "8FV8C9MWwHJ0"}'
# → 503 SITE_NOT_ACCESSIBLE
# Consigne vers PDL10 (state=2) → OK
curl -X POST $BASE/api/v1/mk6/ROU2/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "8FV8CCX2w7X0", "psetpoint": 5000,
"start_time": "2026-03-01T12:00:00Z", "end_time": "2026-03-01T14:00:00Z"}'
# → 201
# Rétablir PDL8
curl -X DELETE $BASE/mock/mk6/ROU2/fault/8FV8C9MWwHJ0
curl $BASE/api/v1/mk6/ROU2/monitoring
# → state: {"8FV8CCX2w7X0": 2, "8FV8C9MWwHJ0": 2, "8FV8C9MWwHH0": 1, "8FV8CCX2w7W0": 2}
Scénario 6 – Clear explicite (DEC-065) — 3 cas
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
curl -X POST $BASE/mock/mk6/ROU1.1/provision -H "Content-Type: application/json" \
-d '{"sites": [{"sycode": "SYC1", "name": "PDL1", "role": "thissite", "pmax_kw": 8340}]}'
# ── Cas 1 : Fin de série ──
# Limitation 5MW 14h-16h
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 5000,
"start_time": "2026-03-01T14:00:00Z", "end_time": "2026-03-01T16:00:00Z"}'
# 15h → psetpoint_recu=5000
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T15:00:00Z"}'
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/SYC1
# → psetpoint_recu: 5000
# 16h30 → consigne expired, production libre → psetpoint_recu=Pmax
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T16:30:00Z"}'
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/SYC1
# → psetpoint_recu: 8340, active_request: null
# ── Cas 2 : Trou au milieu ──
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
curl -X POST $BASE/mock/mk6/ROU1.1/provision -H "Content-Type: application/json" \
-d '{"sites": [{"sycode": "SYC1", "name": "PDL1", "role": "thissite", "pmax_kw": 8340}]}'
# Segment A : 14h-15h 5MW
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 5000,
"start_time": "2026-03-01T14:00:00Z", "end_time": "2026-03-01T15:00:00Z"}'
# Trou : 15h-16h → la GW supprimerait les consignes (DELETE) — retour à Pmax
# Segment B : 16h-18h 5MW
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 5000,
"start_time": "2026-03-01T16:00:00Z", "end_time": "2026-03-01T18:00:00Z"}'
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T14:30:00Z"}'
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/SYC1
# → psetpoint_recu: 5000 (segment A)
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T15:30:00Z"}'
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/SYC1
# → psetpoint_recu: 8340 (Pmax — trou = clear)
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T16:30:00Z"}'
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/SYC1
# → psetpoint_recu: 5000 (segment B)
# ── Cas 3 : Segments vidés ──
# La GW supprime les consignes actives (DELETE) pour permettre retour à Pmax — la consigne existante
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 8340,
"start_time": "2026-03-01T16:00:00Z", "end_time": "2026-03-01T18:00:00Z"}'
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/SYC1
# → psetpoint_recu: 8340 (production libre)
Scénario 7 – Annule-et-remplace (dernière série prévaut)
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
curl -X POST $BASE/mock/mk6/ROU1.1/provision -H "Content-Type: application/json" \
-d '{"sites": [{"sycode": "SYC1", "name": "PDL1", "role": "thissite", "pmax_kw": 8340}]}'
# Commande A : 3MW 14h-18h
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 3000,
"start_time": "2026-03-01T14:00:00Z", "end_time": "2026-03-01T18:00:00Z"}'
# Commande B : 7MW même créneau (seq plus récent → prévaut)
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 7000,
"start_time": "2026-03-01T14:00:00Z", "end_time": "2026-03-01T18:00:00Z"}'
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T15:00:00Z"}'
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/SYC1
# → psetpoint_recu: 7000 (dernière série prévaut)
Scénario 8 – main_reset
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
curl -X POST $BASE/mock/mk6/ROU1.1/provision -H "Content-Type: application/json" \
-d '{"sites": [
{"sycode": "SYC_A", "name": "Site A", "role": "thissite", "pmax_kw": 10000},
{"sycode": "SYC_B", "name": "Site B", "role": "nearsite", "pmax_kw": 10000}
]}'
# 1 active + 2 scheduled
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC_A", "psetpoint": 5000,
"start_time": "2026-03-01T09:00:00Z", "end_time": "2026-03-01T18:00:00Z"}'
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "stop", "site": "SYC_B",
"start_time": "2026-03-01T14:00:00Z", "end_time": "2026-03-01T16:00:00Z"}'
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC_B", "psetpoint": 3000,
"start_time": "2026-03-01T16:00:00Z", "end_time": "2026-03-01T18:00:00Z"}'
# 443 sans confirm
curl -v -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "main_reset"}'
# → 443 CONFIRMATION_REQUIRED
# main_reset avec confirm → purge les scheduled, conserve l'active
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "main_reset", "confirm": true, "reason": "nettoyage test"}'
# → {"deleted_count": 2, ...}
curl $BASE/api/v1/mk6/ROU1.1/planning
# → 1 seule consigne (la active)
Scénario 9 – Pmax par site (R-IT-06)
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
curl -X POST $BASE/mock/mk6/VAI/provision -H "Content-Type: application/json" \
-d '{"sites": [
{"sycode": "8FV897G6wGQ0", "name": "VAI PDL4", "role": "thissite", "pmax_kw": 8340},
{"sycode": "8FV89688wQW0", "name": "VAI PDL2", "role": "nearsite", "pmax_kw": 11120}
]}'
# Monitoring : pmax_wf par site
curl $BASE/api/v1/mk6/VAI/monitoring/sites
# → PDL4: pmax_wf=8340, PDL2: pmax_wf=11120
# 9000 kW accepté pour PDL2 (pmax=11120) mais refusé pour PDL4 (pmax=8340)
curl -X POST $BASE/api/v1/mk6/VAI/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "8FV89688wQW0", "psetpoint": 9000,
"start_time": "2026-03-01T12:00:00Z", "end_time": "2026-03-01T14:00:00Z"}'
# → 201 OK
curl -v -X POST $BASE/api/v1/mk6/VAI/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "8FV897G6wGQ0", "psetpoint": 9000}'
# → 441 INVALID_VALUE {"details": {"max": 8340, "site": "8FV897G6wGQ0"}}
Scénario 10 – Multi-MK6 E2E (R1-E5-S01)
# (reprendre le provisionnement ROUVAI du scénario 1)
# 5MW sur PDL1 (MK6 ROU1.1)
curl -X POST $BASE/api/v1/mk6/ROU1.1/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "8FV898VMwWX0", "psetpoint": 5000,
"start_time": "2026-03-01T10:00:00Z", "end_time": "2026-03-01T18:00:00Z"}'
# STOP sur VAI PDL4 (MK6 VAI)
curl -X POST $BASE/api/v1/mk6/VAI/requests -H "Content-Type: application/json" \
-d '{"set": "stop", "site": "8FV897G6wGQ0",
"start_time": "2026-03-01T10:00:00Z", "end_time": "2026-03-01T18:00:00Z"}'
# Vérifier isolation entre MK6
curl $BASE/api/v1/mk6/ROU1.1/monitoring/sites/8FV898VMwWX0
# → psetpoint_recu: 5000
curl $BASE/api/v1/mk6/VAI/monitoring/sites/8FV897G6wGQ0
# → psetpoint_recu: 0 (stop)
curl $BASE/api/v1/mk6/ROU2/monitoring/sites/8FV8CCX2w7X0
# → psetpoint_recu: 11120 (Pmax, pas de consigne)
curl $BASE/api/v1/mk6/ROU1.2/monitoring/sites/8FV899VJw7Q0
# → psetpoint_recu: 8340 (PDL3, pas de consigne = Pmax)
Scénario 11 – MK6 offline / retour online
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T12:00:00Z"}'
curl -X POST $BASE/mock/mk6/mk6-A/provision -H "Content-Type: application/json" \
-d '{"sites": [{"sycode": "SYC1", "name": "Site1", "role": "thissite", "pmax_kw": 10000}]}'
# MK6 passe offline
curl -X POST $BASE/mock/mk6/mk6-A/offline
# Toute requête API → 503
curl -v $BASE/api/v1/mk6/mk6-A/monitoring
# → 503
curl -v -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "stop", "site": "SYC1"}'
# → 503
# Remettre en ligne → tout remarche
curl -X POST $BASE/mock/mk6/mk6-A/online
curl $BASE/api/v1/mk6/mk6-A/monitoring
# → 200
Scénario 12 – Auth Bearer
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T12:00:00Z"}'
curl -X POST $BASE/mock/mk6/mk6-A/provision -H "Content-Type: application/json" \
-d '{"sites": [{"sycode": "SYC1", "name": "Site1", "role": "thissite", "pmax_kw": 10000}]}'
# Activer auth
curl -X POST $BASE/mock/mk6/mk6-A/auth -H "Content-Type: application/json" \
-d '{"enabled": true, "token": "secret123"}'
# Sans token → 401
curl -v $BASE/api/v1/mk6/mk6-A/monitoring
# → 401
# Avec token → 200
curl -H "Authorization: Bearer secret123" $BASE/api/v1/mk6/mk6-A/monitoring
# → 200
# Token invalide → 401
curl -H "Authorization: Bearer wrong" $BASE/api/v1/mk6/mk6-A/monitoring
# → 401
Scénario 13 – Transitions temporelles
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T10:00:00Z"}'
curl -X POST $BASE/mock/mk6/mk6-A/provision -H "Content-Type: application/json" \
-d '{"sites": [{"sycode": "SYC1", "name": "Site1", "role": "thissite", "pmax_kw": 10000}]}'
# 3 consignes successives sur le même site
curl -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 3000,
"start_time": "2026-03-01T11:00:00Z", "end_time": "2026-03-01T12:00:00Z"}'
curl -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "stop", "site": "SYC1",
"start_time": "2026-03-01T12:00:00Z", "end_time": "2026-03-01T13:00:00Z"}'
curl -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 7000,
"start_time": "2026-03-01T13:00:00Z", "end_time": "2026-03-01T14:00:00Z"}'
# 10h → Pmax (production libre)
curl $BASE/api/v1/mk6/mk6-A/monitoring/sites/SYC1
# → psetpoint_recu: 10000, active_request: null
# 11h30 → 3MW
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T11:30:00Z"}'
curl $BASE/api/v1/mk6/mk6-A/monitoring/sites/SYC1
# → psetpoint_recu: 3000
# 12h30 → STOP
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T12:30:00Z"}'
curl $BASE/api/v1/mk6/mk6-A/monitoring/sites/SYC1
# → psetpoint_recu: 0, stop_wf_active: true
# 13h30 → 7MW
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T13:30:00Z"}'
curl $BASE/api/v1/mk6/mk6-A/monitoring/sites/SYC1
# → psetpoint_recu: 7000
# 14h30 → Pmax (tout expiré)
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T14:30:00Z"}'
curl $BASE/api/v1/mk6/mk6-A/monitoring/sites/SYC1
# → psetpoint_recu: 10000, active_request: null
Scénario 14 – 🔒 Sécurité mode production
curl -X POST $BASE/mock/reset
curl -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T12:00:00Z"}'
curl -X POST $BASE/mock/mk6/mk6-A/provision -H "Content-Type: application/json" \
-d '{"sites": [{"sycode": "SYC1", "name": "Site1", "role": "thissite", "pmax_kw": 10000}]}'
# Basculer en production
curl -X POST $BASE/supervision/mode -H "Content-Type: application/json" \
-d '{"mode": "production"}'
# ❌ Toutes les écritures → 403
curl -v -X POST $BASE/api/v1/mk6/mk6-A/requests -H "Content-Type: application/json" \
-d '{"set": "stop", "site": "SYC1"}'
# → 403 "Écriture bloquée en mode production"
curl -v -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-01T13:00:00Z"}'
# → 403
curl -v -X POST $BASE/mock/reset
# → 403
# ✅ Lectures autorisées
curl $BASE/supervision/api/inventory
# → 200
curl $BASE/supervision/api/time
# → 200 {"source": "system"}
# ✅ Retour en mock
curl -X POST $BASE/supervision/mode -H "Content-Type: application/json" \
-d '{"mode": "mock"}'
# → écritures débloquées
Scénario 15 – Console technique (/debug/requests) (v1.0.0)
La console technique capture toutes les requêtes HTTP reçues par les instances MK6. Utile pour débugger les échanges GW→MK6 en recette sans sniffer le réseau.
BASE=http://localhost:9001
# ── Envoyer quelques requêtes pour remplir le log ──
curl -s $BASE/api/v1/mk6/mk6-A/monitoring > /dev/null
curl -s -X POST $BASE/api/v1/mk6/mk6-A/requests \
-H "Content-Type: application/json" \
-d '{"set": "Psetpoint", "site": "SYC1", "psetpoint": 5000,
"start_time": "2026-03-01T12:00:00Z", "end_time": "2026-03-01T18:00:00Z", "preset": "none"}'
# ── Consulter toutes les requêtes capturées ──
curl -s $BASE/api/v1/debug/requests | python3 -m json.tool
# → [
# {"id": 1, "ts": "2026-03-14T...", "method": "GET", "path": "/api/v1/mk6/mk6-A/monitoring",
# "source": "GW", "endpoint": "monitoring", "mk6_id": "mk6-A", ...},
# {"id": 2, "ts": "2026-03-14T...", "method": "POST", "path": "/api/v1/mk6/mk6-A/requests",
# "source": "GW", "endpoint": "requests", "mk6_id": "mk6-A", "req_body": {...}, ...}
# ]
# ── Polling incrémental (depuis le dernier ID reçu) ──
curl -s "$BASE/api/v1/debug/requests?since_id=1" | python3 -m json.tool
# → Retourne uniquement les entrées avec id > 1
# ── Vider le log ──
curl -s -X DELETE $BASE/api/v1/debug/requests
# → {"message": "Log vidé", "count": 0}
# ── Note : le log est automatiquement tronqué à 1000 entrées ──
# Les entrées les plus anciennes sont supprimées quand la limite est atteinte.
Scénario 16 – Cascade de configuration MK6 (v1.0.0)
Priorité : CLI
--config> ENVMOCK_MK6_CONFIG_PATH> default./mk6_targets.json.
# ── 1. Config par défaut ──
python mock_mk6.py --port 9001
# → Charge ./mk6_targets.json
# ── 2. Config par ENV ──
MOCK_MK6_CONFIG_PATH=/tmp/custom_mk6.json python mock_mk6.py --port 9001
# → Charge /tmp/custom_mk6.json
# ── 3. Config par CLI (prime sur ENV) ──
MOCK_MK6_CONFIG_PATH=/tmp/wrong.json \
python mock_mk6.py --port 9001 --config /tmp/custom_mk6.json
# → Charge /tmp/custom_mk6.json (CLI prime sur ENV)
# ── 4. Mode production par CLI ──
python mock_mk6.py --port 9001 --mode production
# → Démarre en mode production (écritures bloquées)
# ── 5. Mode par ENV ──
MOCK_MK6_MODE=production python mock_mk6.py --port 9001
# → Même résultat via variable d'environnement
Scénario 17 – Contrainte externe DEIE/SCADA (inject_external) (v1.0.0)
Simuler une contrainte externe (DEIE ou SCADA) sur un site pour tester la détection
EXTERNAL_CONSTRAINT_DETECTEDcôté Gateway. La contrainte est visible dans le monitoring et dans le dashboard de supervision.
BASE=http://localhost:9001
# ── Injecter une contrainte DEIE à 3 MW sur un site ──
curl -s -X POST $BASE/mock/mk6/MK6_ROU1.1/sites/8FV898VMwWX0/inject_external \
-H "Content-Type: application/json" \
-d '{"source": "DEIE", "value_kw": 3000}'
# → {"sycode": "8FV898VMwWX0", "external_constraint": {"source": "DEIE", "value_kw": 3000, "since": "..."}}
# ── Vérifier dans le monitoring du site ──
curl -s $BASE/api/v1/mk6/MK6_ROU1.1/monitoring/sites/8FV898VMwWX0 | python3 -m json.tool
# → "external_constraint": {"source": "DEIE", "value_kw": 3000, "since": "..."}
# ── Vérifier dans le state MK6 ──
curl -s $BASE/mock/mk6/MK6_ROU1.1/state | python3 -m json.tool
# → sites.8FV898VMwWX0.external_constraint: {"source": "DEIE", ...}
# ── Injecter une contrainte SCADA sur un autre site ──
curl -s -X POST $BASE/mock/mk6/MK6_ROU1.1/sites/8FV898VPwW20/inject_external \
-H "Content-Type: application/json" \
-d '{"source": "SCADA", "value_kw": 5000}'
# ── Supprimer la contrainte ──
curl -s -X DELETE $BASE/mock/mk6/MK6_ROU1.1/sites/8FV898VMwWX0/inject_external
# → {"sycode": "8FV898VMwWX0", "external_constraint": null}
# ── Contrainte avec fenêtre temporelle (optionnel) ──
# Active uniquement entre start_time et end_time. Avant ou après → invisible dans monitoring.
curl -s -X POST $BASE/mock/mk6/MK6_ROU1.1/sites/8FV898VMwWX0/inject_external \
-H "Content-Type: application/json" \
-d '{"source": "DEIE", "value_kw": 2000,
"start_time": "2026-03-14T10:00:00Z",
"end_time": "2026-03-14T14:00:00Z"}'
# → Active de 10h à 14h UTC. En dehors de cette fenêtre, monitoring retourne null.
# Utile pour simuler des scénarios DEIE avec l'horloge simulée :
curl -s -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-14T12:00:00Z"}'
curl -s $BASE/api/v1/mk6/MK6_ROU1.1/monitoring/sites/8FV898VMwWX0 \
| python3 -c "import json,sys; print(json.load(sys.stdin).get('external_constraint'))"
# → {"source": "DEIE", "value_kw": 2000, ...} (dans la fenêtre)
curl -s -X POST $BASE/mock/time -H "Content-Type: application/json" \
-d '{"now": "2026-03-14T15:00:00Z"}'
curl -s $BASE/api/v1/mk6/MK6_ROU1.1/monitoring/sites/8FV898VMwWX0 \
| python3 -c "import json,sys; print(json.load(sys.stdin).get('external_constraint'))"
# → None (hors fenêtre)
# ── Le monitoring confirme la suppression ──
curl -s $BASE/api/v1/mk6/MK6_ROU1.1/monitoring/sites/8FV898VMwWX0 | python3 -c "
import json, sys; d=json.load(sys.stdin); print('external:', d.get('external_constraint'))"
# → external: None
# Note : dans l'UI simulation (http://localhost:9000/simulation), la contrainte
# est visible dans le panneau MK6 de chaque site avec les contrôles DEIE/SCADA.
# Dans l'UI supervision (http://localhost:9001/supervision), un bandeau ambre
# affiche la contrainte active sous la consigne du site.