diff --git a/HANDOFF.md b/HANDOFF.md index 31ff24d..fe878f3 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1168,6 +1168,22 @@ affiner ; demarrage solaire de l'apres-midi toujours non couvert par le besoin d pour passer de 18 h a 09 h sans EDF (17,87 kWh), il faudrait la batterie a 82 % a 18 h ; si EDF s'engage a 01:24, la maison y achetera environ 4,2 kWh ». Attributs `prochaine_traversee*`. Diagnostics 87/87, mutation qui mord. En reel a 10:53. +- **18:03 « prevision de la nuit indisponible » en vocal** alors que le bilan (5 min) + donnait SOC 83 pour 67,2 requis : statut `degrade`. Constat en base : le bilan passe + `degrade` **chaque soir pendant ~1 h** (19:00-19:55 du 23 au 28/09, 18:00 le 29/09) — + une heure de deficit encore ensoleillee dont l'historique est censure (batterie souvent + pleine a cette heure) est marquee « forme minoree » (`RESOURCE_ESTIMATE`, taux de + censure > 0,25). Avec la courbe horaire VRM/Forecast.Solar, cette forme n'entre plus + dans le calcul de production : l'alerte est tres probablement FAUSSE. Correctif capteur + : `degrade` -> prevision publiee, statut `sous_reserve`, phrase « Sous reserve : + l'historique de production de ces heures de fin de journee est incomplet … la decision + reelle est reprise a 01:24 » ; `indisponible` seulement sans bilan, bilan perime ou + statut inconnu, avec la raison dans `explication`. Diagnostics 90/90, mutation qui mord. + ⚠️ **OUVERT, decision du proprietaire** : pendant cette heure, le controleur ignore + AUSSI le bilan (`balanceStatusOk`) -> plancher du soir retombe sur 85 % fixe, cible et + session sur les regles historiques. A 01:24 le statut est `ok` (pas d'effet sur la + session de nuit constate). Piste : ne pas degrader sur une forme minoree quand la + production horaire vient de la courbe de prevision (et non de l'enveloppe). - SPARK : les transcripts SQLite (`openclaw-agent.sqlite`, droits 600) remontent au 18/07, AUCUNE rotation ; un evenement contient le jeton HA (l'agent a lu `.ha-token`). Signale au proprietaire par SPARK, base non modifiee. diff --git a/tests/diagnostics-agent/run.mjs b/tests/diagnostics-agent/run.mjs index 207b66f..f8bac8b 100644 --- a/tests/diagnostics-agent/run.mjs +++ b/tests/diagnostics-agent/run.mjs @@ -422,6 +422,22 @@ for (const [nom, r, test] of imp) { 'input_boolean.victron_evcs_night_grid_session_committed': 'off', 'input_text.evcs_balance_next_deficit_window': 'déficit en cours', })) m.set(k, { state: v }); return previsionNuit(m, t); })(), (r) => r.etat === 'jour' && !/Repere provisoire/.test(r.attributs.texte_rapport)], + // Releve REEL du 29/09 18:05 : bilan `degrade` (forme minoree), SOC 83, + // requis 67,2, 13,56 kWh a couvrir. Le vocal a dit « indisponible ». + ['29/09 18:05 : bilan degrade -> prevision publiee SOUS RESERVE, pas muette', (() => { + const m = base(); for (const [k, v] of Object.entries({ + 'input_text.evcs_balance_status': 'degrade', 'input_number.evcs_balance_energy_to_cover_kwh': '13.56', + 'input_boolean.evcs_balance_grid_needed': 'off', 'input_number.evcs_balance_required_soc': '67.2', + 'input_number.evcs_balance_required_soc_prudent': '76', 'sensor.victron_battery_soc': '83', + 'input_boolean.victron_evcs_night_grid_session_committed': 'off', 'input_datetime.victron_evcs_hc_night_start': '01:24:00', + })) m.set(k, { state: v }); return previsionNuit(m, t); })(), + (r) => r.etat === 0 && r.attributs.statut === 'sous_reserve' && r.attributs.verdict === 'batterie_suffit' + && /Sous reserve/.test(r.attributs.texte_rapport) && /reprise a 01:24/.test(r.attributs.texte_rapport)], + ['statut inconnu -> indisponible, avec la raison', (() => { + const m = base(); m.set('input_text.evcs_balance_status', { state: 'erreur' }); return previsionNuit(m, t); })(), + (r) => r.etat === 'indisponible' && /etat « erreur »/.test(r.attributs.explication)], + ['bilan perime -> indisponible, dit depuis combien de temps', previsionNuit(base(), new Date('2026-09-28T00:30:00Z')), + (r) => r.etat === 'indisponible' && /recalcule depuis 65 min/.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), diff --git a/tools/diagnostics-agent.mjs b/tools/diagnostics-agent.mjs index f48fdc9..38fe683 100644 --- a/tools/diagnostics-agent.mjs +++ b/tools/diagnostics-agent.mjs @@ -632,10 +632,26 @@ export function previsionNuit(st, maintenant = new Date()) { ? 'la production du matin vient de la prevision VRM, en essai depuis le 28/09 : l heure de retour du solaire reste une estimation' : 'la production du matin vient de Forecast.Solar, qui sous-estime ces jours-ci : l heure de retour du solaire peut etre plus precoce', }; - if (statut !== 'ok' || age === null || age > BILAN_FRAIS_MIN || aCouvrir === null || retour === null) { + // `degrade` n'est PAS une panne (29/09, 18:03 : « prevision indisponible » + // en vocal avec 16 points de marge). Le bilan passe `degrade` chaque soir + // pendant une heure environ (19 h depuis le 23/09, 18 h le 29/09) : une + // heure de deficit dont l'historique de production est censure (batterie + // souvent pleine a cette heure) est marquee « forme minoree ». Le calcul est + // fait et publie ; on le relaie AVEC la reserve. Muet seulement sans bilan, + // bilan perime, ou statut inconnu. + const degrade = statut === 'degrade'; + const pourquoi = age === null || age > BILAN_FRAIS_MIN + ? `le bilan energetique n a pas ete recalcule depuis ${age === null ? 'un moment' : `${age} min`}` + : statut !== 'ok' && !degrade ? `le bilan energetique est en etat « ${statut ?? 'inconnu'} »` + : 'le bilan ne donne pas l energie a couvrir ou l heure de retour du solaire'; + if ((statut !== 'ok' && !degrade) || age === null || age > BILAN_FRAIS_MIN || aCouvrir === null || retour === null) { return { etat: 'indisponible', attributs: { ...attributs, statut: 'indisponible', - explication: 'Le bilan energetique n est pas a jour : prevision de la nuit indisponible.' } }; + explication: `Prevision de la nuit indisponible pour l instant : ${pourquoi}. Elle revient au prochain calcul du bilan (toutes les 5 min).` } }; } + const reserveBilan = degrade + ? ' Sous reserve : l historique de production de ces heures de fin de journee est incomplet (batterie souvent pleine a cette heure) ; la decision reelle est reprise ' + (hcDebut ? `a ${hcDebut}` : 'a l ouverture des heures creuses') + '.' + : ''; + attributs.reserve_bilan = degrade ? 'forme de production minoree sur les heures de deficit' : null; const voiture = attributs.voiture === 'aucune' ? 'Aucun vehicule branche.' : attributs.voiture === 'branchee' @@ -684,9 +700,9 @@ export function previsionNuit(st, maintenant = new Date()) { explication: texteJour, texte_rapport: texteJour } }; } 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}${reserveBilan}`; const etat = verdict === 'edf_engage' || verdict === 'reseau_necessaire' ? (importPrevu ?? manqueKwh) : 0; - return { etat, attributs: { ...attributs, statut: 'frais', explication: texte, texte_rapport: texte } }; + return { etat, attributs: { ...attributs, statut: degrade ? 'sous_reserve' : 'frais', explication: texte, texte_rapport: texte } }; } // ---------------------------------------------------------------------