Diagnostics PV et cellule calcules cote service pour l'agent vocal (G5/G13b)

La fiche OpenClaw basculait G5 de 7/7 a 1/7 sur un ajout sans rapport :
les deux jugements sortent du modele. sensor.evcs_pv_diagnostic (import
teste avant bridage) et sensor.evcs_cellules (perime => aucune valeur
visible), publies meme quand le cycle echoue. Tests 15/15 + mutations.

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:
Tallagrand Thierry
2026-09-25 00:55:17 +02:00
parent d64b634a66
commit b9142aac54
4 changed files with 293 additions and 2 deletions

View File

@@ -0,0 +1,69 @@
// 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, MPPT_MODES } 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 }) {
const m = new Map();
const put = (id, v) => m.set(id, { entity_id: id, state: v === null ? 'unavailable' : String(v) });
put('sensor.victron_pv_power_system', pv);
put('sensor.victron_consumption_power_l1', conso);
put('sensor.victron_grid_power_l1', reseau);
put('sensor.victron_battery_soc', soc);
put('sensor.victron_battery_current', courant);
put('switch.edf_general_ok_switch', edf);
const liste = Array.isArray(modes) ? modes : MPPT_MODES.map(() => modes);
MPPT_MODES.forEach((id, i) => put(id, liste[i]));
return m;
}
const cas = [
['G3 batterie pleine, 7 brides, import nul -> bridage normal',
st({ pv: 1570, conso: 1500, reseau: 0, soc: 97, courant: 1, modes: LIM }), 'bridage_normal'],
['G4 vraie baisse : 7 a fond, SOC 69, +25 A -> ressource limitee',
st({ pv: 1600, conso: 400, reseau: 0, soc: 69, courant: 25, modes: ACT }), 'ressource_limitee'],
['G5 brides MAIS import ~ conso -> substitution',
st({ pv: 300, conso: 1200, reseau: 1150, soc: 97, courant: 0, edf: 'on', modes: LIM }), 'substitution_edf'],
['G7 un seul bride, import nul -> bridage normal (ressource dispo)',
st({ pv: 5200, conso: 2000, reseau: 0, soc: 92, courant: 60, modes: [LIM, ACT, ACT, ACT, ACT, ACT, ACT] }), 'bridage_normal'],
['bruit du compteur (60 W) contacteur ferme : pas une substitution',
st({ pv: 1500, conso: 1400, reseau: 60, soc: 96, edf: 'on', modes: LIM }), 'bridage_normal'],
['un seul bride suffit a signer la substitution si le reseau importe',
st({ pv: 3000, conso: 3500, reseau: 900, edf: 'on', modes: [LIM, ACT, ACT, ACT, ACT, ACT, ACT] }), 'substitution_edf'],
['tous a fond + import -> reseau en appoint, pas une anomalie',
st({ pv: 2000, conso: 3500, reseau: 1500, edf: 'on', modes: ACT }), 'reseau_en_appoint'],
['nuit : PV 0, tous off -> pas de production',
st({ pv: 0, conso: 600, reseau: 0, modes: 'off' }), 'pas_de_production'],
['nuit + reseau ferme : toujours pas de substitution',
st({ pv: 0, conso: 600, reseau: 600, edf: 'on', modes: 'off' }), 'pas_de_production'],
['reseau indisponible -> indetermine (ne pas conclure)',
st({ pv: 1500, conso: 1400, reseau: null, modes: LIM }), 'indetermine'],
['modes illisibles en journee -> indetermine',
st({ pv: 1500, conso: 1400, reseau: 0, modes: 'unavailable' }), 'indetermine'],
];
let ko = 0;
for (const [nom, etats, attendu] of cas) {
const r = diagnosticPv(etats);
const ok = r.etat === attendu;
if (!ok) ko++;
console.log(`${ok ? 'OK ' : 'KO '} ${nom}${ok ? '' : ` (obtenu ${r.etat})`}`);
}
const cel = [
['releve frais -> ecart et module publies', { spread_max: 21, weakest: 'K220803C30160323', age_min: 1 },
(r) => r.etat === 21 && r.attributs.module_le_plus_sensible === 'K220803C30160323'],
['releve a 30 min (limite) -> encore frais', { spread_max: 5, weakest: 'Y1', age_min: 30 }, (r) => r.etat === 5],
['G13b releve a 42 min -> perime, AUCUNE valeur visible', { spread_max: 21, weakest: 'K220803C30160323', age_min: 42 },
(r) => r.etat === 'perime' && !('module_le_plus_sensible' in r.attributs)
&& !JSON.stringify(r).includes('21,') && !JSON.stringify(r).includes('K2208')],
['table vide -> muet', { spread_max: null, weakest: null, age_min: null }, (r) => r.etat === 'muet'],
];
for (const [nom, row, test] of cel) {
const ok = test(etatCellules(row));
if (!ok) ko++;
console.log(`${ok ? 'OK ' : 'KO '} ${nom}`);
}
console.log(`\n${cas.length + cel.length - ko}/${cas.length + cel.length}`);
process.exit(ko ? 1 : 0);