# Concierge véhicule Airtria — spécification (convenue OpenClaw ↔ EVCS, 2026-09-13) ## 🔁 MISE À JOUR 2026-09-15 : déclenchement AU BRANCHEMENT (plus à la session) Le propriétaire veut la question dès le BRANCHEMENT (au branchement du soir, la borne ne tire rien avant les HC de 01:24 ; attendre une session laissait la 1re mal étiquetée). concierge-live.js détecte désormais sur les transitions 'on' de `evenement` (plus sur session_charge). Le helper porte `branchement` = **ts::text VERBATIM** de la transition 'on' (PAS horodatage_ha, antérieur de 3-51 s → EVCS raterait le branchement en cours), qui remplace `sessions`. Fraîcheur = sélecteur changé dans [on−10 min, min(now, on+30 min)] (EVCS). Règle de puissance abandonnée au branchement (pas de session/p_max) → on demande pour TOUT branchement périmé, invité géré par la réponse. Division : Airtria détecte le branchement + demande + publie ; EVCS résout, mémorise, applique aux sessions du câble au fur et à mesure (HC 01:24 comprises), refuse les contradictions. Validé contre l'historique (15 branchements, distribution de fraîcheur inchangée) ; déployé, test de portée vert. ## 🟢 LIVE EN PRODUCTION depuis 2026-09-14 02:22 Kill-switch airtria_concierge_enabled = ON, lecteur EVCS déployé (commit d8ef69b). Preuve end-to-end réelle : session 12/09 11:55 → JEEP, vehicule_sur=t, evenement origine=airtria/source_airtria=whatsapp ; cycle suivant idempotent (etiquette=0). Colonne : 6 confirmées (arbitrages voix du 13/09) / 13 à confirmer. Défaut vehicule_sur basculé à false, 17 lignes rattrapées. Bug de format clef (PG `…+02` vs ISO `…Z`, même instant) attrapé au dry-run EVCS et corrigé côté service (canonisation ISO avant dédup) — côté Airtria rien à changer, on envoie la clef verbatim. Véhicule débranché 14/09 01:31 : prochaine question sur un branchement neuf. Le concierge tourne en régime réel. ## VALIDÉ END-TO-END (2026-09-13, essai réel, kill-switch OFF) Simulation sur le branchement 12/09 11:55 : détection → question WhatsApp → réponse « JEEP » du propriétaire → routage → helper publié `{"sessions":["2026-09-12 11:55:54.493+02"],"label":"JEEP","confirmed_at":…,"source":"whatsapp"}` → confirmation « ✅ Noté ». Base non touchée (kill-switch OFF). ⚠️ LEÇON ROUTAGE : le modèle local (qwen) IGNORE une consigne de réponse discrète (a répondu « Bonjour Thierry » au lieu de router). Il faut un RÉFLEXE PRIORITAIRE en tête du traitement WhatsApp (AGENTS.md §5.0) : vérifier concierge-pending.json et lancer `answer` AVANT toute autre réponse. Avec ça, le routage passe de façon fiable. Cron `concierge-vehicule` ACTIVÉ (enabled) → mode essai : une vraie charge à sélecteur périmé déclenchera une vraie question. Reste pour appliquer en base : kill-switch ON + lecteur EVCS (après validation de son dry-run). ## État d'avancement (2026-09-13) Fichiers dans le workspace : `concierge-detection.js` (détection + calibration), `concierge-write.js` (écriture helper, entité unique + relecture), `concierge-write.test.js` (test de portée, VERT), `concierge-live.js` (sous-commandes `detect` et `answer`). Dépendance `pg` installée dans le workspace. TESTÉ : calibration 2-3-2-1-1-1 / 5 questions / 0 mixte ; aller-retour écriture sur le vrai helper ; detect bootstrap ; chemin question (état en attente, clef debut verbatim) ; chemin réponse (reconnaissance + publication + confirmation). CÂBLÉ : cron `concierge-vehicule` (id d113b997-0942-4d71-9e9b-d64249949540, payload COMMAND `node concierge-live.js detect`, */10, announce whatsapp) — testé : exitCode 0, sortie vide → delivery suppressReason "empty" (aucun WhatsApp vide). Routage des réponses ajouté à AGENTS.md §5bis (sauvegarde AGENTS.md.bak-concierge-*). pg installé dans le workspace. ACTIVATION PAR ÉTAPES (toutes décisions du propriétaire) : (1) `openclaw cron enable d113b997-…` → boucle Q/R en réel, le helper se publie sur une vraie réponse. La BASE n'est pas touchée. (2) EVCS déploie son lecteur en DRY-RUN (redémarre son service) : il journalise ce qu'il écrirait (jointure ±1 s sur les clefs verbatim, extension au groupe) SANS écrire. Valide la moitié EVCS avant que ça compte. (3) kill-switch `input_boolean.airtria_concierge_enabled` → ON + EVCS commit la bascule → les étiquettes corrigent session_charge. ⚠️ La bascule vehicule_sur n'est PAS neutralisée par le kill-switch : le kill-switch gate l'APPLICATION d'un label, pas le DEFAULT de la colonne. Passer DEFAULT false trop tôt afficherait 100 % de sessions « douteuses » (motif faux) sur le Grafana « Les véhicules ». Donc la bascule = TROIS pièces soudées au commit du ON : DEFAULT false + true-sur- confirmation-humaine + libellé Grafana « selecteur change » → honnête. Décision COMMAND vs agentTurn : command choisi car déterministe (pas de LLM, sortie exacte, suppression auto sur vide). Cron existants modèles : daily-email-report (Gmail 8h30), orange-cloture (17h40). Objectif : combler le seul trou qu'aucun capteur ne comble — savoir QUEL véhicule (VW / JEEP / invité / bientôt moto ZERO) est branché sur la borne EVCS, pour que `session_charge` cesse d'attribuer l'énergie au mauvais véhicule. Contexte déclencheur : le sélecteur `input_select.victron_evcs_vehicle` est resté figé sur « VW » du 07/09 au 13/09 → 11 sessions d'affilée mal étiquetées, sans aucun signal. VW et JEEP sont électriquement indiscernables l'une de l'autre (~3 680 vs ~3 655 W à 20 A). ## Principe d'architecture (non négociable) - Le cœur déterministe EVCS reste seul juge. Airtria intervient APRÈS, jamais dans la boucle 30 s. - Une seule main sur chaque variable. Airtria ne touche AUCUN actionneur. - Toute valeur porte son propre horodatage (`confirmed_at`), pas celui du run. - Producteur qui s'arrête : invalidation STRUCTURELLE (le label est lié à une session précise, il ne peut pas contaminer une autre session). On NE vide PAS le helper (fenêtre de perte inutile) ; on laisse la dernière confirmation. ## Décisions du propriétaire (2026-09-13) - Lancement v0 + v1 ENSEMBLE. - Canal de correction = écriture HA restreinte `input_text.airtria_*` (variante PostgreSQL INSERT-only écartée). ## Détection d'une session (v0) - Source = table PostgreSQL `session_charge`, PAS le binary_sensor HA (il flappe : disconnected x2 câble branché le 07/09). - ⚠️ NE PAS utiliser `evcs_lecture` : il a INSERT sur `evenement` (registre d'audit partagé) — inadapté à un agent. Rôle DÉDIÉ `airtria_ro`, LOGIN + SELECT sur `session_charge` et `evenement` uniquement, aucune écriture. Mot de passe = variable `AIRTRIA_RO_PASSWORD`, injectée dans le conteneur par le `env_file: .env` du compose openclaw (secret dans /mnt/ARCH3/docker/openclaw/.env, 600, hors git). EVCS possède le rôle et peut RENOUVELER le mot de passe : le code lit la variable d'environnement (source vivante), jamais un fichier figé — un `.pg-airtria` périmé ferait échouer l'auth en silence. Après un changement du .env, RECRÉER le conteneur (`docker compose up -d`) : un simple restart ne relit pas env_file. Base evcs sur 10.10.10.22:5433. - Colonnes utiles (session_charge, 17 col) : `debut` (clef, MS), `fin`, `terminee`, `vehicule`, `vehicule_sur` (DEFAULT true → à basculer côté service), `p_max_w` + `consigne_max_a` + `courant_max_a` (règle invité), `debranche_a` (frontière de groupe), `energie_compteur_kwh` (l'énergie qui FAIT FOI, pas `energie_kwh` qui rate si le service démarre après le début de charge). - On hérite du débounce du service d'historisation : une ligne `session_charge` ne s'ouvre qu'au-delà de 300 W soutenus, se ferme après 10 min sous le seuil. - Cadence : cron 10 min. La latence n'a aucun effet (session de plusieurs heures). - Airtria garde son propre état (dernière `debut` traitée) dans son workspace. ## Test d'ambiguïté (quand poser la question) — RÈGLE FINALE - Le sélecteur est FRAIS (ne pas demander) SEULEMENT s'il a changé de valeur dans la fenêtre [début du branchement − 10 min, début + 30 min]. Sinon → DEMANDER. Source : `evenement` (genre='intervention', entite=input_select.victron_evcs_vehicle), JAMAIS le `last_changed` HA. Implémenté : `selectorFreshAt()` dans concierge-live.js. - Pourquoi la fenêtre serrée (EVCS, données) : un changement de sélecteur EN COURS de branchement ne peut pas étiqueter ce branchement (câble non bougé) — c'est le producteur mécanique des groupes mixtes. Ma 1ère règle (« bougé depuis le débranchement précédent ») déclarait « frais » EXACTEMENT les 2 branchements mixtes de l'historique. - Validé sur 11 branchements 04-12/09 : 1 seul FRAIS (05/09, réglé à +13 min), 10 à DEMANDER dont les 2 mixtes. Stat marquante : sélecteur réglé au bon moment 1 fois / 12. - Mauvais test (rejeté) : « sélecteur inchangé depuis le début de CETTE session » — vrai aussi quand le propriétaire a bien réglé au branchement. - Avant de conclure « pas de geste humain », vérifier la table `evenement` (un redémarrage HA a pu changer la valeur ; l'auteur y est tracé depuis le 12/09). - ✅ CAVEAT `last_changed` (2026-09-13) CORRIGÉ : le `last_changed` HA est remis à zéro par un passage `unavailable` (blip MQTT) sans vrai changement → il ferait croire « frais » un sélecteur périmé. La règle ci-dessus lit donc `evenement` (qui saute les lectures unavailable), pas `last_changed`. Limites de la source : le journal commence le 04/09 ; la mémoire du détecteur est en-processus (un changement pendant un redémarrage du service n'est pas rattrapé) → l'absence d'événement ne prouve pas tout, mais dans le doute la règle DEMANDE = le bon côté. - En cas de doute : poser la question. Un WhatsApp de trop coûte moins qu'une ligne fausse pour toujours. ## Pré-filtre puissance (réduit les questions) — règle À SENS UNIQUE - Le bon critère n'est pas la puissance brute mais la puissance RELATIVE À LA CONSIGNE. VW/JEEP saturent vers 16 A embarqués quelle que soit la consigne (~3 606 à 3 784 W à 20 A) ; la Megane a suivi jusqu'à 28 A (6 360 W). - Règle conclusive : `consigne_max_a >= 20` ET `p_max_w > 4200` ⟹ **invité**, Airtria pré-étiquette « invité » SANS déranger le propriétaire. - `consigne_max_a < 20` ⟹ AUCUNE information (6 A plafonne ~1 700 W pour tout le monde) → demander. - ⚠️ La réciproque est FAUSSE : `p_max_w <= 4200` ne prouve PAS que c'est un véhicule du foyer (un invité à 16 A embarqués est indiscernable de la VW). Au-dessus du seuil on sait, en dessous on demande. NE JAMAIS inverser. - La consigne vient du contrôleur EVCS : plafond 20 A hors BOOST (`input_number.evcs_plafond_courant_edf_on`, 28 en BOOST). Le cas conclusif est donc fréquent ; le cas muet = sessions à courant réduit (fin de charge solaire à 6 A). - Moto ZERO (fin sept. 2026) : tombera sous 3,6 kW → côté foyer, distinguée par le sélecteur comme les autres. Seuil à réévaluer à son arrivée. ## Regroupement = frontière FACTUELLE via `evenement` (pas le temps) - ⚠️ Ne PAS regrouper par écart de temps : le creux soir→heures creuses fait ~5 h 20 (fin solaire ~20:00 → HC ~01:24), indistinguable d'un vrai débranchement/rebranchement par un seuil de durée. - La frontière est un FAIT : les transitions de `binary_sensor.victron_evcs_vehicle_connected` sont écrites dans `evenement` (off→on au branchement, on→off au débranchement). Un branchement = intervalle [off→on … on→off suivant]. Toutes les lignes `session_charge` dont `debut` tombe dans l'intervalle = même véhicule = UNE seule question. Intervalle ouvert (encore branché) = groupe en cours. - Exemple semaine 08-12/09 : 6 branchements = 2-3-2-1-1-1 sessions (le mercredi 15:55+19:35+01:25 est un TRIPLET, même câble). Un regroupement qui rend des paires soir/nuit est FAUX. - Le groupe reste la notion d'Airtria (propriétaire unique sur la notion dérivée) ; le service EVCS ne fait qu'itérer la liste du helper. - ⚠️ Ne PAS émettre une écriture helper par session (elles écraseraient le même helper ; le service qui poll toutes les 60 s ne verrait que la dernière → perte silencieuse des autres). Le helper porte donc une LISTE de session_debut (voir ci-dessous), publiée en UNE écriture atomique. Le service itère la liste. - Fallback coupure : les transitions ne sont écrites que si le service tourne. Frontière absente/incomplète → groupe INCERTAIN → poser la question PAR session, jamais sur une frontière reconstruite. Un WhatsApp de trop > une étiquette fausse. ## Sources de données (requêtes, lecture seule via airtria_ro) - Frontière des branchements — table `evenement` (colonnes : ts, genre, source, resume, detail jsonb, id). Filtre robuste (pas de parsing de texte) : `detail->>'entite' = 'binary_sensor.victron_evcs_vehicle_connected'`, `detail->>'apres'` = 'on' (branchement) / 'off' (débranchement). Ordonner sur `ts`, borner les intervalles sur `detail->>'horodatage_ha'` (vérité physique après rattrapage). Le champ `entite` n'existe que sur les transitions → les fins de session sont exclues naturellement. - Bonus qualité : les événements de fin de session portent `detail->>'debut'` (= session_charge.debut exact, ms comprises) et les trois énergies côte à côte (`kwh_compteur` fait foi, `kwh_integre`, `kwh_borne`). Un écart entre elles est un signal de qualité à remonter (pas une question au propriétaire). - Accès : Node + node-postgres (pur JS, à ajouter au workspace) ; PG:5433 joignable depuis la machine spark → calibration exécutable en lecture seule. - Implémentation : `concierge-detection.js` (mode calibration, aucune écriture). ## La question WhatsApp - Un seul message, au démarrage d'une session ambiguë (ou d'un groupe). - Ouverte, pas binaire : « Quel véhicule est branché ? » avec options lues dynamiquement dans le sélecteur + « aucune des deux / invité ». - Porte la clef `debut` de la session pour permettre l'application a posteriori. - Réponse possible des heures plus tard, ou jamais. ## Le helper HA (le contrat v1) - `input_text.airtria_vehicle_label`, déclaré **max: 255** (défaut HA = 100 → rejet silencieux ; incident vécu forecast_updated max:32 le 08/09). - Contenu = UN JSON atomique portant une LISTE de sessions (atomicité indispensable, HA n'a pas de transaction entre entités) : {"sessions":["2026-09-08 19:39:12.334+02","2026-09-09 01:26:04.887+02"], "label":"VW","confirmed_at":"","source":"whatsapp"} - Chaque entrée de `sessions` = clef `debut` rendue par la requête SANS reformatage (les `debut` portent les ms, ex. 2026-09-12 11:55:54.493+02 ; un arrondi minute ne matcherait jamais ; pas d'aller-retour par un objet Date). Le service EVCS joint sur ±1 s, pour chaque entrée de la liste. - Taille : 2 sessions ≈ 150 car, 3 ≈ 180, sous 255. Au-delà, publier en deux écritures espacées de > 90 s (le service aura polé entre les deux). - Après CHAQUE écriture : relire l'état et vérifier qu'il est identique à l'envoi (seul moyen de détecter un rejet silencieux HA). - Auto-correction (« JEEP… non VW ») = republier avec `confirmed_at` plus récent ; le service applique le dernier pour une `session_debut` donnée. - On NE vide PAS le helper à {} : l'invalidation est structurelle (session_debut). Le vidage ouvrirait une fenêtre de perte si le service est arrêté entre publication et vidage. ## Le sélecteur temps réel (ajout du 19/09/2026, demande du propriétaire via EVCS) Répondre WhatsApp ne suffisait pas : le sélecteur `input_select.victron_evcs_vehicle` pilote l'heure de départ (VW 08:50 / JEEP 06:30), donc la priorité et l'urgence de recharge de nuit, et il restait à changer À LA MAIN (vécu le 19/09 : VW branchée 11:01, sélecteur resté sur JEEP). Désormais, sur confirmation HUMAINE : - `setVehicleSelector(label, source)` dans concierge-write.js écrit AUSSI le sélecteur, via `input_select.select_option`, PUIS relit l'état — HA IGNORE en silence une option hors liste, la relecture est le seul détecteur. - Options valides EXACTEMENT `VW` et `JEEP` (casse comprise), liste EN DUR dans le code, volontairement PAS lue depuis HA : y ajouter une valeur est une décision, pas un effet de bord d'un changement côté HA. - MEGANE / INVITE / ZERO : on N'ÉCRIT PAS le sélecteur (il ne désigne que les deux véhicules du foyer) ; l'étiquette seule est publiée, le sélecteur reste tel quel. - Source HUMAINE uniquement (`whatsapp`) : la déduction règle-puissance ne conclut jamais VW vs JEEP et ne doit JAMAIS écrire le sélecteur — le garde est dans la fonction, pas seulement chez l'appelant. - Si l'écriture du sélecteur échoue APRÈS la publication de l'étiquette, la réponse WhatsApp le dit (« pense à le passer sur X à la main ») au lieu d'échouer : l'étiquette publiée ne doit pas être perdue pour un sélecteur. ## Garde-fous d'écriture (non négociables — restriction CONVENTIONNELLE) Le canal choisi est un token HA, qui peut techniquement écrire n'importe quel service HA : la restriction ne tient QUE par la discipline du code. Donc, côté OpenClaw : - Les noms des entités écrites sont DEUX CONSTANTES EN DUR (`input_text.airtria_vehicle_label` et `input_select.victron_evcs_vehicle`), jamais construites dynamiquement, jamais interpolées. Pas une de plus. - concierge-write.test.js ÉCHOUE si une écriture cible autre chose que ces deux constantes, si un service autre que set_value/select_option apparaît, si le sélecteur accepte un label hors foyer ou une source non humaine, ou si une relecture manque (équivalent du tests/advisor/run.mjs d'EVCS qui garde advisor_*). - Aucune autre écriture d'actionneur, jamais (liste des variables à propriétaire unique dans le MANUEL EVCS ; `carte-decisions.mjs` / `enquete.mjs` avant tout doute). ## Recoupement a posteriori (qualité de données) - Comparer `session_charge.debranche_a` aux départs attendus : VW 08:50 (`input_datetime.victron_evcs_departure_time`), JEEP 06:30 (`input_datetime.victron_evcs_departure_time_jeep`). - `debranche_a` n'est renseignée que si le service voit le débranchement (1 fois sur 12 récemment) → ne pas supposer qu'elle est toujours là. - Ne signaler que si ça CONTREDIT le label (comportement idiot), pas à chaque session. Pas de notification pour l'état normal. ## Côté service EVCS (v1, fait par la session EVCS) - Lit le helper chaque cycle (60 s), JSON.parse en try/catch ; objet vide / illisible / champs manquants → ne rien faire. - `confirmed_at` antérieur au `debut` → ignoré. Jointure ±1 s sur session_debut. - Met à jour `vehicule` ; `vehicule_sur` passe à VRAI uniquement sur confirmation humaine (changement de sens : défaut basculé à faux ; lignes antérieures gardent l'ancienne sémantique). Trace l'application dans `evenement` avec la source. ## Validation avant mise en service - `tools/campagne-conseiller.mjs` (dépôt EVCS) rejoue des instants réels ; cumuler 3 runs (un run de 5 passages = bruit, écart-type ~0,22). - Jeu de calibrage = les 12 sessions du 07 au 12/09, étiquetées par le propriétaire (voir ci-dessous). ⚠️ Le v0 ne DÉDUIT pas VW vs JEEP (impossible : la semaine, la routine était inversée — femme/JEEP en congés, fils/VW en stage sans retour le midi). Ce qui est testé = le ROUTAGE : MEGANE auto-détectée invité par la règle puissance ; toutes les autres correctement routées vers « demander » (sélecteur périmé). Ce sont les réponses du propriétaire qui donnent VW/JEEP. Si le routage rate une des 12, revoir la règle, pas l'étiquette. ### Cibles de routage du v0 (les 6 branchements du 08-12/09) - 11/09 16:53→18:27 (Megane, 1 session) : auto-invité par la règle puissance, AUCUNE question. - 5 autres branchements (2-3-2-1-1) : routés vers la question (sélecteur périmé). - Total attendu = 5 questions pour 12 sessions. Un regroupement qui rend des paires soir/nuit (au lieu du triplet du mercredi) est FAUX. ### Jeu de calibrage FINAL (corrigé en base, arbitré par le propriétaire 2026-09-13) ``` branchement sessions étiquette sam 12/09 11:55 → (en cours) 11:55 5,94 JEEP sam 12/09 11:42 → 11:53 11:42 0,09 JEEP ven 11/09 16:53 → 18:27 16:54 7,45 6360W/28A MEGANE (auto) jeu 10/09 18:19 → ven 16:27 18:20 2,97 · 01:24 4,26 VW mer 09/09 15:55 → jeu 09:05 15:55 6,61 · 19:35 0,77 · 01:25 4,88 JEEP (triplet) mar 08/09 19:39 → mer 08:57 19:39 0,76 · 01:26 5,01 VW ``` Semaine : 5 VW, 6 JEEP, 1 MEGANE (contre 11 VW + 1 JEEP avant correction). Le triplet mercredi = JEEP les trois : la femme a branché la JEEP, le fils n'a pas voulu la débrancher le soir pour mettre la VW → il a roulé à l'essence le jeudi. Contexte humain qu'aucune règle ne produit — la raison d'être du concierge. MEGANE = voiture de service (Rennes) + dépannage d'amis → « invité » RÉCURRENT. ### Cas tordus que le module DOIT gérer (dans les données de la semaine) - sam 12/09 : on 11:42, off 11:53, on 11:55 = DEUX branchements (2 min de débranchement), pas du flapping. La session 0,09 kWh en est la trace. - ven 11/09 : off 16:27, on 16:53 = DEUX branchements (26 min ; débranche un véhicule foyer pour brancher la Megane). - ⚠️ AUCUN seuil de durée pour fusionner : 2 min et 26 min sont l'une et l'autre de vraies séparations. Règle = « toute transition compte » ; défensive limitée aux incohérences STRUCTURELLES (on déjà ouvert, off sans ouverture). - Le branchement 12/09 11:55 est ENCORE OUVERT (borne haute nulle) → groupe en cours, ne pas conclure qu'il est incomplet. ### Fenêtre d'analyse : commencer sur un 'on' Une fenêtre doit démarrer sur un `on`, jamais au milieu d'un branchement : sinon le premier événement est un `off` orphelin (branchement ouvert avant la fenêtre), la session ouverte avant tombe hors intervalle et est classée « incertaine » à tort (ex. démarrer au 08/09 00:00 fait apparaître 6 questions au lieu de 5, à cause de la session 08/09 01:26). En production, aucun souci (on suit le flux) ; règle valable surtout au rejeu historique. Calibration calée sur 08/09 19:00. ### Invariant à vérifier EN PERMANENCE (le contrôle le plus solide du projet) Un seul câble ⟹ toutes les sessions d'un même branchement portent la MÊME étiquette. Un groupe à étiquettes mixtes = frontière fausse (Airtria) OU ligne fausse (base) — les deux méritent une alerte. Propriété PHYSIQUE : ne nécessite aucune connaissance métier (ni routine, ni horaire, ni puissance), et aucune évolution de règle ne peut l'invalider. À faire tourner en continu, pas seulement à la calibration — c'est ce qui aurait signalé le triplet incohérent le jour même. ## Points de vigilance - ⚠️ Inventaire BMS mensonger (découvert 2026-09-13) : le BMS annonce 4 modules / 400 Ah alors que le parc en compte 9 (le LV-Hub ne remonte que le groupe maître). Mesuré : 0,312 kWh/point de SOC (vs 0,169 sur l'ancien parc de 7). NE JAMAIS utiliser `capacite_installee` ni `nombre_de_modules_en_ligne` comme source de capacité (sous-estiment de ~38 %). Source qui fait foi : `input_number.evcs_balance_modules_en_service` (13 aujourd'hui). Concerne surtout un futur rôle 2 (rétro-évaluation énergétique), pas le concierge véhicule. - Helper HA en place : `input_text.airtria_vehicle_label` (max:255, état initial `unknown` = « rien à appliquer »). Kill-switch consommateur `input_boolean.airtria_concierge_enabled` (OFF) = propriété d'EVCS, Airtria n'y touche pas. Aller-retour écriture/relecture validé le 2026-09-13. - SPOF : OpenClaw vit sur 10.10.10.17, la machine qui porte aussi HA et n8n. Faible enjeu pour l'ID véhicule, mais réel. Chien de garde sur 10.10.10.22. - Prérequis à câbler : (1) accès lecture PostgreSQL pour Airtria (`PG_LECTURE_PASSWORD`, déposé protégé comme le token HA) ; (2) création du helper `input_text.airtria_vehicle_label` (max:255) dans le paquet HA. ## Supervision — alerte d'échec d'auth (2026-09-16) Motivation : le 15/09, EVCS a renouvelé le mot de passe `airtria_ro` ; le concierge a alors échoué à se connecter, mais EN SILENCE — sortie vide, donc aucun WhatsApp émis, aucune trace visible : il cessait simplement de poser la question sans que personne le sache. Même angle mort que le service EVCS (une trace écrite seulement AU RETOUR à la normale, dans PG, donc jamais quand c'est PG qui tombe). Correctif (module `concierge-health.js`, même patron que le moniteur LLM) : - **Battement de cœur À CHAQUE cycle** : `input_datetime.concierge_last_run` reçoit un horodatage (valeur qui CHANGE — HA ne rafraîchit pas le `last_changed` d'un helper réécrit à l'identique). Un concierge mort devient détectable (capteur `binary_sensor` de fraîcheur, > 20 min → « figé »). - **Statut live** : `input_text.concierge_status` (max:255) = `OK · HH:MM` ou `⚠️ · HH:MM`. - **Alerte WhatsApp UNIQUEMENT sur transition** (ok↔erreur), jamais un rappel périodique (« events not states ») : une panne qui dure ne spamme pas le canal. Dédup par état local `concierge-health-state.json`. - **Chemin HA indépendant de PostgreSQL** : l'alerte « auth KO » part précisément quand c'est l'auth qui tombe (le signal de vie n'emprunte pas le chemin qui tombe). Best-effort : si les helpers manquent, le concierge n'échoue pas ; l'alerte WhatsApp part quand même (elle ne dépend pas des helpers). Interrupteur `input_boolean.concierge_monitor_enabled` (OFF = pas de WhatsApp, affichage maintenu). - Refactor de `concierge-detection.js` : `readPgPassword()` LÈVE au lieu de `process.exit`, mot de passe résolu paresseusement dans `makeClient()` → l'échec devient RATTRAPABLE par l'étage live, qui le publie. Paquet HA : `concierge-health-ha-package.yaml.draft`. Machine à états vérifiée par `concierge-health.test.js` (dédup + transitions, sans réseau).