Fixes A/B/C/D incident rebond EDF nuit 07-08/08 + GRAFCET doc

- A: état collant night_recovery_done_idle + bande cible−2 (anti-rebond takeover)
- B: snapshot relit night_session_start_soc/at (fin des réécritures 30s)
- C: hystérésis grid — turn_off_vehicle_unplugged (coupure immédiate, bypass min_on)
- D: evcs_status_raw==3 (charged) = fin de charge immédiate
- golden 19→23 scénarios (2 attendus mis à jour, 4 nouveaux)
- MANUEL_COMPLET §11.6 GRAFCET cycle nuit, HANDOFF §17, vision-notes incident
This commit is contained in:
Tallagrand Thierry
2026-08-08 14:20:21 +02:00
parent b20b3b19fc
commit 9bc4af0ea1
11 changed files with 874 additions and 15 deletions

View File

@@ -383,6 +383,7 @@ const batteryAssistAllowed = !!nightPrev.batteryAssistAllowed;
const nightGridTakeoverNeeded = !!nightPrev.nightGridTakeoverNeeded;
const nightTakeoverLatched = !!nightPrev.nightTakeoverLatched;
const vehicleChargeCompleted = !!nightPrev.vehicleChargeCompleted;
const nightRecoveryDone = !!nightPrev.nightRecoveryDone;
const nightPreferredSourceMode = nightPrev.nightPreferredSourceMode || 'blocked';
// =====================================================
@@ -840,6 +841,7 @@ return [{
nightGridTakeoverNeeded,
nightTakeoverLatched,
vehicleChargeCompleted,
nightRecoveryDone,
nightPreferredSourceMode,
},

View File

@@ -1064,11 +1064,18 @@ else {
// P8 : véhicule plein en session nuit → veille auto-entretenue.
// Le state est relu au cycle suivant (prev_surplus_state) : il maintient
// vehicleChargeCompleted jusqu'au débranchement ou à la fin de fenêtre.
// Fix A (08/08/2026) : une fois la recovery batterie terminée (SOC ≥ cible),
// l'état devient 'night_recovery_done_idle' (collant) → takeover relâché
// définitivement, plus de rebond EDF autour de la cible.
else if (context.profile === 'night_hc' && nightEvcs?.vehicleChargeCompleted) {
setOff({
stateName: 'night_session_done_idle',
stateName: nightEvcs?.nightRecoveryDone
? 'night_recovery_done_idle'
: 'night_session_done_idle',
decisionName: 'hold',
reasonText: 'Charge véhicule terminée : veille nuit, réseau relâché (recharge batterie si sous cible)',
reasonText: nightEvcs?.nightRecoveryDone
? 'Charge véhicule terminée + batterie à la cible : veille nuit, réseau relâché'
: 'Charge véhicule terminée : veille nuit, réseau relâché (recharge batterie si sous cible)',
dynamic: false,
mustStopFlag: false,
blockReason: 'none',

View File

@@ -116,6 +116,8 @@
"allow_grid_hc_night": states("input_boolean.victron_evcs_allow_grid_hc_night"),
"night_takeover_prev": states("input_boolean.victron_evcs_night_grid_takeover_needed"),
"prev_surplus_state": states("input_text.victron_evcs_surplus_state"),
"evcs_night_session_start_soc": states("input_text.victron_evcs_night_session_start_soc"),
"evcs_night_session_start_at": states("input_text.victron_evcs_night_session_start_at"),
"allow_grid_hc_midday": states("input_boolean.victron_evcs_allow_grid_hc_midday"),
"probe_enabled": states("input_boolean.victron_evcs_probe_enabled"),

View File

@@ -317,11 +317,42 @@ const nightTakeoverPrev =
// → DECISION émet night_session_done_idle → le flag s'auto-entretient jusqu'au
// débranchement (state vehicle_not_connected) ou à la fin de la fenêtre nuit.
const prevSurplusState = (input.prev_surplus_state ?? '').toString().trim().toLowerCase();
// Fix D (08/08/2026) : le statut natif EVCS "charged" (3) vaut aussi fin de
// charge, sans attendre le VehicleDemandFilter (désarmé si la session a été
// stoppée par une garde, ex battery_current_hard_stop → détection tardive
// observée la nuit du 07-08/08).
const evcsStatusCharged = Number.parseFloat(parsed.evcsStatusRaw) === 3;
const vehicleChargeCompleted =
nightSessionActive &&
(
prevSurplusState === 'vehicle_full_or_paused' ||
prevSurplusState === 'night_session_done_idle'
prevSurplusState === 'night_session_done_idle' ||
prevSurplusState === 'night_recovery_done_idle' ||
evcsStatusCharged
);
// Fix A (08/08/2026) : recovery batterie post-charge = UNE SEULE passe.
// Sans flag collant, SOC==cible est un point dégénéré (engage soc<=cible,
// release soc>cible, l'ACM recharge pile à la cible) → rebond EDF ON/OFF
// période ~10 min observé la nuit du 07-08/08. Une fois la cible atteinte,
// l'état 'night_recovery_done_idle' (auto-entretenu via prev_surplus_state)
// coupe le takeover. Ré-engagement uniquement sous cible − 2 pts (bande),
// hard floor et fenêtre/débranchement restent prioritaires.
// ⚠ 'night_recovery_done_idle' ne doit matcher AUCUN pattern no-demand de
// l'hystérésis (full_or_paused / not_requesting_power / charge_complete /
// no_demand) — même contrainte que 'night_session_done_idle'.
const NIGHT_RECOVERY_REARM_BAND_PCT = 2;
const nightRecoveryDonePrev = prevSurplusState === 'night_recovery_done_idle';
const nightRecoveryDone =
vehicleChargeCompleted &&
(
parsed.soc >= batteryMorningTargetPct ||
(
nightRecoveryDonePrev &&
parsed.soc > batteryMorningTargetPct - NIGHT_RECOVERY_REARM_BAND_PCT
)
);
// P8 : correction du fix 04/08 (logique inversée : "|| vehicleChargeCompleted"
@@ -350,9 +381,17 @@ const nightGridTakeoverNeeded =
(
nightGridHcPreferred ||
batteryBelowHardFloor ||
batteryAtOrBelowGridTakeoverTrigger ||
batteryRecoveryUrgent ||
nightTakeoverLatched
(
// Fix A : après fin de charge, le seul motif réseau restant est la
// recovery batterie, et elle ne se ré-engage pas une fois done.
vehicleChargeCompleted
? !nightRecoveryDone
: (
batteryAtOrBelowGridTakeoverTrigger ||
batteryRecoveryUrgent ||
nightTakeoverLatched
)
)
);
const nightPreferredSourceMode =
@@ -679,6 +718,7 @@ return [{
nightTakeoverLatched,
nightGridHcPreferred,
vehicleChargeCompleted,
nightRecoveryDone,
nightPreferredSourceMode,
}
}