iobroker.anomaly-detection 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +253 -0
- package/README.md +7 -1
- package/build/main.js +10 -16
- package/build/main.js.map +2 -2
- package/io-package.json +14 -1
- package/package.json +1 -1
package/README.de.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<img src="admin/anomaly-detection.png" alt="Logo" width="400">
|
|
2
|
+
|
|
3
|
+
# ioBroker.anomaly-detection
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/iobroker.anomaly-detection)
|
|
6
|
+
|
|
7
|
+
[English](README.md) · **Deutsch**
|
|
8
|
+
|
|
9
|
+
Der Adapter lernt das normale Verhalten ausgewählter numerischer ioBroker-Zustände und meldet statistisch ungewöhnliche Werte lokal.
|
|
10
|
+
|
|
11
|
+
> **Es wird keine KI verwendet.** Der Adapter nutzt weder Cloud-Dienste noch externe KI- oder Machine-Learning-APIs. Alle Bewertungen werden lokal mit deterministischen mathematischen und statistischen Verfahren berechnet, unter anderem Median, Median Absolute Deviation (MAD), Zeitbereichs-Baselines und robuste Steigungen. Bei gleichen Eingangsdaten und Einstellungen sind die Ergebnisse nachvollziehbar und reproduzierbar.
|
|
12
|
+
|
|
13
|
+
## Funktionsweise
|
|
14
|
+
|
|
15
|
+
Starre Grenzwerte können zeit- oder betriebsabhängiges Verhalten nicht abbilden: 8 kW können morgens normal, an einem warmen Sonntag aber ungewöhnlich sein. Für jeden konfigurierten numerischen Zustand verwaltet der Adapter begrenzte, robuste Baselines. Jede eingehende Zustandsänderung wird ausgewertet; die Quellzustände werden nicht abgefragt (kein Polling).
|
|
16
|
+
|
|
17
|
+
Der Adapter kombiniert folgende deterministische Detektoren:
|
|
18
|
+
|
|
19
|
+
- **Wertabweichungen (MAD):** Erkennt Werte außerhalb des gelernten Normalbereichs, ohne dass ein einzelner Extremwert die Baseline stark verzerrt.
|
|
20
|
+
- **Änderungsrate:** Bewertet `Delta / vergangene Zeit` und berücksichtigt dadurch unregelmäßige Update-Abstände.
|
|
21
|
+
- **Festhängende Werte:** Erkennt wiederholt empfangene identische Werte, die länger als die konfigurierte Dauer anhalten. Ein fehlendes Update allein gilt niemals als festhängend.
|
|
22
|
+
- **Tageszeit-Kontext:** Verwendet standardmäßig Stundenbereiche und kann zusätzlich nach Wochentagen getrennt werden. Bei zu wenigen Daten greift die Hierarchie auf den Zeitbereich und anschließend auf die globale Baseline zurück.
|
|
23
|
+
|
|
24
|
+
`score` ist ein deterministischer Schweregrad von 0 bis 100. Er ist weder eine statistische Wahrscheinlichkeit noch eine Diagnose der physischen Ursache.
|
|
25
|
+
|
|
26
|
+
| Score | Bedeutung |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| 0–49 | normal |
|
|
29
|
+
| 50–69 | ungewöhnlich |
|
|
30
|
+
| 70–84 | Anomalie |
|
|
31
|
+
| 85–100 | starke Anomalie |
|
|
32
|
+
|
|
33
|
+
## Mathematische Grundlagen
|
|
34
|
+
|
|
35
|
+
Der Adapter verwendet begrenzte, robuste Statistik. Er trainiert kein neuronales Netz, ruft keinen KI-Dienst auf und setzt keine konstanten Messintervalle voraus.
|
|
36
|
+
|
|
37
|
+
### Wertabweichungen: Median und MAD
|
|
38
|
+
|
|
39
|
+
Für eine gelernte Baseline mit Werten `x₁ … xₙ` ist der erwartete Wert der Median `m = median(x₁ … xₙ)`. Die Streuung wird mit der Median Absolute Deviation berechnet:
|
|
40
|
+
|
|
41
|
+
`MAD = median(|xᵢ − m|)`
|
|
42
|
+
|
|
43
|
+
Für einen neuen Wert `x` ergibt sich der robuste z-Score:
|
|
44
|
+
|
|
45
|
+
`robuster z-Score = 0,6745 × |x − m| / MAD`
|
|
46
|
+
|
|
47
|
+
Die Option **Sensitivität** legt fest, bei welchem z-Score dieser Detektor den Wert 100 erreicht. Median und MAD werden von einzelnen Ausreißern deutlich weniger verzerrt als Mittelwert und Standardabweichung. Bei `MAD = 0` ist ein unveränderter Wert normal; ein anderer Wert gilt als starke Abweichung.
|
|
48
|
+
|
|
49
|
+
### Änderungsrate
|
|
50
|
+
|
|
51
|
+
Für zwei aufeinanderfolgende Werte wird das tatsächliche Zeitintervall verwendet:
|
|
52
|
+
|
|
53
|
+
`Rate = (aktueller Wert − vorheriger Wert) / vergangene Sekunden`
|
|
54
|
+
|
|
55
|
+
Die Rate-Baseline wird mit derselben Median/MAD-Methode gelernt. So kann eine ungewöhnlich schnelle Änderung erkannt werden, auch wenn der absolute Endwert noch plausibel ist.
|
|
56
|
+
|
|
57
|
+
### Festhängende Werte
|
|
58
|
+
|
|
59
|
+
Dieser Detektor ist regelbasiert. Werden identische Werte wiederholt empfangen, merkt sich der Adapter den ersten Zeitpunkt `t₀`. Ein Treffer ist erst möglich, wenn gilt:
|
|
60
|
+
|
|
61
|
+
`aktueller Zeitpunkt − t₀ ≥ konfigurierte Dauer`
|
|
62
|
+
|
|
63
|
+
Ein fehlendes Update zählt nicht als wiederholter Wert und kann daher keine Festhängend-Anomalie auslösen.
|
|
64
|
+
|
|
65
|
+
### Tageszeit- und Wochentags-Kontext
|
|
66
|
+
|
|
67
|
+
Zeitbereiche werden aus der lokalen Tageszeit gebildet:
|
|
68
|
+
|
|
69
|
+
`Zeitbereich = floor(Minuten seit Mitternacht / Bucket-Größe)`
|
|
70
|
+
|
|
71
|
+
Mit aktivierter Wochentagsoption wird der Wochentag in den Schlüssel aufgenommen. Eine Baseline wird nur verwendet, wenn sie mindestens **Mindestanzahl gelernter Messwerte** enthält. Die Fallback-Reihenfolge lautet:
|
|
72
|
+
|
|
73
|
+
`Wochentag + Zeitbereich → Zeitbereich → globale Baseline`
|
|
74
|
+
|
|
75
|
+
### Kontextabhängige Baseline
|
|
76
|
+
|
|
77
|
+
Kontextzustände erzeugen zusätzliche Schlüssel, zum Beispiel `pumpe.ein=true`, `modus=eco` oder `temperatur=10–15`. Numerische Kontextwerte werden in feste Intervalle eingeteilt:
|
|
78
|
+
|
|
79
|
+
`Intervallbeginn = floor(Kontextwert / Bucket-Breite) × Bucket-Breite`
|
|
80
|
+
|
|
81
|
+
Bei einer Breite von 5 gehört der Wert 12,3 zum Intervall 10–15. Ein Kontext muss mindestens die konfigurierte Mindestanzahl an Messwerten enthalten, bevor er als Baseline verwendet wird. Ein gültiger, aber noch nicht ausgereifter Kontext bleibt im Status `learning` und wird nicht mit einer fremden globalen Baseline verglichen. Fehlende oder ungültige Kontextwerte verwenden den normalen Zeit-/Global-Fallback. Kombinationen, Kategorien und Samples sind begrenzt, damit der Speicher nicht unkontrolliert wächst.
|
|
82
|
+
|
|
83
|
+
### Dauerhafte Pegelverschiebung
|
|
84
|
+
|
|
85
|
+
Die Auswertung arbeitet mit Residuen:
|
|
86
|
+
|
|
87
|
+
`Residuum = tatsächlicher Wert − erwarteter Wert`
|
|
88
|
+
|
|
89
|
+
Der robuste Median eines begrenzten aktuellen Residuenfensters wird mit dem langfristig erwarteten Residuum verglichen und anhand der MAD-Baseline normiert. Eine Verschiebung muss bei mehreren Auswertungen in dieselbe Richtung anhalten; ein einzelner Peak wird dadurch nicht zu einem Change Point.
|
|
90
|
+
|
|
91
|
+
### Trend
|
|
92
|
+
|
|
93
|
+
Auch der Trend wird auf Residuen berechnet, damit normale Tagesmuster zuvor abgezogen werden. Für alle gültigen Wertepaarungen wird die Steigung bestimmt:
|
|
94
|
+
|
|
95
|
+
`Steigungᵢⱼ = (Residuumⱼ − Residuumᵢ) / (Zeitstempelⱼ − Zeitstempelᵢ)`
|
|
96
|
+
|
|
97
|
+
Die finale Steigung ist der Median dieser Steigungen (robuste Theil-Sen-ähnliche Methode). Isolierte Ausreißer haben dadurch deutlich weniger Einfluss als bei einer gewöhnlichen linearen Regression. Ein Trend benötigt mindestens 12 Messwerte über mindestens sechs Stunden.
|
|
98
|
+
|
|
99
|
+
### Kombinierter Score und Lernen
|
|
100
|
+
|
|
101
|
+
Jeder Detektor liefert einen begrenzten Score von 0 bis 100. Wert-/Kontext-, Pegelverschiebungs- und Trendsignale gelten als zusammengehörige Evidenz; nur das stärkste dieser Signale wird gewichtet, damit nichts doppelt gezählt wird. Rate- und Festhängend-Signale können unabhängig beitragen. Mehrere unabhängige starke Signale erzeugen einen kleinen begrenzten Bonus. Der Endwert wird immer auf 0–100 begrenzt.
|
|
102
|
+
|
|
103
|
+
Messwerte ab **Anomalie-Schwellenwert** werden normalerweise nicht weiter gelernt, damit ein anhaltender Fehler nicht sofort als normal gilt. Bestätigte Pegelverschiebungen sind eine Ausnahme: Ihre Werte werden schrittweise aufgenommen, damit ein legitimes neues Betriebsniveau zur neuen Baseline werden kann.
|
|
104
|
+
|
|
105
|
+
## Konfiguration
|
|
106
|
+
|
|
107
|
+
Füge in der Tabelle **Überwachte numerische Zustände** für jede Quelle eine Zeile hinzu. Unterstützt werden nur ioBroker-Zustände mit `common.type: number`.
|
|
108
|
+
|
|
109
|
+
Die wichtigsten Standardwerte sind bewusst zurückhaltend:
|
|
110
|
+
|
|
111
|
+
- Vor der Überwachung werden standardmäßig 30 Messwerte gelernt.
|
|
112
|
+
- Die Sensitivität beträgt 3,5 robuste z-Scores.
|
|
113
|
+
- Die persistente Anomalieanzeige benötigt standardmäßig Score 70 für fünf Minuten.
|
|
114
|
+
- Eine Hysterese von 10 Punkten verhindert Flattern an der Schwelle.
|
|
115
|
+
- Der Festhängend-Detektor ist standardmäßig deaktiviert und verwendet bei Aktivierung 120 Minuten.
|
|
116
|
+
- Der Tageszeitkontext arbeitet standardmäßig mit 60-Minuten-Bereichen; der Wochentagskontext ist optional.
|
|
117
|
+
|
|
118
|
+
Änderungen werden nach dem Neustart des Adapters wirksam. Doppelte Quell-IDs werden nach der ersten aktivierten Zeile ignoriert. Fehlende konfigurierte Objekte bleiben abonniert, damit sie später automatisch verwendet werden können; beim Start wird eine Warnung protokolliert.
|
|
119
|
+
|
|
120
|
+
Wird eine Quelle aus der Konfiguration entfernt und der Adapter neu gestartet, löscht er den zugehörigen generierten Teilbaum unter `anomaly-detection.0.sources` sowie das gespeicherte Modell. Dabei wird die ursprüngliche Quell-ID aus den Metadaten des generierten Geräts verwendet. Eine nur vorübergehend nicht verfügbare, weiterhin konfigurierte Quelle bleibt erhalten.
|
|
121
|
+
|
|
122
|
+
## Analyseansicht und Verlauf
|
|
123
|
+
|
|
124
|
+
Die Registerkarte **Aktuelle Anomaliebewertungen** enthält pro überwachte Quelle ein responsives, zunächst eingeklapptes Diagramm **Verlauf & Anomalien**. Der Verlauf wird erst beim Öffnen geladen. Zur Auswahl stehen **1 h**, **6 h**, **24 h** (Standard) und **7 Tage**.
|
|
125
|
+
|
|
126
|
+
Das Diagramm verwendet die Einheit des ioBroker-Zustands, zum Beispiel `W`, `%` oder `°C`, und passt seine Breite an die tatsächliche Kartenbreite an. Es zeigt:
|
|
127
|
+
|
|
128
|
+
- **Wert:** historische Messwerte aus dem konfigurierten History-Adapter.
|
|
129
|
+
- **Erwartungsbereich:** ein transparentes Band ausschließlich aus den zu jedem historischen Zeitpunkt gespeicherten `decisionLow`/`decisionHigh`-Grenzen. Fehlende Grenzen bleiben leer; der aktuelle Bereich wird nicht rückwirkend projiziert.
|
|
130
|
+
- **Kontext:** eine dezente Hervorhebung für Zeitabschnitte, deren gespeicherter Kontext zum aktuellen Kontext passt. Wenn vorhanden, werden die ioBroker-`common.name`-Namen angezeigt.
|
|
131
|
+
- **Anomalie:** ein rotes Dreieck nur bei historischen Bewertungen mit `detected: true`.
|
|
132
|
+
|
|
133
|
+
Beim Überfahren oder Berühren eines Punktes werden Wert und Einheit, historischer Erwartungsbereich (oder ein Hinweis, dass er nicht verfügbar ist), Status, übersetzter Grund, Score und Kontext für genau diesen Zeitpunkt angezeigt. Bei Wert-/Kontextabweichungen wird zusätzlich die konkrete Distanz oberhalb oder unterhalb des historischen Bereichs angegeben. Rate, Festhängend, Trend und Pegelverschiebung behalten ihre jeweilige Detektor-Erklärung.
|
|
134
|
+
|
|
135
|
+
## Lernen und Persistenz
|
|
136
|
+
|
|
137
|
+
Normale Beobachtungen aktualisieren globale und zeitbasierte Baselines schrittweise. Messwerte ab dem Anomalie-Schwellenwert werden nicht gelernt. Pro Baseline werden höchstens 240 Samples und kompakte Modelldaten gespeichert; eine unbegrenzte Rohdaten-Zeitreihe wird nicht angelegt.
|
|
138
|
+
|
|
139
|
+
Alle erweiterten Detektoren sind standardmäßig deaktiviert. Kontextabhängige Erkennung kann bis zu drei boolesche, Text-/Enum- oder numerisch gebucketete Zustände getrennt lernen. Eine gültige, aber noch zu kleine Kontext-Baseline bleibt im Lernstatus. Pegelverschiebung benötigt wiederholte Evidenz; Trend benötigt mindestens 12 Residuen über sechs Stunden.
|
|
140
|
+
|
|
141
|
+
Der Adapter erkennt statistische Ungewöhnlichkeit, diagnostiziert aber keinen Gerätefehler und beweist keine Ursache. Ein History-Import trainiert nur globale, Zeit-, Wochentags- und Rate-Modelle. Kontextbaselines lernen aus Live-Werten, weil allgemeine History-Adapter beliebige Kontextzustände nicht zuverlässig zeitlich rekonstruieren können.
|
|
142
|
+
|
|
143
|
+
## Optionen im Überblick
|
|
144
|
+
|
|
145
|
+
| Option | Zweck und Anwendung | Beispiel |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| **Aktiviert** | Startet oder stoppt die Überwachung dieser Zeile. Deaktivierte Zeilen bleiben konfiguriert. | Während einer Wartung deaktivieren. |
|
|
148
|
+
| **Quellstatus-ID** | Numerischer ioBroker-Zustand, der bewertet wird. Über den Objekt-Picker auswählen. | `alias.0.pumpe.leistung` |
|
|
149
|
+
| **Name** | Lesbarer Name für das generierte Quellgerät; ändert nicht die Quell-ID. | `Gartenpumpe Leistung` |
|
|
150
|
+
| **Initiales Training** | Nur Live-Werte lernen oder vorhandene History importieren. | Für einen Power-Meter mit InfluxDB-History: History importieren. |
|
|
151
|
+
| **History-Quelle** | Kompatible, aktivierte History-Adapterinstanz für den Import. | `influxdb.0` |
|
|
152
|
+
| **Zeitraum des History-Trainings (Tage)** | Wie weit der Import zurückreicht. Nur repräsentative Normalzeiten auswählen. | `30` Tage |
|
|
153
|
+
| **Maximal importierte Proben** | Begrenzung des Imports zum Schutz von Speicher und Raspberry Pi. | `1000` |
|
|
154
|
+
| **Überwachung nach History-Import starten** | Startet nach ausreichendem Import direkt den Status `monitoring`. | Für eine etablierte Messreihe aktivieren. |
|
|
155
|
+
| **Mindestens gelernte Proben** | Mindestgröße einer vertrauenswürdigen Baseline. | `30` für häufige Leistungswerte. |
|
|
156
|
+
| **Zeit-Bucket-Größe (Minuten)** | Größe der Tageszeitbereiche. Kleinere Bereiche sind genauer, lernen aber langsamer. | `60` Minuten |
|
|
157
|
+
| **Sensitivität (robuster z-Score)** | Niedriger ist empfindlicher, höher toleranter. | Auf `5` erhöhen, wenn normale Schwankungen zu viele Meldungen erzeugen. |
|
|
158
|
+
| **Anomalie-Schwellenwert** | Score, ab dem eine Anomalie gespeichert und nicht mehr gelernt wird. | `70` |
|
|
159
|
+
| **Mindestdauer einer Anomalie (Minuten)** | Zeit, die die Schwelle überschritten sein muss, bevor `detected` dauerhaft gesetzt wird. | `5` Minuten |
|
|
160
|
+
| **Detektor für Wertabweichungen (MAD)** | Erkennt ungewöhnliche absolute Werte gegenüber dem gelernten Bereich. | Für Temperatur, Druck, Verbrauch und Leistung aktivieren. |
|
|
161
|
+
| **Detektor für Änderungsrate** | Erkennt ungewöhnlich schnelle Änderungen. | Für Durchfluss oder Füllstand aktivieren; bei normalen Lastsprüngen deaktiviert lassen. |
|
|
162
|
+
| **Detektor für festhängende Werte** | Erkennt wiederholt empfangene identische Werte über die konfigurierte Dauer. | Für regelmäßig wechselnde Sensoren aktivieren, nicht für lange konstante Setpoints. |
|
|
163
|
+
| **Dauer eines festhängenden Wertes** | Wird nur bei aktiviertem Festhängend-Detektor angezeigt; legt dessen Mindestdauer fest. | `120` Minuten |
|
|
164
|
+
| **Tageszeit-Kontext verwenden** | Trennt Baselines nach Tageszeit. | `60`-Minuten-Buckets für Haushaltsverbrauch. |
|
|
165
|
+
| **Wochentags-Kontext verwenden** | Trennt zusätzlich nach Wochentag; funktioniert zusammen mit dem Tageszeit-Kontext. | Büroverbrauch werktags gegenüber Wochenende. |
|
|
166
|
+
| **Kontextbezogene Erkennung aktivieren** | Lernt abhängig von bis zu drei weiteren Zuständen. | `pumpe.ein`, `modus`, Außentemperatur. |
|
|
167
|
+
| **Kontextzustände** | Die Zustände, die den Betriebszustand beschreiben. Boolesche und Enum-Werte benötigen keine Breite; numerische Werte verwenden Buckets. | `pumpe.ein=true`, Temperatur-Breite `5`. |
|
|
168
|
+
| **Dauerhafte Erkennung von Pegelverschiebungen** | Erkennt ein anhaltend verändertes Betriebsniveau. | Standby-Leistung dauerhaft von 5 W auf 12 W gestiegen. |
|
|
169
|
+
| **Trenderkennung aktivieren** | Erkennt langsame Aufwärts- oder Abwärtsentwicklung über Residuen. | Heizdauer steigt über mehrere Tage. |
|
|
170
|
+
|
|
171
|
+
### Auswahlhilfe für Detektoren
|
|
172
|
+
|
|
173
|
+
- **MAD/Wertabweichungen:** Für unerwartete Einzelwerte relativ zum gelernten Normalverhalten.
|
|
174
|
+
- **Änderungsrate:** Wenn ungewöhnlich schnelle Änderungen relevant sind; deaktivieren, wenn schnelle Änderungen normal sind.
|
|
175
|
+
- **Festhängend:** Für Sensoren, die sich regelmäßig verändern müssen; nicht für Werte, die lange legitim konstant bleiben.
|
|
176
|
+
- **Kontextbezogen:** Wenn ein anderer Zustand das erwartete Verhalten erklärt. Boolesche/Enum-Kontexte beschreiben Betriebsart oder Anwesenheit, numerische Kontexte zum Beispiel Außentemperatur, Ladezustand oder Last.
|
|
177
|
+
- **Pegelverschiebung:** Für dauerhafte Änderungen des normalen Betriebsniveaus.
|
|
178
|
+
- **Trend:** Für langsame Drift oder Verschlechterung; bei stark schwankenden Signalen meist deaktiviert lassen.
|
|
179
|
+
|
|
180
|
+
### Praxisbeispiel: Pumpenleistung
|
|
181
|
+
|
|
182
|
+
Für die elektrische Leistung einer Pumpe sind zunächst **Detektor für Wertabweichungen**, **kontextbezogene Erkennung** und **dauerhafte Erkennung von Pegelverschiebungen** sinnvoll. **Detektor für Änderungsrate**, **Detektor für festhängende Werte** und **Trenderkennung** bleiben zunächst deaktiviert, weil normale Start-/Stoppvorgänge schnelle Änderungen verursachen und eine ausgeschaltete Pumpe legitim bei `0 W` stehen kann.
|
|
183
|
+
|
|
184
|
+
Als Kontext eignen sich der Ein-/Aus-Zustand und die Betriebsart. So entstehen getrennte Normalmodelle für beispielsweise Pumpe AUS, Pumpe EIN im Normalbetrieb und Pumpe EIN im Boost-Modus. Weitere Zustände nur hinzufügen, wenn sie die erwartete Leistung nachweislich erklären; zu viele Kontextzustände verteilen die Samples auf zu viele Kombinationen.
|
|
185
|
+
|
|
186
|
+
## Optionales History-Training
|
|
187
|
+
|
|
188
|
+
Für eine Quelle **Vorhandene History importieren** auswählen und danach eine **History-Quelle** aus der Auswahlliste wählen. Die Liste enthält nur vollständige, aktivierte History-Provider wie `influxdb.0`, `history.0` oder `sql.1`, die für genau diesen Zustand aktiviert sind. Wenn keine Quelle angezeigt wird, ist für den Zustand oder sein Alias-Ziel keine kompatible History aktiviert; **Nur Live-Werte lernen** bleibt trotzdem verfügbar.
|
|
189
|
+
|
|
190
|
+
Standardmäßig werden 30 Tage und höchstens 1.000 Samples importiert. Ungültige Werte und Zeitstempel werden verworfen, doppelte Zeitstempel zusammengeführt und übergroße Ergebnismengen gleichmäßig über den gesamten Zeitraum verteilt. Offensichtliche historische Extremwerte werden vor dem Lernen robust ausgeschlossen. Große Zeitlücken erzeugen niemals eine Festhängend-Anomalie.
|
|
191
|
+
|
|
192
|
+
Ein importiertes Modell wird nach einem Neustart wiederverwendet. Eine Änderung von History-Quelle, Zeitraum oder Sample-Limit startet einen neuen Import. Bei fehlgeschlagenem oder zu kleinem Import bleibt die Quelle nutzbar und lernt live weiter. Über `sources.<sanitized-source-id>.retrain = true` kann ein Modell ausdrücklich verworfen und neu trainiert werden; der Adapter setzt den Wert anschließend mit `ack=true` auf `false` zurück.
|
|
193
|
+
|
|
194
|
+
## Erzeugte Objekte
|
|
195
|
+
|
|
196
|
+
Für jede Quelle legt der Adapter unter folgendem Pfad eine sichere, deterministische Objekt-ID an:
|
|
197
|
+
|
|
198
|
+
`anomaly-detection.0.sources.<sanitized-source-id>.analysis`
|
|
199
|
+
|
|
200
|
+
| State | Typ/Rolle | Bedeutung |
|
|
201
|
+
| --- | --- | --- |
|
|
202
|
+
| `actual` | number / `value` | zuletzt akzeptierter Quellwert |
|
|
203
|
+
| `expected` | number / `value` | Median der verwendeten Baseline |
|
|
204
|
+
| `deviation` | number / `value` | `actual - expected` |
|
|
205
|
+
| `score` | number / `value` | normierter Anomalie-Score 0–100 |
|
|
206
|
+
| `detected` | boolean / `indicator` | persistente Anomalieanzeige |
|
|
207
|
+
| `status` | string / `info.status` | `insufficientData`, `learning` oder `monitoring` |
|
|
208
|
+
| `reason` | string / `text` | deterministische Erklärung der aktuellen Bewertung |
|
|
209
|
+
| `lastAnomaly` | string / `date` | Zeitpunkt der letzten hohen Bewertung |
|
|
210
|
+
| `sampleCount` | number / `value` | gespeicherte globale Samples, maximal 240 |
|
|
211
|
+
| `baselineScope` | string / `info.status` | verwendete Baseline: `context`, `time`, `global` oder `insufficient` |
|
|
212
|
+
| `activeContext` | string / `text` | aktueller normalisierter Kontext |
|
|
213
|
+
| `baselineSampleCount` | number / `value` | Samples der tatsächlich verwendeten Baseline |
|
|
214
|
+
| `contextSampleCount` | number / `value` | Samples im passenden Kontextmodell |
|
|
215
|
+
| `bootstrapStatus` | string / `info.status` | Ergebnis des optionalen History-Imports |
|
|
216
|
+
| `retrain` | boolean / `button` | `true` schreiben, um Quelle und Modell zurückzusetzen |
|
|
217
|
+
|
|
218
|
+
Alle vom Adapter geschriebenen Ergebnis-States verwenden `ack=true`. `retrain` ist der einzige Befehls-State und wird nach der Verarbeitung zurückgesetzt.
|
|
219
|
+
|
|
220
|
+
## Einschränkungen und Datenschutz
|
|
221
|
+
|
|
222
|
+
Eine Anomalie bedeutet, dass ein Wert oder Verhalten relativ zu den gelernten Beobachtungen ungewöhnlich ist. Sie beweist weder einen Gerätefehler noch dessen Ursache. Ungeeignete Einstellungen, nicht repräsentative Trainingszeiträume oder sich änderndes Anlagenverhalten können Fehlalarme oder übersehene Anomalien verursachen.
|
|
223
|
+
|
|
224
|
+
Verarbeitung und Modellspeicherung erfolgen vollständig lokal in ioBroker. Kein Messwert wird an einen externen KI-, Analyse- oder Cloud-Dienst gesendet.
|
|
225
|
+
|
|
226
|
+
## Changelog
|
|
227
|
+
|
|
228
|
+
### 0.2.1 (2026-09-08)
|
|
229
|
+
|
|
230
|
+
- Absturz in der Analyseansicht behoben, wenn ein Kontextzustand `null` oder ungültig ist.
|
|
231
|
+
|
|
232
|
+
### 0.2.0 (2026-09-08)
|
|
233
|
+
|
|
234
|
+
- Aktuelle Anomaliebewertungen, erklärende Details und responsive Verlaufsdiagramme ergänzt.
|
|
235
|
+
- `Normal` wird als Grund für niedrige Scores angezeigt.
|
|
236
|
+
- Kontext-, Entscheidungsbereich- und History-Daten für die Analyseansicht erweitert.
|
|
237
|
+
|
|
238
|
+
### 0.1.1 (2026-09-07)
|
|
239
|
+
|
|
240
|
+
- Konfigurationsoberfläche, Layout und Übersetzungen verbessert.
|
|
241
|
+
- Detektor-Hinweise und kontextabhängige Optionsanzeige ergänzt.
|
|
242
|
+
|
|
243
|
+
### 0.1.0 (2026-09-06)
|
|
244
|
+
|
|
245
|
+
- Lokale statistische Anomalieerkennung als MVP.
|
|
246
|
+
- Optionales, begrenztes History-Training.
|
|
247
|
+
- Auswahl aktivierter History-Quellen pro konfiguriertem Zustand.
|
|
248
|
+
|
|
249
|
+
## Lizenz
|
|
250
|
+
|
|
251
|
+
MIT License
|
|
252
|
+
|
|
253
|
+
Copyright (c) 2026 Voodoo2man <Voodoo2man@outlook.de>
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# ioBroker.anomaly-detection
|
|
4
4
|
|
|
5
|
+
[English](README.md) · **Deutsch** ([README.de.md](README.de.md))
|
|
6
|
+
|
|
5
7
|
[](https://www.npmjs.com/package/iobroker.anomaly-detection)
|
|
6
8
|
|
|
7
9
|
Learns the normal behaviour of selected numerical ioBroker states and reports statistically unusual values locally.
|
|
@@ -325,7 +327,11 @@ Processing and model storage are completely local to ioBroker. No monitored valu
|
|
|
325
327
|
|
|
326
328
|
## Changelog
|
|
327
329
|
|
|
328
|
-
### 0.2.
|
|
330
|
+
### 0.2.1 (2026-09-08)
|
|
331
|
+
|
|
332
|
+
- Fix a crash in the analysis view when a context-state entry is `null` or invalid.
|
|
333
|
+
|
|
334
|
+
### 0.2.0 (2026-09-08)
|
|
329
335
|
|
|
330
336
|
- Report `Normal` as the current reason for scores below 50 instead of showing a technical detector message for a minor deviation.
|
|
331
337
|
- Improve analysis charts, historical explanations, and responsive layout.
|
package/build/main.js
CHANGED
|
@@ -67,7 +67,7 @@ class AnomalyDetection extends utils.Adapter {
|
|
|
67
67
|
this.on("unload", this.onUnload.bind(this));
|
|
68
68
|
}
|
|
69
69
|
async onReady() {
|
|
70
|
-
var _a, _b, _c, _d, _e, _f, _g, _h
|
|
70
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
71
71
|
await this.ensureModelState();
|
|
72
72
|
const persistedValue = (_a = await this.getStateAsync(MODEL_STATE_ID)) == null ? void 0 : _a.val;
|
|
73
73
|
let stored = (0, import_source_monitor.parseStoredModel)(persistedValue);
|
|
@@ -109,17 +109,17 @@ class AnomalyDetection extends utils.Adapter {
|
|
|
109
109
|
this.sourceUnits.set(id, object.common.unit);
|
|
110
110
|
}
|
|
111
111
|
for (const context of (_e = source.contextStates) != null ? _e : []) {
|
|
112
|
-
const contextId =
|
|
112
|
+
const contextId = context && typeof context.id === "string" ? context.id.trim() : "";
|
|
113
113
|
if (!contextId) {
|
|
114
114
|
continue;
|
|
115
115
|
}
|
|
116
116
|
const contextObject = await this.getForeignObjectAsync(contextId);
|
|
117
|
-
const name = (
|
|
117
|
+
const name = (_f = contextObject == null ? void 0 : contextObject.common) == null ? void 0 : _f.name;
|
|
118
118
|
if (typeof name === "string") {
|
|
119
119
|
this.contextNames.set(contextId, name);
|
|
120
120
|
} else if (name && typeof name === "object") {
|
|
121
121
|
const localized = name;
|
|
122
|
-
const value = (
|
|
122
|
+
const value = (_h = (_g = localized.de) != null ? _g : localized.en) != null ? _h : Object.values(localized)[0];
|
|
123
123
|
if (typeof value === "string") {
|
|
124
124
|
this.contextNames.set(contextId, value);
|
|
125
125
|
}
|
|
@@ -232,10 +232,10 @@ class AnomalyDetection extends utils.Adapter {
|
|
|
232
232
|
name: ((_a = this.sources.get(safeId)) == null ? void 0 : _a.name) || sourceId,
|
|
233
233
|
unit: this.sourceUnits.get(sourceId),
|
|
234
234
|
contextLabels: Object.fromEntries(
|
|
235
|
-
((_c = (_b = this.sources.get(safeId)) == null ? void 0 : _b.contextStates) != null ? _c : []).map((context) =>
|
|
236
|
-
context.id.trim()
|
|
237
|
-
this.contextNames.get(
|
|
238
|
-
|
|
235
|
+
((_c = (_b = this.sources.get(safeId)) == null ? void 0 : _b.contextStates) != null ? _c : []).filter((context) => context && typeof context.id === "string" && context.id.trim()).map((context) => {
|
|
236
|
+
const contextId = context.id.trim();
|
|
237
|
+
return [contextId, this.contextNames.get(contextId) || contextId];
|
|
238
|
+
})
|
|
239
239
|
),
|
|
240
240
|
evaluation: (_i = this.evaluations.get(sourceId)) != null ? _i : {
|
|
241
241
|
statusCode: ((_d = this.monitors.get(sourceId)) == null ? void 0 : _d.hasSufficientData) ? "normal" : "learning",
|
|
@@ -299,10 +299,7 @@ class AnomalyDetection extends utils.Adapter {
|
|
|
299
299
|
if (((_b = (_a = source.contextStates) == null ? void 0 : _a.length) != null ? _b : 0) > 3) {
|
|
300
300
|
this.warnContext(`Only the first 3 context states are used for ${sourceId}`);
|
|
301
301
|
}
|
|
302
|
-
for (const context of ((_c = source.contextStates) != null ? _c : []).filter((item) => {
|
|
303
|
-
var _a2;
|
|
304
|
-
return (_a2 = item.id) == null ? void 0 : _a2.trim();
|
|
305
|
-
}).slice(0, 3)) {
|
|
302
|
+
for (const context of ((_c = source.contextStates) != null ? _c : []).filter((item) => item && typeof item.id === "string" && item.id.trim()).slice(0, 3)) {
|
|
306
303
|
const id = context.id.trim();
|
|
307
304
|
let sources = this.contextSources.get(id);
|
|
308
305
|
if (!sources) {
|
|
@@ -330,10 +327,7 @@ class AnomalyDetection extends utils.Adapter {
|
|
|
330
327
|
return void 0;
|
|
331
328
|
}
|
|
332
329
|
const values = this.contextValues.get(sourceId);
|
|
333
|
-
const parts = ((_a = source.contextStates) != null ? _a : []).filter((item) => {
|
|
334
|
-
var _a2;
|
|
335
|
-
return (_a2 = item.id) == null ? void 0 : _a2.trim();
|
|
336
|
-
}).slice(0, 3).map((context) => {
|
|
330
|
+
const parts = ((_a = source.contextStates) != null ? _a : []).filter((item) => item && typeof item.id === "string" && item.id.trim()).slice(0, 3).map((context) => {
|
|
337
331
|
const value = values == null ? void 0 : values.get(context.id.trim());
|
|
338
332
|
if (typeof value === "string" && value.trim() && value.length <= 96) {
|
|
339
333
|
const categoryKey = `${sourceId}\0${context.id}`;
|
package/build/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["/* Created with @iobroker/create-adapter v3.1.5 */\nimport * as utils from \"@iobroker/adapter-core\";\nimport { sanitizeHistory, type HistoryProvider } from \"./lib/history-provider\";\nimport { HistorySourceResolver, selectHistorySource, type HistorySource } from \"./lib/history-source-resolver\";\nimport { cleanupStaleSources } from \"./lib/source-cleanup\";\nimport { SourceMonitor, parseStoredModel, type ObservationResult, type SourceSettings } from \"./lib/source-monitor\";\nimport { createContextPart } from \"./lib/context-value\";\n\nconst MODEL_STATE_ID = \"models\";\nconst PERSIST_DELAY_MS = 30_000;\nconst HISTORY_TIMEOUT_MS = 30_000;\nconst HISTORY_CONCURRENCY = 2;\ninterface ConfiguredSource extends SourceSettings {\n\tname: string;\n}\n\nclass IoBrokerHistoryProvider implements HistoryProvider {\n\tpublic constructor(\n\t\tprivate readonly adapter: ioBroker.Adapter,\n\t\tprivate readonly instance: string,\n\t) {}\n\n\tpublic async getHistory(sourceId: string, start: number, end: number, limit: number): Promise<unknown> {\n\t\tconst response = await this.adapter.sendToAsync(this.instance, \"getHistory\", {\n\t\t\tid: sourceId,\n\t\t\toptions: { start, end, aggregate: \"none\", count: limit, returnNewestEntries: false },\n\t\t});\n\t\treturn response;\n\t}\n}\n\nclass AnomalyDetection extends utils.Adapter {\n\tprivate readonly monitors = new Map<string, SourceMonitor>();\n\tprivate readonly sourceIds = new Map<string, string>();\n\tprivate readonly sources = new Map<string, ConfiguredSource>();\n\tprivate readonly sourceUnits = new Map<string, string>();\n\tprivate readonly contextNames = new Map<string, string>();\n\tprivate readonly invalidValueWarnings = new Set<string>();\n\tprivate readonly invalidHistoryConfigurationWarnings = new Set<string>();\n\tprivate readonly contextValues = new Map<string, Map<string, ioBroker.StateValue>>();\n\tprivate readonly contextSources = new Map<string, Set<string>>();\n\tprivate readonly contextCategories = new Map<string, Set<string>>();\n\tprivate readonly contextWarnings = new Set<string>();\n\tprivate readonly evaluations = new Map<string, ObservationResult>();\n\tprivate readonly historySourceResolver = new HistorySourceResolver(this);\n\tprivate persistTimer: ioBroker.Timeout | undefined;\n\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({ ...options, name: \"anomaly-detection\" });\n\t\tthis.on(\"ready\", this.onReady.bind(this));\n\t\tthis.on(\"stateChange\", this.onStateChange.bind(this));\n\t\tthis.on(\"message\", this.onMessage.bind(this));\n\t\tthis.on(\"unload\", this.onUnload.bind(this));\n\t}\n\n\tprivate async onReady(): Promise<void> {\n\t\tawait this.ensureModelState();\n\t\tconst persistedValue = (await this.getStateAsync(MODEL_STATE_ID))?.val;\n\t\tlet stored = parseStoredModel(persistedValue);\n\t\tif (\n\t\t\ttypeof persistedValue === \"string\" &&\n\t\t\tpersistedValue !== \"\" &&\n\t\t\tpersistedValue !== \"{}\" &&\n\t\t\tObject.keys(stored).length === 0\n\t\t) {\n\t\t\tthis.log.warn(\"Ignoring corrupt or incompatible persisted anomaly models\");\n\t\t}\n\t\tconst configuredSourceIds = new Set(\n\t\t\t(this.config.sources ?? []).flatMap(source => (source.id?.trim() ? [source.id.trim()] : [])),\n\t\t);\n\t\tstored = await this.cleanupStaleSources(configuredSourceIds, stored);\n\t\tconst configuredIds = new Set<string>();\n\t\tconst bootstrapTasks: Array<() => Promise<void>> = [];\n\t\tfor (const source of this.config.sources ?? []) {\n\t\t\tif (!source.enabled || !source.id?.trim()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst id = source.id.trim();\n\t\t\tif (configuredIds.has(id)) {\n\t\t\t\tthis.log.warn(`Ignoring duplicate configured source state: ${id}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconfiguredIds.add(id);\n\t\t\tconst object = await this.getForeignObjectAsync(id);\n\t\t\tif (!object) {\n\t\t\t\tthis.log.warn(`Configured source state does not exist yet: ${id}`);\n\t\t\t} else if (object.type !== \"state\" || object.common.type !== \"number\") {\n\t\t\t\tthis.log.warn(`Configured source is not a numerical state: ${id}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst safeId = this.createSourceId(id);\n\t\t\tthis.sourceIds.set(id, safeId);\n\t\t\tconst monitor = new SourceMonitor(source, stored[safeId]);\n\t\t\tthis.monitors.set(id, monitor);\n\t\t\tthis.sources.set(safeId, source);\n\t\t\tif (object?.common.unit) {\n\t\t\t\tthis.sourceUnits.set(id, object.common.unit);\n\t\t\t}\n\t\t\tfor (const context of source.contextStates ?? []) {\n\t\t\t\tconst contextId = context.id?.trim();\n\t\t\t\tif (!contextId) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst contextObject = await this.getForeignObjectAsync(contextId);\n\t\t\t\tconst name = contextObject?.common?.name;\n\t\t\t\tif (typeof name === \"string\") {\n\t\t\t\t\tthis.contextNames.set(contextId, name);\n\t\t\t\t} else if (name && typeof name === \"object\") {\n\t\t\t\t\tconst localized = name as Record<string, unknown>;\n\t\t\t\t\tconst value = localized.de ?? localized.en ?? Object.values(localized)[0];\n\t\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\t\tthis.contextNames.set(contextId, value);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait this.registerContexts(id, source);\n\t\t\tawait this.ensureSourceObjects(safeId, source, object?.common.unit);\n\t\t\tthis.subscribeForeignStates(id);\n\t\t\tif (this.shouldBootstrap(source, monitor)) {\n\t\t\t\tbootstrapTasks.push(() => this.bootstrapSource(id, safeId, source, monitor));\n\t\t\t}\n\t\t}\n\t\tthis.subscribeStates(\"sources.*.retrain\");\n\t\tawait this.runWithConcurrency(bootstrapTasks, HISTORY_CONCURRENCY);\n\t\tthis.log.info(`Monitoring ${this.monitors.size} numerical state${this.monitors.size === 1 ? \"\" : \"s\"}`);\n\t}\n\n\tprivate onStateChange(id: string, state: ioBroker.State | null | undefined): void {\n\t\tif (!state) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const sourceId of this.contextSources.get(id) ?? []) {\n\t\t\tthis.contextValues.get(sourceId)?.set(id, state.val);\n\t\t}\n\t\tconst retrainMatch = id.match(/sources\\.([^.]+)\\.retrain$/);\n\t\tif (retrainMatch && state.ack === false && state.val === true) {\n\t\t\tconst safeId = retrainMatch[1];\n\t\t\tconst sourceId = [...this.sourceIds].find(([, value]) => value === safeId)?.[0];\n\t\t\tconst source = this.sources.get(safeId);\n\t\t\tconst monitor = sourceId ? this.monitors.get(sourceId) : undefined;\n\t\t\tif (sourceId && source && monitor) {\n\t\t\t\tvoid this.retrainSource(sourceId, safeId, source, monitor);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tconst monitor = this.monitors.get(id);\n\t\tconst safeId = this.sourceIds.get(id);\n\t\tif (monitor && safeId) {\n\t\t\tvoid this.processState(id, safeId, monitor, state);\n\t\t}\n\t}\n\n\tprivate onMessage(message: ioBroker.Message): void {\n\t\tif (!message.callback) {\n\t\t\treturn;\n\t\t}\n\t\tif (message.command === \"getHistoryProviders\") {\n\t\t\tvoid this.replyWithHistoryProviders(message);\n\t\t} else if (message.command === \"tab\") {\n\t\t\tvoid this.replyWithAnomalyTab(message);\n\t\t} else if (message.command === \"chartData\") {\n\t\t\tvoid this.replyWithChartData(message);\n\t\t}\n\t}\n\n\tprivate async replyWithChartData(message: ioBroker.Message): Promise<void> {\n\t\tconst sourceId = typeof message.message?.sourceId === \"string\" ? message.message.sourceId.trim() : \"\";\n\t\tconst start = Number(message.message?.start) || Date.now() - 24 * 60 * 60 * 1000;\n\t\tconst end = Number(message.message?.end) || Date.now();\n\t\tconst limit = Math.min(1000, Math.max(50, Number(message.message?.limit) || 500));\n\t\tconst safeId = this.sourceIds.get(sourceId);\n\t\tconst source = safeId ? this.sources.get(safeId) : undefined;\n\t\tconst monitor = this.monitors.get(sourceId);\n\t\tif (!source || !monitor || !source.historyInstance?.trim()) {\n\t\t\tthis.sendTo(\n\t\t\t\tmessage.from,\n\t\t\t\tmessage.command,\n\t\t\t\t{ data: { samples: [], diagnostics: [], available: false } },\n\t\t\t\tmessage.callback,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst historySource = await this.resolveConfiguredHistorySource(sourceId, source.historyInstance.trim());\n\t\t\tconst provider = new IoBrokerHistoryProvider(this, source.historyInstance.trim());\n\t\t\tconst raw = await provider.getHistory(historySource.sourceId, start, end, limit);\n\t\t\tconst diagnostics = monitor.diagnosticSnapshots.filter(\n\t\t\t\tdiagnostic => diagnostic.timestamp >= start && diagnostic.timestamp <= end,\n\t\t\t);\n\t\t\tthis.sendTo(\n\t\t\t\tmessage.from,\n\t\t\t\tmessage.command,\n\t\t\t\t{\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tsamples: sanitizeHistory(\n\t\t\t\t\t\t\traw,\n\t\t\t\t\t\t\tlimit,\n\t\t\t\t\t\t\tdiagnostics.map(diagnostic => diagnostic.timestamp),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tdiagnostics,\n\t\t\t\t\t\tavailable: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tmessage.callback,\n\t\t\t);\n\t\t} catch {\n\t\t\tthis.sendTo(\n\t\t\t\tmessage.from,\n\t\t\t\tmessage.command,\n\t\t\t\t{ data: { samples: [], diagnostics: [], available: false } },\n\t\t\t\tmessage.callback,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate replyWithAnomalyTab(message: ioBroker.Message): void {\n\t\tconst sources = [...this.sourceIds.entries()].map(([sourceId, safeId]) => ({\n\t\t\tid: sourceId,\n\t\t\tsafeId,\n\t\t\tname: this.sources.get(safeId)?.name || sourceId,\n\t\t\tunit: this.sourceUnits.get(sourceId),\n\t\t\tcontextLabels: Object.fromEntries(\n\t\t\t\t(this.sources.get(safeId)?.contextStates ?? []).map(context => [\n\t\t\t\t\tcontext.id.trim(),\n\t\t\t\t\tthis.contextNames.get(context.id.trim()) || context.id.trim(),\n\t\t\t\t]),\n\t\t\t),\n\t\t\tevaluation: this.evaluations.get(sourceId) ?? {\n\t\t\t\tstatusCode: this.monitors.get(sourceId)?.hasSufficientData ? \"normal\" : \"learning\",\n\t\t\t\tseverity: \"normal\",\n\t\t\t\tsampleCount: this.monitors.get(sourceId)?.sampleCount ?? 0,\n\t\t\t\trequiredSamples: this.sources.get(safeId)?.minimumSamples ?? 30,\n\t\t\t},\n\t\t}));\n\t\tthis.sendTo(message.from, message.command, { data: { sources } }, message.callback);\n\t}\n\n\tprivate async replyWithHistoryProviders(message: ioBroker.Message): Promise<void> {\n\t\tconst sourceId = typeof message.message?.sourceId === \"string\" ? message.message.sourceId.trim() : \"\";\n\t\tconst sources = await this.historySourcesForAdmin(sourceId);\n\t\tthis.sendTo(\n\t\t\tmessage.from,\n\t\t\tmessage.command,\n\t\t\tsources.map(source => ({ value: source.instanceId, label: source.instanceId })),\n\t\t\tmessage.callback,\n\t\t);\n\t}\n\n\tprivate async historySourcesForAdmin(sourceId: string): Promise<HistorySource[]> {\n\t\tif (!sourceId) {\n\t\t\treturn [];\n\t\t}\n\t\ttry {\n\t\t\treturn await this.historySourceResolver.resolve(sourceId);\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`Could not resolve history sources for ${sourceId}: ${(error as Error).message}`);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate async processState(\n\t\tid: string,\n\t\tsafeId: string,\n\t\tmonitor: SourceMonitor,\n\t\tstate: ioBroker.State,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tconst result = monitor.observe(\n\t\t\t\tstate.val,\n\t\t\t\ttypeof state.ts === \"number\" ? state.ts : Date.now(),\n\t\t\t\tthis.contextKey(id),\n\t\t\t);\n\t\t\tif (!result) {\n\t\t\t\tif (!this.invalidValueWarnings.has(id)) {\n\t\t\t\t\tthis.invalidValueWarnings.add(id);\n\t\t\t\t\tthis.log.warn(`Ignoring invalid non-numerical value from ${id}`);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.invalidValueWarnings.delete(id);\n\t\t\tthis.evaluations.set(id, result);\n\t\t\tawait this.writeResult(safeId, result);\n\t\t\tthis.schedulePersistence();\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Could not process source state ${id}: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\tprivate async registerContexts(sourceId: string, source: ConfiguredSource): Promise<void> {\n\t\tif (!source.enableContext) {\n\t\t\treturn;\n\t\t}\n\t\tif ((source.contextStates?.length ?? 0) > 3) {\n\t\t\tthis.warnContext(`Only the first 3 context states are used for ${sourceId}`);\n\t\t}\n\t\tfor (const context of (source.contextStates ?? []).filter(item => item.id?.trim()).slice(0, 3)) {\n\t\t\tconst id = context.id.trim();\n\t\t\tlet sources = this.contextSources.get(id);\n\t\t\tif (!sources) {\n\t\t\t\tsources = new Set();\n\t\t\t\tthis.contextSources.set(id, sources);\n\t\t\t\tthis.subscribeForeignStates(id);\n\t\t\t}\n\t\t\tsources.add(sourceId);\n\t\t\tconst state = await this.getForeignStateAsync(id);\n\t\t\tif (state) {\n\t\t\t\tlet values = this.contextValues.get(sourceId);\n\t\t\t\tif (!values) {\n\t\t\t\t\tvalues = new Map();\n\t\t\t\t\tthis.contextValues.set(sourceId, values);\n\t\t\t\t}\n\t\t\t\tvalues.set(id, state.val);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate contextKey(sourceId: string): string | undefined {\n\t\tconst safeId = this.sourceIds.get(sourceId);\n\t\tconst source = safeId ? this.sources.get(safeId) : undefined;\n\t\tif (!source?.enableContext) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst values = this.contextValues.get(sourceId);\n\t\tconst parts = (source.contextStates ?? [])\n\t\t\t.filter(item => item.id?.trim())\n\t\t\t.slice(0, 3)\n\t\t\t.map(context => {\n\t\t\t\tconst value = values?.get(context.id.trim());\n\t\t\t\tif (typeof value === \"string\" && value.trim() && value.length <= 96) {\n\t\t\t\t\tconst categoryKey = `${sourceId}\\u0000${context.id}`;\n\t\t\t\t\tlet categories = this.contextCategories.get(categoryKey);\n\t\t\t\t\tif (!categories) {\n\t\t\t\t\t\tcategories = new Set();\n\t\t\t\t\t\tthis.contextCategories.set(categoryKey, categories);\n\t\t\t\t\t}\n\t\t\t\t\tif (!categories.has(value) && categories.size >= 16) {\n\t\t\t\t\t\tthis.warnContext(`Ignoring additional categorical values for ${context.id}; limit is 16`);\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tcategories.add(value);\n\t\t\t\t}\n\t\t\t\treturn createContextPart(context.id, value, context.bucketWidth);\n\t\t\t});\n\t\treturn parts.length > 0 && parts.every(Boolean) ? parts.join(\"|\") : undefined;\n\t}\n\n\tprivate warnContext(message: string): void {\n\t\tif (!this.contextWarnings.has(message)) {\n\t\t\tthis.contextWarnings.add(message);\n\t\t\tthis.log.warn(message);\n\t\t}\n\t}\n\n\tprivate async ensureModelState(): Promise<void> {\n\t\tawait this.setObjectNotExistsAsync(MODEL_STATE_ID, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Persisted models\", type: \"string\", role: \"json\", read: true, write: false, def: \"\" },\n\t\t\tnative: {},\n\t\t});\n\t}\n\n\tprivate async cleanupStaleSources(\n\t\tconfiguredSourceIds: ReadonlySet<string>,\n\t\tstoredModels: ReturnType<typeof parseStoredModel>,\n\t): Promise<ReturnType<typeof parseStoredModel>> {\n\t\ttry {\n\t\t\tconst result = await cleanupStaleSources(\n\t\t\t\t{\n\t\t\t\t\tlistGeneratedSourceDevices: async () => {\n\t\t\t\t\t\tconst devices = await this.getForeignObjectsAsync(`${this.namespace}.sources.*`, \"device\");\n\t\t\t\t\t\treturn Object.entries(devices).map(([id, device]) => ({\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\tsourceId: typeof device.native?.sourceId === \"string\" ? device.native.sourceId : undefined,\n\t\t\t\t\t\t}));\n\t\t\t\t\t},\n\t\t\t\t\tdeleteGeneratedSourceTree: relativeId => this.delObjectAsync(relativeId, { recursive: true }),\n\t\t\t\t},\n\t\t\t\tthis.namespace,\n\t\t\t\tconfiguredSourceIds,\n\t\t\t\tstoredModels,\n\t\t\t\tthis.createSourceId.bind(this),\n\t\t\t);\n\t\t\tif (result.modelsChanged) {\n\t\t\t\tawait this.setStateAsync(MODEL_STATE_ID, { val: JSON.stringify(result.models), ack: true });\n\t\t\t}\n\t\t\tfor (const sourceId of result.removedSourceIds) {\n\t\t\t\tthis.log.info(`Removed generated anomaly objects and model for deleted source: ${sourceId}`);\n\t\t\t}\n\t\t\treturn result.models;\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Could not clean up stale monitored sources: ${(error as Error).message}`);\n\t\t\treturn storedModels;\n\t\t}\n\t}\n\n\tprivate async ensureSourceObjects(safeId: string, source: ConfiguredSource, unit?: string): Promise<void> {\n\t\tconst base = `sources.${safeId}`;\n\t\tawait this.setObjectNotExistsAsync(base, {\n\t\t\ttype: \"device\",\n\t\t\tcommon: { name: source.name || source.id },\n\t\t\tnative: { sourceId: source.id },\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Anomaly analysis\" },\n\t\t\tnative: {},\n\t\t});\n\t\tconst numberState = (\n\t\t\tname: string,\n\t\t\trole: string,\n\t\t\textra: Partial<ioBroker.StateCommon> = {},\n\t\t): ioBroker.SettableObject => ({\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name, type: \"number\", role, read: true, write: false, ...extra },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.actual`,\n\t\t\tnumberState(\"Actual value\", \"value\", unit ? { unit } : {}),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.expected`,\n\t\t\tnumberState(\"Expected value\", \"value\", unit ? { unit } : {}),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.deviation`,\n\t\t\tnumberState(\"Deviation\", \"value\", unit ? { unit } : {}),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.score`,\n\t\t\tnumberState(\"Anomaly score\", \"value\", { min: 0, max: 100, unit: \"%\" }),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.sampleCount`,\n\t\t\tnumberState(\"Learned sample count\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.baselineScope`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Active baseline scope\", type: \"string\", role: \"info.status\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.activeContext`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Active context\", type: \"string\", role: \"text\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.baselineSampleCount`,\n\t\t\tnumberState(\"Active baseline sample count\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.contextSampleCount`,\n\t\t\tnumberState(\"Active context sample count\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.detected`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Persistent anomaly detected\",\n\t\t\t\ttype: \"boolean\",\n\t\t\t\trole: \"indicator\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.status`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Model status\", type: \"string\", role: \"info.status\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.reason`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Anomaly reason\", type: \"string\", role: \"text\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.lastAnomaly`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Last high-score anomaly\", type: \"string\", role: \"date\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.retrain`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Retrain model\", type: \"boolean\", role: \"button\", read: false, write: true, def: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.bootstrapStatus`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Historical bootstrap status\",\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"info.status\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.ensureExplainabilityObjects(base, numberState);\n\t}\n\n\tprivate async ensureExplainabilityObjects(\n\t\tbase: string,\n\t\tnumberState: (name: string, role: string, extra?: Partial<ioBroker.StateCommon>) => ioBroker.SettableObject,\n\t): Promise<void> {\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Current anomaly assessment\" },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Evaluation details\" },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.detectors`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Detector scores\" },\n\t\t\tnative: {},\n\t\t});\n\t\tconst textState = (name: string, role = \"text\"): ioBroker.SettableObject => ({\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name, type: \"string\", role, read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tconst booleanState: ioBroker.SettableObject = {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Anomaly active\",\n\t\t\t\ttype: \"boolean\",\n\t\t\t\trole: \"indicator\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t};\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.active`, booleanState);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.anomaly.score`,\n\t\t\tnumberState(\"Overall anomaly score\", \"value\", { min: 0, max: 100, unit: \"%\" }),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.severity`, textState(\"Severity\", \"info.status\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.reasonCode`, textState(\"Reason code\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.reason`, textState(\"Human-readable reason\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.since`, textState(\"Anomaly since\", \"date\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.lastNormal`, textState(\"Last normal evaluation\", \"date\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.status`, textState(\"Evaluation status\", \"info.status\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.currentValue`, numberState(\"Current value\", \"value\"));\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.expectedLow`,\n\t\t\tnumberState(\"Expected range lower bound\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.expectedHigh`,\n\t\t\tnumberState(\"Expected range upper bound\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.decisionLow`,\n\t\t\tnumberState(\"Decision range lower bound\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.decisionHigh`,\n\t\t\tnumberState(\"Decision range upper bound\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.baselineType`, textState(\"Baseline type\"));\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.baselineSamples`,\n\t\t\tnumberState(\"Baseline samples\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.context`, textState(\"Used context\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.timeBucket`, textState(\"Time bucket\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.lastEvaluated`, textState(\"Last evaluation\", \"date\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.detectors.active`, textState(\"Active detectors\"));\n\t\tfor (const [id, label] of [\n\t\t\t[\"valueDeviation\", \"Value deviation score\"],\n\t\t\t[\"rateOfChange\", \"Rate-of-change score\"],\n\t\t\t[\"stuck\", \"Stuck-state score\"],\n\t\t\t[\"levelShift\", \"Level-shift score\"],\n\t\t\t[\"trend\", \"Trend score\"],\n\t\t] as const) {\n\t\t\tawait this.setObjectNotExistsAsync(\n\t\t\t\t`${base}.detectors.${id}`,\n\t\t\t\tnumberState(label, \"value\", { min: 0, max: 100 }),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate shouldBootstrap(source: ConfiguredSource, monitor: SourceMonitor): boolean {\n\t\treturn (\n\t\t\tsource.initialTraining === \"history\" &&\n\t\t\t!!source.historyInstance?.trim() &&\n\t\t\t(!monitor.hasSufficientData || !monitor.bootstrapMatches(this.bootstrapConfigKey(source)))\n\t\t);\n\t}\n\n\tprivate async bootstrapSource(\n\t\tsourceId: string,\n\t\tsafeId: string,\n\t\tsource: ConfiguredSource,\n\t\tmonitor: SourceMonitor,\n\t): Promise<void> {\n\t\tconst providerName = source.historyInstance!.trim();\n\t\tconst end = Date.now();\n\t\tconst start = end - Math.max(7, source.trainingDays ?? 30) * 24 * 60 * 60 * 1000;\n\t\tawait this.setStateAsync(`sources.${safeId}.analysis.status`, { val: \"importingHistory\", ack: true });\n\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\tval: \"Importing historical data\",\n\t\t\tack: true,\n\t\t});\n\t\ttry {\n\t\t\tconst historySource = await this.resolveConfiguredHistorySource(sourceId, providerName);\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\t\tval: `Importing history from ${historySource.sourceId} via ${providerName}`,\n\t\t\t\tack: true,\n\t\t\t});\n\t\t\tconst provider = new IoBrokerHistoryProvider(this, providerName);\n\t\t\tconst raw = await this.withHistoryTimeout(\n\t\t\t\tprovider.getHistory(historySource.sourceId, start, end, source.maxHistorySamples ?? 1000),\n\t\t\t);\n\t\t\tconst samples = sanitizeHistory(raw, source.maxHistorySamples ?? 1000);\n\t\t\tif (samples.length === 0) {\n\t\t\t\tthrow new Error(\"History provider returned no valid samples\");\n\t\t\t}\n\t\t\tmonitor.reset();\n\t\t\tconst imported = monitor.bootstrapFromHistory(\n\t\t\t\tsamples,\n\t\t\t\tproviderName,\n\t\t\t\thistorySource.sourceId,\n\t\t\t\tstart,\n\t\t\t\tend,\n\t\t\t\tthis.bootstrapConfigKey(source),\n\t\t\t);\n\t\t\tif (imported === 0) {\n\t\t\t\tthrow new Error(\"No usable samples remained after historical outlier filtering\");\n\t\t\t}\n\t\t\tawait this.persistModels();\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.sampleCount`, { val: monitor.sampleCount, ack: true });\n\t\t\tconst status =\n\t\t\t\tmonitor.hasSufficientData && (source.autoStartMonitoringAfterImport ?? true)\n\t\t\t\t\t? \"monitoring\"\n\t\t\t\t\t: \"learning\";\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.status`, { val: status, ack: true });\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\t\tval: `Historical training completed (${imported} samples)`,\n\t\t\t\tack: true,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`Historical bootstrap failed for ${sourceId}: ${(error as Error).message}`);\n\t\t\tawait this.persistModels();\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.status`, {\n\t\t\t\tval: monitor.hasSufficientData ? \"monitoring\" : \"learning\",\n\t\t\t\tack: true,\n\t\t\t});\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\t\tval: \"Insufficient historical data, continuing live learning\",\n\t\t\t\tack: true,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async resolveConfiguredHistorySource(sourceId: string, instanceId: string): Promise<HistorySource> {\n\t\ttry {\n\t\t\treturn selectHistorySource(sourceId, instanceId, await this.historySourceResolver.resolve(sourceId));\n\t\t} catch (error) {\n\t\t\tthis.warnInvalidHistoryConfiguration((error as Error).message);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate warnInvalidHistoryConfiguration(message: string): void {\n\t\tif (!this.invalidHistoryConfigurationWarnings.has(message)) {\n\t\t\tthis.invalidHistoryConfigurationWarnings.add(message);\n\t\t\tthis.log.warn(message);\n\t\t}\n\t}\n\n\tprivate async retrainSource(\n\t\tsourceId: string,\n\t\tsafeId: string,\n\t\tsource: ConfiguredSource,\n\t\tmonitor: SourceMonitor,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tmonitor.reset();\n\t\t\tif (source.initialTraining === \"history\" && source.historyInstance?.trim()) {\n\t\t\t\tawait this.bootstrapSource(sourceId, safeId, source, monitor);\n\t\t\t} else {\n\t\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.status`, { val: \"learning\", ack: true });\n\t\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\t\t\tval: \"Model reset; learning live values\",\n\t\t\t\t\tack: true,\n\t\t\t\t});\n\t\t\t\tawait this.persistModels();\n\t\t\t}\n\t\t} finally {\n\t\t\tawait this.setStateAsync(`sources.${safeId}.retrain`, { val: false, ack: true });\n\t\t}\n\t}\n\n\tprivate async runWithConcurrency(tasks: Array<() => Promise<void>>, concurrency: number): Promise<void> {\n\t\tconst queue = [...tasks];\n\t\tawait Promise.all(\n\t\t\tArray.from({ length: Math.min(concurrency, queue.length) }, async () => {\n\t\t\t\twhile (queue.length) {\n\t\t\t\t\tawait queue.shift()!();\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\tprivate async withHistoryTimeout<T>(operation: Promise<T>): Promise<T> {\n\t\treturn new Promise<T>((resolve, reject) => {\n\t\t\tconst timer = this.setTimeout(() => reject(new Error(\"History request timed out\")), HISTORY_TIMEOUT_MS);\n\t\t\toperation.then(\n\t\t\t\tvalue => {\n\t\t\t\t\tthis.clearTimeout(timer);\n\t\t\t\t\tresolve(value);\n\t\t\t\t},\n\t\t\t\terror => {\n\t\t\t\t\tthis.clearTimeout(timer);\n\t\t\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t},\n\t\t\t);\n\t\t});\n\t}\n\n\tprivate bootstrapConfigKey(source: ConfiguredSource): string {\n\t\treturn JSON.stringify({\n\t\t\tprovider: source.historyInstance?.trim(),\n\t\t\tdays: source.trainingDays ?? 30,\n\t\t\tlimit: source.maxHistorySamples ?? 1000,\n\t\t});\n\t}\n\n\tprivate async writeResult(safeId: string, result: ObservationResult): Promise<void> {\n\t\tconst base = `sources.${safeId}.analysis`;\n\t\tconst values: Record<string, ioBroker.StateValue> = {\n\t\t\tactual: result.actual,\n\t\t\tscore: result.score,\n\t\t\tdetected: result.detected,\n\t\t\tstatus: result.status,\n\t\t\treason: result.reason,\n\t\t\tsampleCount: result.sampleCount,\n\t\t\tbaselineScope: result.baselineScope,\n\t\t\tactiveContext: result.activeContext,\n\t\t\tbaselineSampleCount: result.baselineSampleCount,\n\t\t\tcontextSampleCount: result.contextSampleCount,\n\t\t};\n\t\tvalues.expected = result.expected ?? null;\n\t\tvalues.deviation = result.deviation ?? null;\n\t\tif (result.lastAnomaly !== undefined) {\n\t\t\tvalues.lastAnomaly = new Date(result.lastAnomaly).toISOString();\n\t\t}\n\t\tconst explainability: Record<string, ioBroker.StateValue> = {\n\t\t\t\"anomaly.active\": result.statusCode === \"anomaly\",\n\t\t\t\"anomaly.score\": result.score,\n\t\t\t\"anomaly.severity\": result.severity,\n\t\t\t\"anomaly.reasonCode\": result.reasonCode,\n\t\t\t\"anomaly.reason\": result.reason,\n\t\t\t\"anomaly.since\": result.anomalySince ? new Date(result.anomalySince).toISOString() : null,\n\t\t\t\"anomaly.lastNormal\": result.lastNormal ? new Date(result.lastNormal).toISOString() : null,\n\t\t\t\"evaluation.status\": result.statusCode,\n\t\t\t\"evaluation.currentValue\": result.actual,\n\t\t\t\"evaluation.expectedLow\": result.expectedLow ?? null,\n\t\t\t\"evaluation.expectedHigh\": result.expectedHigh ?? null,\n\t\t\t\"evaluation.decisionLow\": result.decisionLow ?? null,\n\t\t\t\"evaluation.decisionHigh\": result.decisionHigh ?? null,\n\t\t\t\"evaluation.baselineType\": result.baselineScope,\n\t\t\t\"evaluation.baselineSamples\": result.baselineSampleCount,\n\t\t\t\"evaluation.context\": result.activeContext || null,\n\t\t\t\"evaluation.timeBucket\": result.timeBucket ?? null,\n\t\t\t\"evaluation.lastEvaluated\": new Date(result.lastEvaluated).toISOString(),\n\t\t\t\"detectors.active\": result.detectors.map(detector => detector.name).join(\", \") || null,\n\t\t\t\"detectors.valueDeviation\": null,\n\t\t\t\"detectors.rateOfChange\": null,\n\t\t\t\"detectors.stuck\": null,\n\t\t\t\"detectors.levelShift\": null,\n\t\t\t\"detectors.trend\": null,\n\t\t};\n\t\tconst detectorIds: Record<string, string> = {\n\t\t\tvalue: \"valueDeviation\",\n\t\t\tcontext: \"valueDeviation\",\n\t\t\trate: \"rateOfChange\",\n\t\t\tstuck: \"stuck\",\n\t\t\tchangePoint: \"levelShift\",\n\t\t\ttrend: \"trend\",\n\t\t};\n\t\tfor (const detector of result.detectors) {\n\t\t\texplainability[`detectors.${detectorIds[detector.name]}`] = detector.score;\n\t\t}\n\t\tawait Promise.all(\n\t\t\t[...Object.entries(values), ...Object.entries(explainability)].map(([key, value]) =>\n\t\t\t\tthis.setStateAsync(`${key.includes(\".\") ? `sources.${safeId}` : base}.${key}`, {\n\t\t\t\t\tval: value,\n\t\t\t\t\tack: true,\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t}\n\n\tprivate schedulePersistence(): void {\n\t\tif (this.persistTimer) {\n\t\t\treturn;\n\t\t}\n\t\tthis.persistTimer = this.setTimeout(() => {\n\t\t\tthis.persistTimer = undefined;\n\t\t\tvoid this.persistModels();\n\t\t}, PERSIST_DELAY_MS);\n\t}\n\n\tprivate async persistModels(): Promise<void> {\n\t\ttry {\n\t\t\tconst models = Object.fromEntries(\n\t\t\t\t[...this.monitors].map(([sourceId, monitor]) => [\n\t\t\t\t\tthis.sourceIds.get(sourceId)!,\n\t\t\t\t\t{ ...monitor.toJSON(), configuredSourceId: sourceId },\n\t\t\t\t]),\n\t\t\t);\n\t\t\tawait this.setStateAsync(MODEL_STATE_ID, { val: JSON.stringify(models), ack: true });\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Could not persist anomaly models: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\tprivate createSourceId(sourceId: string): string {\n\t\tconst normalized =\n\t\t\tsourceId\n\t\t\t\t.replace(/[^A-Za-z0-9_-]+/g, \"_\")\n\t\t\t\t.replace(/^_+|_+$/g, \"\")\n\t\t\t\t.slice(0, 48) || \"source\";\n\t\tconst hash = [...sourceId]\n\t\t\t.reduce((value, character) => (value * 31 + character.charCodeAt(0)) >>> 0, 0)\n\t\t\t.toString(36);\n\t\treturn `${normalized}_${hash}`;\n\t}\n\n\tprivate onUnload(callback: () => void): void {\n\t\tif (this.persistTimer) {\n\t\t\tthis.clearTimeout(this.persistTimer);\n\t\t}\n\t\tvoid this.persistModels().finally(callback);\n\t}\n}\n\nif (require.main !== module) {\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AnomalyDetection(options);\n} else {\n\t(() => new AnomalyDetection())();\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/* Created with @iobroker/create-adapter v3.1.5 */\nimport * as utils from \"@iobroker/adapter-core\";\nimport { sanitizeHistory, type HistoryProvider } from \"./lib/history-provider\";\nimport { HistorySourceResolver, selectHistorySource, type HistorySource } from \"./lib/history-source-resolver\";\nimport { cleanupStaleSources } from \"./lib/source-cleanup\";\nimport { SourceMonitor, parseStoredModel, type ObservationResult, type SourceSettings } from \"./lib/source-monitor\";\nimport { createContextPart } from \"./lib/context-value\";\n\nconst MODEL_STATE_ID = \"models\";\nconst PERSIST_DELAY_MS = 30_000;\nconst HISTORY_TIMEOUT_MS = 30_000;\nconst HISTORY_CONCURRENCY = 2;\ninterface ConfiguredSource extends SourceSettings {\n\tname: string;\n}\n\nclass IoBrokerHistoryProvider implements HistoryProvider {\n\tpublic constructor(\n\t\tprivate readonly adapter: ioBroker.Adapter,\n\t\tprivate readonly instance: string,\n\t) {}\n\n\tpublic async getHistory(sourceId: string, start: number, end: number, limit: number): Promise<unknown> {\n\t\tconst response = await this.adapter.sendToAsync(this.instance, \"getHistory\", {\n\t\t\tid: sourceId,\n\t\t\toptions: { start, end, aggregate: \"none\", count: limit, returnNewestEntries: false },\n\t\t});\n\t\treturn response;\n\t}\n}\n\nclass AnomalyDetection extends utils.Adapter {\n\tprivate readonly monitors = new Map<string, SourceMonitor>();\n\tprivate readonly sourceIds = new Map<string, string>();\n\tprivate readonly sources = new Map<string, ConfiguredSource>();\n\tprivate readonly sourceUnits = new Map<string, string>();\n\tprivate readonly contextNames = new Map<string, string>();\n\tprivate readonly invalidValueWarnings = new Set<string>();\n\tprivate readonly invalidHistoryConfigurationWarnings = new Set<string>();\n\tprivate readonly contextValues = new Map<string, Map<string, ioBroker.StateValue>>();\n\tprivate readonly contextSources = new Map<string, Set<string>>();\n\tprivate readonly contextCategories = new Map<string, Set<string>>();\n\tprivate readonly contextWarnings = new Set<string>();\n\tprivate readonly evaluations = new Map<string, ObservationResult>();\n\tprivate readonly historySourceResolver = new HistorySourceResolver(this);\n\tprivate persistTimer: ioBroker.Timeout | undefined;\n\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({ ...options, name: \"anomaly-detection\" });\n\t\tthis.on(\"ready\", this.onReady.bind(this));\n\t\tthis.on(\"stateChange\", this.onStateChange.bind(this));\n\t\tthis.on(\"message\", this.onMessage.bind(this));\n\t\tthis.on(\"unload\", this.onUnload.bind(this));\n\t}\n\n\tprivate async onReady(): Promise<void> {\n\t\tawait this.ensureModelState();\n\t\tconst persistedValue = (await this.getStateAsync(MODEL_STATE_ID))?.val;\n\t\tlet stored = parseStoredModel(persistedValue);\n\t\tif (\n\t\t\ttypeof persistedValue === \"string\" &&\n\t\t\tpersistedValue !== \"\" &&\n\t\t\tpersistedValue !== \"{}\" &&\n\t\t\tObject.keys(stored).length === 0\n\t\t) {\n\t\t\tthis.log.warn(\"Ignoring corrupt or incompatible persisted anomaly models\");\n\t\t}\n\t\tconst configuredSourceIds = new Set(\n\t\t\t(this.config.sources ?? []).flatMap(source => (source.id?.trim() ? [source.id.trim()] : [])),\n\t\t);\n\t\tstored = await this.cleanupStaleSources(configuredSourceIds, stored);\n\t\tconst configuredIds = new Set<string>();\n\t\tconst bootstrapTasks: Array<() => Promise<void>> = [];\n\t\tfor (const source of this.config.sources ?? []) {\n\t\t\tif (!source.enabled || !source.id?.trim()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst id = source.id.trim();\n\t\t\tif (configuredIds.has(id)) {\n\t\t\t\tthis.log.warn(`Ignoring duplicate configured source state: ${id}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconfiguredIds.add(id);\n\t\t\tconst object = await this.getForeignObjectAsync(id);\n\t\t\tif (!object) {\n\t\t\t\tthis.log.warn(`Configured source state does not exist yet: ${id}`);\n\t\t\t} else if (object.type !== \"state\" || object.common.type !== \"number\") {\n\t\t\t\tthis.log.warn(`Configured source is not a numerical state: ${id}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst safeId = this.createSourceId(id);\n\t\t\tthis.sourceIds.set(id, safeId);\n\t\t\tconst monitor = new SourceMonitor(source, stored[safeId]);\n\t\t\tthis.monitors.set(id, monitor);\n\t\t\tthis.sources.set(safeId, source);\n\t\t\tif (object?.common.unit) {\n\t\t\t\tthis.sourceUnits.set(id, object.common.unit);\n\t\t\t}\n\t\t\tfor (const context of source.contextStates ?? []) {\n\t\t\t\tconst contextId = context && typeof context.id === \"string\" ? context.id.trim() : \"\";\n\t\t\t\tif (!contextId) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst contextObject = await this.getForeignObjectAsync(contextId);\n\t\t\t\tconst name = contextObject?.common?.name;\n\t\t\t\tif (typeof name === \"string\") {\n\t\t\t\t\tthis.contextNames.set(contextId, name);\n\t\t\t\t} else if (name && typeof name === \"object\") {\n\t\t\t\t\tconst localized = name as Record<string, unknown>;\n\t\t\t\t\tconst value = localized.de ?? localized.en ?? Object.values(localized)[0];\n\t\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\t\tthis.contextNames.set(contextId, value);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait this.registerContexts(id, source);\n\t\t\tawait this.ensureSourceObjects(safeId, source, object?.common.unit);\n\t\t\tthis.subscribeForeignStates(id);\n\t\t\tif (this.shouldBootstrap(source, monitor)) {\n\t\t\t\tbootstrapTasks.push(() => this.bootstrapSource(id, safeId, source, monitor));\n\t\t\t}\n\t\t}\n\t\tthis.subscribeStates(\"sources.*.retrain\");\n\t\tawait this.runWithConcurrency(bootstrapTasks, HISTORY_CONCURRENCY);\n\t\tthis.log.info(`Monitoring ${this.monitors.size} numerical state${this.monitors.size === 1 ? \"\" : \"s\"}`);\n\t}\n\n\tprivate onStateChange(id: string, state: ioBroker.State | null | undefined): void {\n\t\tif (!state) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const sourceId of this.contextSources.get(id) ?? []) {\n\t\t\tthis.contextValues.get(sourceId)?.set(id, state.val);\n\t\t}\n\t\tconst retrainMatch = id.match(/sources\\.([^.]+)\\.retrain$/);\n\t\tif (retrainMatch && state.ack === false && state.val === true) {\n\t\t\tconst safeId = retrainMatch[1];\n\t\t\tconst sourceId = [...this.sourceIds].find(([, value]) => value === safeId)?.[0];\n\t\t\tconst source = this.sources.get(safeId);\n\t\t\tconst monitor = sourceId ? this.monitors.get(sourceId) : undefined;\n\t\t\tif (sourceId && source && monitor) {\n\t\t\t\tvoid this.retrainSource(sourceId, safeId, source, monitor);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tconst monitor = this.monitors.get(id);\n\t\tconst safeId = this.sourceIds.get(id);\n\t\tif (monitor && safeId) {\n\t\t\tvoid this.processState(id, safeId, monitor, state);\n\t\t}\n\t}\n\n\tprivate onMessage(message: ioBroker.Message): void {\n\t\tif (!message.callback) {\n\t\t\treturn;\n\t\t}\n\t\tif (message.command === \"getHistoryProviders\") {\n\t\t\tvoid this.replyWithHistoryProviders(message);\n\t\t} else if (message.command === \"tab\") {\n\t\t\tvoid this.replyWithAnomalyTab(message);\n\t\t} else if (message.command === \"chartData\") {\n\t\t\tvoid this.replyWithChartData(message);\n\t\t}\n\t}\n\n\tprivate async replyWithChartData(message: ioBroker.Message): Promise<void> {\n\t\tconst sourceId = typeof message.message?.sourceId === \"string\" ? message.message.sourceId.trim() : \"\";\n\t\tconst start = Number(message.message?.start) || Date.now() - 24 * 60 * 60 * 1000;\n\t\tconst end = Number(message.message?.end) || Date.now();\n\t\tconst limit = Math.min(1000, Math.max(50, Number(message.message?.limit) || 500));\n\t\tconst safeId = this.sourceIds.get(sourceId);\n\t\tconst source = safeId ? this.sources.get(safeId) : undefined;\n\t\tconst monitor = this.monitors.get(sourceId);\n\t\tif (!source || !monitor || !source.historyInstance?.trim()) {\n\t\t\tthis.sendTo(\n\t\t\t\tmessage.from,\n\t\t\t\tmessage.command,\n\t\t\t\t{ data: { samples: [], diagnostics: [], available: false } },\n\t\t\t\tmessage.callback,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tconst historySource = await this.resolveConfiguredHistorySource(sourceId, source.historyInstance.trim());\n\t\t\tconst provider = new IoBrokerHistoryProvider(this, source.historyInstance.trim());\n\t\t\tconst raw = await provider.getHistory(historySource.sourceId, start, end, limit);\n\t\t\tconst diagnostics = monitor.diagnosticSnapshots.filter(\n\t\t\t\tdiagnostic => diagnostic.timestamp >= start && diagnostic.timestamp <= end,\n\t\t\t);\n\t\t\tthis.sendTo(\n\t\t\t\tmessage.from,\n\t\t\t\tmessage.command,\n\t\t\t\t{\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tsamples: sanitizeHistory(\n\t\t\t\t\t\t\traw,\n\t\t\t\t\t\t\tlimit,\n\t\t\t\t\t\t\tdiagnostics.map(diagnostic => diagnostic.timestamp),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tdiagnostics,\n\t\t\t\t\t\tavailable: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tmessage.callback,\n\t\t\t);\n\t\t} catch {\n\t\t\tthis.sendTo(\n\t\t\t\tmessage.from,\n\t\t\t\tmessage.command,\n\t\t\t\t{ data: { samples: [], diagnostics: [], available: false } },\n\t\t\t\tmessage.callback,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate replyWithAnomalyTab(message: ioBroker.Message): void {\n\t\tconst sources = [...this.sourceIds.entries()].map(([sourceId, safeId]) => ({\n\t\t\tid: sourceId,\n\t\t\tsafeId,\n\t\t\tname: this.sources.get(safeId)?.name || sourceId,\n\t\t\tunit: this.sourceUnits.get(sourceId),\n\t\t\tcontextLabels: Object.fromEntries(\n\t\t\t\t(this.sources.get(safeId)?.contextStates ?? [])\n\t\t\t\t\t.filter(context => context && typeof context.id === \"string\" && context.id.trim())\n\t\t\t\t\t.map(context => {\n\t\t\t\t\t\tconst contextId = context.id.trim();\n\t\t\t\t\t\treturn [contextId, this.contextNames.get(contextId) || contextId];\n\t\t\t\t\t}),\n\t\t\t),\n\t\t\tevaluation: this.evaluations.get(sourceId) ?? {\n\t\t\t\tstatusCode: this.monitors.get(sourceId)?.hasSufficientData ? \"normal\" : \"learning\",\n\t\t\t\tseverity: \"normal\",\n\t\t\t\tsampleCount: this.monitors.get(sourceId)?.sampleCount ?? 0,\n\t\t\t\trequiredSamples: this.sources.get(safeId)?.minimumSamples ?? 30,\n\t\t\t},\n\t\t}));\n\t\tthis.sendTo(message.from, message.command, { data: { sources } }, message.callback);\n\t}\n\n\tprivate async replyWithHistoryProviders(message: ioBroker.Message): Promise<void> {\n\t\tconst sourceId = typeof message.message?.sourceId === \"string\" ? message.message.sourceId.trim() : \"\";\n\t\tconst sources = await this.historySourcesForAdmin(sourceId);\n\t\tthis.sendTo(\n\t\t\tmessage.from,\n\t\t\tmessage.command,\n\t\t\tsources.map(source => ({ value: source.instanceId, label: source.instanceId })),\n\t\t\tmessage.callback,\n\t\t);\n\t}\n\n\tprivate async historySourcesForAdmin(sourceId: string): Promise<HistorySource[]> {\n\t\tif (!sourceId) {\n\t\t\treturn [];\n\t\t}\n\t\ttry {\n\t\t\treturn await this.historySourceResolver.resolve(sourceId);\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`Could not resolve history sources for ${sourceId}: ${(error as Error).message}`);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate async processState(\n\t\tid: string,\n\t\tsafeId: string,\n\t\tmonitor: SourceMonitor,\n\t\tstate: ioBroker.State,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tconst result = monitor.observe(\n\t\t\t\tstate.val,\n\t\t\t\ttypeof state.ts === \"number\" ? state.ts : Date.now(),\n\t\t\t\tthis.contextKey(id),\n\t\t\t);\n\t\t\tif (!result) {\n\t\t\t\tif (!this.invalidValueWarnings.has(id)) {\n\t\t\t\t\tthis.invalidValueWarnings.add(id);\n\t\t\t\t\tthis.log.warn(`Ignoring invalid non-numerical value from ${id}`);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.invalidValueWarnings.delete(id);\n\t\t\tthis.evaluations.set(id, result);\n\t\t\tawait this.writeResult(safeId, result);\n\t\t\tthis.schedulePersistence();\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Could not process source state ${id}: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\tprivate async registerContexts(sourceId: string, source: ConfiguredSource): Promise<void> {\n\t\tif (!source.enableContext) {\n\t\t\treturn;\n\t\t}\n\t\tif ((source.contextStates?.length ?? 0) > 3) {\n\t\t\tthis.warnContext(`Only the first 3 context states are used for ${sourceId}`);\n\t\t}\n\t\tfor (const context of (source.contextStates ?? [])\n\t\t\t.filter(item => item && typeof item.id === \"string\" && item.id.trim())\n\t\t\t.slice(0, 3)) {\n\t\t\tconst id = context.id.trim();\n\t\t\tlet sources = this.contextSources.get(id);\n\t\t\tif (!sources) {\n\t\t\t\tsources = new Set();\n\t\t\t\tthis.contextSources.set(id, sources);\n\t\t\t\tthis.subscribeForeignStates(id);\n\t\t\t}\n\t\t\tsources.add(sourceId);\n\t\t\tconst state = await this.getForeignStateAsync(id);\n\t\t\tif (state) {\n\t\t\t\tlet values = this.contextValues.get(sourceId);\n\t\t\t\tif (!values) {\n\t\t\t\t\tvalues = new Map();\n\t\t\t\t\tthis.contextValues.set(sourceId, values);\n\t\t\t\t}\n\t\t\t\tvalues.set(id, state.val);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate contextKey(sourceId: string): string | undefined {\n\t\tconst safeId = this.sourceIds.get(sourceId);\n\t\tconst source = safeId ? this.sources.get(safeId) : undefined;\n\t\tif (!source?.enableContext) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst values = this.contextValues.get(sourceId);\n\t\tconst parts = (source.contextStates ?? [])\n\t\t\t.filter(item => item && typeof item.id === \"string\" && item.id.trim())\n\t\t\t.slice(0, 3)\n\t\t\t.map(context => {\n\t\t\t\tconst value = values?.get(context.id.trim());\n\t\t\t\tif (typeof value === \"string\" && value.trim() && value.length <= 96) {\n\t\t\t\t\tconst categoryKey = `${sourceId}\\u0000${context.id}`;\n\t\t\t\t\tlet categories = this.contextCategories.get(categoryKey);\n\t\t\t\t\tif (!categories) {\n\t\t\t\t\t\tcategories = new Set();\n\t\t\t\t\t\tthis.contextCategories.set(categoryKey, categories);\n\t\t\t\t\t}\n\t\t\t\t\tif (!categories.has(value) && categories.size >= 16) {\n\t\t\t\t\t\tthis.warnContext(`Ignoring additional categorical values for ${context.id}; limit is 16`);\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tcategories.add(value);\n\t\t\t\t}\n\t\t\t\treturn createContextPart(context.id, value, context.bucketWidth);\n\t\t\t});\n\t\treturn parts.length > 0 && parts.every(Boolean) ? parts.join(\"|\") : undefined;\n\t}\n\n\tprivate warnContext(message: string): void {\n\t\tif (!this.contextWarnings.has(message)) {\n\t\t\tthis.contextWarnings.add(message);\n\t\t\tthis.log.warn(message);\n\t\t}\n\t}\n\n\tprivate async ensureModelState(): Promise<void> {\n\t\tawait this.setObjectNotExistsAsync(MODEL_STATE_ID, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Persisted models\", type: \"string\", role: \"json\", read: true, write: false, def: \"\" },\n\t\t\tnative: {},\n\t\t});\n\t}\n\n\tprivate async cleanupStaleSources(\n\t\tconfiguredSourceIds: ReadonlySet<string>,\n\t\tstoredModels: ReturnType<typeof parseStoredModel>,\n\t): Promise<ReturnType<typeof parseStoredModel>> {\n\t\ttry {\n\t\t\tconst result = await cleanupStaleSources(\n\t\t\t\t{\n\t\t\t\t\tlistGeneratedSourceDevices: async () => {\n\t\t\t\t\t\tconst devices = await this.getForeignObjectsAsync(`${this.namespace}.sources.*`, \"device\");\n\t\t\t\t\t\treturn Object.entries(devices).map(([id, device]) => ({\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\tsourceId: typeof device.native?.sourceId === \"string\" ? device.native.sourceId : undefined,\n\t\t\t\t\t\t}));\n\t\t\t\t\t},\n\t\t\t\t\tdeleteGeneratedSourceTree: relativeId => this.delObjectAsync(relativeId, { recursive: true }),\n\t\t\t\t},\n\t\t\t\tthis.namespace,\n\t\t\t\tconfiguredSourceIds,\n\t\t\t\tstoredModels,\n\t\t\t\tthis.createSourceId.bind(this),\n\t\t\t);\n\t\t\tif (result.modelsChanged) {\n\t\t\t\tawait this.setStateAsync(MODEL_STATE_ID, { val: JSON.stringify(result.models), ack: true });\n\t\t\t}\n\t\t\tfor (const sourceId of result.removedSourceIds) {\n\t\t\t\tthis.log.info(`Removed generated anomaly objects and model for deleted source: ${sourceId}`);\n\t\t\t}\n\t\t\treturn result.models;\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Could not clean up stale monitored sources: ${(error as Error).message}`);\n\t\t\treturn storedModels;\n\t\t}\n\t}\n\n\tprivate async ensureSourceObjects(safeId: string, source: ConfiguredSource, unit?: string): Promise<void> {\n\t\tconst base = `sources.${safeId}`;\n\t\tawait this.setObjectNotExistsAsync(base, {\n\t\t\ttype: \"device\",\n\t\t\tcommon: { name: source.name || source.id },\n\t\t\tnative: { sourceId: source.id },\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Anomaly analysis\" },\n\t\t\tnative: {},\n\t\t});\n\t\tconst numberState = (\n\t\t\tname: string,\n\t\t\trole: string,\n\t\t\textra: Partial<ioBroker.StateCommon> = {},\n\t\t): ioBroker.SettableObject => ({\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name, type: \"number\", role, read: true, write: false, ...extra },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.actual`,\n\t\t\tnumberState(\"Actual value\", \"value\", unit ? { unit } : {}),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.expected`,\n\t\t\tnumberState(\"Expected value\", \"value\", unit ? { unit } : {}),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.deviation`,\n\t\t\tnumberState(\"Deviation\", \"value\", unit ? { unit } : {}),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.score`,\n\t\t\tnumberState(\"Anomaly score\", \"value\", { min: 0, max: 100, unit: \"%\" }),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.sampleCount`,\n\t\t\tnumberState(\"Learned sample count\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.baselineScope`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Active baseline scope\", type: \"string\", role: \"info.status\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.activeContext`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Active context\", type: \"string\", role: \"text\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.baselineSampleCount`,\n\t\t\tnumberState(\"Active baseline sample count\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.analysis.contextSampleCount`,\n\t\t\tnumberState(\"Active context sample count\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.detected`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Persistent anomaly detected\",\n\t\t\t\ttype: \"boolean\",\n\t\t\t\trole: \"indicator\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.status`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Model status\", type: \"string\", role: \"info.status\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.reason`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Anomaly reason\", type: \"string\", role: \"text\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.lastAnomaly`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Last high-score anomaly\", type: \"string\", role: \"date\", read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.retrain`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name: \"Retrain model\", type: \"boolean\", role: \"button\", read: false, write: true, def: false },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.analysis.bootstrapStatus`, {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Historical bootstrap status\",\n\t\t\t\ttype: \"string\",\n\t\t\t\trole: \"info.status\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.ensureExplainabilityObjects(base, numberState);\n\t}\n\n\tprivate async ensureExplainabilityObjects(\n\t\tbase: string,\n\t\tnumberState: (name: string, role: string, extra?: Partial<ioBroker.StateCommon>) => ioBroker.SettableObject,\n\t): Promise<void> {\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Current anomaly assessment\" },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Evaluation details\" },\n\t\t\tnative: {},\n\t\t});\n\t\tawait this.setObjectNotExistsAsync(`${base}.detectors`, {\n\t\t\ttype: \"channel\",\n\t\t\tcommon: { name: \"Detector scores\" },\n\t\t\tnative: {},\n\t\t});\n\t\tconst textState = (name: string, role = \"text\"): ioBroker.SettableObject => ({\n\t\t\ttype: \"state\",\n\t\t\tcommon: { name, type: \"string\", role, read: true, write: false },\n\t\t\tnative: {},\n\t\t});\n\t\tconst booleanState: ioBroker.SettableObject = {\n\t\t\ttype: \"state\",\n\t\t\tcommon: {\n\t\t\t\tname: \"Anomaly active\",\n\t\t\t\ttype: \"boolean\",\n\t\t\t\trole: \"indicator\",\n\t\t\t\tread: true,\n\t\t\t\twrite: false,\n\t\t\t\tdef: false,\n\t\t\t},\n\t\t\tnative: {},\n\t\t};\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.active`, booleanState);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.anomaly.score`,\n\t\t\tnumberState(\"Overall anomaly score\", \"value\", { min: 0, max: 100, unit: \"%\" }),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.severity`, textState(\"Severity\", \"info.status\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.reasonCode`, textState(\"Reason code\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.reason`, textState(\"Human-readable reason\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.since`, textState(\"Anomaly since\", \"date\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.anomaly.lastNormal`, textState(\"Last normal evaluation\", \"date\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.status`, textState(\"Evaluation status\", \"info.status\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.currentValue`, numberState(\"Current value\", \"value\"));\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.expectedLow`,\n\t\t\tnumberState(\"Expected range lower bound\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.expectedHigh`,\n\t\t\tnumberState(\"Expected range upper bound\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.decisionLow`,\n\t\t\tnumberState(\"Decision range lower bound\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.decisionHigh`,\n\t\t\tnumberState(\"Decision range upper bound\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.baselineType`, textState(\"Baseline type\"));\n\t\tawait this.setObjectNotExistsAsync(\n\t\t\t`${base}.evaluation.baselineSamples`,\n\t\t\tnumberState(\"Baseline samples\", \"value\"),\n\t\t);\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.context`, textState(\"Used context\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.timeBucket`, textState(\"Time bucket\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.evaluation.lastEvaluated`, textState(\"Last evaluation\", \"date\"));\n\t\tawait this.setObjectNotExistsAsync(`${base}.detectors.active`, textState(\"Active detectors\"));\n\t\tfor (const [id, label] of [\n\t\t\t[\"valueDeviation\", \"Value deviation score\"],\n\t\t\t[\"rateOfChange\", \"Rate-of-change score\"],\n\t\t\t[\"stuck\", \"Stuck-state score\"],\n\t\t\t[\"levelShift\", \"Level-shift score\"],\n\t\t\t[\"trend\", \"Trend score\"],\n\t\t] as const) {\n\t\t\tawait this.setObjectNotExistsAsync(\n\t\t\t\t`${base}.detectors.${id}`,\n\t\t\t\tnumberState(label, \"value\", { min: 0, max: 100 }),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate shouldBootstrap(source: ConfiguredSource, monitor: SourceMonitor): boolean {\n\t\treturn (\n\t\t\tsource.initialTraining === \"history\" &&\n\t\t\t!!source.historyInstance?.trim() &&\n\t\t\t(!monitor.hasSufficientData || !monitor.bootstrapMatches(this.bootstrapConfigKey(source)))\n\t\t);\n\t}\n\n\tprivate async bootstrapSource(\n\t\tsourceId: string,\n\t\tsafeId: string,\n\t\tsource: ConfiguredSource,\n\t\tmonitor: SourceMonitor,\n\t): Promise<void> {\n\t\tconst providerName = source.historyInstance!.trim();\n\t\tconst end = Date.now();\n\t\tconst start = end - Math.max(7, source.trainingDays ?? 30) * 24 * 60 * 60 * 1000;\n\t\tawait this.setStateAsync(`sources.${safeId}.analysis.status`, { val: \"importingHistory\", ack: true });\n\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\tval: \"Importing historical data\",\n\t\t\tack: true,\n\t\t});\n\t\ttry {\n\t\t\tconst historySource = await this.resolveConfiguredHistorySource(sourceId, providerName);\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\t\tval: `Importing history from ${historySource.sourceId} via ${providerName}`,\n\t\t\t\tack: true,\n\t\t\t});\n\t\t\tconst provider = new IoBrokerHistoryProvider(this, providerName);\n\t\t\tconst raw = await this.withHistoryTimeout(\n\t\t\t\tprovider.getHistory(historySource.sourceId, start, end, source.maxHistorySamples ?? 1000),\n\t\t\t);\n\t\t\tconst samples = sanitizeHistory(raw, source.maxHistorySamples ?? 1000);\n\t\t\tif (samples.length === 0) {\n\t\t\t\tthrow new Error(\"History provider returned no valid samples\");\n\t\t\t}\n\t\t\tmonitor.reset();\n\t\t\tconst imported = monitor.bootstrapFromHistory(\n\t\t\t\tsamples,\n\t\t\t\tproviderName,\n\t\t\t\thistorySource.sourceId,\n\t\t\t\tstart,\n\t\t\t\tend,\n\t\t\t\tthis.bootstrapConfigKey(source),\n\t\t\t);\n\t\t\tif (imported === 0) {\n\t\t\t\tthrow new Error(\"No usable samples remained after historical outlier filtering\");\n\t\t\t}\n\t\t\tawait this.persistModels();\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.sampleCount`, { val: monitor.sampleCount, ack: true });\n\t\t\tconst status =\n\t\t\t\tmonitor.hasSufficientData && (source.autoStartMonitoringAfterImport ?? true)\n\t\t\t\t\t? \"monitoring\"\n\t\t\t\t\t: \"learning\";\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.status`, { val: status, ack: true });\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\t\tval: `Historical training completed (${imported} samples)`,\n\t\t\t\tack: true,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.log.warn(`Historical bootstrap failed for ${sourceId}: ${(error as Error).message}`);\n\t\t\tawait this.persistModels();\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.status`, {\n\t\t\t\tval: monitor.hasSufficientData ? \"monitoring\" : \"learning\",\n\t\t\t\tack: true,\n\t\t\t});\n\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\t\tval: \"Insufficient historical data, continuing live learning\",\n\t\t\t\tack: true,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async resolveConfiguredHistorySource(sourceId: string, instanceId: string): Promise<HistorySource> {\n\t\ttry {\n\t\t\treturn selectHistorySource(sourceId, instanceId, await this.historySourceResolver.resolve(sourceId));\n\t\t} catch (error) {\n\t\t\tthis.warnInvalidHistoryConfiguration((error as Error).message);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate warnInvalidHistoryConfiguration(message: string): void {\n\t\tif (!this.invalidHistoryConfigurationWarnings.has(message)) {\n\t\t\tthis.invalidHistoryConfigurationWarnings.add(message);\n\t\t\tthis.log.warn(message);\n\t\t}\n\t}\n\n\tprivate async retrainSource(\n\t\tsourceId: string,\n\t\tsafeId: string,\n\t\tsource: ConfiguredSource,\n\t\tmonitor: SourceMonitor,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tmonitor.reset();\n\t\t\tif (source.initialTraining === \"history\" && source.historyInstance?.trim()) {\n\t\t\t\tawait this.bootstrapSource(sourceId, safeId, source, monitor);\n\t\t\t} else {\n\t\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.status`, { val: \"learning\", ack: true });\n\t\t\t\tawait this.setStateAsync(`sources.${safeId}.analysis.bootstrapStatus`, {\n\t\t\t\t\tval: \"Model reset; learning live values\",\n\t\t\t\t\tack: true,\n\t\t\t\t});\n\t\t\t\tawait this.persistModels();\n\t\t\t}\n\t\t} finally {\n\t\t\tawait this.setStateAsync(`sources.${safeId}.retrain`, { val: false, ack: true });\n\t\t}\n\t}\n\n\tprivate async runWithConcurrency(tasks: Array<() => Promise<void>>, concurrency: number): Promise<void> {\n\t\tconst queue = [...tasks];\n\t\tawait Promise.all(\n\t\t\tArray.from({ length: Math.min(concurrency, queue.length) }, async () => {\n\t\t\t\twhile (queue.length) {\n\t\t\t\t\tawait queue.shift()!();\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\tprivate async withHistoryTimeout<T>(operation: Promise<T>): Promise<T> {\n\t\treturn new Promise<T>((resolve, reject) => {\n\t\t\tconst timer = this.setTimeout(() => reject(new Error(\"History request timed out\")), HISTORY_TIMEOUT_MS);\n\t\t\toperation.then(\n\t\t\t\tvalue => {\n\t\t\t\t\tthis.clearTimeout(timer);\n\t\t\t\t\tresolve(value);\n\t\t\t\t},\n\t\t\t\terror => {\n\t\t\t\t\tthis.clearTimeout(timer);\n\t\t\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t},\n\t\t\t);\n\t\t});\n\t}\n\n\tprivate bootstrapConfigKey(source: ConfiguredSource): string {\n\t\treturn JSON.stringify({\n\t\t\tprovider: source.historyInstance?.trim(),\n\t\t\tdays: source.trainingDays ?? 30,\n\t\t\tlimit: source.maxHistorySamples ?? 1000,\n\t\t});\n\t}\n\n\tprivate async writeResult(safeId: string, result: ObservationResult): Promise<void> {\n\t\tconst base = `sources.${safeId}.analysis`;\n\t\tconst values: Record<string, ioBroker.StateValue> = {\n\t\t\tactual: result.actual,\n\t\t\tscore: result.score,\n\t\t\tdetected: result.detected,\n\t\t\tstatus: result.status,\n\t\t\treason: result.reason,\n\t\t\tsampleCount: result.sampleCount,\n\t\t\tbaselineScope: result.baselineScope,\n\t\t\tactiveContext: result.activeContext,\n\t\t\tbaselineSampleCount: result.baselineSampleCount,\n\t\t\tcontextSampleCount: result.contextSampleCount,\n\t\t};\n\t\tvalues.expected = result.expected ?? null;\n\t\tvalues.deviation = result.deviation ?? null;\n\t\tif (result.lastAnomaly !== undefined) {\n\t\t\tvalues.lastAnomaly = new Date(result.lastAnomaly).toISOString();\n\t\t}\n\t\tconst explainability: Record<string, ioBroker.StateValue> = {\n\t\t\t\"anomaly.active\": result.statusCode === \"anomaly\",\n\t\t\t\"anomaly.score\": result.score,\n\t\t\t\"anomaly.severity\": result.severity,\n\t\t\t\"anomaly.reasonCode\": result.reasonCode,\n\t\t\t\"anomaly.reason\": result.reason,\n\t\t\t\"anomaly.since\": result.anomalySince ? new Date(result.anomalySince).toISOString() : null,\n\t\t\t\"anomaly.lastNormal\": result.lastNormal ? new Date(result.lastNormal).toISOString() : null,\n\t\t\t\"evaluation.status\": result.statusCode,\n\t\t\t\"evaluation.currentValue\": result.actual,\n\t\t\t\"evaluation.expectedLow\": result.expectedLow ?? null,\n\t\t\t\"evaluation.expectedHigh\": result.expectedHigh ?? null,\n\t\t\t\"evaluation.decisionLow\": result.decisionLow ?? null,\n\t\t\t\"evaluation.decisionHigh\": result.decisionHigh ?? null,\n\t\t\t\"evaluation.baselineType\": result.baselineScope,\n\t\t\t\"evaluation.baselineSamples\": result.baselineSampleCount,\n\t\t\t\"evaluation.context\": result.activeContext || null,\n\t\t\t\"evaluation.timeBucket\": result.timeBucket ?? null,\n\t\t\t\"evaluation.lastEvaluated\": new Date(result.lastEvaluated).toISOString(),\n\t\t\t\"detectors.active\": result.detectors.map(detector => detector.name).join(\", \") || null,\n\t\t\t\"detectors.valueDeviation\": null,\n\t\t\t\"detectors.rateOfChange\": null,\n\t\t\t\"detectors.stuck\": null,\n\t\t\t\"detectors.levelShift\": null,\n\t\t\t\"detectors.trend\": null,\n\t\t};\n\t\tconst detectorIds: Record<string, string> = {\n\t\t\tvalue: \"valueDeviation\",\n\t\t\tcontext: \"valueDeviation\",\n\t\t\trate: \"rateOfChange\",\n\t\t\tstuck: \"stuck\",\n\t\t\tchangePoint: \"levelShift\",\n\t\t\ttrend: \"trend\",\n\t\t};\n\t\tfor (const detector of result.detectors) {\n\t\t\texplainability[`detectors.${detectorIds[detector.name]}`] = detector.score;\n\t\t}\n\t\tawait Promise.all(\n\t\t\t[...Object.entries(values), ...Object.entries(explainability)].map(([key, value]) =>\n\t\t\t\tthis.setStateAsync(`${key.includes(\".\") ? `sources.${safeId}` : base}.${key}`, {\n\t\t\t\t\tval: value,\n\t\t\t\t\tack: true,\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t}\n\n\tprivate schedulePersistence(): void {\n\t\tif (this.persistTimer) {\n\t\t\treturn;\n\t\t}\n\t\tthis.persistTimer = this.setTimeout(() => {\n\t\t\tthis.persistTimer = undefined;\n\t\t\tvoid this.persistModels();\n\t\t}, PERSIST_DELAY_MS);\n\t}\n\n\tprivate async persistModels(): Promise<void> {\n\t\ttry {\n\t\t\tconst models = Object.fromEntries(\n\t\t\t\t[...this.monitors].map(([sourceId, monitor]) => [\n\t\t\t\t\tthis.sourceIds.get(sourceId)!,\n\t\t\t\t\t{ ...monitor.toJSON(), configuredSourceId: sourceId },\n\t\t\t\t]),\n\t\t\t);\n\t\t\tawait this.setStateAsync(MODEL_STATE_ID, { val: JSON.stringify(models), ack: true });\n\t\t} catch (error) {\n\t\t\tthis.log.error(`Could not persist anomaly models: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\tprivate createSourceId(sourceId: string): string {\n\t\tconst normalized =\n\t\t\tsourceId\n\t\t\t\t.replace(/[^A-Za-z0-9_-]+/g, \"_\")\n\t\t\t\t.replace(/^_+|_+$/g, \"\")\n\t\t\t\t.slice(0, 48) || \"source\";\n\t\tconst hash = [...sourceId]\n\t\t\t.reduce((value, character) => (value * 31 + character.charCodeAt(0)) >>> 0, 0)\n\t\t\t.toString(36);\n\t\treturn `${normalized}_${hash}`;\n\t}\n\n\tprivate onUnload(callback: () => void): void {\n\t\tif (this.persistTimer) {\n\t\t\tthis.clearTimeout(this.persistTimer);\n\t\t}\n\t\tvoid this.persistModels().finally(callback);\n\t}\n}\n\nif (require.main !== module) {\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AnomalyDetection(options);\n} else {\n\t(() => new AnomalyDetection())();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["_a", "safeId", "monitor"]
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "anomaly-detection",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.2.1": {
|
|
7
|
+
"en": "Fix null context entries in the analysis view",
|
|
8
|
+
"de": "Null-Einträge bei Kontextzuständen in der Analyseansicht behoben",
|
|
9
|
+
"ru": "Исправлена обработка пустых контекстных состояний в представлении анализа",
|
|
10
|
+
"pt": "Corrigido o tratamento de estados de contexto nulos na vista de análise",
|
|
11
|
+
"nl": "Null-contextitems in de analyseweergave opgelost",
|
|
12
|
+
"fr": "Correction des entrées de contexte nulles dans la vue d’analyse",
|
|
13
|
+
"it": "Corretta la gestione dei contesti nulli nella vista analisi",
|
|
14
|
+
"es": "Corregidas las entradas de contexto nulas en la vista de análisis",
|
|
15
|
+
"pl": "Naprawiono puste wpisy kontekstu w widoku analizy",
|
|
16
|
+
"uk": "Виправлено нульові контексти у поданні аналізу",
|
|
17
|
+
"zh-cn": "修复分析视图中的空上下文条目"
|
|
18
|
+
},
|
|
6
19
|
"0.2.0": {
|
|
7
20
|
"en": "Improve analysis charts, historical explanations, and responsive layout",
|
|
8
21
|
"de": "Analyse-Diagramme, historische Erklärungen und responsives Layout verbessert",
|