Le conseiller ne fixe plus la cible du matin

Le premier cablage reel (§20, 09/08) faisait suivre a la cible SOC du matin
`input_number.evcs_advisor_night_target_soc`, derriere quatre gardes :
toggle, statut ok, fraicheur de 120 minutes, bornage [22, 90].

Depuis que le conseiller est devenu observateur le 12/09, il n emet plus de
cible et ce helper est fige a 55 %. Les quatre gardes continuaient pourtant
de passer toutes les quatre, parce que la garde de fraicheur date le RUN et
non la VALEUR : le conseiller tourne toujours, donc son horodatage est
frais. Activer le toggle aurait applique un chiffre perime sans qu aucune
garde ne s y oppose.

Retire : les quatre parametres d INPUT_PARSE, le bloc de WINDOWS_SOURCEV2,
le passe-plat de BATTERY_CLOUD_LOCKS, la trace (ADVISOR) de DECISION et les
quatre champs du gabarit de snapshot. Le cablage du BILAN (§39) est intact.
`parseAdvisorRunToMs` devient `parseRunStampToMs` : le bilan publie le meme
format et reste son seul appelant.

Golden 98 → 96. Les trois scenarios du cablage sont remplaces par un seul,
`advisor-cible-ignoree`, qui pinne l ABSENCE de cablage : toutes les
conditions de l ancienne regle reunies, et la cible doit rester celle des
regles de prevision. Mutation : cablage restaure → il tombe, avec
« target=22% (ADVISOR) » dans la raison ; retire → 96/96.

Gabarit re-rendu contre Home Assistant : 196 champs, JSON valide, aucun
null. Pousse en production, cycle suivant en succes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
Tallagrand Thierry
2026-09-13 12:14:38 +02:00
parent 4a9ad0bc65
commit 4fb401be96
8 changed files with 55 additions and 103 deletions

View File

