From 60165e139bd15201959a7e441c251a05991af2f5 Mon Sep 17 00:00:00 2001 From: Tallagrand Thierry Date: Mon, 14 Sep 2026 03:22:18 +0200 Subject: [PATCH] Calibration des regles d anomalie : trois tiennent, une ne tient pas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L oscillation rapide se calibre sans ambiguite : sur le contacteur reseau, le plus grand ecart pathologique vaut 193 s et le plus petit ecart normal 857 s, facteur 4,4 sans aucune valeur entre les deux. Quatre bascules en dix minutes isole exactement la salve du 12/09 et reste muet sur dix entites pendant dix jours. Piege trouve en calibrant : les plus petits ecarts normaux de plusieurs entites valent 242 a 320 s, c est-a-dire la cadence des workflows. Ce ne sont pas des oscillations mais deux executions consecutives qui changent d avis — toute regle a deux bascules en cinq minutes se declenche sur le fonctionnement normal. En revanche la regle des sessions reseau repetees NE SE CALIBRE PAS : les ecarts entre fermetures forment un continuum de 4 minutes a 24 heures, sans discontinuite. Or c est la pathologie la plus couteuse des deux. Je ne pose pas de seuil dessus : elle demande une formulation metier et plus d historique, et la bacler serait refaire l erreur que ce projet a payee six fois. La regle de la mesure figee, elle, se valide sur un incident reel : le 05/09 la tension et le courant batterie se sont tus 27 minutes pendant que le SOC, issu du meme flux, publiait toutes les deux minutes. Le discriminant est relatif, pas absolu — un silence long est normal pour une grandeur intermittente. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj --- .../mesures/20260914-calibration-anomalies.md | 86 +++++++++++++++++++ tools/calibrer-anomalies.mjs | 69 +++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 docs/mesures/20260914-calibration-anomalies.md create mode 100644 tools/calibrer-anomalies.mjs diff --git a/docs/mesures/20260914-calibration-anomalies.md b/docs/mesures/20260914-calibration-anomalies.md new file mode 100644 index 0000000..0bdd40b --- /dev/null +++ b/docs/mesures/20260914-calibration-anomalies.md @@ -0,0 +1,86 @@ +# Calibration des regles d anomalie — 14/09/2026 + +Rejouable : `node tools/calibrer-anomalies.mjs`. Historique disponible : du +04/09 (creation de la table `evenement`) au 14/09, dix entites suivies. + +## Regle 1 — oscillation rapide : CALIBREE, sans ambiguite + +Les 41 bascules du contacteur reseau se separent sans recouvrement : + +| | valeur | +|---|---| +| plus grand ecart **pathologique** (salve du 12/09) | **193 s** | +| plus petit ecart **normal** | **857 s** | + +Un facteur 4,4 entre les deux, aucune valeur entre. Balayage des couples +(N bascules, T fenetre) sur les dix entites : + +| seuil | ce qui sort | +|---|---| +| 2 en 5 min | **19 episodes** sur 8 entites — inutilisable | +| 3 en 15 min | 11 episodes sur 6 entites | +| 3 en 10 min | la salve du 12/09 + 3 faux positifs | +| **4 en 10 min** | **la salve du 12/09, et RIEN d autre** | +| 3 en 5 min | idem, mais moins de marge | + +**Retenu : 4 bascules en 10 minutes.** Il isole exactement +`12/09 06:25 -> 06:35, 6 bascules en 579 s` et reste muet sur les dix entites +pendant dix jours. + +⚠️ **Piege trouve en calibrant** : les plus petits ecarts « normaux » de +plusieurs entites valent **242, 249, 251, 258, 320 s** — c est-a-dire ~5 minutes, +la CADENCE des workflows du bilan et de l ACM. Ce ne sont pas des oscillations, +ce sont deux executions consecutives qui changent d avis. Toute regle avec +N=2 et T <= 5 min se declenche donc sur le fonctionnement normal. + +## Regle 2 — sessions reseau repetees : NON CALIBRABLE EN L ETAT + +Le second oscillateur connu est LENT : la nuit du 26-27/08 comptait trois +sessions reseau (2,5 kWh importes), celle du 20-21/08 six, avec une periode +d environ trente minutes. **Une fenetre de dix minutes ne peut pas le voir.** + +La pathologie est bien presente dans les donnees actuelles : 7 fermetures du +contacteur le 11/09, dont quatre le soir (16:58, 20:13, 22:39, 23:43) hors de +toute salve rapide, et 3 le 06/09 — dont deux sont le defaut documente de la +regle de protection, corrige le meme jour. + +Mais les ecarts entre fermetures forment un **continuum** : + + 1155 · 196 · 732 · 87 · 702 · 1413 · 1476 · 1391 · 1459 · 963 · 195 · + 147 · 64 · 403 · 4 · 4 · 542 · 282 · 306 (minutes) + +De 4 minutes a 24 heures sans discontinuite. Tout seuil « N fermetures en H +heures » y serait arbitraire, et melangerait les sessions de nuit avec les +fermetures diurnes de la regle de protection, qui sont une autre affaire. + +**Decision : ne PAS figer cette regle.** Elle demande une formulation metier — +« plus d une session par fenetre HC », ou « re-fermeture alors que les +conditions du relachement tiennent toujours » — et davantage d historique. La +poser au juge serait exactement l erreur que ce projet a deja payee six fois. + +## Regle 3 — mesure figee : CALIBRABLE, validee sur un incident reel + +L incident du 05/09 (HANDOFF §57) est parfaitement visible : + +| grandeur | plus long trou, 20:30-22:30 | points | +|---|---:|---:| +| `battery_voltage` | **27 min** | 92 | +| `battery_current` | **27 min** | 94 | +| `battery_soc` | 2 min | 119 | +| `consumption_power_l1` | 1 min | 119 | + +Le discriminant n est PAS le silence absolu — l ecart median de toutes les +grandeurs vaut 61-63 s, mais le maximum atteint **22 heures** pour la puissance +de la borne, et c est normal : le service n enregistre que les changements, et +un vehicule debranche ne change pas. Le discriminant est **relatif** : une +grandeur se tait pendant qu une SOEUR de la meme source continue de publier. +Le 05/09, le courant batterie s est tu 27 minutes — 26 fois sa cadence — quand +le SOC, issu du meme flux MQTT Victron, publiait toutes les deux minutes. + +## Ce que la calibration change au plan + +Le plan approuve prevoyait quatre regles. **Trois sont construisibles** +(oscillation rapide, mesure figee, engagement non relache) ; la quatrieme — +sessions repetees — ne l est pas sans formulation metier. C est la plus +couteuse des deux pathologies d oscillation (2,5 kWh contre ~0,5), donc elle +merite son propre chantier plutot qu un seuil pose a la hate. diff --git a/tools/calibrer-anomalies.mjs b/tools/calibrer-anomalies.mjs new file mode 100644 index 0000000..4be6bba --- /dev/null +++ b/tools/calibrer-anomalies.mjs @@ -0,0 +1,69 @@ +#!/usr/bin/env node +// CALIBRATION DES REGLES D ANOMALIE — LECTURE SEULE, rejouable. +// +// node tools/calibrer-anomalies.mjs +// +// On ne fige pas un seuil au juge. Les six oscillateurs de ce projet sont tous +// nes de constantes posees « au pif » (cf. la memoire du 24/08). Un seuil se +// CHOISIT sur l historique, en exigeant qu il SEPARE le cas pathologique connu +// des bascules normales — et s il ne separe pas, on ne le fige pas. +// +// A REJOUER apres toute evolution du parc ou des workflows : la cadence des +// automates change les ecarts, donc les seuils. +// On ne fige pas un seuil au juge : on le choisit sur l historique, en exigeant +// qu il SEPARE le cas pathologique connu (la salve du 12/09) des bascules +// normales du cycle jour/nuit. +import fs from 'fs'; +import { execFileSync } from 'child_process'; +const env = fs.readFileSync('.env', 'utf8'); +const mdp = (env.match(/^PG_LECTURE_PASSWORD=(.*)$/m)?.[1] || '').trim().replace(/^["']|["']$/g, ''); +const sql = (q) => execFileSync('psql', + ['-h','10.10.10.22','-p','5433','-U','evcs_lecture','-d','evcs','-At','-F','\t','-c',q], + { env: { ...process.env, PGPASSWORD: mdp }, encoding: 'utf8' }) + .trim().split('\n').filter(Boolean).map(l => l.split('\t')); + +const ENTITES = sql(`select detail->>'entite', count(*) from evenement + where detail ? 'avant' group by 1 having count(*) >= 10 order by 2 desc`); + +// Un episode = N bascules dans une fenetre glissante de T secondes. +function episodes(ts, N, T) { + const out = []; + for (let i = 0; i + N - 1 < ts.length; i++) { + if (ts[i + N - 1] - ts[i] <= T * 1000) { + const dernier = out[out.length - 1]; + // On etend l episode en cours au lieu d en ouvrir un par fenetre. + if (dernier && ts[i] - dernier.fin <= T * 1000) { dernier.fin = ts[i + N - 1]; dernier.n++; } + else out.push({ debut: ts[i], fin: ts[i + N - 1], n: N }); + } + } + return out; +} + +const fr = (ms) => new Date(ms).toLocaleString('fr-FR', { timeZone: 'Europe/Paris', day:'2-digit', month:'2-digit', hour:'2-digit', minute:'2-digit' }); + +console.log('ENTITE bascules plus petit ecart'); +console.log('-'.repeat(78)); +const donnees = new Map(); +for (const [e, n] of ENTITES) { + const ts = sql(`select extract(epoch from ts)*1000 from evenement + where detail->>'entite'='${e}' order by ts`).map(r => Number(r[0])); + donnees.set(e, ts); + let min = Infinity; + for (let i = 1; i < ts.length; i++) min = Math.min(min, (ts[i] - ts[i-1]) / 1000); + console.log(`${e.slice(0,44).padEnd(46)} ${String(n).padStart(5)} ${Math.round(min)} s`); +} + +console.log('\n\nCE QUE CHAQUE COUPLE (N, T) DECLENCHE\n' + '='.repeat(78)); +for (const [N, T] of [[3,600],[3,300],[4,600],[2,300],[3,900]]) { + console.log(`\n--- ${N} bascules en ${T/60} min ---`); + for (const [e, ts] of donnees) { + const eps = episodes(ts, N, T); + const court = e.replace(/^(switch|input_boolean|input_text|binary_sensor|sensor)\./,''); + if (!eps.length) { console.log(` ${court.slice(0,40).padEnd(42)} muet`); continue; } + console.log(` ${court.slice(0,40).padEnd(42)} ${eps.length} episode(s)`); + for (const x of eps.slice(0, 4)) { + console.log(` ${fr(x.debut)} -> ${fr(x.fin)} ${x.n} bascules en ${Math.round((x.fin-x.debut)/1000)} s`); + } + if (eps.length > 4) console.log(` … et ${eps.length - 4} autre(s)`); + } +}