MK6 v2.2.0

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 > ENV MOCK_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_DETECTED cô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.