Le conseiller ne lisait pas mal : on lui parlait mal

Reprise de la campagne apres la specialisation en observateur. Le harnais
affichait encore `strategy` et `night_target_soc`, deux champs que
l observateur n emet plus : il mesurait des undefined. Reecrit pour noter
ce qui compte chez un observateur — la gravite tombe-t-elle dans la
fourchette attendue, et a-t-il NOMME la chose, par des mots-cles choisis
hors du vocabulaire du prompt.

Quatre defauts trouves, aucun n etant un defaut de raisonnement du modele :

- « Contacteur FERME » : pour un relais, ferme veut dire qui laisse passer.
  Le modele l a lu comme coupe et a demande, reseau present, « pourquoi le
  contacteur reste FERME alors que le bilan juge le reseau necessaire ».
- Le signe de l excedent : le prompt affichait « 14 W », il a ecrit
  « negatif (-14 W) » — et sur un autre cas « negatif » puis « positif » au
  passage suivant, sur le MEME prompt.
- « day_vehicle_full_idle » ne veut rien dire pour lui : il signalait une
  ressource refusee sans voir que le vehicule est PLEIN.
- Un excedent calcule sous censure ne veut rien dire : sur le BOOST du
  11/09 il concluait « pas de soleil, donc le reseau est normal » alors que
  les 121 W de PV sont bas PARCE QUE la consigne de 8260 W les evince.

Plus une grandeur derivee de plus, `consigne - maison`, parce qu il voyait
la version flagrante du defaut (SOC 20, 0 A) et manquait la version
discrete (SOC 45, +2 A). Elle a d abord fait TOMBER le cas du BOOST, en
annoncant « il reste 7005 W pour recharger » comme une bonne nouvelle sur
une batterie a 96 % : corrigee en lui faisant dire l inverse quand il n y a
rien a recharger, la question ayant deja une reponse dans le bilan.

Trois defauts etaient les miens : deux fixtures decrivaient un etat que
l installation ne peut pas prendre — c est le conseiller qui l a signale —
et un attendu exigeait « haute » la ou l echelle de gravite du prompt ancre
elle-meme cet incident sur « moyenne ».

Incidents 39/40 au final, contre 23/35 au depart. Les deux cas de vrai
silence restent silencieux 5/5.

⚠️ Trouve en marge : cinq helpers du conseiller sont geles depuis la
specialisation, et le cablage de la cible SOC matin les lit encore — ses
quatre gardes passent toutes, la fraicheur datant le RUN et non la VALEUR.

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 11:59:24 +02:00
parent 7973fa4c3f
commit 4a9ad0bc65
16 changed files with 12179 additions and 470 deletions

View File

