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

@@ -5964,9 +5964,30 @@ traverse. Si le toggle `evcs_advisor_apply_night_target` est active, la cible de
la nuit devient un 55 fige d hier matin. la nuit devient un 55 fige d hier matin.
La garde de fraicheur ne protege donc pas ce qu elle croit proteger : elle date La garde de fraicheur ne protege donc pas ce qu elle croit proteger : elle date
le RUN, pas la VALEUR. Le toggle est OFF aujourd hui. Le cablage est devenu du le RUN, pas la VALEUR.
code mort qui ne peut que se tromper — a retirer (trois scenarios golden), mais
c est une decision du proprietaire, §20 etant un jalon qu il avait valide. **RETIRE le 13/09/2026 sur decision du proprietaire.** Les quatre parametres
d INPUT_PARSE, le bloc de WINDOWS_SOURCEV2, le passe-plat de BATTERY_CLOUD_LOCKS,
la trace `(ADVISOR)` de DECISION_ENGINEV2 et les quatre champs du gabarit de
snapshot. `batteryMorningTargetBasePct` vaut desormais simplement la regle de
prevision ; le cablage du BILAN (§39), lui, est intact et continue de ne pouvoir
que RELEVER cette cible.
`parseAdvisorRunToMs` est renommee `parseRunStampToMs` : elle sert encore au
bilan, qui publie le meme format, mais son nom designait un appelant disparu.
Golden 98 → 96 : les trois scenarios du cablage (applique / perime / toggle OFF)
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. Preuve par mutation : cablage restaure → ce scenario
tombe avec `target=22% (ADVISOR)` dans la raison ; retire → 96/96.
Gabarit re-rendu contre Home Assistant : 196 champs, JSON valide, aucun null,
plus aucun champ advisor.
⚠️ RESTE : les cinq helpers eux-memes existent toujours cote Home Assistant et
la carte « Conseiller IA » affiche encore « Cible SOC matin (agent) : 55 % »,
figee depuis le 12/09. Plus rien ne la lit, mais un chiffre fige affiche comme
une recommandation courante est exactement le fantome qui a cree le danger
ci-dessus. A nettoyer (paquet + carte).
--- ---
*Document mis a jour le : 13/09/2026 — campagne conseiller : quatre defauts du prompt corriges, incidents 39/40, table `observation`* *Document mis a jour le : 13/09/2026 — campagne conseiller : quatre defauts du prompt corriges, incidents 39/40, table `observation`*

View File

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

View File

