Sans injection reseau, PV_mesuree = min(ressource, conso + absorption batterie). La mesure est donc censuree a droite des que la batterie est pleine ou que le reseau alimente AC-IN. Mesure sur l'historique du 28/07 au 24/08 : 48 % des heures de jour sont censurees, 77 a 96 % a partir de 13 h. Un profil de production bati sur la mesure brute mesure en realite sa propre consommation. src/energy-balance/HA_SNAPSHOT_TEMPLATE.json.tpl : 26 champs, dont tous les signaux qu'aucun workflow ne lisait jusqu'ici — etats des trois MPPT, etat VE.Bus, alarme de surcharge onduleur, ESS BatteryLife, etat du bus DC, statut natif de la borne, prevision horaire, elevation du soleil. src/energy-balance/CENSORSHIP.js : qualifie chaque echantillon (censure et ses motifs, exploitabilite) et verifie en continu l'identite du bus DC PV + VE.Bus = batterie, qui boucle a 95 W pres — un capteur qui derive devient detectable. Ne decide rien. tests/energy-balance/ : 10 scenarios, dont un snapshot reel du 24/08 14:29 (soleil 52 degres, prevision 3462 W, production bridee a 1159 W, SOC 94 %) correctement classe censure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
141 lines
4.2 KiB
JavaScript
141 lines
4.2 KiB
JavaScript
// Scénarios du nœud CENSORSHIP.
|
|
// `fixture-live.json` est un snapshot RÉEL capturé le 24/08/2026 à 14:29 :
|
|
// soleil à 52°, prévision 3462 W pour l'heure suivante, production mesurée
|
|
// 1159 W, SOC 94 %, batterie à +2,3 A. C'est exactement le cas de censure —
|
|
// il ne manquait pas de soleil, il manquait un preneur.
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
const live = JSON.parse(fs.readFileSync(path.join(HERE, 'fixture-live.json'), 'utf8'));
|
|
|
|
// Base non censurée : plein soleil, batterie à mi-charge qui encaisse, hors réseau.
|
|
const libre = {
|
|
...live,
|
|
soc_percent: '62',
|
|
battery_current_a: '95',
|
|
battery_voltage_v: '51.2',
|
|
pv_power_w: '7400',
|
|
house_ac_power_w: '2500',
|
|
evcs_ac_power_w: '0',
|
|
vebus_dc_power_w: '-2537',
|
|
multiplus_state: 'bulk',
|
|
dc_battery_state: 'charging',
|
|
ess_batterylife_state: 'self_consumption',
|
|
acin1_available: '0',
|
|
grid_switch: 'off',
|
|
mppt_1_state: 'external_control',
|
|
mppt_2_state: 'external_control',
|
|
mppt_3_state: 'external_control',
|
|
sun_elevation: 52.1,
|
|
};
|
|
|
|
export const scenarios = [
|
|
{
|
|
id: 'live-censure-batterie-pleine',
|
|
label: 'Snapshot réel 24/08 14:29 — soleil 52°, SOC 94 %, production bridée à 1159 W',
|
|
snapshot: live,
|
|
expect: {
|
|
pvCensored: true,
|
|
reasons: 'batterie_pleine_sans_puits',
|
|
sampleExploitable: false,
|
|
healthOk: true,
|
|
},
|
|
},
|
|
{
|
|
id: 'echantillon-exploitable',
|
|
label: 'Plein soleil, batterie à 62 % qui encaisse 95 A, hors réseau → mesure exploitable',
|
|
snapshot: libre,
|
|
expect: {
|
|
pvCensored: false,
|
|
reasons: '',
|
|
sampleExploitable: true,
|
|
healthOk: true,
|
|
},
|
|
},
|
|
{
|
|
id: 'censure-reseau-present',
|
|
label: 'AC-IN présent : le réseau alimente les charges, le MPPT est bridé',
|
|
snapshot: { ...libre, acin1_available: '1' },
|
|
expect: {
|
|
pvCensored: true,
|
|
reasons: 'reseau_present',
|
|
sampleExploitable: false,
|
|
},
|
|
},
|
|
{
|
|
id: 'censure-fin-de-charge',
|
|
label: 'VE.Bus en absorption : le courant de charge est volontairement réduit',
|
|
snapshot: { ...libre, multiplus_state: 'absorption' },
|
|
expect: {
|
|
pvCensored: true,
|
|
reasons: 'fin_de_charge_absorption',
|
|
sampleExploitable: false,
|
|
},
|
|
},
|
|
{
|
|
id: 'censure-mppt-arretes',
|
|
label: 'Deux trackers à l\'arrêt en plein jour : écrêtage direct du MPPT',
|
|
snapshot: { ...libre, mppt_1_state: 'off', mppt_3_state: 'off' },
|
|
expect: {
|
|
pvCensored: true,
|
|
reasons: 'mppt_arretes_2_sur_3',
|
|
sampleExploitable: false,
|
|
},
|
|
},
|
|
{
|
|
id: 'censure-bus-dc-au-repos',
|
|
label: 'Bus DC au repos alors que la production tourne : plus aucun puits',
|
|
snapshot: { ...libre, dc_battery_state: 'idle', battery_current_a: '0.2' },
|
|
expect: {
|
|
pvCensored: true,
|
|
reasons: 'bus_dc_au_repos_en_plein_jour',
|
|
sampleExploitable: false,
|
|
},
|
|
},
|
|
{
|
|
id: 'nuit-pas-de-censure-mais-inexploitable',
|
|
label: 'Nuit : rien n\'est censuré, mais la mesure ne dit rien de la ressource',
|
|
snapshot: {
|
|
...libre,
|
|
sun_elevation: -12,
|
|
pv_power_w: '0',
|
|
battery_current_a: '-14',
|
|
vebus_dc_power_w: '-716',
|
|
dc_battery_state: 'discharging',
|
|
multiplus_state: 'inverting',
|
|
},
|
|
expect: {
|
|
pvCensored: false,
|
|
sampleExploitable: false,
|
|
},
|
|
},
|
|
{
|
|
id: 'alerte-surcharge-multiplus',
|
|
label: 'Alarme de surcharge onduleur — jusqu\'ici jamais lue par aucun workflow',
|
|
snapshot: { ...libre, multiplus_overload_alarm: 'warning' },
|
|
expect: {
|
|
alerts: 'multiplus_overload_warning',
|
|
},
|
|
},
|
|
{
|
|
id: 'alerte-batterylife-limite-soc',
|
|
label: 'ESS BatteryLife impose sa propre limite de SOC, à l\'insu du contrôleur',
|
|
snapshot: { ...libre, ess_batterylife_state: 'soc_below_battery_life_dynamic_soc_limit' },
|
|
expect: {
|
|
alerts: 'batterylife_limite_le_soc',
|
|
},
|
|
},
|
|
{
|
|
id: 'sante-capteur-incoherent',
|
|
label: 'Identité du bus DC rompue (PV + VE.Bus != batterie) → un capteur ment',
|
|
snapshot: { ...libre, vebus_dc_power_w: '-200' },
|
|
expect: {
|
|
healthOk: false,
|
|
alerts: 'incoherence_mesures_bus_dc',
|
|
},
|
|
},
|
|
];
|