@@ -107,6 +107,14 @@ const ctx = {
// Meme principe que le bilan : on lui donne la grandeur, pas les ingredients.
const maisonHorsVoitureW = Math.max(0, ctx.houseW - (ctx.evcsCharging ? ctx.evcsCurrentA * 230 : 0));
const excedentSolaireW = Math.round(ctx.pvW - maisonHorsVoitureW);
const margeRechargeW = ctx.gridOn && Number.isFinite(ctx.gridSetpointW)
? Math.round(ctx.gridSetpointW - maisonHorsVoitureW)
: null;
// Le bilan dit lui-meme si la batterie doit monter. A defaut de bilan
// exploitable on ne tranche pas, et la marge reste un simple constat.
const batterieDoitMonter = Number.isFinite(ctx.balanceRequiredSoc) && Number.isFinite(ctx.soc)
? ctx.soc < ctx.balanceRequiredSoc
: null;
const margeDechargeA = Number.isFinite(ctx.bmsMaxDischargeA) && Number.isFinite(ctx.batteryCurrentA)
? Math.round(ctx.bmsMaxDischargeA - Math.abs(Math.min(0, ctx.batteryCurrentA)))
: null;
@@ -213,14 +221,46 @@ const systemPrompt = [
' evince la production — 5,6 kW de solaire remplaces par du reseau.',
'- haute : un actionneur bascule de facon repetee, OU le materiel est sollicite',
' pres de ses limites, OU un automate defait ce qu un autre vient de',
' faire. Exemple reel : le contacteur reseau qui bascule six fois en',
' dix minutes le 12/09 — chaque bascule coute 50 a 150 Wh et use le',
' relais, et aucun automate ne le voyait.',
' faire. Deux exemples reels : le contacteur reseau qui bascule six',
' fois en dix minutes le 12/09 — chaque bascule coute 50 a 150 Wh et',
' use le relais, et aucun automate ne le voyait ; et le 05/09 a 20:40,',
' le parc a -97,4 A pour une limite declaree de 100 A, soit 3 A de',
' marge, pendant que le reseau restait disponible et inutilise.',
'UNE OSCILLATION EST TOUJOURS DE GRAVITE HAUTE, meme si chaque bascule prise',
'isolement parait anodine : c est la repetition qui fait le degat, et c est',
'precisement ce qu aucune regle locale ne peut voir.',
].join('\n');
// Les etats du controleur sont des identifiants internes. Le conseiller n'a
// aucune raison de savoir que `day_vehicle_full_idle` veut dire « le vehicule
// est plein, il n'absorbera plus rien » — et sans le savoir il signale comme
// une anomalie un systeme qui a raison.
const ETATS = {
vehicle_not_connected: 'aucun vehicule branche',
idle_wait_conditions: 'a l arret, conditions de demarrage non reunies',
idle_wait_source: 'a l arret, en attente d une source',
day_vehicle_full_idle: 'vehicule PLEIN, il n absorbera plus rien (memoire du jour)',
night_recovery_done_idle: 'charge de nuit terminee ET batterie a la cible',
night_session_done_idle: 'charge de nuit terminee, batterie encore sous la cible',
departure_done_idle: 'charge avant depart terminee',
vehicle_full_or_paused: 'le vehicule ne demande plus de courant',
start_min_current: 'demarrage au courant minimal',
start_grid_hc_min_current: 'demarrage sur le reseau en heures creuses',
start_manual_local_min_current: 'demarrage manuel au courant minimal',
charging_hold_grid: 'en charge sur le reseau, consigne tenue',
charging_hold_manual_local: 'en charge, consigne manuelle',
charging_hold_cloud_tolerance: 'en charge, tolerance nuage en cours',
charging_step_up_battery: 'en charge, courant en hausse sur le solaire',
charging_step_up_grid_hc: 'en charge, courant en hausse sur le reseau',
charging_step_down_battery: 'en charge, courant en baisse',
charging_step_down_system_cap: 'en charge, courant ramene au plafond du systeme',
battery_current_hard_stop: 'ARRET : la batterie se dechargeait trop fort',
stop_conditions_lost: 'ARRET : conditions de maintien perdues',
stop_source_lost: 'ARRET : la source a disparu',
hold_min_run: 'maintenu le temps de la duree minimale de marche',
};
const etatLisible = (e) => ETATS[e] ? `${ETATS[e]} (${e})` : e;
function nombre(v, unite, defaut) {
return Number.isFinite(v) ? `${v}${unite}` : (defaut || 'inconnu');
}
@@ -247,13 +287,13 @@ const userPrompt = [
`- PV : ${Math.round(ctx.pvW)} W instantane, ${Math.round(ctx.pvAvg10mW)} W sur 10 min`,
`- Chargeurs solaires : ${ctx.mppt.aFond}/${ctx.mppt.total} a pleine puissance, ${ctx.mppt.brides} qui se BRIDENT`,
`- Maison : ${Math.round(ctx.houseW)} W, dont voiture ${ctx.evcsCharging ? Math.round(ctx.evcsCurrentA * 230) : 0} W`,
`- EXCEDENT SOLAIRE (production moins maison hors voiture) : ${excedentSolaireW} W`,
`- EXCEDENT SOLAIRE (production moins maison hors voiture) : ${excedentSolaireW > 0 ? `+${excedentSolaireW} W, il RESTE du solaire` : excedentSolaireW < 0 ? `${excedentSolaireW} W` : '0 W, exactement a l equilibre'}${ctx.mppt.brides > 0 ? ` — ATTENTION, ce chiffre est CENSURE : ${ctx.mppt.brides} chargeurs se retiennent, la ressource reelle est PLUS HAUTE${excedentSolaireW < 0 ? ' et ce chiffre negatif ne prouve PAS qu il manque du soleil' : ''}` : excedentSolaireW < 0 ? ', DEFICIT reel : la batterie porte deja la maison' : ''}`,
`- MARGE DE DECHARGE du parc : ${margeDechargeA === null ? 'inconnue' : `${margeDechargeA} A restants`}${partDuPlafond === null ? '' : ` — la batterie est a ${partDuPlafond} % de son plafond`}`,
`- Contacteur EDF : ${ctx.gridOn ? 'FERME' : 'ouvert'} (AC-IN ${ctx.acinAvailable ? 'present' : 'absent'})`,
`- Consigne de puissance reseau : ${nombre(ctx.gridSetpointW, ' W')}`,
`- Reseau EDF : ${ctx.gridOn ? 'PRESENT, il peut alimenter la maison' : 'ABSENT, la maison vit sur batterie et solaire'} (contacteur ${ctx.gridOn ? 'ferme' : 'ouvert'}, AC-IN ${ctx.acinAvailable ? 'present' : 'absent'})`,
`- Consigne de puissance reseau : ${nombre(ctx.gridSetpointW, ' W')}${margeRechargeW === null ? '' : batterieDoitMonter === false && margeRechargeW > 0 ? ` — la batterie n a PAS besoin de monter (SOC ${ctx.soc} %, bilan ${ctx.balanceRequiredSoc} %), donc ces ${margeRechargeW} W de demande reseau n ont aucune destination : ils ne peuvent que remplacer du solaire` : margeRechargeW > 0 ? ` — il reste ${margeRechargeW} W pour RECHARGER la batterie, qui en a besoin` : ' — une fois la maison servie il ne reste RIEN pour recharger la batterie'}`,
`- Limites declarees par le parc : charge ${nombre(ctx.bmsMaxChargeA, ' A')}, decharge ${nombre(ctx.bmsMaxDischargeA, ' A')}`,
`- Vehicule : ${ctx.vehicleConnected ? `BRANCHE (${ctx.vehicleSelected})` : 'AUCUN branche'}${ctx.evcsCharging ? `, en charge a ${ctx.evcsCurrentA} A` : ''}`,
`- Controleur : ${ctx.surplusState} | source ${ctx.sourceMode} | strategie reelle ${ctx.userStrategy}`,
`- Controleur : ${etatLisible(ctx.surplusState)} | source ${ctx.sourceMode} | strategie reelle ${ctx.userStrategy}`,
`- Cible SOC matin actuellement appliquee : ${ctx.nightTargetActual} %`,
`- Prevision : ${ctx.todayRemainingKwh} kWh restant aujourd hui, ${ctx.tomorrowKwh} kWh demain`,
'',