L auteur des transitions etait lisible depuis le debut, on cherchait le mauvais champ
auteurDeLaTransition filtrait sur x.context_name. Ce champ n existe pas dans le journal de bord de Home Assistant : la fonction rendait null a tous les coups et les 416 transitions journalisees portaient un auteur vide. Personne ne s en etait apercu parce qu un auteur absent ressemble a un auteur inconnaissable. Le contexte etait la, sous d autres noms, avec trois signatures distinctes relevees sur des transitions dont je connaissais l auteur : une ligne voisine script/automation qui se nomme ; un service exterieur dont la rafale se CLOT par une ligne de narration portant le nom du workflow ET sa raison ; ou une simple derivation depuis un autre capteur — qui n est pas un auteur et ne doit pas etre annoncee comme tel. On balaie vers l AVANT pour trouver la narration : un workflow ecrit ses helpers puis se journalise, donc vers l arriere on ramasserait celle du precedent. Verifie sur la seconde ou l Auto Charge Manager et le Water Heater Advisor ecrivent ensemble — chaque helper revient a son propre workflow, 3 sur 3. Benefice non cherche : le declencheur porte desormais la raison que le workflow a lui-meme publiee. C est ce que « expliquer les decisions » reclamait, et c etait deja dans le journal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
@@ -171,6 +171,36 @@ const txt = (s) => (s ?? '').toString().trim();
|
||||
//
|
||||
// Un appel par transition reelle — quelques-uns par jour, pas par cycle. En cas
|
||||
// d echec on n invente rien : pas d auteur, et la ligne est ecrite quand meme.
|
||||
// ---------------------------------------------------------------------
|
||||
// QUI A PROVOQUE CETTE TRANSITION — reecrit le 14/09/2026
|
||||
// ---------------------------------------------------------------------
|
||||
// ⚠️ La version precedente filtrait sur `x.context_name`. CE CHAMP N EXISTE
|
||||
// PAS dans le journal de bord de Home Assistant : la fonction rendait `null` a
|
||||
// tous les coups et les 416 transitions journalisees portaient un auteur vide.
|
||||
// Personne ne s en etait apercu parce qu un auteur absent ressemble a un auteur
|
||||
// inconnaissable. Or le contexte EST la, sous d autres noms.
|
||||
//
|
||||
// Trois signatures, relevees sur des transitions dont je connaissais l auteur :
|
||||
//
|
||||
// automatisation / script ligne voisine dont l entity_id est `script.*` ou
|
||||
// `automation.*` — elle se nomme elle-meme.
|
||||
// service exterieur (n8n) `context_user_id` sur la ligne cible, et la rafale
|
||||
// se CLOT par une ligne de NARRATION (un `message`
|
||||
// sans `state`) qui porte le nom du workflow et sa
|
||||
// raison : « Victron Auto Charge », « EVCS Bilan
|
||||
// energetique (ombre) ».
|
||||
// derivation ni l un ni l autre, mais `context_entity_id` : la
|
||||
// valeur DECOULE d un autre capteur. Ce n est PAS un
|
||||
// auteur et il ne faut pas l annoncer comme tel.
|
||||
//
|
||||
// ⚠️ ON BALAIE VERS L AVANT pour trouver la narration, jamais vers l arriere :
|
||||
// un workflow ecrit ses helpers PUIS se journalise. Vers l arriere on
|
||||
// ramasserait la narration du workflow PRECEDENT. Verifie sur le cas ou l Auto
|
||||
// Charge Manager et le Water Heater Advisor ecrivent dans la MEME seconde :
|
||||
// chaque helper revient bien a son propre workflow, 3 sur 3.
|
||||
const NARRATION = (x) => x && x.message !== undefined && x.state === undefined;
|
||||
const DELAI_NARRATION_MS = 1500;
|
||||
|
||||
async function auteurDeLaTransition(entite, horodatageHa) {
|
||||
try {
|
||||
const t = Date.parse(horodatageHa);
|
||||
@@ -183,14 +213,38 @@ async function auteurDeLaTransition(entite, horodatageHa) {
|
||||
);
|
||||
if (!r.ok) return null;
|
||||
const lignes = await r.json();
|
||||
const l = (Array.isArray(lignes) ? lignes : []).find((x) => x.entity_id === entite && x.context_name);
|
||||
if (!l) return null;
|
||||
return {
|
||||
auteur: l.context_entity_id || l.context_name,
|
||||
nom: l.context_name,
|
||||
type: l.context_domain || l.context_event_type || null,
|
||||
declencheur: l.context_message || null,
|
||||
};
|
||||
if (!Array.isArray(lignes)) return null;
|
||||
|
||||
const i = lignes.findIndex((x) => x.entity_id === entite && x.state !== undefined
|
||||
&& Math.abs(Date.parse(x.when) - t) < 2000);
|
||||
if (i < 0) return null;
|
||||
const cible = lignes[i];
|
||||
|
||||
const acteur = lignes.find((x) => /^(automation|script)\./.test(String(x.entity_id || ''))
|
||||
&& Math.abs(Date.parse(x.when) - t) < 2000);
|
||||
if (acteur) {
|
||||
return { auteur: acteur.entity_id, nom: acteur.name || acteur.entity_id,
|
||||
type: acteur.entity_id.split('.')[0], declencheur: cible.context_service || null };
|
||||
}
|
||||
|
||||
if (cible.context_user_id) {
|
||||
const n = lignes.slice(i + 1).find(NARRATION);
|
||||
if (n && Date.parse(n.when) - t < DELAI_NARRATION_MS) {
|
||||
// `declencheur` porte la RAISON que le workflow a lui-meme publiee.
|
||||
// C est ce que « expliquer les decisions » reclamait, et c etait deja la.
|
||||
return { auteur: n.entity_id || n.name, nom: n.name, type: 'service',
|
||||
declencheur: (n.message || '').slice(0, 200) };
|
||||
}
|
||||
return { auteur: cible.context_user_id, nom: 'service externe (non nomme)', type: 'service',
|
||||
declencheur: cible.context_domain && cible.context_service
|
||||
? `${cible.context_domain}.${cible.context_service}` : null };
|
||||
}
|
||||
|
||||
if (cible.context_entity_id) {
|
||||
return { auteur: cible.context_entity_id, nom: cible.context_entity_id,
|
||||
type: 'derivation', declencheur: null };
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -409,8 +463,10 @@ async function detecterEvenements(sql, st) {
|
||||
// que Grafana affiche en annotation sur tous les tableaux et dans les
|
||||
// panneaux « journal des evenements », et que le rapport de nuit reprend.
|
||||
// Le mettre la le rend visible partout sans toucher a un seul tableau.
|
||||
// Une derivation n'est pas un ecrivain : « par sensor.X » serait un
|
||||
// contresens pour une valeur qui DECOULE d'un autre capteur.
|
||||
const resume = `${libelle} : ${prec} -> ${cur}`
|
||||
+ (qui ? ` — par ${qui.nom}` : '');
|
||||
+ (qui ? (qui.type === 'derivation' ? ` — derive de ${qui.nom}` : ` — par ${qui.nom}`) : '');
|
||||
await journaliser(sql, genre, resume, {
|
||||
entite: id, avant: prec, apres: cur,
|
||||
horodatage_ha: e.last_changed,
|
||||
|
||||
Reference in New Issue
Block a user