Chantier etape 1 : detection de la censure de la production PV
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
This commit is contained in:
@@ -34,6 +34,20 @@ Heure de croisement PV/conso · énergie à couvrir d'ici là · niveau de batte
|
||||
requis (remplace `grid_soc_off` fixe) · marge disponible pour le véhicule ·
|
||||
drapeau « production actuellement bridée » · drapeau de santé des mesures.
|
||||
|
||||
## Avancement
|
||||
|
||||
- [x] **Étape 1 — détection de la censure** (24/08). `src/energy-balance/` :
|
||||
snapshot HA de 26 champs incluant les signaux jamais lus (états MPPT, état
|
||||
VE.Bus, alarme de surcharge, ESS BatteryLife, état du bus DC, statut natif de
|
||||
la borne, prévision horaire) et nœud `CENSORSHIP.js` qui qualifie chaque
|
||||
échantillon. Suite `tests/energy-balance/` : 10 scénarios, dont un snapshot
|
||||
réel du 24/08 14:29 correctement classé censuré. Mesure sur l'historique :
|
||||
**48 % des heures de jour sont censurées, 77 à 96 % à partir de 13 h.**
|
||||
- [ ] Étape 2 — profil de consommation mesuré (non censuré).
|
||||
- [ ] Étape 3 — estimateur de ressource PV = prévision recalée sur les heures
|
||||
exploitables de l'étape 1.
|
||||
- [ ] Étape 4 — bilan et ses sorties.
|
||||
|
||||
## Backlog
|
||||
|
||||
- [ ] **Doublon `unique_id: victron_vebus_dc_power`** — déclaré deux fois, avec
|
||||
|
||||
205
src/energy-balance/CENSORSHIP.js
Normal file
205
src/energy-balance/CENSORSHIP.js
Normal file
@@ -0,0 +1,205 @@
|
||||
// EVCS Energy Balance — CENSORSHIP
|
||||
// =====================================================================
|
||||
// Rôle : dire si la production PV mesurée à cet instant représente la
|
||||
// RESSOURCE solaire disponible, ou si elle est CENSURÉE.
|
||||
//
|
||||
// L'installation est un ESS Victron sans injection réseau. Le MPPT ne produit
|
||||
// que ce que le système peut absorber immédiatement :
|
||||
//
|
||||
// PV_mesurée = min( ressource réelle , conso maison + absorption batterie )
|
||||
//
|
||||
// Dès que la batterie est pleine ou que le réseau alimente AC-IN, la mesure est
|
||||
// une observation censurée à droite : elle dit « on n'avait pas besoin de plus »,
|
||||
// pas « il n'y avait pas plus ». Sur l'historique du 28/07 au 24/08, 48 % des
|
||||
// heures de jour sont censurées — 77 à 96 % à partir de 13 h. Bâtir un profil de
|
||||
// production sur la mesure brute revient donc à mesurer sa propre consommation.
|
||||
//
|
||||
// Ce nœud ne décide RIEN. Il qualifie l'échantillon, pour que l'estimation de
|
||||
// ressource ne soit recalée que sur les heures exploitables.
|
||||
//
|
||||
// Sortie : censorship { pvCensored, reasons[], sampleExploitable, ... }
|
||||
// health { residualW, ok } — contrôle de vraisemblance du bus DC.
|
||||
// =====================================================================
|
||||
|
||||
function num(v, fallback = NaN) {
|
||||
const n = Number.parseFloat(v);
|
||||
return Number.isFinite(n) ? n : fallback;
|
||||
}
|
||||
|
||||
function low(v) {
|
||||
return (v ?? '').toString().trim().toLowerCase();
|
||||
}
|
||||
|
||||
function round1(v) {
|
||||
return Number.isFinite(v) ? Math.round(v * 10) / 10 : null;
|
||||
}
|
||||
|
||||
const input = $input.first().json;
|
||||
|
||||
const CFG = {
|
||||
// Le soleil doit être assez haut pour qu'une mesure de production ait un sens.
|
||||
SUN_ELEVATION_MIN_DEG: 10,
|
||||
// En dessous, on est dans le bruit (aube, crépuscule, couverture totale).
|
||||
PV_MEANINGFUL_MIN_W: 200,
|
||||
// SOC à partir duquel la batterie ne peut plus servir de puits significatif.
|
||||
SOC_NEAR_FULL_PCT: 90,
|
||||
// Puissance de charge batterie en dessous de laquelle, batterie quasi pleine,
|
||||
// on considère qu'il n'y a plus de preneur pour le surplus.
|
||||
BATTERY_SINK_MIN_W: 500,
|
||||
// États VE.Bus de fin de charge : le courant est volontairement réduit.
|
||||
MULTIPLUS_TAPER_STATES: ['absorption', 'float', 'storage'],
|
||||
// Tolérance du contrôle de vraisemblance PV + VE.Bus = batterie.
|
||||
HEALTH_ABS_TOLERANCE_W: 150,
|
||||
HEALTH_REL_TOLERANCE: 0.15,
|
||||
};
|
||||
|
||||
const pvPowerW = num(input.pv_power_w, 0);
|
||||
const housePowerW = num(input.house_ac_power_w, 0);
|
||||
const evcsPowerW = num(input.evcs_ac_power_w, 0);
|
||||
const socPct = num(input.soc_percent, NaN);
|
||||
const batteryCurrentA = num(input.battery_current_a, NaN);
|
||||
const batteryVoltageV = num(input.battery_voltage_v, NaN);
|
||||
const vebusDcPowerW = num(input.vebus_dc_power_w, NaN);
|
||||
const sunElevationDeg = num(input.sun_elevation, NaN);
|
||||
const forecastNextHourW = num(input.forecast_next_hour_w, NaN);
|
||||
|
||||
const batteryPowerW =
|
||||
Number.isFinite(batteryCurrentA) && Number.isFinite(batteryVoltageV)
|
||||
? batteryCurrentA * batteryVoltageV
|
||||
: NaN;
|
||||
|
||||
const multiplusState = low(input.multiplus_state);
|
||||
const systemState = low(input.system_state);
|
||||
const essBatteryLifeState = low(input.ess_batterylife_state);
|
||||
const dcBatteryState = low(input.dc_battery_state);
|
||||
const overloadAlarm = low(input.multiplus_overload_alarm);
|
||||
const evcsNativeStatus = low(input.evcs_native_status);
|
||||
|
||||
const mpptStates = [input.mppt_1_state, input.mppt_2_state, input.mppt_3_state]
|
||||
.map(low)
|
||||
.filter(s => s !== '' && s !== 'unavailable' && s !== 'unknown');
|
||||
|
||||
// AC-IN physiquement présent : c'est lui qui compte, pas la position du switch
|
||||
// (le switch peut être ON alors que le disjoncteur est coupé — incident 08/07).
|
||||
const gridPresent =
|
||||
num(input.acin1_available, 0) === 1 || low(input.grid_switch) === 'on';
|
||||
|
||||
const sunUp =
|
||||
!Number.isFinite(sunElevationDeg) || sunElevationDeg >= CFG.SUN_ELEVATION_MIN_DEG;
|
||||
|
||||
// =====================================================================
|
||||
// CENSURE — chaque motif s'appuie sur un signal réellement lu, pas sur un seuil
|
||||
// supposé. Ils sont cumulables : on garde la liste complète pour le diagnostic.
|
||||
// =====================================================================
|
||||
const reasons = [];
|
||||
|
||||
if (gridPresent) {
|
||||
reasons.push('reseau_present');
|
||||
}
|
||||
|
||||
if (CFG.MULTIPLUS_TAPER_STATES.includes(multiplusState)) {
|
||||
reasons.push(`fin_de_charge_${multiplusState}`);
|
||||
}
|
||||
|
||||
const batteryNearFull =
|
||||
Number.isFinite(socPct) && socPct >= CFG.SOC_NEAR_FULL_PCT;
|
||||
const batterySinkWeak =
|
||||
!Number.isFinite(batteryPowerW) || batteryPowerW < CFG.BATTERY_SINK_MIN_W;
|
||||
|
||||
if (sunUp && batteryNearFull && batterySinkWeak) {
|
||||
reasons.push('batterie_pleine_sans_puits');
|
||||
}
|
||||
|
||||
if (sunUp && dcBatteryState === 'idle' && pvPowerW > CFG.PV_MEANINGFUL_MIN_W) {
|
||||
reasons.push('bus_dc_au_repos_en_plein_jour');
|
||||
}
|
||||
|
||||
// Un tracker à l'arrêt en plein jour n'est pas une panne : c'est le MPPT qui
|
||||
// coupe faute de preneur. C'est le signal le plus direct d'un écrêtage.
|
||||
const mpptOffCount = mpptStates.filter(s => s === 'off').length;
|
||||
if (sunUp && mpptOffCount > 0 && mpptStates.length > 0) {
|
||||
reasons.push(`mppt_arretes_${mpptOffCount}_sur_${mpptStates.length}`);
|
||||
}
|
||||
|
||||
const pvCensored = reasons.length > 0;
|
||||
|
||||
// Un échantillon n'est exploitable pour estimer la ressource que s'il n'est pas
|
||||
// censuré ET qu'il y a assez de soleil pour que la mesure signifie quelque chose.
|
||||
const sampleExploitable =
|
||||
!pvCensored && sunUp && pvPowerW >= CFG.PV_MEANINGFUL_MIN_W;
|
||||
|
||||
// Indication (non un fait) de ce qu'on laisse passer : la prévision porte sur
|
||||
// l'heure suivante, pas sur l'instant présent — à ne pas cumuler comme un
|
||||
// compteur d'énergie perdue.
|
||||
const shortfallHintW =
|
||||
pvCensored && Number.isFinite(forecastNextHourW)
|
||||
? Math.max(0, forecastNextHourW - pvPowerW)
|
||||
: 0;
|
||||
|
||||
// =====================================================================
|
||||
// SANTÉ DES MESURES — identité du bus DC : PV + VE.Bus = batterie.
|
||||
// Vérifiée à 95 W près le 24/08/2026. Le jour où elle diverge, un capteur ment.
|
||||
// =====================================================================
|
||||
const healthMeasurable =
|
||||
Number.isFinite(pvPowerW) && Number.isFinite(vebusDcPowerW) && Number.isFinite(batteryPowerW);
|
||||
|
||||
const residualW = healthMeasurable ? pvPowerW + vebusDcPowerW - batteryPowerW : NaN;
|
||||
const healthTolerance = Math.max(
|
||||
CFG.HEALTH_ABS_TOLERANCE_W,
|
||||
CFG.HEALTH_REL_TOLERANCE * Math.abs(batteryPowerW || 0)
|
||||
);
|
||||
const healthOk = healthMeasurable ? Math.abs(residualW) <= healthTolerance : null;
|
||||
|
||||
// =====================================================================
|
||||
// ALERTES SYSTÈME — jusqu'ici jamais lues par aucun workflow.
|
||||
// =====================================================================
|
||||
const alerts = [];
|
||||
if (overloadAlarm && overloadAlarm !== 'no_alarm') alerts.push(`multiplus_overload_${overloadAlarm}`);
|
||||
if (essBatteryLifeState.includes('soc_below')) alerts.push('batterylife_limite_le_soc');
|
||||
if (healthOk === false) alerts.push('incoherence_mesures_bus_dc');
|
||||
|
||||
return [{
|
||||
json: {
|
||||
ts: input.ts,
|
||||
|
||||
measured: {
|
||||
pvPowerW: round1(pvPowerW),
|
||||
housePowerW: round1(housePowerW),
|
||||
houseWithoutEvcsW: round1(Math.max(0, housePowerW - evcsPowerW)),
|
||||
evcsPowerW: round1(evcsPowerW),
|
||||
socPct: round1(socPct),
|
||||
batteryPowerW: round1(batteryPowerW),
|
||||
vebusDcPowerW: round1(vebusDcPowerW),
|
||||
sunElevationDeg: round1(sunElevationDeg),
|
||||
forecastNextHourW: Number.isFinite(forecastNextHourW) ? forecastNextHourW : null,
|
||||
},
|
||||
|
||||
states: {
|
||||
multiplusState,
|
||||
systemState,
|
||||
essBatteryLifeState,
|
||||
dcBatteryState,
|
||||
mpptStates,
|
||||
evcsNativeStatus,
|
||||
gridPresent,
|
||||
},
|
||||
|
||||
censorship: {
|
||||
pvCensored,
|
||||
reasons,
|
||||
sampleExploitable,
|
||||
shortfallHintW: round1(shortfallHintW),
|
||||
sunUp,
|
||||
},
|
||||
|
||||
health: {
|
||||
measurable: healthMeasurable,
|
||||
residualW: round1(residualW),
|
||||
toleranceW: round1(healthTolerance),
|
||||
ok: healthOk,
|
||||
},
|
||||
|
||||
alerts,
|
||||
cfg: CFG,
|
||||
},
|
||||
}];
|
||||
37
src/energy-balance/HA_SNAPSHOT_TEMPLATE.json.tpl
Normal file
37
src/energy-balance/HA_SNAPSHOT_TEMPLATE.json.tpl
Normal file
@@ -0,0 +1,37 @@
|
||||
{{ {
|
||||
"ts": now().isoformat(),
|
||||
|
||||
"pv_power_w": states("sensor.victron_pv_power_system"),
|
||||
|
||||
"house_ac_power_w": states("sensor.victron_consumption_power_l1"),
|
||||
"vebus_ac_out_l1_power_w": states("sensor.victron_ve_bus_ac_out_l1_power"),
|
||||
"evcs_ac_power_w": states("sensor.victron_evcs_ac_power"),
|
||||
|
||||
"soc_percent": states("sensor.victron_battery_soc"),
|
||||
"battery_current_a": states("sensor.victron_battery_current"),
|
||||
"battery_voltage_v": states("sensor.victron_battery_voltage"),
|
||||
"vebus_dc_power_w": states("sensor.victron_ve_bus_dc_power_system"),
|
||||
|
||||
"grid_switch": states("switch.edf_general_ok_switch"),
|
||||
"acin1_available": states("sensor.victron_ac_in1_available"),
|
||||
"grid_power_l1_w": states("sensor.victron_grid_power_l1"),
|
||||
|
||||
"multiplus_state": states("sensor.airtria_mpii_v1_state"),
|
||||
"multiplus_overload_alarm": states("sensor.airtria_mpii_v1_overload_alarm"),
|
||||
"system_state": states("sensor.gx_device_system_state"),
|
||||
"ess_batterylife_state": states("sensor.gx_device_ess_batterylife_state"),
|
||||
"dc_battery_state": states("sensor.gx_device_dc_battery_state"),
|
||||
|
||||
"mppt_1_state": states("sensor.airtria_mppt1_state"),
|
||||
"mppt_2_state": states("sensor.mppt_450_200_hq2449yr9pt_state"),
|
||||
"mppt_3_state": states("sensor.v1_state"),
|
||||
|
||||
"evcs_native_status": states("sensor.evcs_hq2239dnt7p_status"),
|
||||
|
||||
"forecast_today_remaining_kwh": states("sensor.energy_production_today_remaining"),
|
||||
"forecast_today_kwh": states("sensor.energy_production_today"),
|
||||
"forecast_tomorrow_kwh": states("sensor.energy_production_tomorrow"),
|
||||
"forecast_next_hour_w": states("sensor.power_production_next_hour"),
|
||||
|
||||
"sun_elevation": state_attr("sun.sun", "elevation")
|
||||
} | tojson }}
|
||||
29
tests/energy-balance/fixture-live.json
Normal file
29
tests/energy-balance/fixture-live.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"acin1_available": "0",
|
||||
"battery_current_a": "2.299999952316284",
|
||||
"battery_voltage_v": "51.189998626708984",
|
||||
"dc_battery_state": "charging",
|
||||
"ess_batterylife_state": "self_consumption_soc_exceeds_85",
|
||||
"evcs_ac_power_w": "0.0",
|
||||
"evcs_native_status": "waiting_for_start",
|
||||
"forecast_next_hour_w": "3462",
|
||||
"forecast_today_kwh": "30.241",
|
||||
"forecast_today_remaining_kwh": "15.917",
|
||||
"forecast_tomorrow_kwh": "29.076",
|
||||
"grid_power_l1_w": "0",
|
||||
"grid_switch": "off",
|
||||
"house_ac_power_w": "929.0",
|
||||
"mppt_1_state": "external_control",
|
||||
"mppt_2_state": "external_control",
|
||||
"mppt_3_state": "external_control",
|
||||
"multiplus_overload_alarm": "no_alarm",
|
||||
"multiplus_state": "inverting",
|
||||
"pv_energy_total_kwh": "0.345",
|
||||
"pv_power_w": "1159.3799828340113",
|
||||
"soc_percent": "94.0",
|
||||
"sun_elevation": 52.14,
|
||||
"system_state": "inverting",
|
||||
"ts": "2026-08-24T14:29:07.705982+02:00",
|
||||
"vebus_ac_out_l1_power_w": "929",
|
||||
"vebus_dc_power_w": "-933.0"
|
||||
}
|
||||
72
tests/energy-balance/run.mjs
Normal file
72
tests/energy-balance/run.mjs
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env node
|
||||
// Suite de tests du nœud CENSORSHIP (chantier bilan énergétique).
|
||||
// Exécute le VRAI code de src/energy-balance/CENSORSHIP.js dans une sandbox vm,
|
||||
// avec le shim $input de n8n, sur des snapshots HA (réel + cas construits).
|
||||
//
|
||||
// Usage : node tests/energy-balance/run.mjs [--print] [--only=<motif>]
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import vm from 'node:vm';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { scenarios } from './scenarios.mjs';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const CODE = fs.readFileSync(path.join(ROOT, 'src/energy-balance/CENSORSHIP.js'), 'utf8');
|
||||
|
||||
const PRINT = process.argv.includes('--print');
|
||||
const ONLY = (process.argv.find(a => a.startsWith('--only=')) || '').split('=')[1];
|
||||
|
||||
function runNode(snapshot) {
|
||||
const sandbox = {
|
||||
$input: { first: () => ({ json: snapshot }) },
|
||||
console,
|
||||
};
|
||||
vm.createContext(sandbox);
|
||||
const out = vm.runInContext(`(function(){ ${CODE} })()`, sandbox, { timeout: 5000 });
|
||||
return out[0].json;
|
||||
}
|
||||
|
||||
function summarize(o) {
|
||||
return {
|
||||
pvCensored: o.censorship.pvCensored,
|
||||
reasons: o.censorship.reasons.join('|'),
|
||||
sampleExploitable: o.censorship.sampleExploitable,
|
||||
healthOk: o.health.ok,
|
||||
alerts: o.alerts.join('|'),
|
||||
};
|
||||
}
|
||||
|
||||
let failed = 0;
|
||||
for (const sc of scenarios) {
|
||||
if (ONLY && !sc.id.includes(ONLY)) continue;
|
||||
const got = summarize(runNode(sc.snapshot));
|
||||
|
||||
if (PRINT) {
|
||||
console.log(`─── ${sc.id} | ${sc.label}`);
|
||||
console.log(' ' + JSON.stringify(got));
|
||||
continue;
|
||||
}
|
||||
|
||||
const diffs = [];
|
||||
for (const [k, want] of Object.entries(sc.expect)) {
|
||||
if (JSON.stringify(got[k]) !== JSON.stringify(want)) {
|
||||
diffs.push(` ${k}: attendu ${JSON.stringify(want)}, obtenu ${JSON.stringify(got[k])}`);
|
||||
}
|
||||
}
|
||||
if (diffs.length) {
|
||||
failed++;
|
||||
console.log(`❌ ${sc.id} | ${sc.label}`);
|
||||
diffs.forEach(d => console.log(d));
|
||||
} else {
|
||||
console.log(`✅ ${sc.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (PRINT) process.exit(0);
|
||||
console.log('');
|
||||
if (failed) {
|
||||
console.log(`${failed} ÉCHEC(S) — NE PAS POUSSER EN PRODUCTION`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`ENERGY BALANCE OK — ${scenarios.length}/${scenarios.length} scénarios conformes`);
|
||||
140
tests/energy-balance/scenarios.mjs
Normal file
140
tests/energy-balance/scenarios.mjs
Normal file
@@ -0,0 +1,140 @@
|
||||
// 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',
|
||||
},
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user