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:
74
tools/patch-20260808-c-unplug-cut.mjs
Normal file
74
tools/patch-20260808-c-unplug-cut.mjs
Normal 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)`);
|
||||
Reference in New Issue
Block a user