airtria: lecteur HA deterministe (anti-fabrication) + UTC->Paris

Le 16/09 Airtria a FABRIQUE un "jeton HA 401, regenere cote HA" alors que le jeton
renvoie 200 : elle raisonnait sur l'etat HA au lieu de le lire. Faute de methode de
lecture fiable, elle confabulait (valeurs a 03:25, puis une panne a 04:35).

- airtria-ha-read.js : lit /api/states, imprime etat + unite + last_changed converti en
  heure de Paris + age (marque FIGE si >15 min) ; en echec, imprime le VRAI code HTTP.
  Airtria lit via ce script et rapporte ce qu'il imprime. Verifie en live (SOC 46 %,
  power -408 W = decharge ; source_mode et night_target correctement marques FIGE).
- AGENTS.md 1.7 : ne jamais fabriquer une panne/cause ; lire pour de vrai via
  airtria-ha-read.js, ne pas raisonner sur une valeur.
- AGENTS.md 5ter : pointe vers le lecteur ; rappelle que /api/states est en UTC.
- .gitignore : whitelist elargie workspace/airtria-*.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZsbhyGmRwZogdoBwT6C6y
This commit is contained in:
2026-09-16 08:13:59 +02:00
parent f09e2efd6e
commit 78577eda85
3 changed files with 78 additions and 2 deletions

4
.gitignore vendored
View File

@@ -26,8 +26,8 @@ workspace/*
!workspace/bourse-config.json
# code du concierge véhicule (aucun secret : lit les tokens depuis des fichiers ignorés)
!workspace/concierge-*.js
# lecteur SQL lecture-seule de la session de charge en cours (aucun secret)
!workspace/airtria-charge-session.js
# scripts Airtria (lecteurs lecture-seule PG/HA ; aucun secret embarqué)
!workspace/airtria-*.js
# moniteur LLM/RAM + brouillons de config HA (aucun secret)
!workspace/llm-monitor.js
!workspace/*.yaml.draft

View File

@@ -75,6 +75,14 @@ grandeurs voisines comme si elles répondaient → 5 affirmations fausses sur 6)
comme ACTUELLE : figée depuis des heures ≠ « maintenant » / « cette nuit ».
- **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.
- **Ne jamais FABRIQUER une panne ni une cause.** Si une lecture échoue, rapporter l'erreur
RÉELLE (le code / l'état que l'outil imprime), jamais une cause inventée : le 16/09 Airtria
a affirmé « le jeton HA renvoie 401, régénéré côté HA » alors que le jeton était VALIDE
(HTTP 200). Un diagnostic non observé est une fabrication — pire qu'un « je n'ai pas pu lire ».
- **Lire pour de vrai, ne pas raisonner sur une valeur.** Toute valeur HA se lit en exécutant
`node /home/node/.openclaw/workspace/airtria-ha-read.js <entité> …` (voir §5ter) et se
rapporte telle qu'imprimée (état + horodatage déjà converti en heure de Paris). Ne jamais
deviner ce qu'une valeur « devrait » être.
---
@@ -329,6 +337,12 @@ réponse reconnue.
Référence EVCS (propriétaire du système EVCS), 16/09. Utiliser CES entités et appliquer
§1.7 (ne pas substituer, vérifier `last_changed`).
Lire toute valeur HA avec `node /home/node/.openclaw/workspace/airtria-ha-read.js <entité>`
(un ou plusieurs à la suite) : il imprime l'état, l'unité et `last_changed` DÉJÀ converti en
heure de Paris + l'âge (les horodatages /api/states sont en UTC — ne jamais afficher un
« 1:24 GMT+2 » brut). En cas d'échec il imprime le VRAI code HTTP : le rapporter, ne rien
inventer.
- **SOC de la voiture (VW/JEEP)** : N'EXISTE PAS (aucun capteur véhicule). Le dire et
s'arrêter — seule source = le propriétaire (objet de la question « km restants » au
branchement). Ne jamais le déduire d'autre chose.

View File

@@ -0,0 +1,62 @@
// airtria-ha-read.js <entité> [<entité> …] — lecture Home Assistant fiable, UNE vérité.
// -----------------------------------------------------------------------------
// 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é,
// et `last_changed` CONVERTI en heure de Paris + âge (marqué FIGÉ au-delà de 15 min). En cas
// d'échec, imprime le VRAI code HTTP / la vraie erreur — pour que la panne rapportée soit
// réelle, pas inventée (le 16/09 Airtria a affirmé un 401 alors que le jeton renvoyait 200).
// Rappel EVCS 16/09 : les horodatages /api/states sont en UTC → toujours convertir.
// -----------------------------------------------------------------------------
'use strict';
const fs = require('fs');
const path = require('path');
const HA_BASE = process.env.HA_BASE || 'http://10.10.10.17:8123';
const token = () => fs.readFileSync(
process.env.HA_TOKEN_FILE || path.join(__dirname, '.ha-token'), 'utf8').trim();
function parisAge(iso) {
if (!iso) return '?';
const d = new Date(iso);
const t = d.toLocaleString('fr-FR',
{ timeZone: 'Europe/Paris', dateStyle: 'short', timeStyle: 'medium' });
const ageMin = Math.round((Date.now() - d.getTime()) / 60000);
const stale = ageMin > 15 ? ' ⚠️ FIGÉ (>15 min, PAS une mesure actuelle)' : '';
return `${t} (Paris, il y a ${ageMin} min)${stale}`;
}
async function readOne(entity) {
const ac = new AbortController();
const to = setTimeout(() => ac.abort(), 10000);
let r;
try {
r = await fetch(`${HA_BASE}/api/states/${entity}`,
{ headers: { Authorization: `Bearer ${token()}` }, signal: ac.signal });
} catch (e) {
clearTimeout(to);
console.log(`${entity} : ERREUR réseau (${e.name}) — ne rien inventer`);
return;
}
clearTimeout(to);
if (r.status === 404) {
console.log(`${entity} : INEXISTANT (HTTP 404) — cette entité n'existe pas, ne pas substituer`);
return;
}
if (!r.ok) {
console.log(`${entity} : ERREUR HTTP ${r.status} — rapporter ce code, ne rien inventer`);
return;
}
const j = await r.json();
const unit = j.attributes && j.attributes.unit_of_measurement
? ` ${j.attributes.unit_of_measurement}` : '';
console.log(`${entity} = ${j.state}${unit} | maj ${parisAge(j.last_changed)}`);
}
(async () => {
const entities = process.argv.slice(2);
if (!entities.length) {
console.error('usage: airtria-ha-read.js <entity_id> [entity_id ...]');
process.exit(2);
}
for (const e of entities) await readOne(e);
})().catch(e => { console.error('[ha-read]', e.message); process.exit(1); });