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
138 lines
6.6 KiB
JavaScript
138 lines
6.6 KiB
JavaScript
// =====================================================================
|
|
// Diagnostics publies pour l'agent vocal OpenClaw — calcules ICI, pas par lui.
|
|
// =====================================================================
|
|
//
|
|
// POURQUOI (25/09/2026). Le banc PV d'OpenClaw a montre que la fiche de l'agent
|
|
// est sur le fil du rasoir : 96 caracteres ajoutes dans le bloc CELLULE, sans
|
|
// aucun rapport avec la substitution, ont fait tomber G5 (substitution EDF) de
|
|
// 7/7 a 1/7. Une regle qu'un ajout sans rapport fait basculer cassera au
|
|
// prochain ajout. On sort donc les deux jugements du modele : ils sont calcules
|
|
// ici, testes (tests/diagnostics-agent/run.mjs), et l'agent n'a plus qu'a LIRE
|
|
// un verdict et l'expliquer.
|
|
//
|
|
// Fonctions PURES (etats HA en entree, objet en sortie) : aucun appel reseau,
|
|
// pour que les tests rejouent exactement ce qui tourne.
|
|
// =====================================================================
|
|
|
|
// Les sept chargeurs lus par l'agent (MANUEL §0, HANDOFF §77). Le RS 450/200
|
|
// expose un mode d'appareil ET un mode par tracker : on les compte tous, comme
|
|
// le banc.
|
|
export const MPPT_MODES = [
|
|
'sensor.airtria_mppt1_mppt_operation_mode',
|
|
'sensor.mppt_450_200_hq2449yr9pt_mppt_operation_mode',
|
|
'sensor.mppt_450_200_hq2449yr9pt_pv_tracker_solar_1_operation_mode',
|
|
'sensor.mppt_450_200_hq2449yr9pt_pv_tracker_solar_2_operation_mode',
|
|
'sensor.mppt_450_200_hq2449yr9pt_pv_tracker_solar_3_operation_mode',
|
|
'sensor.mppt_450_200_hq2449yr9pt_pv_tracker_solar_4_operation_mode',
|
|
'sensor.v1_mppt_operation_mode',
|
|
];
|
|
|
|
// Bruit de mesure du compteur reseau, PAS un reglage de decision : contacteur
|
|
// ferme, l'ESS preleve quelques dizaines de watts meme a consigne nulle. En
|
|
// dessous, « import ≈ 0 » au sens de la regle du 31/08.
|
|
export const IMPORT_BRUIT_W = 100;
|
|
// Sous cette puissance PV, il n'y a pas de production a discuter (nuit, aube).
|
|
export const PV_NUIT_W = 50;
|
|
|
|
function nombre(st, id) {
|
|
const s = st.get(id);
|
|
if (!s) return null;
|
|
const v = Number(s.state);
|
|
return Number.isFinite(v) ? v : null;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// G5 — bridage normal, vraie baisse, ou substitution par EDF ?
|
|
// ---------------------------------------------------------------------
|
|
// Regle du 31/08 (HANDOFF §26-§31, §77) : un chargeur qui se retient prouve
|
|
// qu'il reste de la ressource refusee. Si le reseau n'importe rien, c'est le
|
|
// bridage normal d'une installation sans injection. S'il importe, EDF fournit
|
|
// ce que le solaire aurait pu donner : c'est la SUBSTITUTION, l'anomalie.
|
|
// L'import se verifie AVANT de conclure au bridage, par construction : c'est
|
|
// l'ordre des tests ci-dessous.
|
|
export function diagnosticPv(st) {
|
|
const pv = nombre(st, 'sensor.victron_pv_power_system');
|
|
const conso = nombre(st, 'sensor.victron_consumption_power_l1');
|
|
const reseau = nombre(st, 'sensor.victron_grid_power_l1'); // > 0 = import
|
|
const soc = nombre(st, 'sensor.victron_battery_soc');
|
|
const courant = nombre(st, 'sensor.victron_battery_current'); // > 0 = charge
|
|
const edf = st.get('switch.edf_general_ok_switch')?.state ?? null;
|
|
|
|
const modes = MPPT_MODES
|
|
.map((id) => String(st.get(id)?.state ?? '').trim().toLowerCase())
|
|
.filter((m) => m !== '' && m !== 'unavailable' && m !== 'unknown');
|
|
const limites = modes.filter((m) => m.includes('limited')).length;
|
|
const actifs = modes.filter((m) => m === 'mppt_active').length;
|
|
|
|
const importW = reseau === null ? null : Math.max(0, reseau);
|
|
const attributs = {
|
|
pv_w: pv, conso_w: conso, import_w: importW, soc_pct: soc,
|
|
courant_batterie_a: courant, contacteur_edf: edf,
|
|
mppt_lisibles: modes.length, mppt_bridés: limites, mppt_a_fond: actifs,
|
|
part_reseau_pct: importW !== null && conso ? Math.round((100 * importW) / conso) : null,
|
|
};
|
|
const verdict = (etat, explication) => ({ etat, explication, ...attributs });
|
|
|
|
if (pv === null || conso === null || reseau === null) {
|
|
return verdict('indetermine', 'Mesure PV, consommation ou reseau indisponible : ne pas conclure.');
|
|
}
|
|
if (limites === 0 && pv < PV_NUIT_W) {
|
|
return verdict('pas_de_production', 'Pas de production solaire (nuit ou aube) : rien a diagnostiquer.');
|
|
}
|
|
if (modes.length === 0) {
|
|
return verdict('indetermine', 'Aucun mode de chargeur lisible : impossible de distinguer bridage et baisse.');
|
|
}
|
|
if (limites > 0) {
|
|
if (importW > IMPORT_BRUIT_W) {
|
|
return verdict('substitution_edf',
|
|
`ANOMALIE : ${limites} chargeur(s) bride(s) alors que le reseau importe ${Math.round(importW)} W ` +
|
|
`— EDF fournit ce que le solaire pourrait donner. A signaler.`);
|
|
}
|
|
return verdict('bridage_normal',
|
|
`${limites} chargeur(s) se retiennent et le reseau n'importe rien : bridage normal d'une ` +
|
|
`installation sans injection. De la ressource solaire reste disponible pour une charge de plus.`);
|
|
}
|
|
if (importW > IMPORT_BRUIT_W) {
|
|
return verdict('reseau_en_appoint',
|
|
`Tous les chargeurs donnent leur maximum ; le reseau complete (${Math.round(importW)} W). ` +
|
|
`Le solaire n'est pas gache.`);
|
|
}
|
|
return verdict('ressource_limitee',
|
|
'Tous les chargeurs donnent leur maximum : la production est limitee par le soleil, rien n\'est gache.');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// G13b — detail cellule : une valeur perimee ne doit plus etre VISIBLE
|
|
// ---------------------------------------------------------------------
|
|
// Le releve serie tourne toutes les 10 min (deploy/releve-cellules/compose.yaml,
|
|
// --suivi 600). Perime au-dela de 3 pas, comme la garde anti-blocage de
|
|
// pylontech-console.mjs (§75) : au-dela, le lecteur est considere fige.
|
|
// Perime => ni ecart ni module publies : l'agent ne peut pas citer ce qu'il ne
|
|
// voit pas.
|
|
export const RELEVE_PAS_MIN = 10;
|
|
export const RELEVE_PERIME_MIN = 3 * RELEVE_PAS_MIN;
|
|
|
|
export function etatCellules(r) {
|
|
if (!r || r.age_min === null || r.age_min === undefined) {
|
|
return { etat: 'muet', attributs: { statut: 'muet', explication: 'Aucun releve cellule en base.' } };
|
|
}
|
|
const age = Number(r.age_min);
|
|
if (age > RELEVE_PERIME_MIN) {
|
|
return {
|
|
etat: 'perime',
|
|
attributs: {
|
|
statut: 'perime', age_releve_min: age, seuil_perime_min: RELEVE_PERIME_MIN,
|
|
explication: `Releve cellule muet depuis ${age} min : aucune valeur actuelle a donner.`,
|
|
},
|
|
};
|
|
}
|
|
return {
|
|
etat: r.spread_max === null ? 'muet' : Number(r.spread_max),
|
|
attributs: {
|
|
statut: r.spread_max === null ? 'muet' : 'frais',
|
|
age_releve_min: age, seuil_perime_min: RELEVE_PERIME_MIN,
|
|
module_le_plus_sensible: r.weakest || null,
|
|
},
|
|
};
|
|
}
|