Chaque transition dit desormais QUI l a provoquee
Le 12/09, le contacteur reseau a bascule six fois en dix minutes. Trouver le coupable a demande sept requetes enchainees — executions n8n des deux workflows, liste des ecrivains, puis automations.yaml — parce que la table des evenements disait QUE ca avait bascule, jamais PAR QUI. Home Assistant le savait pourtant. Son logbook resout le contexte d un changement d etat et nomme l auteur. Sur la coupure de 06:29:44 : auteur : automation.victron_grid_off_si_soc_35 declencheur : triggered by numeric state of sensor.victron_battery_soc Un appel par transition REELLE — quelques-uns par jour, pas par cycle. Echec de lecture => pas d auteur, la ligne est ecrite quand meme : on ne devine jamais. ⚠️ L auteur est colle au RESUME, pas seulement au detail jsonb. C est le resume 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. remonte l auteur et son declencheur : l enquete de ce matin tient desormais en UNE commande, et l observateur pourra nommer le coupable au lieu de dire « un des trois ». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
@@ -71,11 +71,13 @@ if (!commande || commande === 'outils') {
|
|||||||
});
|
});
|
||||||
} else if (commande === 'bascules') {
|
} else if (commande === 'bascules') {
|
||||||
const [motif, h = '6'] = args;
|
const [motif, h = '6'] = args;
|
||||||
const r = sql(`select to_char(ts at time zone 'Europe/Paris','YYYY-MM-DD HH24:MI:SS'), genre, resume
|
const r = sql(`select to_char(ts at time zone 'Europe/Paris','YYYY-MM-DD HH24:MI:SS'), genre, resume,
|
||||||
|
coalesce(detail->>'auteur',''), coalesce(detail->>'declencheur','')
|
||||||
from evenement where ts > now() - interval '${Number(h)} hours'
|
from evenement where ts > now() - interval '${Number(h)} hours'
|
||||||
${motif ? `and resume ilike '%${motif.replace(/'/g, '')}%'` : ''}
|
${motif ? `and resume ilike '%${motif.replace(/'/g, '')}%'` : ''}
|
||||||
order by ts desc limit 80`);
|
order by ts desc limit 80`);
|
||||||
const lignes = r.map(([quand, genre, resume]) => ({ quand, genre, resume }));
|
const lignes = r.map(([quand, genre, resume, auteur, declencheur]) =>
|
||||||
|
({ quand, genre, resume, ...(auteur ? { auteur } : {}), ...(declencheur ? { declencheur } : {}) }));
|
||||||
// Un ecart court entre deux transitions du meme objet est la signature d une
|
// Un ecart court entre deux transitions du meme objet est la signature d une
|
||||||
// oscillation : on le calcule ici pour que l appelant n ait pas a le refaire.
|
// oscillation : on le calcule ici pour que l appelant n ait pas a le refaire.
|
||||||
for (let i = 0; i < lignes.length - 1; i++) {
|
for (let i = 0; i < lignes.length - 1; i++) {
|
||||||
|
|||||||
@@ -155,6 +155,47 @@ const num = (v) => {
|
|||||||
};
|
};
|
||||||
const txt = (s) => (s ?? '').toString().trim();
|
const txt = (s) => (s ?? '').toString().trim();
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// QUI A PROVOQUE CETTE TRANSITION (12/09/2026)
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Le 12/09, le contacteur reseau a bascule six fois en dix minutes. Trouver le
|
||||||
|
// coupable a demande sept requetes enchainees — executions n8n des deux
|
||||||
|
// workflows, liste des ecrivains, puis automations.yaml — parce que la table
|
||||||
|
// des evenements disait QUE ca avait bascule, jamais PAR QUI.
|
||||||
|
//
|
||||||
|
// Home Assistant le sait pourtant : son logbook resout le contexte d un
|
||||||
|
// changement d etat et nomme l automatisation, le script ou l utilisateur qui
|
||||||
|
// l a declenche. Pour la coupure de 06:29:44 il rend :
|
||||||
|
// context_entity_id : automation.victron_grid_off_si_soc_35
|
||||||
|
// context_message : triggered by numeric state of sensor.victron_battery_soc
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
async function auteurDeLaTransition(entite, horodatageHa) {
|
||||||
|
try {
|
||||||
|
const t = Date.parse(horodatageHa);
|
||||||
|
if (!Number.isFinite(t)) return null;
|
||||||
|
const debut = new Date(t - 5000).toISOString();
|
||||||
|
const fin = new Date(t + 5000).toISOString();
|
||||||
|
const r = await fetch(
|
||||||
|
`${HA_URL}/api/logbook/${debut}?end_time=${encodeURIComponent(fin)}&entity_id=${encodeURIComponent(entite)}`,
|
||||||
|
{ headers: { Authorization: `Bearer ${HA_TOKEN}` }, signal: AbortSignal.timeout(8000) },
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function etats() {
|
async function etats() {
|
||||||
const r = await fetch(`${HA_URL}/api/states`, {
|
const r = await fetch(`${HA_URL}/api/states`, {
|
||||||
headers: { Authorization: `Bearer ${HA_TOKEN}` },
|
headers: { Authorization: `Bearer ${HA_TOKEN}` },
|
||||||
@@ -332,9 +373,17 @@ async function detecterEvenements(sql, st) {
|
|||||||
const prec = memoire.valeurs.get(id);
|
const prec = memoire.valeurs.get(id);
|
||||||
if (prec === undefined) { memoire.valeurs.set(id, cur); continue; }
|
if (prec === undefined) { memoire.valeurs.set(id, cur); continue; }
|
||||||
if (prec === cur) continue;
|
if (prec === cur) continue;
|
||||||
await journaliser(sql, genre, `${libelle} : ${prec} -> ${cur}`, {
|
const qui = await auteurDeLaTransition(id, e.last_changed);
|
||||||
|
// L auteur est colle au RESUME, pas seulement au detail : c est le resume
|
||||||
|
// 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.
|
||||||
|
const resume = `${libelle} : ${prec} -> ${cur}`
|
||||||
|
+ (qui ? ` — par ${qui.nom}` : '');
|
||||||
|
await journaliser(sql, genre, resume, {
|
||||||
entite: id, avant: prec, apres: cur,
|
entite: id, avant: prec, apres: cur,
|
||||||
horodatage_ha: e.last_changed,
|
horodatage_ha: e.last_changed,
|
||||||
|
...(qui ? { auteur: qui.auteur, auteur_nom: qui.nom, auteur_type: qui.type, declencheur: qui.declencheur } : {}),
|
||||||
});
|
});
|
||||||
memoire.valeurs.set(id, cur);
|
memoire.valeurs.set(id, cur);
|
||||||
n++;
|
n++;
|
||||||
|
|||||||
Reference in New Issue
Block a user