@@ -360,7 +360,6 @@ const batteryMorningTargetPct =
Number.isFinite(nightPrev.batteryMorningTargetPct)
? nightPrev.batteryMorningTargetPct
: params.evcsNightTargetWeakForecastPct;
const advisorNightTargetApplied = !!nightPrev.advisorNightTargetApplied;
const balanceMorningTargetApplied = !!nightPrev.balanceMorningTargetApplied;
const nightGridSessionCommitted = !!nightPrev.nightGridSessionCommitted;
const maxBatteryDischargePoints =
@@ -1081,7 +1080,6 @@ return [{
batteryHardFloorPct,
batteryMorningTargetPct,
advisorNightTargetApplied,
balanceMorningTargetApplied,
nightGridSessionCommitted,
maxBatteryDischargePoints,

View File

@@ -1319,7 +1319,7 @@ else {
// =====================================================
if (nightEvcs?.nightSessionActive) {
reason.push(
`Nuit EVCS: target=${round1(nightEvcs.batteryMorningTargetPct)}%${nightEvcs.advisorNightTargetApplied ? ' (ADVISOR)' : ''}${nightEvcs.balanceMorningTargetApplied ? ' (BILAN)' : ''} | trigger réseau=${round1(nightEvcs.gridTakeoverTriggerPct)}%`
`Nuit EVCS: target=${round1(nightEvcs.batteryMorningTargetPct)}%${nightEvcs.balanceMorningTargetApplied ? ' (BILAN)' : ''} | trigger réseau=${round1(nightEvcs.gridTakeoverTriggerPct)}%`
);
if (Number.isFinite(nightEvcs.nightSessionStartSocPct)) {

View File

@@ -141,10 +141,6 @@
"prev_surplus_state": states("input_text.victron_evcs_surplus_state"),
"evcs_night_session_start_soc": states("input_text.victron_evcs_night_session_start_soc"),
"evcs_night_session_start_at": states("input_text.victron_evcs_night_session_start_at"),
"advisor_apply_night_target": states("input_boolean.evcs_advisor_apply_night_target"),
"advisor_night_target_soc": states("input_number.evcs_advisor_night_target_soc"),
"advisor_status": states("input_text.evcs_advisor_status"),
"advisor_last_run": states("input_text.evcs_advisor_last_run"),
"balance_required_soc": states("input_number.evcs_balance_required_soc"),
"balance_energy_to_cover_kwh": states("input_number.evcs_balance_energy_to_cover_kwh"),

View File

@@ -172,10 +172,12 @@ function parseChargingTimeToSec(s) {
return matched ? sec : null;
}
// Advisor IA : input_text.evcs_advisor_last_run au format "DD/MM HH:mm"
// Horodatage "DD/MM HH:mm" publie par le bilan energetique
// (input_text.evcs_balance_last_run). Le conseiller employait le meme format ;
// son cablage a ete retire le 13/09/2026, la fonction sert desormais au bilan.
// (pas d'année). On suppose l'année courante ; si le résultat est dans le
// futur (> 1 jour), c'est un passage d'année → on retire un an.
function parseAdvisorRunToMs(s) {
function parseRunStampToMs(s) {
const m = (s ?? '').toString().trim().match(/^(\d{2})\/(\d{2}) (\d{2}):(\d{2})$/);
if (!m) return null;
@@ -464,17 +466,13 @@ const params = {
// Advisor IA — premier câblage réel (09/08/2026, HANDOFF §20) :
// cible SOC matin recommandée, appliquée UNIQUEMENT si le toggle est ON.
// Garde-fous consommés dans WINDOWS_SOURCEV2 (statut ok + fraîcheur + bornes).
advisorApplyNightTarget: low(input.advisor_apply_night_target || 'off') === 'on',
advisorNightTargetSocPct: num(input.advisor_night_target_soc, NaN),
advisorStatusOk: cleanText(input.advisor_status).toLowerCase() === 'ok',
advisorLastRunMs: parseAdvisorRunToMs(cleanText(input.advisor_last_run)),
// BILAN ENERGETIQUE (03/09/2026). Meme format d'horodatage que l'advisor
// ("DD/MM HH:mm"), d'ou la reutilisation du parseur.
balanceRequiredSocPct: num(input.balance_required_soc, NaN),
balanceEnergyToCoverKwh: num(input.balance_energy_to_cover_kwh, NaN),
balanceStatusOk: cleanText(input.balance_status).toLowerCase() === 'ok',
balanceLastRunMs: parseAdvisorRunToMs(cleanText(input.balance_last_run)),
balanceLastRunMs: parseRunStampToMs(cleanText(input.balance_last_run)),
// Duree de la fenetre d'etablissement. Valeur mesuree le 31/08/2026 : le
// transitoire de fermeture dure ~150 s (import 1985 -> 25 W et 2600 -> 52 W,

View File

@@ -268,31 +268,18 @@ const batteryMorningTargetRulePct =
: params.evcsNightTargetWeakForecastPct;
// =====================================================
// ADVISOR IA — PREMIER CÂBLAGE RÉEL (09/08/2026, HANDOFF §20)
// La cible SOC matin suit la recommandation de l'advisor UNIQUEMENT si :
// - toggle input_boolean.evcs_advisor_apply_night_target ON (rollback 1 clic)
// - dernier run advisor en statut 'ok'
// - recommandation fraîche (≤ 120 min, l'advisor tourne toutes les 15 min)
// - valeur bornée [max(22, hard floor), 90]
// Sinon : règles forecast historiques (comportement inchangé).
// LE CONSEILLER NE FIXE PLUS LA CIBLE (retire le 13/09/2026)
// =====================================================
const ADVISOR_TARGET_MAX_AGE_MIN = 120;
const advisorTargetAgeMin = minutesSince(params.advisorLastRunMs, nowMs);
const advisorNightTargetApplied =
params.advisorApplyNightTarget &&
params.advisorStatusOk &&
Number.isFinite(params.advisorNightTargetSocPct) &&
advisorTargetAgeMin !== null &&
advisorTargetAgeMin <= ADVISOR_TARGET_MAX_AGE_MIN;
const batteryMorningTargetBasePct =
advisorNightTargetApplied
? clamp(
params.advisorNightTargetSocPct,
Math.max(22, batteryHardFloorPct),
90
)
: batteryMorningTargetRulePct;
// Le premier cablage reel (09/08/2026, HANDOFF §20) faisait suivre a la cible
// du matin `input_number.evcs_advisor_night_target_soc`, derriere un toggle,
// un statut `ok`, une fraicheur de 120 min et un bornage.
// Le 12/09 le conseiller est devenu OBSERVATEUR : il n'emet plus de cible, et
// ce helper est fige depuis. Les quatre gardes continuaient pourtant de passer
// toutes les quatre — la garde de fraicheur date le RUN, pas la VALEUR, et le
// conseiller tourne toujours. Activer le toggle aurait donc applique un chiffre
// perime sans qu'aucune garde ne s'y oppose. Un cablage qui ne peut plus que se
// tromper se retire ; le scenario `advisor-cible-ignoree` le verifie.
const batteryMorningTargetBasePct = batteryMorningTargetRulePct;
// =====================================================
// CIBLE DU MATIN : UNE ENERGIE A COUVRIR, PAS UN PALIER (03/09/2026)
@@ -1059,7 +1046,6 @@ return [{
balanceTargetAgeMin:
balanceTargetAgeMin === null ? null : Math.round(balanceTargetAgeMin * 10) / 10,
batteryMorningTargetRulePct,
advisorNightTargetApplied,
maxBatteryDischargePoints,
gridTakeoverTriggerPct,