Diagnostics PV et cellule calcules cote service pour l'agent vocal (G5/G13b)
La fiche OpenClaw basculait G5 de 7/7 a 1/7 sur un ajout sans rapport : les deux jugements sortent du modele. sensor.evcs_pv_diagnostic (import teste avant bridage) et sensor.evcs_cellules (perime => aucune valeur visible), publies meme quand le cycle echoue. Tests 15/15 + mutations. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uJ47NxgfpGnuuMr3f2n7Z
This commit is contained in:
@@ -30,6 +30,7 @@ import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import pg from 'pg';
|
||||
import { texteRapport, envoyer } from './rapport-nuit.mjs';
|
||||
import { diagnosticPv, etatCellules } from './diagnostics-agent.mjs';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const ONCE = process.argv.includes('--once');
|
||||
@@ -1810,8 +1811,41 @@ async function ecrireHelperNombre(entite, valeur) {
|
||||
// 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).
|
||||
// Publie un capteur calcule (etat + attributs) via l'API d'etats de HA. Il
|
||||
// disparait a un redemarrage de HA jusqu'au cycle suivant (<= 1 min) : l'agent
|
||||
// lit alors une entite absente, donc « muet », jamais une valeur fausse.
|
||||
async function publierCapteur(entite, etat, attributs) {
|
||||
if (DRY) return;
|
||||
const r = await fetch(`${HA_URL}/api/states/${entite}`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${HA_TOKEN}`, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ state: String(etat), attributes: attributs }),
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!r.ok) throw new Error(`${entite} : HTTP ${r.status}`);
|
||||
}
|
||||
|
||||
// Verdict PV pour l'agent vocal (G5 du banc) : bridage normal / substitution
|
||||
// EDF / ressource limitee, calcule par tools/diagnostics-agent.mjs et teste.
|
||||
// Hors base : ne depend que des etats HA, donc survit a une panne PostgreSQL.
|
||||
async function publierDiagnosticPv(st) {
|
||||
const d = diagnosticPv(st);
|
||||
await publierCapteur('sensor.evcs_pv_diagnostic', d.etat, {
|
||||
friendly_name: 'EVCS — diagnostic production PV', icon: 'mdi:solar-power-variant', ...d,
|
||||
calcule_a: new Date().toISOString(),
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
|
||||
async function publierCellulesHelpers(client) {
|
||||
if (DRY || !client) return 0;
|
||||
if (DRY) return 0;
|
||||
// Base injoignable : on publie « muet » plutot que de laisser au capteur son
|
||||
// dernier etat « frais », qui vieillirait sans que rien ne le dise.
|
||||
const muet = (cause) => publierCapteur('sensor.evcs_cellules', 'muet', {
|
||||
friendly_name: 'EVCS cellules — ecart max du pack', icon: 'mdi:battery-heart-variant',
|
||||
statut: 'muet', explication: `Detail cellule indisponible (${cause}).`,
|
||||
});
|
||||
if (!client) { await muet('base injoignable'); 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
|
||||
@@ -1821,7 +1855,16 @@ async function publierCellulesHelpers(client) {
|
||||
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`);
|
||||
(SELECT round(EXTRACT(EPOCH FROM (now() - t)) / 60)::int FROM mx) AS age_min`)
|
||||
.catch(async (e) => { await muet('base injoignable').catch(() => {}); throw e; });
|
||||
// Capteur unique pour l'agent (25/09) : releve perime => ni ecart ni module
|
||||
// visibles (G13b du banc OpenClaw). Publie avant le test de table vide, pour
|
||||
// que « muet » soit dit aussi quand il n'y a rien.
|
||||
const cel = etatCellules(r);
|
||||
await publierCapteur('sensor.evcs_cellules', cel.etat, {
|
||||
friendly_name: 'EVCS cellules — ecart max du pack', unit_of_measurement: typeof cel.etat === 'number' ? 'mV' : undefined,
|
||||
icon: 'mdi:battery-heart-variant', ...cel.attributs,
|
||||
});
|
||||
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));
|
||||
@@ -1862,6 +1905,9 @@ async function cycle(client) {
|
||||
ordre: await appliquerOrdreAirtria(sql, client, st).catch((e) => {
|
||||
console.error(`ordre airtria en echec : ${e.message}`); return 0;
|
||||
}),
|
||||
pvDiagnostic: DRY ? 0 : await publierDiagnosticPv(st).catch((e) => {
|
||||
console.error(`diagnostic PV en echec : ${e.message}`); return 0;
|
||||
}),
|
||||
// Ingestion des relevés de cellule deposes par le poste au cable serie.
|
||||
// Hors essai a blanc : l'insertion est massive et idempotente, la simuler
|
||||
// n'apprendrait rien.
|
||||
@@ -2001,6 +2047,17 @@ for (;;) {
|
||||
cyclesEnEchec += 1;
|
||||
dernierEchec = String(e.message).slice(0, 200);
|
||||
console.error(`${new Date().toISOString()} cycle en echec : ${e.message}`);
|
||||
// Les deux capteurs de l'agent vocal ne doivent pas rester figes sur leur
|
||||
// dernier etat quand le cycle tombe (base injoignable : cycle() n'est meme
|
||||
// pas appele). Le diagnostic PV ne depend que de HA ; le detail cellule, lui,
|
||||
// passe a « muet ».
|
||||
if (!DRY) {
|
||||
await etats().then(publierDiagnosticPv).catch(() => {});
|
||||
await publierCapteur('sensor.evcs_cellules', 'muet', {
|
||||
friendly_name: 'EVCS cellules — ecart max du pack', icon: 'mdi:battery-heart-variant',
|
||||
statut: 'muet', explication: `Detail cellule indisponible (${dernierEchec}).`,
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
// HORS du try/catch ci-dessus, et volontairement : c'est ce qui fait que la
|
||||
|
||||
Reference in New Issue
Block a user