Capteur de charge : origine explicite par libelle (incident 25/09, batterie publiee en solaire)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uJ47NxgfpGnuuMr3f2n7Z
This commit is contained in:
18
HANDOFF.md
18
HANDOFF.md
@@ -602,5 +602,21 @@ deja ailleurs (controleur EVCS), l'agent la RELAIE, il ne la recalcule jamais.
|
||||
Limite connue : si le service d'historisation meurt, les quatre capteurs se
|
||||
figent ; l'agent verifie `sensor.evcs_historisation_heartbeat`.
|
||||
|
||||
### Incident du matin 25/09 — le capteur de charge disait « solaire » la nuit
|
||||
Le proprio : « l'agent a delire ce matin sur la charge voiture ». Cause = MON
|
||||
capteur, pas l'agent. `chargeSolaire` classait toute source qui ne commence pas
|
||||
par « Reseau » comme solaire : de **01:25 a 06:00**, `sensor.evcs_charge_solaire`
|
||||
a publie `en_charge_solaire` alors que la JEEP chargeait sur la BATTERIE
|
||||
(« Assistance batterie nuit », puis reseau force de 06:02 a 06:42 ; session
|
||||
7,9 kWh dont 2,2 reseau). L'agent a relaye fidelement une fausse verite.
|
||||
Correctif : correspondance EXPLICITE libelle par libelle de `sourceModeLabel`
|
||||
(HA_SERVICE_CALLS_OUTPUT.js), nouveaux etats `en_charge_batterie`,
|
||||
`en_charge_sonde`, et `en_charge` sans origine pour un libelle inconnu. Test
|
||||
garde-fou qui lit la table DANS le code du controleur : un libelle ajoute la-bas
|
||||
fait tomber les tests (mutation verifiee). Tests 32/32.
|
||||
⚠️ Lecon : un capteur-verdict deplace la fragilite de la fiche vers le code ; le
|
||||
code doit donc etre teste sur TOUT le domaine d'entree (ici les sept libelles),
|
||||
pas seulement sur les cas du banc.
|
||||
|
||||
---
|
||||
*Document mis a jour le : 25/09/2026 — agent vocal : la partie energie relaie des capteurs-verdict (banc 15/15), cadrage multi-agents*
|
||||
*Document mis a jour le : 25/09/2026 — capteur de charge corrige (origine explicite par libelle), incident du matin*
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Tests des diagnostics publies pour l'agent vocal (tools/diagnostics-agent.mjs).
|
||||
// Les cas PV reprennent les gammes du banc OpenClaw (HANDOFF §77) : G3, G4, G5, G7.
|
||||
// node tests/diagnostics-agent/run.mjs
|
||||
import { diagnosticPv, etatCellules, productionJour, chargeSolaire, MPPT_MODES } from '../../tools/diagnostics-agent.mjs';
|
||||
import fs from 'node:fs';
|
||||
import { diagnosticPv, etatCellules, productionJour, chargeSolaire, MPPT_MODES, ORIGINE_PAR_SOURCE } from '../../tools/diagnostics-agent.mjs';
|
||||
|
||||
const LIM = 'voltage_current_limited', ACT = 'mppt_active';
|
||||
function st({ pv, conso, reseau, soc = 80, courant = 0, edf = 'off', modes }) {
|
||||
@@ -114,6 +115,14 @@ const charge = [
|
||||
(r) => r.etat === 'en_attente' && r.attributs.raison_controleur === 'Excedent insuffisant' && r.attributs.excedent_w === -1854],
|
||||
['en charge sur source solaire -> en_charge_solaire', borne({ p: 3600, source: 'Solaire' }), (r) => r.etat === 'en_charge_solaire'],
|
||||
['en charge source reseau -> en_charge_reseau', borne({ p: 3600, source: 'Réseau HC autorisé' }), (r) => r.etat === 'en_charge_reseau'],
|
||||
// Incident du 25/09 01:25-06:00 : JEEP sur batterie la nuit, publie « solaire ».
|
||||
['INCIDENT 25/09 : assistance batterie nuit -> en_charge_batterie, JAMAIS solaire',
|
||||
borne({ p: 1198, source: 'Assistance batterie nuit', pv: 0, conso: 900, modes: 'off' }),
|
||||
(r) => r.etat === 'en_charge_batterie' && !/solaire/i.test(r.attributs.explication.split('(')[0])],
|
||||
['sonde hybride -> en_charge_sonde', borne({ p: 2300, source: 'Probe hybride' }), (r) => r.etat === 'en_charge_sonde'],
|
||||
['potentiel solaire -> en_charge_solaire', borne({ p: 2300, source: 'Potentiel solaire' }), (r) => r.etat === 'en_charge_solaire'],
|
||||
['reseau force -> en_charge_reseau', borne({ p: 3600, source: 'Réseau forcé' }), (r) => r.etat === 'en_charge_reseau'],
|
||||
['libelle inconnu -> en_charge sans origine devinee', borne({ p: 3600, source: 'nouveau_mode' }), (r) => r.etat === 'en_charge'],
|
||||
['non branchee -> non_branchee', borne({ branche: 'off' }), (r) => r.etat === 'non_branchee'],
|
||||
['G7 excedent positif et bride, mais controleur bloque -> en_attente (on RELAIE, on ne rejuge pas)',
|
||||
borne({ pv: 5000, conso: 1500, modes: LIM, source: 'Bloqué', raison: 'Vehicule plein' }),
|
||||
@@ -127,6 +136,16 @@ for (const [nom, etats, test] of charge) {
|
||||
console.log(`${ok ? 'OK ' : 'KO '} ${nom}`);
|
||||
}
|
||||
|
||||
const n = cas.length + cel.length + prod.length + charge.length;
|
||||
// Chaque libelle publie par le controleur doit avoir une origine explicite :
|
||||
// lu dans SON code, pour qu'un libelle ajoute la-bas fasse tomber ce test.
|
||||
const srcCtrl = fs.readFileSync(new URL('../../src/nodes/HA_SERVICE_CALLS_OUTPUT.js', import.meta.url), 'utf8');
|
||||
const bloc = srcCtrl.slice(srcCtrl.indexOf('const sourceModeLabel ='), srcCtrl.indexOf('context.sourceMode;', srcCtrl.indexOf('const sourceModeLabel =')));
|
||||
const libelles = [...bloc.matchAll(/\? '([^']+)'/g)].map((m) => m[1]).filter((l) => l !== 'Bloqué');
|
||||
const nonCouverts = libelles.filter((l) => !ORIGINE_PAR_SOURCE[l]);
|
||||
const okLib = libelles.length >= 6 && nonCouverts.length === 0;
|
||||
if (!okLib) ko++;
|
||||
console.log(`${okLib ? 'OK ' : 'KO '} les ${libelles.length} libelles de source du controleur ont une origine${okLib ? '' : ` (manquent : ${nonCouverts.join(', ') || 'table introuvable'})`}`);
|
||||
|
||||
const n = cas.length + cel.length + prod.length + charge.length + 1;
|
||||
console.log(`\n${n - ko}/${n}`);
|
||||
process.exit(ko ? 1 : 0);
|
||||
|
||||
@@ -208,6 +208,29 @@ export function productionJour(st, maintenant = new Date()) {
|
||||
// l'expliquer (excedent, chargeurs brides), sans decider.
|
||||
export const SEUIL_CHARGE_W = 300; // meme seuil que les sessions de charge
|
||||
|
||||
// ⚠️ Correspondance EXPLICITE, libelle par libelle, de la table
|
||||
// `sourceModeLabel` de src/nodes/HA_SERVICE_CALLS_OUTPUT.js. Defaut du
|
||||
// 25/09 : la premiere version classait « tout ce qui n'est pas Reseau » comme
|
||||
// solaire, et a publie `en_charge_solaire` de 01:25 a 06:00 pendant que la JEEP
|
||||
// chargeait sur la BATTERIE en pleine nuit (« Assistance batterie nuit »). L'agent
|
||||
// l'a repete fidelement. Un libelle inconnu donne `en_charge` sans origine :
|
||||
// on ne devine jamais d'ou vient l'energie.
|
||||
export const ORIGINE_PAR_SOURCE = {
|
||||
'Solaire': 'en_charge_solaire',
|
||||
'Potentiel solaire': 'en_charge_solaire',
|
||||
'Probe hybride': 'en_charge_sonde',
|
||||
'Assistance batterie nuit': 'en_charge_batterie',
|
||||
'Réseau HC autorisé': 'en_charge_reseau',
|
||||
'Réseau forcé': 'en_charge_reseau',
|
||||
};
|
||||
const PHRASE_ORIGINE = {
|
||||
en_charge_solaire: 'sur le solaire',
|
||||
en_charge_sonde: 'en essai court pour provoquer la production solaire',
|
||||
en_charge_batterie: 'sur la batterie de la maison (assistance de nuit)',
|
||||
en_charge_reseau: 'sur le reseau EDF',
|
||||
en_charge: 'depuis une source que le controleur ne precise pas',
|
||||
};
|
||||
|
||||
export function chargeSolaire(st) {
|
||||
const txt = (id) => { const s = st.get(id)?.state; return s === undefined || s === 'unavailable' || s === 'unknown' ? null : String(s); };
|
||||
const branche = txt('binary_sensor.victron_evcs_vehicle_connected');
|
||||
@@ -235,9 +258,10 @@ export function chargeSolaire(st) {
|
||||
return r('non_branchee', 'Aucun vehicule branche. Une fois branche, le controleur decidera lui-meme.');
|
||||
}
|
||||
if (puissance > SEUIL_CHARGE_W) {
|
||||
const reseau = /^R[ée]seau/.test(source);
|
||||
return r(reseau ? 'en_charge_reseau' : 'en_charge_solaire',
|
||||
`La voiture charge (${Math.round(puissance)} W), source decidee par le controleur : ${source}.`);
|
||||
const etat = ORIGINE_PAR_SOURCE[source] || 'en_charge';
|
||||
return r(etat,
|
||||
`La voiture charge (${Math.round(puissance)} W) ${PHRASE_ORIGINE[etat]} ` +
|
||||
`(source decidee par le controleur : ${source}).`);
|
||||
}
|
||||
return r('en_attente',
|
||||
`Voiture branchee mais pas en charge. Le controleur dit : ${raison || 'sans raison publiee'} (source : ${source}).`);
|
||||
|
||||
Reference in New Issue
Block a user