Détail cellule exposé en 3 helpers HA pour l'agent vocal OpenClaw

Les trames BMS cellule de HA sont mortes depuis le 15/09 ; le relevé série est la
source vivante. Le service publie evcs_cell_spread_max_mv, evcs_cell_weakest_module
et evcs_cell_releve_age_min à chaque cycle (publierCellulesHelpers). L'âge donne à
l'agent la discipline anti-valeur-figée (comme le gel GX). Vérifié en réel :
21 mV, âge 1 min, K220803C30160323. Package evcs_cellules.yaml (hors dépôt, HA).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
Tallagrand Thierry
2026-09-25 00:07:28 +02:00
parent d68d8ee34c
commit d64b634a66
2 changed files with 65 additions and 0 deletions

View File

@@ -1792,6 +1792,43 @@ async function ecrireHelperTexte(entite, valeur) {
} catch { /* le helper reste a sa valeur, on reessaiera au prochain cycle */ }
}
async function ecrireHelperNombre(entite, valeur) {
if (DRY || valeur === null || valeur === undefined || !Number.isFinite(valeur)) return;
try {
await fetch(`${HA_URL}/api/services/input_number/set_value`, {
method: 'POST',
headers: { Authorization: `Bearer ${HA_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ entity_id: entite, value: valeur }),
signal: AbortSignal.timeout(8000),
});
} catch { /* on reessaiera au prochain cycle */ }
}
// Publie le detail cellule (relevé serie, table `cellule`) dans des helpers HA,
// pour que l'agent vocal OpenClaw le lise sans taper PostgreSQL. Les trames BMS
// cellule de HA sont mortes depuis le 15/09 (§69) : ce relevé serie est la seule
// source vivante. On expose l'ECART max du pack, le module extreme, et l'AGE du
// dernier relevé — c'est l'age qui permet a l'agent d'appliquer la meme
// discipline que pour le gel GX (frais -> il le donne, vieux -> il dit muet).
async function publierCellulesHelpers(client) {
if (DRY || !client) return 0;
const { rows: [r] } = await client.query(
`WITH mx AS (SELECT max(ts) AS t FROM cellule),
recent AS (SELECT DISTINCT ON (module) module, ts FROM cellule
WHERE ts > (SELECT t FROM mx) - interval '5 minutes' ORDER BY module, ts DESC),
spreads AS (SELECT c.module, max(c.mv) - min(c.mv) AS ecart
FROM cellule c JOIN recent rc ON rc.module = c.module AND rc.ts = c.ts
GROUP BY c.module)
SELECT (SELECT max(ecart) FROM spreads) AS spread_max,
(SELECT module FROM spreads ORDER BY ecart DESC NULLS LAST LIMIT 1) AS weakest,
(SELECT round(EXTRACT(EPOCH FROM (now() - t)) / 60)::int FROM mx) AS age_min`);
if (!r || r.age_min === null) return 0; // table cellule vide : rien a publier
await ecrireHelperNombre('input_number.evcs_cell_spread_max_mv', r.spread_max === null ? null : Number(r.spread_max));
await ecrireHelperNombre('input_number.evcs_cell_releve_age_min', Number(r.age_min));
await ecrireHelperTexte('input_text.evcs_cell_weakest_module', r.weakest || 'indisponible');
return 1;
}
async function cycle(client) {
// En blanc, on compte ce qu'on ECRIRAIT. Un essai a blanc qui renvoie
// toujours zero ne verifie rien — c'est le piege classique du test qui ne
@@ -1840,6 +1877,12 @@ async function cycle(client) {
console.error(`ingestion cellules en echec : ${e.message}`);
return 0;
}),
// Publie le detail cellule dans des helpers HA (pour l'agent vocal), APRES
// l'ingestion pour utiliser le relevé qui vient d'arriver.
cellulesHelpers: DRY ? 0 : await publierCellulesHelpers(client).catch((e) => {
console.error(`publication helpers cellule en echec : ${e.message}`);
return 0;
}),
};
const total = r.mesures + r.bilan + r.decision + r.prevision + r.evenement + r.session + r.cellules + r.observation + r.etiquette + (r.rapport || 0);
if (total > 0 || ONCE) {