@@ -1319,7 +1319,7 @@ else {
// ===================================================== // =====================================================
if (nightEvcs?.nightSessionActive) { if (nightEvcs?.nightSessionActive) {
reason.push( 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)) { if (Number.isFinite(nightEvcs.nightSessionStartSocPct)) {

View File

@@ -141,10 +141,6 @@
"prev_surplus_state": states("input_text.victron_evcs_surplus_state"), "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_soc": states("input_text.victron_evcs_night_session_start_soc"),
"evcs_night_session_start_at": states("input_text.victron_evcs_night_session_start_at"), "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_required_soc": states("input_number.evcs_balance_required_soc"),
"balance_energy_to_cover_kwh": states("input_number.evcs_balance_energy_to_cover_kwh"), "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; 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 // (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. // 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})$/); const m = (s ?? '').toString().trim().match(/^(\d{2})\/(\d{2}) (\d{2}):(\d{2})$/);
if (!m) return null; if (!m) return null;
@@ -464,17 +466,13 @@ const params = {
// Advisor IA — premier câblage réel (09/08/2026, HANDOFF §20) : // 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. // 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). // 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 // BILAN ENERGETIQUE (03/09/2026). Meme format d'horodatage que l'advisor
// ("DD/MM HH:mm"), d'ou la reutilisation du parseur. // ("DD/MM HH:mm"), d'ou la reutilisation du parseur.
balanceRequiredSocPct: num(input.balance_required_soc, NaN), balanceRequiredSocPct: num(input.balance_required_soc, NaN),
balanceEnergyToCoverKwh: num(input.balance_energy_to_cover_kwh, NaN), balanceEnergyToCoverKwh: num(input.balance_energy_to_cover_kwh, NaN),
balanceStatusOk: cleanText(input.balance_status).toLowerCase() === 'ok', 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 // 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, // transitoire de fermeture dure ~150 s (import 1985 -> 25 W et 2600 -> 52 W,

View File

@@ -268,31 +268,18 @@ const batteryMorningTargetRulePct =
: params.evcsNightTargetWeakForecastPct; : params.evcsNightTargetWeakForecastPct;
// ===================================================== // =====================================================
// ADVISOR IA — PREMIER CÂBLAGE RÉEL (09/08/2026, HANDOFF §20) // LE CONSEILLER NE FIXE PLUS LA CIBLE (retire le 13/09/2026)
// 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é).
// ===================================================== // =====================================================
const ADVISOR_TARGET_MAX_AGE_MIN = 120; // Le premier cablage reel (09/08/2026, HANDOFF §20) faisait suivre a la cible
const advisorTargetAgeMin = minutesSince(params.advisorLastRunMs, nowMs); // du matin `input_number.evcs_advisor_night_target_soc`, derriere un toggle,
const advisorNightTargetApplied = // un statut `ok`, une fraicheur de 120 min et un bornage.
params.advisorApplyNightTarget && // Le 12/09 le conseiller est devenu OBSERVATEUR : il n'emet plus de cible, et
params.advisorStatusOk && // ce helper est fige depuis. Les quatre gardes continuaient pourtant de passer
Number.isFinite(params.advisorNightTargetSocPct) && // toutes les quatre — la garde de fraicheur date le RUN, pas la VALEUR, et le
advisorTargetAgeMin !== null && // conseiller tourne toujours. Activer le toggle aurait donc applique un chiffre
advisorTargetAgeMin <= ADVISOR_TARGET_MAX_AGE_MIN; // 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 = const batteryMorningTargetBasePct = batteryMorningTargetRulePct;
advisorNightTargetApplied
? clamp(
params.advisorNightTargetSocPct,
Math.max(22, batteryHardFloorPct),
90
)
: batteryMorningTargetRulePct;
// ===================================================== // =====================================================
// CIBLE DU MATIN : UNE ENERGIE A COUVRIR, PAS UN PALIER (03/09/2026) // CIBLE DU MATIN : UNE ENERGIE A COUVRIR, PAS UN PALIER (03/09/2026)
@@ -1059,7 +1046,6 @@ return [{
balanceTargetAgeMin: balanceTargetAgeMin:
balanceTargetAgeMin === null ? null : Math.round(balanceTargetAgeMin * 10) / 10, balanceTargetAgeMin === null ? null : Math.round(balanceTargetAgeMin * 10) / 10,
batteryMorningTargetRulePct, batteryMorningTargetRulePct,
advisorNightTargetApplied,
maxBatteryDischargePoints, maxBatteryDischargePoints,
gridTakeoverTriggerPct, gridTakeoverTriggerPct,

View File

@@ -140,12 +140,16 @@ export const scenarios = [
}, },
}, },
// ===== ADVISOR IA — premier câblage réel (09/08/2026, HANDOFF §20) ===== // ===== LE CONSEILLER NE FIXE PLUS LA CIBLE (13/09/2026) =====
// Référence : night-soc-sous-cible (SOC 30 < cible règle 35 → grid_forced). // Les trois scenarios du premier cablage (applique / perime / toggle OFF)
// Avec la cible advisor 22 APPLIQUÉE : SOC 30 ≥ 22 → grid_hc_allowed. // sont remplaces par celui-ci, qui pinne l'ABSENCE de cablage : toutes les
// conditions de l'ancienne regle sont reunies — toggle ON, statut ok,
// horodatage frais, valeur dans les bornes — et la cible doit rester celle
// des regles de prevision. Si quelqu'un recable un jour, ce test tombe.
// Reference : night-soc-sous-cible (SOC 30 < cible regle 35 → grid_forced).
{ {
id: 'night-advisor-target-applique', id: 'advisor-cible-ignoree',
label: 'Nuit HC, SOC 30, advisor cible 22 appliquée (toggle ON, ok, frais) → hc_allowed au lieu de grid_forced', label: 'Nuit HC, SOC 30, helpers advisor au complet et frais → IGNORES, cible des regles (grid_forced)',
time: NIGHT, time: NIGHT,
overrides: { overrides: {
...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on', ...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on',
@@ -154,52 +158,6 @@ export const scenarios = [
advisor_status: 'ok', advisor_status: 'ok',
advisor_last_run: '08/08 01:20', advisor_last_run: '08/08 01:20',
}, },
expect: {
profile: 'night_hc',
sourceMode: 'grid_hc_allowed',
state: 'night_wait_grid_takeover',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
edfOnAction: true,
takeoverHelperService: 'turn_on',
},
},
{
id: 'night-advisor-target-stale',
label: 'Nuit HC, SOC 30, advisor cible 22 mais recommandation > 120 min → fallback règles (grid_forced)',
time: NIGHT,
overrides: {
...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on',
advisor_apply_night_target: 'on',
advisor_night_target_soc: '22',
advisor_status: 'ok',
advisor_last_run: '07/08 20:00',
},
expect: {
profile: 'night_hc',
sourceMode: 'grid_forced',
state: 'night_wait_grid_takeover',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
edfOnAction: true,
takeoverHelperService: 'turn_on',
},
},
{
id: 'night-advisor-toggle-off',
label: 'Nuit HC, SOC 30, advisor cible 22 mais toggle APPLIQUER OFF → règles inchangées (grid_forced)',
time: NIGHT,
overrides: {
...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on',
advisor_apply_night_target: 'off',
advisor_night_target_soc: '22',
advisor_status: 'ok',
advisor_last_run: '08/08 01:20',
},
expect: { expect: {
profile: 'night_hc', profile: 'night_hc',
sourceMode: 'grid_forced', sourceMode: 'grid_forced',
@@ -212,11 +170,6 @@ export const scenarios = [
takeoverHelperService: 'turn_on', takeoverHelperService: 'turn_on',
}, },
}, },
// ===== P9 09/08/2026 : charge réseau avant départ (hors HC) + sélecteur véhicule =====
// VW départ 08:50 (helper existant), JEEP départ 06:30 (nouveau helper).
// À 08:00 (morning_solar, PV nul) : VW → fenêtre départ active (50 min) ;
// JEEP → départ passé depuis 90 min → rien.
{ {
id: 'p9-depart-vw-grid', id: 'p9-depart-vw-grid',
label: 'Matin 08:00, VW sélectionnée, départ 08:50, toggle P9 ON, SOC 45, PV nul → réseau autorisé + EDF demandé', label: 'Matin 08:00, VW sélectionnée, départ 08:50, toggle P9 ON, SOC 45, PV nul → réseau autorisé + EDF demandé',

File diff suppressed because one or more lines are too long