Calibration des regles d anomalie : trois tiennent, une ne tient pas
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
86
docs/mesures/20260914-calibration-anomalies.md
Normal file
86
docs/mesures/20260914-calibration-anomalies.md
Normal file
@@ -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.
|
||||
69
tools/calibrer-anomalies.mjs
Normal file
69
tools/calibrer-anomalies.mjs
Normal file
@@ -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)`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user