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

@@ -0,0 +1,74 @@
#!/usr/bin/env node
// Patch 08/08/2026 — FIX C : coupure EDF immédiate au débranchement du véhicule.
// Incident nuit 07-08/08 : après débranchement, EDF restait ON 25 min
// (hold_on_min_runtime) → l'utilisateur a dû couper à la main.
// Cible : workflows/victron-grid-hysteresis-v1.json (node "Compute Grid Hysteresis").
// Rejouable : détecte si déjà appliqué.
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const FILE = path.join(ROOT, 'workflows/victron-grid-hysteresis-v1.json');
const wf = JSON.parse(fs.readFileSync(FILE, 'utf8'));
const node = wf.nodes.find(n => n.name === 'Compute Grid Hysteresis');
if (!node) throw new Error('Node "Compute Grid Hysteresis" introuvable');
let code = node.parameters.jsCode;
if (code.includes('turn_off_vehicle_unplugged')) {
console.log('Déjà appliqué — rien à faire.');
process.exit(0);
}
const anchor = ` reason.push('Force grid + voiture pleine : coupure immediate du reseau');
forcedCut = true;
}
`;
const insertion = ` reason.push('Force grid + voiture pleine : coupure immediate du reseau');
forcedCut = true;
}
// ======================================================
// FIX C (08/08/2026): VEHICULE DEBRANCHE -> COUPURE IMMEDIATE
// Incident nuit 07-08/08 : apres debranchement du vehicule, EDF restait
// maintenu ON 25 min (hold_on_min_runtime) alors que plus rien ne le
// justifiait (SOC >= seuil OFF). Ici : coupure immediate, bypass min_on.
// ======================================================
if (
!forcedCut &&
latchIsOn &&
gridIsOn &&
!evcsGridOverride &&
evcsSurplusState === 'vehicle_not_connected' &&
soc >= gridSocOff
) {
desiredGrid = 'off';
desiredLatch = 'off';
state = 'turn_off_vehicle_unplugged';
reason.push('Vehicule debranche + SOC >= seuil OFF : coupure immediate du reseau (bypass min_on)');
forcedCut = true;
}
`;
if (!code.includes(anchor)) throw new Error('Ancre force_grid/forcedCut introuvable — code inattendu');
code = code.replace(anchor, insertion);
// Notification dédiée (facultative mais utile) : réutilise le bloc turn_off_recovered.
const notifAnchor = `} else if (state === 'turn_off_recovered') {`;
const notifInsertion = `} else if (state === 'turn_off_vehicle_unplugged') {
notifyTitle = '✅ EDF coupé (véhicule débranché)';
notifyMessage = \`Véhicule débranché et SOC \${round1(soc)}% >= \${gridSocOff}%. Coupure immédiate du réseau.\`;
notifyWanted = true;
} else if (state === 'turn_off_recovered') {`;
if (!code.includes(notifAnchor)) throw new Error('Ancre notification turn_off_recovered introuvable');
code = code.replace(notifAnchor, notifInsertion);
node.parameters.jsCode = code;
fs.writeFileSync(FILE, JSON.stringify(wf, null, 2) + '\n');
console.log('Patch appliqué →', FILE, `(jsCode ${code.length} chars)`);