// 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);