airtria-ha-read: age = fait neutre, pas de verdict "fige" (EVCS)
EVCS a mesure que "FIGE >15 min" crie au loup : un systeme stable a des entites metier IMMOBILES par construction (voiture branchee depuis 12 h, mode "Bloque" inchange), et last_reported == last_changed -- aucun champ de l'API ne distingue un ecrivain mort d'une reecriture identique. L'age n'est donc pas une preuve de panne. - airtria-ha-read.js : "inchange depuis X" (fait neutre), plus de marqueur FIGE. - AGENTS.md 1.7 : l'age d'une entite metier n'est pas une panne ; la vivacite se lit sur le battement de coeur seul ; entite metier lue pour sa VALEUR, pas sa fraicheur. La vraie erreur du "12,1 kWh" = le perimetre (compteur de vie), pas l'age. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZsbhyGmRwZogdoBwT6C6y
This commit is contained in:
@@ -60,7 +60,7 @@ Pour vérifier qu'un PNG est correct, utiliser **`du -b`** (taille en octets) vi
|
|||||||
Ne JAMAIS utiliser `view_image` ni `read` sur un `.png` : c'est lent, coûteux en contexte,
|
Ne JAMAIS utiliser `view_image` ni `read` sur un `.png` : c'est lent, coûteux en contexte,
|
||||||
et inutile. Un PNG de graphe valide fait > 5000 octets.
|
et inutile. Un PNG de graphe valide fait > 5000 octets.
|
||||||
|
|
||||||
### 1.7 Mesures : ne JAMAIS substituer une grandeur absente, vérifier `last_changed`
|
### 1.7 Mesures : ne pas substituer, ne pas fabriquer, ne pas confondre âge et panne
|
||||||
|
|
||||||
Pour toute question chiffrée (énergie, SOC, puissance, prix). Ces règles ont manqué le
|
Pour toute question chiffrée (énergie, SOC, puissance, prix). Ces règles ont manqué le
|
||||||
16/09 (« SOC de la VW ? » : la grandeur n'existe pas ; Airtria l'a dit, puis a servi les
|
16/09 (« SOC de la VW ? » : la grandeur n'existe pas ; Airtria l'a dit, puis a servi les
|
||||||
@@ -71,8 +71,16 @@ grandeurs voisines comme si elles répondaient → 5 affirmations fausses sur 6)
|
|||||||
(« je n'ai pas X ; j'ai Y, qui est différent »), jamais comme la réponse. Le remplacement
|
(« je n'ai pas X ; j'ai Y, qui est différent »), jamais comme la réponse. Le remplacement
|
||||||
silencieux est pire qu'un « je ne sais pas » : il change une surveillance en fausse
|
silencieux est pire qu'un « je ne sais pas » : il change une surveillance en fausse
|
||||||
réassurance.
|
réassurance.
|
||||||
- **Vérifier `last_changed`** (donné par `/api/states/<entité>`) avant de citer une valeur
|
- **L'âge d'une entité métier n'est PAS une preuve de panne.** Un système stable produit des
|
||||||
comme ACTUELLE : figée depuis des heures ≠ « maintenant » / « cette nuit ».
|
entités IMMOBILES (voiture branchée depuis 12 h, mode « Bloqué » inchangé) : c'est normal.
|
||||||
|
Aucun champ de l'API ne distingue un écrivain mort d'un écrivain qui réécrit la même valeur
|
||||||
|
(`last_reported` = `last_changed`). Donc : lire une entité métier pour sa VALEUR, jamais
|
||||||
|
pour sa fraîcheur, et ne jamais dire « figé » d'une valeur ancienne. La VIVACITÉ (« le
|
||||||
|
contrôleur est-il muet ? ») se lit UNIQUEMENT sur le battement de cœur (état = horodatage
|
||||||
|
qui change forcément à chaque écriture).
|
||||||
|
- **Ne pas attribuer une valeur à une fenêtre qu'elle ne couvre pas** : un compteur de vie
|
||||||
|
(kWh cumulés) n'est pas « cette nuit » — la vraie erreur du « 12,1 kWh », c'est le
|
||||||
|
périmètre, pas l'âge.
|
||||||
- **Grandeur directionnelle** (une batterie monte/descend) : ne jamais publier la valeur
|
- **Grandeur directionnelle** (une batterie monte/descend) : ne jamais publier la valeur
|
||||||
sans le SIGNE de la puissance associée, sinon on affirme « ça monte » quand ça descend.
|
sans le SIGNE de la puissance associée, sinon on affirme « ça monte » quand ça descend.
|
||||||
- **Ne jamais FABRIQUER une panne ni une cause.** Si une lecture échoue, rapporter l'erreur
|
- **Ne jamais FABRIQUER une panne ni une cause.** Si une lecture échoue, rapporter l'erreur
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Airtria doit LIRE l'état via ce script (exec), jamais raisonner sur ce qu'une valeur
|
// Airtria doit LIRE l'état via ce script (exec), jamais raisonner sur ce qu'une valeur
|
||||||
// « devrait » être, jamais fabriquer une erreur. Pour chaque entité, imprime : état, unité,
|
// « devrait » être, jamais fabriquer une erreur. Pour chaque entité, imprime : état, unité,
|
||||||
// et `last_changed` CONVERTI en heure de Paris + âge (marqué FIGÉ au-delà de 15 min). En cas
|
// et `last_changed` converti en heure de Paris + âge comme FAIT neutre (« inchangé depuis
|
||||||
// d'échec, imprime le VRAI code HTTP / la vraie erreur — pour que la panne rapportée soit
|
// X »), JAMAIS un verdict « figé » : un système stable a des entités immobiles (voiture
|
||||||
// réelle, pas inventée (le 16/09 Airtria a affirmé un 401 alors que le jeton renvoyait 200).
|
// branchée depuis 12 h, mode inchangé) et aucun champ de l'API ne distingue un écrivain mort
|
||||||
// Rappel EVCS 16/09 : les horodatages /api/states sont en UTC → toujours convertir.
|
// d'une réécriture identique (EVCS 16/09) — la vivacité se lit sur le battement de cœur, pas
|
||||||
|
// sur l'âge d'une entité métier. En cas d'échec, imprime le VRAI code HTTP / la vraie erreur
|
||||||
|
// (le 16/09 Airtria a affirmé un 401 alors que le jeton renvoyait 200 : ne rien inventer).
|
||||||
|
// Rappel EVCS : les horodatages /api/states sont en UTC → toujours convertir.
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
'use strict';
|
'use strict';
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -15,14 +18,22 @@ const HA_BASE = process.env.HA_BASE || 'http://10.10.10.17:8123';
|
|||||||
const token = () => fs.readFileSync(
|
const token = () => fs.readFileSync(
|
||||||
process.env.HA_TOKEN_FILE || path.join(__dirname, '.ha-token'), 'utf8').trim();
|
process.env.HA_TOKEN_FILE || path.join(__dirname, '.ha-token'), 'utf8').trim();
|
||||||
|
|
||||||
function parisAge(iso) {
|
// Âge = FAIT neutre, jamais un verdict de panne (cf. en-tête). La péremption d'une valeur
|
||||||
|
// ne se déduit PAS de son âge : ancienneté ≠ panne pour une entité métier.
|
||||||
|
function since(iso) {
|
||||||
if (!iso) return '?';
|
if (!iso) return '?';
|
||||||
const d = new Date(iso);
|
const d = new Date(iso);
|
||||||
const t = d.toLocaleString('fr-FR',
|
const t = d.toLocaleString('fr-FR',
|
||||||
{ timeZone: 'Europe/Paris', dateStyle: 'short', timeStyle: 'medium' });
|
{ timeZone: 'Europe/Paris', dateStyle: 'short', timeStyle: 'short' });
|
||||||
const ageMin = Math.round((Date.now() - d.getTime()) / 60000);
|
const min = Math.round((Date.now() - d.getTime()) / 60000);
|
||||||
const stale = ageMin > 15 ? ' ⚠️ FIGÉ (>15 min, PAS une mesure actuelle)' : '';
|
let age;
|
||||||
return `${t} (Paris, il y a ${ageMin} min)${stale}`;
|
if (min < 1) age = "à l'instant";
|
||||||
|
else if (min < 90) age = `inchangé depuis ${min} min`;
|
||||||
|
else {
|
||||||
|
const h = Math.floor(min / 60), m = min % 60;
|
||||||
|
age = `inchangé depuis ${h} h${m ? ' ' + String(m).padStart(2, '0') : ''}`;
|
||||||
|
}
|
||||||
|
return `${t} (Paris) · ${age}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readOne(entity) {
|
async function readOne(entity) {
|
||||||
@@ -49,7 +60,7 @@ async function readOne(entity) {
|
|||||||
const j = await r.json();
|
const j = await r.json();
|
||||||
const unit = j.attributes && j.attributes.unit_of_measurement
|
const unit = j.attributes && j.attributes.unit_of_measurement
|
||||||
? ` ${j.attributes.unit_of_measurement}` : '';
|
? ` ${j.attributes.unit_of_measurement}` : '';
|
||||||
console.log(`${entity} = ${j.state}${unit} | maj ${parisAge(j.last_changed)}`);
|
console.log(`${entity} = ${j.state}${unit} | ${since(j.last_changed)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user