Le conseiller devient observateur, et une couche d enquete voit le jour
SPECIALISATION. Campagne du 12/09 : lui demander d observer ET de planifier degradait les deux — l ecart de cible entre passages est passe de 0 a 35 points sur certains cas des l ajout de la mission d observation. Il ne planifie plus : la planification est faite par le bilan, qui est calcule, mesure et teste a 48/48. Sortie reduite a constat / gravite / ce_qui_cloche / piste. Prompt 7609 -> 6250 caracteres. Mesure apres specialisation, horloge epinglee : oscillation reelle detectee 5/5, contrefactuel calme 5/5. La discrimination est parfaite. ⚠️ Mais la gravite est retombee de « haute » a « moyenne » : mon echelle a quatre niveaux n est ancree sur aucun exemple. Defaut de briefing a corriger, pas defaut de detection. Le compteur mesure desormais le VOLUME de signalements : on ne peut pas verifier automatiquement s il a raison, on peut mesurer s il crie au loup. COUCHE D ENQUETE. Trouver le coupable ce matin a demande sept requetes enchainees ; rien n y exigeait un humain, seulement d appeler des outils et d enchainer d apres ce qu on vient de lire. Six sous-commandes en LECTURE SEULE, exposees en ligne de commande pour qu OpenClaw — que le proprietaire veut utiliser — ou tout autre pilote les appelle sans reecriture : bascules, mesures, decisions, ecrivains, bilan, cherche. `bascules` calcule l ecart entre transitions successives du meme objet : c est la signature d une oscillation, et l appelant n a pas a la refaire. L enquete de ce matin se rejoue en trois commandes, jusqu a automations.yaml ligne 22. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
@@ -34,64 +34,30 @@ setText('input_text.evcs_advisor_last_run', stampParis());
|
||||
setText('input_text.evcs_advisor_model', modelUsed);
|
||||
|
||||
if (status === 'ok' && advice) {
|
||||
// Observation — la sortie principale desormais.
|
||||
// Observation — la seule sortie desormais. Le conseiller ne planifie plus :
|
||||
// la planification est faite par le bilan, qui est calcule et mesure, et la
|
||||
// campagne du 12/09 a montre que lui demander les deux degradait les deux
|
||||
// (ecart de cible passe de 0 a 35 points sur certains cas des l ajout de la
|
||||
// mission d observation).
|
||||
setText('input_text.evcs_advisor_constat', advice.constat);
|
||||
setText('input_text.evcs_advisor_gravite', advice.gravite);
|
||||
setText('input_text.evcs_advisor_ce_qui_cloche', advice.ceQuiCloche || 'rien de signale');
|
||||
setText('input_text.evcs_advisor_reason', `[${Math.round(advice.confidence * 100)}%] ${advice.piste || advice.constat}`);
|
||||
|
||||
// Recommandations
|
||||
setText('input_text.evcs_advisor_strategy', advice.strategy);
|
||||
// Les champs d action ne sont publies que s ils sont exploitables : un
|
||||
// helper numerique ne doit jamais porter une valeur hors bornes.
|
||||
if (advice.actionUtilisable) {
|
||||
setNumber('input_number.evcs_advisor_night_target_soc', advice.nightTargetSoc);
|
||||
setNumber('input_number.evcs_advisor_current_max_a', advice.currentMaxA);
|
||||
}
|
||||
setText('input_text.evcs_advisor_ev_charge_tonight', advice.evChargeTonight);
|
||||
setText('input_text.evcs_advisor_reason', `[${Math.round(advice.confidence * 100)}%] ${advice.reason}`);
|
||||
|
||||
// Divergence vs réel (stratégie + cible nuit)
|
||||
const diffs = [];
|
||||
if (advice.strategy !== context.userStrategy) {
|
||||
diffs.push(`stratégie: agent=${advice.strategy} ≠ réel=${context.userStrategy}`);
|
||||
}
|
||||
if (Math.abs(advice.nightTargetSoc - context.nightTargetActual) > 10) {
|
||||
diffs.push(`cible nuit: agent=${advice.nightTargetSoc}% vs réel=${context.nightTargetActual}%`);
|
||||
}
|
||||
const divergenceText = diffs.length ? diffs.join(' | ') : 'accord avec le réel';
|
||||
setText('input_text.evcs_advisor_divergence', divergenceText);
|
||||
|
||||
// ⚠️ LE COMPTEUR MESURAIT LE MAUVAIS CHAMP (corrige le 12/09/2026).
|
||||
// Il comparait la STRATEGIE, qui vaut `auto_solar_plus` la quasi-totalite du
|
||||
// temps : 517 accords contre 23 divergences, soit 95,7 % — un score qui
|
||||
// mesurait une constante, pas une decision. Pendant ce temps la cible SOC,
|
||||
// seul champ reellement cable, divergeait de treize points du bilan sans
|
||||
// que rien ne le compte (12/09 05:30 : conseiller 35 %, bilan 48,4 %).
|
||||
//
|
||||
// On compte desormais l'accord sur la CIBLE, et contre le BILAN — la
|
||||
// reference, pas contre la valeur appliquee qui peut elle-meme etre fausse.
|
||||
// Bilan indisponible => on ne compte rien plutot que de compter faux.
|
||||
const refSoc = Number(context.balanceRequiredSoc);
|
||||
const bilanUtilisable = advice.actionUtilisable && context.balanceStatus === 'ok' && Number.isFinite(refSoc);
|
||||
if (bilanUtilisable) {
|
||||
const ecart = Math.abs(advice.nightTargetSoc - refSoc);
|
||||
if (ecart <= 5) {
|
||||
setNumber('input_number.evcs_advisor_agree_count', context.agreeCount + 1);
|
||||
} else {
|
||||
setNumber('input_number.evcs_advisor_diverge_count', context.divergeCount + 1);
|
||||
}
|
||||
setText(
|
||||
'input_text.evcs_advisor_divergence',
|
||||
`cible: agent=${advice.nightTargetSoc}% vs bilan=${Math.round(refSoc)}% (ecart ${Math.round(ecart)} pts)`
|
||||
+ (diffs.length ? ` | ${diffs.join(' | ')}` : '')
|
||||
);
|
||||
// Un observateur qui signale en permanence ne sert a rien : on compte le
|
||||
// VOLUME de signalements pour pouvoir le constater.
|
||||
if (advice.gravite === 'aucune') {
|
||||
setNumber('input_number.evcs_advisor_agree_count', context.agreeCount + 1);
|
||||
} else {
|
||||
setNumber('input_number.evcs_advisor_diverge_count', context.divergeCount + 1);
|
||||
}
|
||||
|
||||
// Trace logbook pour l'historique long terme
|
||||
push('logbook', 'log', {
|
||||
name: 'EVCS Advisor (ombre)',
|
||||
message: `[${advice.gravite}] ${advice.constat}${advice.ceQuiCloche ? ` || ${advice.ceQuiCloche}` : ''} || ${advice.strategy} | cible nuit ${advice.nightTargetSoc}% | ${advice.evChargeTonight === 'oui' ? 'charge VE cette nuit' : advice.evChargeTonight === 'non' ? 'pas de charge nuit' : 'VE sans objet'} | ${divergenceText} | ${advice.reason}`,
|
||||
entity_id: 'input_text.evcs_advisor_strategy',
|
||||
name: 'EVCS Advisor (observateur)',
|
||||
message: `[${advice.gravite}] ${advice.constat}`
|
||||
+ (advice.ceQuiCloche ? ` || ${advice.ceQuiCloche}` : '')
|
||||
+ (advice.piste ? ` || piste : ${advice.piste}` : ''),
|
||||
entity_id: 'input_text.evcs_advisor_constat',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -192,45 +192,20 @@ const systemPrompt = [
|
||||
'est un ecrivain AUTRE QUE SON PROPRIETAIRE. Nomme-le.',
|
||||
// CARTE GENEREE >>>
|
||||
'',
|
||||
'STRATEGIES POSSIBLES :',
|
||||
'- auto_solar_plus : automatique, solaire prioritaire, reseau en heures creuses si besoin',
|
||||
'- auto_solar : solaire strict, jamais de reseau',
|
||||
'- force_grid_now : charge sur reseau EDF, immediate',
|
||||
'- force_charge_now : charge locale immediate quelle que soit la source',
|
||||
'- stop : tout couper',
|
||||
'',
|
||||
'TA MISSION PRINCIPALE : REPERER CE QUI CLOCHE.',
|
||||
'Cinq automates ecrivent sur la meme installation : le controleur EVCS, un',
|
||||
'automate d hysteresis pour le contacteur reseau, un gestionnaire de charge',
|
||||
'batterie, un bilan energetique, et des automatisations Home Assistant. Chacun',
|
||||
'est localement correct et aucun ne voit les autres. Les pannes de ce systeme',
|
||||
'ne sont donc presque jamais des erreurs de calcul : ce sont des CONTRADICTIONS',
|
||||
'entre automates, et elles se voient dans la SUITE des evenements, pas dans',
|
||||
'un instantane.',
|
||||
'',
|
||||
'Exemple reel du 12/09 : le contacteur reseau a bascule toutes les 4 minutes',
|
||||
'pendant dix minutes — coupe, remis, coupe. Un automate coupait a 35 % de SOC,',
|
||||
'un autre rallumait. Chacun faisait son travail. Le resultat etait absurde, et',
|
||||
'seul un humain qui regardait l a vu.',
|
||||
'',
|
||||
'Un comportement est ABSURDE quand : un actionneur bascule plusieurs fois en',
|
||||
'peu de temps ; deux decisions se contredisent ; une grandeur reste figee alors',
|
||||
'que ce qui la pilote bouge ; une action est repetee sans effet ; une valeur',
|
||||
'contredit ce que le bilan demande.',
|
||||
'',
|
||||
'Tu as le DERNIER HISTORIQUE DES EVENEMENTS plus bas. Sers-t en.',
|
||||
'TU NE PILOTES RIEN ET TU NE CHOISIS AUCUNE STRATEGIE.',
|
||||
'La planification energetique est faite par le bilan, qui est calcule et mesure.',
|
||||
'Ton seul travail est de REGARDER LE SYSTEME SE COMPORTER et de dire ce qui cloche.',
|
||||
'',
|
||||
'Reponds UNIQUEMENT avec un objet JSON valide, sans texte autour, au format EXACT :',
|
||||
'{"constat":"ce que tu observes, factuel, max 200 caracteres",',
|
||||
'"gravite":"aucune|faible|moyenne|haute",',
|
||||
'"ce_qui_cloche":"la contradiction ou l incoherence, ou \'\' si rien",',
|
||||
'"strategy":"auto_solar_plus|auto_solar|force_grid_now|force_charge_now|stop",',
|
||||
'"night_target_soc":<entier 22-90>,"current_max_a":<entier 6-28>,',
|
||||
'"ev_charge_tonight":"oui|non|sans_objet","confidence":<0.0-1.0>,',
|
||||
'"reason":"une phrase courte en francais (max 200 caracteres)"}',
|
||||
'"piste":"ce qu il faudrait verifier ou corriger, ou \'\' si rien",',
|
||||
'"confidence":<0.0-1.0>}',
|
||||
'',
|
||||
'gravite "aucune" quand tout est coherent — c est le cas le plus frequent et',
|
||||
'il ne faut pas inventer un probleme pour justifier une reponse.',
|
||||
'gravite "aucune" quand tout est coherent — c est le cas le plus frequent et de',
|
||||
'loin. N invente pas un probleme pour justifier une reponse : un observateur qui',
|
||||
'signale en permanence ne sert a rien.',
|
||||
].join('\n');
|
||||
|
||||
function nombre(v, unite, defaut) {
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
const resp = $input.first().json;
|
||||
const context = $('BUILD_PROMPT').first().json.context;
|
||||
|
||||
const STRATEGIES = ['auto_solar_plus', 'auto_solar', 'force_grid_now', 'force_charge_now', 'stop'];
|
||||
const TONIGHT = ['oui', 'non', 'sans_objet'];
|
||||
|
||||
function fail(status, detail) {
|
||||
return [{
|
||||
@@ -52,36 +50,6 @@ try {
|
||||
}
|
||||
|
||||
// 4. Validation stricte du schéma (enums + bornes)
|
||||
const strategy = (obj.strategy ?? '').toString().trim().toLowerCase();
|
||||
if (!STRATEGIES.includes(strategy)) return fail('invalid', `strategy hors enum: ${strategy}`);
|
||||
|
||||
const nightTargetRaw = Number(obj.night_target_soc);
|
||||
const nightTargetValide = Number.isFinite(nightTargetRaw) && nightTargetRaw >= 22 && nightTargetRaw <= 90;
|
||||
const nightTarget = nightTargetValide ? nightTargetRaw : null;
|
||||
|
||||
// ⚠️ L OBSERVATION NE DOIT PAS ETRE PERDUE A CAUSE D UN CHAMP D ACTION.
|
||||
// Essai du 12/09 : sur l oscillation du contacteur, le modele a produit un
|
||||
// constat JUSTE (« bascule toutes les 1-2 min depuis 06:25, gravite haute »)
|
||||
// ET un `current_max_a` de 100, hors bornes. Une validation tout-ou-rien
|
||||
// jetait le diagnostic avec le champ fautif — c est-a-dire exactement ce
|
||||
// qu on lui demande, perdu a cause de ce qu on ne lui demande plus.
|
||||
// Les champs d ACTION restent bornes aussi strictement qu avant ; seulement,
|
||||
// hors bornes, ils sont NEUTRALISES au lieu de faire echouer le constat.
|
||||
const currentMaxRaw = Number(obj.current_max_a);
|
||||
const currentMaxValide = Number.isFinite(currentMaxRaw) && currentMaxRaw >= 6 && currentMaxRaw <= 28;
|
||||
const currentMax = currentMaxValide ? currentMaxRaw : null;
|
||||
|
||||
const tonight = (obj.ev_charge_tonight ?? '').toString().trim().toLowerCase();
|
||||
if (!TONIGHT.includes(tonight)) return fail('invalid', `ev_charge_tonight hors enum: ${tonight}`);
|
||||
|
||||
const confidence = Number(obj.confidence);
|
||||
if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
|
||||
return fail('invalid', `confidence hors bornes: ${obj.confidence}`);
|
||||
}
|
||||
|
||||
const reason = (obj.reason ?? '').toString().trim();
|
||||
if (!reason) return fail('invalid', 'reason vide');
|
||||
|
||||
// ── OBSERVATION (12/09/2026) ───────────────────────────────────────────
|
||||
// La mission principale du conseiller n est plus de choisir une strategie
|
||||
// parmi cinq, mais de REPERER CE QUI CLOCHE. Les pannes de ce systeme sont
|
||||
@@ -95,6 +63,12 @@ if (!constat) return fail('invalid', 'constat vide');
|
||||
const gravite = (obj.gravite ?? '').toString().trim().toLowerCase();
|
||||
if (!GRAVITES.includes(gravite)) return fail('invalid', `gravite hors enum: ${obj.gravite}`);
|
||||
const ceQuiCloche = (obj.ce_qui_cloche ?? '').toString().trim();
|
||||
const piste = (obj.piste ?? '').toString().trim();
|
||||
|
||||
const confidence = Number(obj.confidence);
|
||||
if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
|
||||
return fail('invalid', `confidence hors bornes: ${obj.confidence}`);
|
||||
}
|
||||
|
||||
return [{
|
||||
json: {
|
||||
@@ -104,15 +78,8 @@ return [{
|
||||
constat: constat.slice(0, 200),
|
||||
gravite,
|
||||
ceQuiCloche: ceQuiCloche.slice(0, 200),
|
||||
strategy,
|
||||
nightTargetSoc: nightTarget === null ? null : Math.round(nightTarget),
|
||||
currentMaxA: currentMax === null ? null : Math.round(currentMax),
|
||||
// Vrai seulement si TOUS les champs d action sont dans leurs bornes.
|
||||
// C est ce drapeau, et lui seul, qui autoriserait un jour un cablage.
|
||||
actionUtilisable: currentMaxValide && nightTargetValide,
|
||||
evChargeTonight: tonight,
|
||||
piste: piste.slice(0, 200),
|
||||
confidence: Math.round(confidence * 100) / 100,
|
||||
reason: reason.slice(0, 250),
|
||||
},
|
||||
modelUsed: (resp.model || 'inconnu').toString().slice(0, 60),
|
||||
context,
|
||||
|
||||
Reference in New Issue
Block a user