// 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', }, }, ];