Prevision de nuit : annoncer l'achat EDF reel (maison en HC + manque batterie)
Nuit du 28-29/09 : « environ 0,14 kWh a prendre sur EDF » annonces, 4,6 importes. 0,14 etait le manque de la batterie ; une session engagee fait passer toute la maison sur le reseau jusqu'a 07:24. Le bilan publie la maison non couverte sur la fenetre HC de nuit (input_number.evcs_balance_night_hc_house_kwh) et sensor.evcs_prevision_nuit annonce import_prevu_kwh = maison + manque ; reseau_necessaire_kwh supprime. Bilan 72/72, diagnostics 85/85. 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:
31
HANDOFF.md
31
HANDOFF.md
@@ -1132,5 +1132,34 @@ recalee x1,092), 0,34 kWh EDF annonces -> la session de 01:24 devrait s'engager.
|
|||||||
complete de la ZERO a mesurer pour remplacer les 15 kWh annonces ; capacite JEEP 8,7 a
|
complete de la ZERO a mesurer pour remplacer les 15 kWh annonces ; capacite JEEP 8,7 a
|
||||||
affiner ; demarrage solaire de l'apres-midi toujours non couvert par le besoin de nuit.
|
affiner ; demarrage solaire de l'apres-midi toujours non couvert par le besoin de nuit.
|
||||||
|
|
||||||
|
### 29/09 matin — Verification de la nuit, et le chiffre d'EDF annonce etait le mauvais
|
||||||
|
- **Session de 01:24 engagee** (contacteur 01:24:21 -> 07:24:29, une seule fermeture). SOC
|
||||||
|
a 01:24 = 40 (pas 45 : 5,18 kWh de maison entre 21:30 et 01:24), requis 42,1. Batterie
|
||||||
|
TENUE a 40 % toute la session (consigne 950-1 460 W, courant ~0 A) ; le requis passant
|
||||||
|
sous 40 des 02:00, pas de remontee a 42 : normal. **Import 4,61 kWh**, tout en HC.
|
||||||
|
- **SOC 9 h = 37 %** (prevu ~35 % avec EDF). Plus bas 37 % vers 08:24, recharge solaire
|
||||||
|
des 09:15.
|
||||||
|
- **Nuit 21:30-09:00 = 10,25 kWh** pour 11,6 prevus (-12 %) : la soiree forte (6,27) a
|
||||||
|
annonce une nuit forte, elle a ete moyenne. Point ajoute au nuage.
|
||||||
|
- Rapport de 07:30 envoye a 07:30:01, juste (4,6 kWh, plus bas 39 % a 07:28 — la
|
||||||
|
batterie a encore baisse ensuite jusqu'a 37).
|
||||||
|
- **Agent vocal** (transcripts : desormais dans `agents/main/agent/openclaw-agent.sqlite`,
|
||||||
|
table `transcript_events`, plus dans les `.jsonl`) : 21:16 « la batterie suffit »
|
||||||
|
(ancien capteur, avant le correctif) ; 21:30 « juste » ; 21:32/21:34 « EDF sera branche,
|
||||||
|
environ 0,14 kWh ». Verdict juste, CHIFFRE trompeur : 0,14 = le MANQUE de la batterie,
|
||||||
|
alors qu'une session engagee fait passer TOUTE la maison sur le reseau jusqu'a 07:24.
|
||||||
|
- **Correctif** (demande du proprietaire) : le bilan calcule la maison non couverte par le
|
||||||
|
solaire sur la fenetre HC de nuit en cours ou a venir (profil p50 recale sur la soiree,
|
||||||
|
depuis maintenant si la fenetre est ouverte) -> `input_number.evcs_balance_night_hc_house_kwh`
|
||||||
|
(gabarit : `night_hc_start_ms` / `night_hc_end_ms`). `sensor.evcs_prevision_nuit` :
|
||||||
|
etat = **achat prevu** (maison + manque batterie) si `edf_engage` / `reseau_necessaire`,
|
||||||
|
attributs `import_prevu_kwh`, `maison_en_heures_creuses_kwh`, `manque_batterie_kwh`,
|
||||||
|
`fin_heures_creuses` ; **`reseau_necessaire_kwh` SUPPRIME** (lu comme un achat). Phrase :
|
||||||
|
« EDF sera branche de 01:24 a 07:24 : environ 4,6 kWh achetes en heures creuses (maison
|
||||||
|
4,5, batterie 0,14), car la maison passe la nuit sur le reseau ». Bilan 72/72,
|
||||||
|
diagnostics 85/85, mutations qui mordent (une premiere ne mordait pas : scenario a
|
||||||
|
03:00 pile, horizon sans tranche anterieure -> deplace a 03:30). Fiche de l'agent a
|
||||||
|
mettre a jour par SPARK.
|
||||||
|
|
||||||
---
|
---
|
||||||
*Document mis a jour le : 29/09/2026 (~00:10) — regle HC active, vehicule branche a midi (JEEP/VW/ZERO), prevision de nuit a trois verdicts, nuits superposees et bilan recale sur la soiree*
|
*Document mis a jour le : 29/09/2026 (~10:50) — nuit du 28-29 verifiee, achat EDF annonce = maison en HC + manque batterie ; regle HC active, vehicule branche a midi (JEEP/VW/ZERO), prevision de nuit a trois verdicts, nuits superposees et bilan recale sur la soiree*
|
||||||
|
|||||||
@@ -113,6 +113,14 @@ input_number:
|
|||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
mode: box
|
mode: box
|
||||||
icon: mdi:clock-alert-outline
|
icon: mdi:clock-alert-outline
|
||||||
|
evcs_balance_night_hc_house_kwh:
|
||||||
|
name: "Bilan — maison sur le réseau en HC de nuit (si session)"
|
||||||
|
min: 0
|
||||||
|
max: 50
|
||||||
|
step: 0.01
|
||||||
|
unit_of_measurement: "kWh"
|
||||||
|
mode: box
|
||||||
|
icon: mdi:transmission-tower-import
|
||||||
evcs_balance_energy_to_cover_kwh:
|
evcs_balance_energy_to_cover_kwh:
|
||||||
name: "Bilan — énergie à couvrir"
|
name: "Bilan — énergie à couvrir"
|
||||||
min: 0
|
min: 0
|
||||||
|
|||||||
@@ -394,6 +394,33 @@ const hcFloorPrudentPct = Math.max(hcReachFloorPct(slotsP80, nextHcStartMs), hcF
|
|||||||
const typical = coverage(slotsP50, hcFloorTypicalPct);
|
const typical = coverage(slotsP50, hcFloorTypicalPct);
|
||||||
const prudent = coverage(slotsP80, hcFloorPrudentPct);
|
const prudent = coverage(slotsP80, hcFloorPrudentPct);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// CE QUE LA SESSION DE NUIT ACHETERA AU RESEAU (29/09/2026)
|
||||||
|
//
|
||||||
|
// Incident du 28/09 : la prevision annoncait « 0,14 kWh a prendre sur EDF » et
|
||||||
|
// le reseau en a fourni 4,6. Les deux etaient justes, mais le premier est le
|
||||||
|
// MANQUE de la batterie : une fois engagee, la session tient la batterie et la
|
||||||
|
// maison passe TOUTE la fenetre HC de nuit sur le reseau (§40). Le chiffre qui
|
||||||
|
// repond a « combien vais-je acheter » est donc la maison non couverte par le
|
||||||
|
// solaire sur la fenetre (profil habituel, recale sur la soiree), depuis
|
||||||
|
// maintenant si la fenetre est ouverte. Le manque batterie s'y ajoute, a part.
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const nightHcStartMs = num(input.night_hc_start_ms, NaN);
|
||||||
|
const nightHcEndMs = num(input.night_hc_end_ms, NaN);
|
||||||
|
const nightHcActive = Number.isFinite(nightHcStartMs) && Number.isFinite(nightHcEndMs)
|
||||||
|
&& nightHcEndMs > nightHcStartMs;
|
||||||
|
let nightHcHouseKwh = null;
|
||||||
|
if (nightHcActive) {
|
||||||
|
const depuis = Math.max(nightHcStartMs, num(input.nowMs, Date.now()));
|
||||||
|
nightHcHouseKwh = 0;
|
||||||
|
for (const sl of slotsP50) {
|
||||||
|
const debut = num(sl.startMs, NaN);
|
||||||
|
if (!Number.isFinite(debut)) continue;
|
||||||
|
const a = Math.max(debut, depuis), b = Math.min(debut + 3600000, nightHcEndMs);
|
||||||
|
if (b > a) nightHcHouseKwh += Math.max(0, -sl.netKwh) * (b - a) / 3600000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
// VEHICULE BRANCHE EN HC AVEC UN DEPART AVANT LA PROCHAINE HC (28/09/2026)
|
// VEHICULE BRANCHE EN HC AVEC UN DEPART AVANT LA PROCHAINE HC (28/09/2026)
|
||||||
//
|
//
|
||||||
@@ -667,6 +694,14 @@ return [{
|
|||||||
forecastKwh: num(input.night_house_forecast_kwh, NaN) || null,
|
forecastKwh: num(input.night_house_forecast_kwh, NaN) || null,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Maison sur le reseau pendant la fenetre HC de nuit, si la session s'engage (29/09).
|
||||||
|
nightHcImport: {
|
||||||
|
active: nightHcActive,
|
||||||
|
startMs: nightHcActive ? nightHcStartMs : null,
|
||||||
|
endMs: nightHcActive ? nightHcEndMs : null,
|
||||||
|
houseKwh: nightHcHouseKwh === null ? null : round2(nightHcHouseKwh),
|
||||||
|
},
|
||||||
|
|
||||||
// Voiture branchee en HC, depart avant la prochaine HC (28/09).
|
// Voiture branchee en HC, depart avant la prochaine HC (28/09).
|
||||||
vehicleMidday: {
|
vehicleMidday: {
|
||||||
active: vehicleRuleActive,
|
active: vehicleRuleActive,
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ const vm = balance.vehicleMidday || {};
|
|||||||
setBool('input_boolean.evcs_balance_vehicle_grid_needed', !!vm.gridNeeded);
|
setBool('input_boolean.evcs_balance_vehicle_grid_needed', !!vm.gridNeeded);
|
||||||
setNumber('input_number.evcs_balance_vehicle_need_kwh', vm.active ? vm.reachableKwh : 0);
|
setNumber('input_number.evcs_balance_vehicle_need_kwh', vm.active ? vm.reachableKwh : 0);
|
||||||
setNumber('input_number.evcs_balance_vehicle_solar_kwh', vm.active ? vm.solarKwh : 0);
|
setNumber('input_number.evcs_balance_vehicle_solar_kwh', vm.active ? vm.solarKwh : 0);
|
||||||
|
// Maison sur le reseau pendant la fenetre HC de nuit si la session s'engage (29/09).
|
||||||
|
const nh = balance.nightHcImport || {};
|
||||||
|
setNumber('input_number.evcs_balance_night_hc_house_kwh', nh.active && nh.houseKwh !== null ? nh.houseKwh : 0);
|
||||||
setNumber('input_number.evcs_balance_energy_to_cover_kwh', c.energyToCoverKwh);
|
setNumber('input_number.evcs_balance_energy_to_cover_kwh', c.energyToCoverKwh);
|
||||||
setNumber('input_number.evcs_balance_grid_energy_needed_kwh', c.gridEnergyNeededKwh);
|
setNumber('input_number.evcs_balance_grid_energy_needed_kwh', c.gridEnergyNeededKwh);
|
||||||
setNumber('input_number.evcs_balance_vehicle_surplus_kwh', balance.vehicle.surplusAvailableKwh);
|
setNumber('input_number.evcs_balance_vehicle_surplus_kwh', balance.vehicle.surplusAvailableKwh);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
instants : la fin de la fenetre HC en cours (vide hors fenetre) et le debut
|
instants : la fin de la fenetre HC en cours (vide hors fenetre) et le debut
|
||||||
de la fenetre HC suivante. Strategie du proprietaire : la batterie doit
|
de la fenetre HC suivante. Strategie du proprietaire : la batterie doit
|
||||||
tenir jusqu'a la prochaine fenetre HC. -#}
|
tenir jusqu'a la prochaine fenetre HC. -#}
|
||||||
{%- set ns = namespace(fin='', suivant='', nuit='', fen=[]) -%}
|
{%- set ns = namespace(fin='', suivant='', nuit='', fen=[], nd='', nf='') -%}
|
||||||
{%- for j in [0, 1, 2] -%}
|
{%- for j in [0, 1, 2] -%}
|
||||||
{%- for a, b, t in [('victron_evcs_hc_night_start', 'victron_evcs_hc_night_stop', 'nuit'), ('victron_evcs_hc_midday_start', 'victron_evcs_hc_midday_stop', 'midi')] -%}
|
{%- for a, b, t in [('victron_evcs_hc_night_start', 'victron_evcs_hc_night_stop', 'nuit'), ('victron_evcs_hc_midday_start', 'victron_evcs_hc_midday_stop', 'midi')] -%}
|
||||||
{%- set d = today_at(states('input_datetime.' ~ a)) + timedelta(days=j) -%}
|
{%- set d = today_at(states('input_datetime.' ~ a)) + timedelta(days=j) -%}
|
||||||
@@ -20,6 +20,11 @@
|
|||||||
{%- if w[0] >= repere and ns.suivant == '' -%}{%- set ns.suivant = w[0] -%}{%- endif -%}
|
{%- if w[0] >= repere and ns.suivant == '' -%}{%- set ns.suivant = w[0] -%}{%- endif -%}
|
||||||
{%- if w[0] >= repere and w[2] == 'nuit' and ns.nuit == '' -%}{%- set ns.nuit = w[0] -%}{%- endif -%}
|
{%- if w[0] >= repere and w[2] == 'nuit' and ns.nuit == '' -%}{%- set ns.nuit = w[0] -%}{%- endif -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
|
{#- Fenetre HC de NUIT en cours, sinon la prochaine (29/09) : sert a prevoir
|
||||||
|
ce que la maison achetera au reseau si la session de nuit s'engage. -#}
|
||||||
|
{%- for w in ns.fen | sort(attribute='0') -%}
|
||||||
|
{%- if w[2] == 'nuit' and w[1] > now() and ns.nd == '' -%}{%- set ns.nd = w[0] -%}{%- set ns.nf = w[1] -%}{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
{#- Vehicule branche a midi (28/09/2026) — JEEP, VW ou ZERO, selon le
|
{#- Vehicule branche a midi (28/09/2026) — JEEP, VW ou ZERO, selon le
|
||||||
selecteur. Pour chacun, HA porte le calendrier (jours et heure de depart
|
selecteur. Pour chacun, HA porte le calendrier (jours et heure de depart
|
||||||
apres le dejeuner), la capacite et la puissance de charge MESUREES ; le
|
apres le dejeuner), la capacite et la puissance de charge MESUREES ; le
|
||||||
@@ -74,6 +79,8 @@
|
|||||||
"hc_window_end_ms": (as_timestamp(ns.fin) * 1000) if ns.fin != '' else none,
|
"hc_window_end_ms": (as_timestamp(ns.fin) * 1000) if ns.fin != '' else none,
|
||||||
"next_hc_start_ms": (as_timestamp(ns.suivant) * 1000) if ns.suivant != '' else none,
|
"next_hc_start_ms": (as_timestamp(ns.suivant) * 1000) if ns.suivant != '' else none,
|
||||||
"next_night_hc_start_ms": (as_timestamp(ns.nuit) * 1000) if ns.nuit != '' else none,
|
"next_night_hc_start_ms": (as_timestamp(ns.nuit) * 1000) if ns.nuit != '' else none,
|
||||||
|
"night_hc_start_ms": (as_timestamp(ns.nd) * 1000) if ns.nd != '' else none,
|
||||||
|
"night_hc_end_ms": (as_timestamp(ns.nf) * 1000) if ns.nf != '' else none,
|
||||||
"nowMs": as_timestamp(now()) * 1000,
|
"nowMs": as_timestamp(now()) * 1000,
|
||||||
|
|
||||||
"balance_enabled": states("input_boolean.evcs_balance_enabled"),
|
"balance_enabled": states("input_boolean.evcs_balance_enabled"),
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ return [{
|
|||||||
hc_window_end_ms: snapshot.hc_window_end_ms,
|
hc_window_end_ms: snapshot.hc_window_end_ms,
|
||||||
next_hc_start_ms: snapshot.next_hc_start_ms,
|
next_hc_start_ms: snapshot.next_hc_start_ms,
|
||||||
next_night_hc_start_ms: snapshot.next_night_hc_start_ms,
|
next_night_hc_start_ms: snapshot.next_night_hc_start_ms,
|
||||||
|
// Fenetre HC de nuit en cours ou prochaine (29/09) : import prevu si la
|
||||||
|
// session de nuit s'engage.
|
||||||
|
night_hc_start_ms: snapshot.night_hc_start_ms,
|
||||||
|
night_hc_end_ms: snapshot.night_hc_end_ms,
|
||||||
|
|
||||||
// Repris pour l'écriture des helpers, sans influencer le calcul.
|
// Repris pour l'écriture des helpers, sans influencer le calcul.
|
||||||
resourceUsable: resource.usable,
|
resourceUsable: resource.usable,
|
||||||
|
|||||||
@@ -336,7 +336,8 @@ for (const [nom, r, test] of imp) {
|
|||||||
const t = new Date('2026-09-27T23:30:00Z'); // 01:30 a Paris
|
const t = new Date('2026-09-27T23:30:00Z'); // 01:30 a Paris
|
||||||
const pn = [
|
const pn = [
|
||||||
['nuit 28/09 : 5,77 kWh EDF, 5,47 a couvrir, retour 09 h, aucune voiture', previsionNuit(base(), t),
|
['nuit 28/09 : 5,77 kWh EDF, 5,47 a couvrir, retour 09 h, aucune voiture', previsionNuit(base(), t),
|
||||||
(r) => r.etat === 5.77 && r.attributs.voiture === 'aucune' && /5.77 kWh a prendre sur EDF/.test(r.attributs.explication)],
|
(r) => r.etat === 5.77 && r.attributs.voiture === 'aucune' && /au moins 5,77 kWh achetes/.test(r.attributs.explication)
|
||||||
|
&& /plus la consommation de la maison/.test(r.attributs.explication)],
|
||||||
['jamais 73,7 ni 26,95 dans la prevision (incident 27/09)', previsionNuit(base(), t),
|
['jamais 73,7 ni 26,95 dans la prevision (incident 27/09)', previsionNuit(base(), t),
|
||||||
(r) => !/73\.7|26\.95/.test(JSON.stringify(r))],
|
(r) => !/73\.7|26\.95/.test(JSON.stringify(r))],
|
||||||
['bilan perime (> 30 min) -> indisponible', previsionNuit(base(), new Date('2026-09-28T00:30:00Z')),
|
['bilan perime (> 30 min) -> indisponible', previsionNuit(base(), new Date('2026-09-28T00:30:00Z')),
|
||||||
@@ -370,6 +371,34 @@ for (const [nom, r, test] of imp) {
|
|||||||
'sensor.victron_battery_soc': '70', 'input_boolean.victron_evcs_night_grid_session_committed': 'off',
|
'sensor.victron_battery_soc': '70', 'input_boolean.victron_evcs_night_grid_session_committed': 'off',
|
||||||
})) m.set(k, { state: v }); return previsionNuit(m, t); })(),
|
})) m.set(k, { state: v }); return previsionNuit(m, t); })(),
|
||||||
(r) => r.attributs.verdict === 'juste'],
|
(r) => r.attributs.verdict === 'juste'],
|
||||||
|
// Releve REEL du 28/09 21:32 : verdict reseau_necessaire, manque 0,14 kWh.
|
||||||
|
// L'agent a dit « environ 0,14 kWh a prendre sur EDF » ; le reseau en a
|
||||||
|
// fourni 4,6 (la maison sur le reseau de 01:24 a 07:24).
|
||||||
|
['28/09 21:32 : manque 0,14 mais la maison passe la nuit sur le reseau -> « environ 4,6 kWh achetes »', (() => {
|
||||||
|
const m = base(); for (const [k, v] of Object.entries({
|
||||||
|
'input_number.evcs_balance_grid_energy_needed_kwh': '0.14', 'input_boolean.victron_evcs_night_grid_session_committed': 'off',
|
||||||
|
'input_number.evcs_balance_night_hc_house_kwh': '4.5', 'sensor.evcs_reseau': 'non_raccorde',
|
||||||
|
'input_datetime.victron_evcs_hc_night_start': '01:24:00', 'input_datetime.victron_evcs_hc_night_stop': '07:24:00',
|
||||||
|
})) m.set(k, { state: v }); return previsionNuit(m, t); })(),
|
||||||
|
(r) => r.etat === 4.6 && r.attributs.verdict === 'reseau_necessaire' && r.attributs.import_prevu_kwh === 4.6
|
||||||
|
&& r.attributs.manque_batterie_kwh === 0.14
|
||||||
|
&& /EDF sera branche de 01:24 a 07:24 : environ 4,6 kWh achetes/.test(r.attributs.explication)
|
||||||
|
&& /maison 4,5, batterie 0,14/.test(r.attributs.explication)
|
||||||
|
&& !/0,14 kWh a prendre/.test(r.attributs.explication)],
|
||||||
|
['session engagee avec la maison connue -> achat = maison + batterie, jusqu a 07:24', (() => {
|
||||||
|
const m = base(); for (const [k, v] of Object.entries({
|
||||||
|
'input_number.evcs_balance_night_hc_house_kwh': '3.2', 'input_datetime.victron_evcs_hc_night_stop': '07:24:00',
|
||||||
|
})) m.set(k, { state: v }); return previsionNuit(m, t); })(),
|
||||||
|
(r) => r.etat === 9 && /jusqu a 07:24 : environ 9 kWh achetes/.test(r.attributs.explication)],
|
||||||
|
['« c est juste » annonce aussi l achat si EDF s engage', (() => {
|
||||||
|
const m = base(); for (const [k, v] of Object.entries({
|
||||||
|
'input_boolean.evcs_balance_grid_needed': 'off', 'input_number.evcs_balance_required_soc': '57.5',
|
||||||
|
'input_number.evcs_balance_required_soc_prudent': '68.5', 'sensor.victron_battery_soc': '58',
|
||||||
|
'input_boolean.victron_evcs_night_grid_session_committed': 'off', 'input_number.evcs_balance_night_hc_house_kwh': '4.46',
|
||||||
|
})) m.set(k, { state: v }); return previsionNuit(m, t); })(),
|
||||||
|
(r) => r.etat === 0 && r.attributs.verdict === 'juste' && /la maison y achetera alors environ 4,5 kWh/.test(r.attributs.explication)],
|
||||||
|
['plus jamais d attribut reseau_necessaire_kwh (lu comme un achat le 28/09)', previsionNuit(base(), t),
|
||||||
|
(r) => !('reseau_necessaire_kwh' in r.attributs) && 'import_prevu_kwh' in r.attributs],
|
||||||
['jamais d attribut qui ressemble a un SOC du matin (incident 21:16)', previsionNuit(base(), t),
|
['jamais d attribut qui ressemble a un SOC du matin (incident 21:16)', previsionNuit(base(), t),
|
||||||
(r) => !('soc_requis_au_retour_solaire_pct' in r.attributs) && 'soc_requis_maintenant_pct' in r.attributs],
|
(r) => !('soc_requis_au_retour_solaire_pct' in r.attributs) && 'soc_requis_maintenant_pct' in r.attributs],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ const SUMMARIZE = {
|
|||||||
// Nuit prevue par la soiree (28/09).
|
// Nuit prevue par la soiree (28/09).
|
||||||
nightModelActive: o.nightModel ? o.nightModel.active : false,
|
nightModelActive: o.nightModel ? o.nightModel.active : false,
|
||||||
nightFactor: o.nightModel ? o.nightModel.factor : null,
|
nightFactor: o.nightModel ? o.nightModel.factor : null,
|
||||||
|
// Maison sur le reseau pendant la fenetre HC de nuit (29/09).
|
||||||
|
nightHcActive: o.nightHcImport ? o.nightHcImport.active : false,
|
||||||
|
nightHcHouseKwh: o.nightHcImport ? o.nightHcImport.houseKwh : null,
|
||||||
// Voiture branchee en HC, depart avant la prochaine HC (28/09).
|
// Voiture branchee en HC, depart avant la prochaine HC (28/09).
|
||||||
vehicleActive: o.vehicleMidday ? o.vehicleMidday.active : false,
|
vehicleActive: o.vehicleMidday ? o.vehicleMidday.active : false,
|
||||||
vehicleNeedKwh: o.vehicleMidday ? o.vehicleMidday.needKwh : null,
|
vehicleNeedKwh: o.vehicleMidday ? o.vehicleMidday.needKwh : null,
|
||||||
|
|||||||
@@ -557,4 +557,47 @@ export const balanceScenarios = [
|
|||||||
night_house_computed_ms: Date.parse('2026-09-27T22:57:00+02:00') }),
|
night_house_computed_ms: Date.parse('2026-09-27T22:57:00+02:00') }),
|
||||||
expect: { usable: true, nightModelActive: true, nightFactor: 1.45, requiredSocPct: 59.8, prudentRequiredSocPct: 62.2 },
|
expect: { usable: true, nightModelActive: true, nightFactor: 1.45, requiredSocPct: 59.8, prudentRequiredSocPct: 62.2 },
|
||||||
},
|
},
|
||||||
|
// ======== CE QUE LA SESSION DE NUIT ACHETERA (29/09/2026) ========
|
||||||
|
// Nuit du 28-29/09 : « 0,14 kWh a prendre sur EDF » annonces, 4,6 importes :
|
||||||
|
// la session engagee fait passer TOUTE la maison sur le reseau de 01:24 a
|
||||||
|
// 07:24. Verifie a la main sur le profil p50 (pas de soleil avant 8 h) :
|
||||||
|
// 0,6 x 0,80 + 0,70 + 0,60 + 0,70 + 0,70 + 0,70 + 0,4 x 0,70 = 4,16 kWh.
|
||||||
|
{
|
||||||
|
node: 'BALANCE',
|
||||||
|
id: 'bilan-hc-nuit-maison-sur-le-reseau',
|
||||||
|
label: '23:00, fenetre HC de nuit 01:24-07:24 a venir : la maison y prendra 4,16 kWh au reseau',
|
||||||
|
snapshot: Object.assign(snap2709('2026-09-27T23:00:00+02:00', 52, null), {
|
||||||
|
night_hc_start_ms: Date.parse('2026-09-28T01:24:00+02:00'), night_hc_end_ms: Date.parse('2026-09-28T07:24:00+02:00') }),
|
||||||
|
expect: { usable: true, nightHcActive: true, nightHcHouseKwh: 4.16, requiredSocPct: 47.9 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: 'BALANCE',
|
||||||
|
id: 'bilan-hc-nuit-maison-recalee-sur-la-soiree',
|
||||||
|
label: 'Soiree chargee (facteur 1,092) : la maison en HC de nuit monte a 4,53 kWh',
|
||||||
|
// Tranches recalees puis arrondies : 0,87 / 0,76 / 0,66 -> 0,6 x 0,87 +
|
||||||
|
// 0,76 + 0,66 + 3 x 0,76 + 0,4 x 0,76 = 4,53.
|
||||||
|
snapshot: Object.assign(snap2709('2026-09-27T23:00:00+02:00', 52, null), {
|
||||||
|
night_house_factor: 1.092,
|
||||||
|
night_house_start_ms: Date.parse('2026-09-27T21:30:00+02:00'), night_house_end_ms: Date.parse('2026-09-28T09:00:00+02:00'),
|
||||||
|
night_house_computed_ms: Date.parse('2026-09-27T22:57:00+02:00'),
|
||||||
|
night_hc_start_ms: Date.parse('2026-09-28T01:24:00+02:00'), night_hc_end_ms: Date.parse('2026-09-28T07:24:00+02:00') }),
|
||||||
|
expect: { usable: true, nightModelActive: true, nightHcActive: true, nightHcHouseKwh: 4.53 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: 'BALANCE',
|
||||||
|
id: 'bilan-hc-nuit-fenetre-ouverte-depuis-maintenant',
|
||||||
|
label: '03:30, fenetre ouverte : seul le reste de la fenetre compte (2,68 kWh)',
|
||||||
|
// La tranche de 03 h n'est comptee que pour sa demi-heure restante :
|
||||||
|
// 0,5 x 0,60 + 0,70 + 0,70 + 0,70 + 0,4 x 0,70 = 2,68.
|
||||||
|
snapshot: Object.assign(snap2709('2026-09-28T03:30:00+02:00', 45, null), {
|
||||||
|
night_hc_start_ms: Date.parse('2026-09-28T01:24:00+02:00'), night_hc_end_ms: Date.parse('2026-09-28T07:24:00+02:00') }),
|
||||||
|
expect: { usable: true, nightHcActive: true, nightHcHouseKwh: 2.68 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: 'BALANCE',
|
||||||
|
id: 'bilan-hc-nuit-fenetre-inconnue',
|
||||||
|
label: 'Fenetre HC de nuit non transmise : rien n est annonce',
|
||||||
|
snapshot: snap2709('2026-09-27T23:00:00+02:00', 52, null),
|
||||||
|
expect: { usable: true, nightHcActive: false, nightHcHouseKwh: null },
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -593,6 +593,16 @@ export function previsionNuit(st, maintenant = new Date()) {
|
|||||||
const session = txt('input_boolean.victron_evcs_night_grid_session_committed') === 'on';
|
const session = txt('input_boolean.victron_evcs_night_grid_session_committed') === 'on';
|
||||||
const branche = txt('binary_sensor.victron_evcs_vehicle_connected');
|
const branche = txt('binary_sensor.victron_evcs_vehicle_connected');
|
||||||
const hcDebut = (txt('input_datetime.victron_evcs_hc_night_start') || '').slice(0, 5) || null;
|
const hcDebut = (txt('input_datetime.victron_evcs_hc_night_start') || '').slice(0, 5) || null;
|
||||||
|
const hcFin = (txt('input_datetime.victron_evcs_hc_night_stop') || '').slice(0, 5) || null;
|
||||||
|
// CE QUI SERA ACHETE, pas le manque de la batterie (incident 28/09 21:32 :
|
||||||
|
// « 0,14 kWh a prendre sur EDF » annonces, 4,6 importes). Une session engagee
|
||||||
|
// tient la batterie et fait passer TOUTE la maison sur le reseau jusqu'a la
|
||||||
|
// fin des heures creuses : achat = maison sur la fenetre (calculee par le
|
||||||
|
// bilan) + manque de la batterie. 0 publie = fenetre inconnue -> null.
|
||||||
|
const maisonHc = nombre(st, 'input_number.evcs_balance_night_hc_house_kwh');
|
||||||
|
const maisonHcKwh = maisonHc !== null && maisonHc > 0 ? maisonHc : null;
|
||||||
|
const manqueKwh = reseauNecessaire && reseauKwh !== null ? reseauKwh : 0;
|
||||||
|
const importPrevu = maisonHcKwh !== null ? Math.round((maisonHcKwh + manqueKwh) * 10) / 10 : null;
|
||||||
const marge = soc !== null && socRequis !== null ? Math.round((soc - socRequis) * 10) / 10 : null;
|
const marge = soc !== null && socRequis !== null ? Math.round((soc - socRequis) * 10) / 10 : null;
|
||||||
|
|
||||||
// TROIS verdicts, pas deux (incident 28/09 21:16 : « la batterie suffit, sans
|
// TROIS verdicts, pas deux (incident 28/09 21:16 : « la batterie suffit, sans
|
||||||
@@ -607,10 +617,13 @@ export function previsionNuit(st, maintenant = new Date()) {
|
|||||||
|
|
||||||
const attributs = {
|
const attributs = {
|
||||||
maison_kwh_a_couvrir: aCouvrir, heure_retour_solaire: retour,
|
maison_kwh_a_couvrir: aCouvrir, heure_retour_solaire: retour,
|
||||||
reseau_necessaire: reseauNecessaire, reseau_necessaire_kwh: reseauNecessaire ? reseauKwh : 0,
|
reseau_necessaire: reseauNecessaire,
|
||||||
|
// ⚠️ Plus de `reseau_necessaire_kwh` (29/09) : c'etait le manque de la
|
||||||
|
// batterie, lu comme ce qui serait achete. Deux grandeurs, deux noms.
|
||||||
|
import_prevu_kwh: importPrevu, maison_en_heures_creuses_kwh: maisonHcKwh, manque_batterie_kwh: manqueKwh,
|
||||||
verdict,
|
verdict,
|
||||||
soc_actuel_pct: soc, soc_requis_maintenant_pct: socRequis, soc_requis_prudent_maintenant_pct: socPrudent,
|
soc_actuel_pct: soc, soc_requis_maintenant_pct: socRequis, soc_requis_prudent_maintenant_pct: socPrudent,
|
||||||
marge_points: marge, debut_heures_creuses: hcDebut,
|
marge_points: marge, debut_heures_creuses: hcDebut, fin_heures_creuses: hcFin,
|
||||||
contacteur_edf: reseau, session_reseau_nuit_engagee: session,
|
contacteur_edf: reseau, session_reseau_nuit_engagee: session,
|
||||||
voiture: branche === 'on' ? 'branchee' : branche === 'off' ? 'aucune' : 'inconnue',
|
voiture: branche === 'on' ? 'branchee' : branche === 'off' ? 'aucune' : 'inconnue',
|
||||||
bilan_calcule_il_y_a_min: age,
|
bilan_calcule_il_y_a_min: age,
|
||||||
@@ -629,16 +642,26 @@ export function previsionNuit(st, maintenant = new Date()) {
|
|||||||
? 'Un vehicule est branche : sa charge s ajoute et sera decidee par le controleur.'
|
? 'Un vehicule est branche : sa charge s ajoute et sera decidee par le controleur.'
|
||||||
: 'Etat du vehicule inconnu.';
|
: 'Etat du vehicule inconnu.';
|
||||||
const hc = hcDebut ? `a ${hcDebut}` : 'a l ouverture des heures creuses';
|
const hc = hcDebut ? `a ${hcDebut}` : 'a l ouverture des heures creuses';
|
||||||
|
const kwh = (x) => String(x).replace('.', ',');
|
||||||
|
const jusqua = hcFin ? `jusqu a ${hcFin}` : 'jusqu a la fin des heures creuses';
|
||||||
|
const plage = hcDebut && hcFin ? `de ${hcDebut} a ${hcFin}` : 'en heures creuses';
|
||||||
|
const detail = maisonHcKwh === null ? ''
|
||||||
|
: manqueKwh > 0 ? ` (maison ${kwh(maisonHcKwh)}, batterie ${kwh(manqueKwh)})` : '';
|
||||||
|
const achat = importPrevu !== null
|
||||||
|
? `environ ${kwh(importPrevu)} kWh achetes en heures creuses${detail}, car la maison passe la nuit sur le reseau`
|
||||||
|
: manqueKwh > 0 ? `au moins ${kwh(manqueKwh)} kWh achetes en heures creuses, plus la consommation de la maison` : null;
|
||||||
const edf = {
|
const edf = {
|
||||||
edf_engage: `EDF est branche pour la nuit, en heures creuses${reseauNecessaire ? ` : environ ${reseauKwh} kWh a prendre sur EDF avant le retour du solaire` : ''} ; la batterie est tenue jusqu au matin.`,
|
edf_engage: `EDF est branche pour la nuit, ${jusqua}${achat ? ` : ${achat}` : ''} ; la batterie est tenue jusqu au matin.`,
|
||||||
reseau_necessaire: `Environ ${reseauKwh} kWh a prendre sur EDF en heures creuses avant le retour du solaire.`,
|
reseau_necessaire: `EDF sera branche ${plage}${achat ? ` : ${achat}` : ''}.`,
|
||||||
batterie_suffit: 'La batterie suffit jusqu au retour du solaire, meme si la maison consomme plus que d habitude : EDF ne devrait pas etre necessaire.',
|
batterie_suffit: 'La batterie suffit jusqu au retour du solaire, meme si la maison consomme plus que d habitude : EDF ne devrait pas etre necessaire.',
|
||||||
juste: `C est juste : la batterie suffit seulement si la maison consomme comme d habitude${marge !== null ? ` (marge ${String(marge).replace('.', ',')} point${Math.abs(marge) >= 2 ? 's' : ''})` : ''}. ` +
|
juste: `C est juste : la batterie suffit seulement si la maison consomme comme d habitude${marge !== null ? ` (marge ${kwh(marge)} point${Math.abs(marge) >= 2 ? 's' : ''})` : ''}. ` +
|
||||||
`Le systeme refait le calcul ${hc} : si la soiree a consomme davantage, il prendra EDF en heures creuses.`,
|
`Le systeme refait le calcul ${hc} : si la soiree a consomme davantage, il prendra EDF en heures creuses` +
|
||||||
|
`${maisonHcKwh !== null ? `, et la maison y achetera alors environ ${kwh(Math.round(maisonHcKwh * 10) / 10)} kWh` : ''}.`,
|
||||||
}[verdict];
|
}[verdict];
|
||||||
const texte = `La maison doit couvrir environ ${aCouvrir} kWh jusqu au retour du solaire vers ${retour}. ` +
|
const texte = `La maison doit couvrir environ ${aCouvrir} kWh jusqu au retour du solaire vers ${retour}. ` +
|
||||||
`${edf} Batterie a ${soc !== null ? Math.round(soc) : '?'} %. ${voiture}`;
|
`${edf} Batterie a ${soc !== null ? Math.round(soc) : '?'} %. ${voiture}`;
|
||||||
return { etat: reseauNecessaire ? reseauKwh : 0, attributs: { ...attributs, statut: 'frais', explication: texte, texte_rapport: texte } };
|
const etat = verdict === 'edf_engage' || verdict === 'reseau_necessaire' ? (importPrevu ?? manqueKwh) : 0;
|
||||||
|
return { etat, attributs: { ...attributs, statut: 'frais', explication: texte, texte_rapport: texte } };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user