iobroker.anomaly-detection 0.1.0
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/LICENSE +21 -0
- package/README.md +323 -0
- package/admin/anomaly-detection-icon.png +0 -0
- package/admin/anomaly-detection.png +0 -0
- package/admin/i18n/de.json +32 -0
- package/admin/i18n/en.json +32 -0
- package/admin/i18n/es.json +32 -0
- package/admin/i18n/fr.json +32 -0
- package/admin/i18n/it.json +32 -0
- package/admin/i18n/nl.json +32 -0
- package/admin/i18n/pl.json +32 -0
- package/admin/i18n/pt.json +32 -0
- package/admin/i18n/ru.json +32 -0
- package/admin/i18n/uk.json +32 -0
- package/admin/i18n/zh-cn.json +32 -0
- package/admin/jsonConfig.json +220 -0
- package/build/lib/advanced-detectors.js +149 -0
- package/build/lib/advanced-detectors.js.map +7 -0
- package/build/lib/context-value.js +42 -0
- package/build/lib/context-value.js.map +7 -0
- package/build/lib/detectors.js +76 -0
- package/build/lib/detectors.js.map +7 -0
- package/build/lib/history-provider.js +58 -0
- package/build/lib/history-provider.js.map +7 -0
- package/build/lib/history-source-resolver.js +109 -0
- package/build/lib/history-source-resolver.js.map +7 -0
- package/build/lib/model/contextual-model.js +98 -0
- package/build/lib/model/contextual-model.js.map +7 -0
- package/build/lib/model/statistics.js +78 -0
- package/build/lib/model/statistics.js.map +7 -0
- package/build/lib/model/temporal-model.js +98 -0
- package/build/lib/model/temporal-model.js.map +7 -0
- package/build/lib/scoring.js +57 -0
- package/build/lib/scoring.js.map +7 -0
- package/build/lib/source-cleanup.js +58 -0
- package/build/lib/source-cleanup.js.map +7 -0
- package/build/lib/source-monitor.js +348 -0
- package/build/lib/source-monitor.js.map +7 -0
- package/build/main.js +589 -0
- package/build/main.js.map +7 -0
- package/io-package.json +95 -0
- package/package.json +105 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var source_cleanup_exports = {};
|
|
20
|
+
__export(source_cleanup_exports, {
|
|
21
|
+
cleanupStaleSources: () => cleanupStaleSources
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(source_cleanup_exports);
|
|
24
|
+
async function cleanupStaleSources(store, namespace, configuredSourceIds, storedModels, createSourceId) {
|
|
25
|
+
const configuredSafeIds = new Set([...configuredSourceIds].map(createSourceId));
|
|
26
|
+
const removedSourceIds = [];
|
|
27
|
+
const prefix = `${namespace}.sources.`;
|
|
28
|
+
for (const device of await store.listGeneratedSourceDevices()) {
|
|
29
|
+
if (!device.sourceId || configuredSourceIds.has(device.sourceId)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const safeId = createSourceId(device.sourceId);
|
|
33
|
+
const expectedId = `${prefix}${safeId}`;
|
|
34
|
+
if (device.id !== expectedId) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
await store.deleteGeneratedSourceTree(`sources.${safeId}`);
|
|
38
|
+
removedSourceIds.push(device.sourceId);
|
|
39
|
+
}
|
|
40
|
+
const models = Object.fromEntries(
|
|
41
|
+
Object.entries(storedModels).filter(([safeId, model]) => {
|
|
42
|
+
if (model.configuredSourceId) {
|
|
43
|
+
return configuredSourceIds.has(model.configuredSourceId) && createSourceId(model.configuredSourceId) === safeId;
|
|
44
|
+
}
|
|
45
|
+
return configuredSafeIds.has(safeId);
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
return {
|
|
49
|
+
models,
|
|
50
|
+
modelsChanged: Object.keys(models).length !== Object.keys(storedModels).length,
|
|
51
|
+
removedSourceIds
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
55
|
+
0 && (module.exports = {
|
|
56
|
+
cleanupStaleSources
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=source-cleanup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/source-cleanup.ts"],
|
|
4
|
+
"sourcesContent": ["import type { SourceModelData } from \"./source-monitor\";\n\nexport interface GeneratedSourceDevice {\n\tid: string;\n\tsourceId?: string;\n}\n\nexport interface SourceCleanupStore {\n\tlistGeneratedSourceDevices(): Promise<GeneratedSourceDevice[]>;\n\tdeleteGeneratedSourceTree(relativeId: string): Promise<void>;\n}\n\nexport interface SourceCleanupResult {\n\tmodels: Record<string, SourceModelData>;\n\tmodelsChanged: boolean;\n\tremovedSourceIds: string[];\n}\n\n/** Removes only generated source trees whose persisted source metadata is no longer configured. */\nexport async function cleanupStaleSources(\n\tstore: SourceCleanupStore,\n\tnamespace: string,\n\tconfiguredSourceIds: ReadonlySet<string>,\n\tstoredModels: Record<string, SourceModelData>,\n\tcreateSourceId: (sourceId: string) => string,\n): Promise<SourceCleanupResult> {\n\tconst configuredSafeIds = new Set([...configuredSourceIds].map(createSourceId));\n\tconst removedSourceIds: string[] = [];\n\tconst prefix = `${namespace}.sources.`;\n\tfor (const device of await store.listGeneratedSourceDevices()) {\n\t\tif (!device.sourceId || configuredSourceIds.has(device.sourceId)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst safeId = createSourceId(device.sourceId);\n\t\tconst expectedId = `${prefix}${safeId}`;\n\t\tif (device.id !== expectedId) {\n\t\t\tcontinue;\n\t\t}\n\t\tawait store.deleteGeneratedSourceTree(`sources.${safeId}`);\n\t\tremovedSourceIds.push(device.sourceId);\n\t}\n\tconst models = Object.fromEntries(\n\t\tObject.entries(storedModels).filter(([safeId, model]) => {\n\t\t\tif (model.configuredSourceId) {\n\t\t\t\treturn (\n\t\t\t\t\tconfiguredSourceIds.has(model.configuredSourceId) &&\n\t\t\t\t\tcreateSourceId(model.configuredSourceId) === safeId\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn configuredSafeIds.has(safeId);\n\t\t}),\n\t) as Record<string, SourceModelData>;\n\treturn {\n\t\tmodels,\n\t\tmodelsChanged: Object.keys(models).length !== Object.keys(storedModels).length,\n\t\tremovedSourceIds,\n\t};\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,eAAsB,oBACrB,OACA,WACA,qBACA,cACA,gBAC+B;AAC/B,QAAM,oBAAoB,IAAI,IAAI,CAAC,GAAG,mBAAmB,EAAE,IAAI,cAAc,CAAC;AAC9E,QAAM,mBAA6B,CAAC;AACpC,QAAM,SAAS,GAAG,SAAS;AAC3B,aAAW,UAAU,MAAM,MAAM,2BAA2B,GAAG;AAC9D,QAAI,CAAC,OAAO,YAAY,oBAAoB,IAAI,OAAO,QAAQ,GAAG;AACjE;AAAA,IACD;AACA,UAAM,SAAS,eAAe,OAAO,QAAQ;AAC7C,UAAM,aAAa,GAAG,MAAM,GAAG,MAAM;AACrC,QAAI,OAAO,OAAO,YAAY;AAC7B;AAAA,IACD;AACA,UAAM,MAAM,0BAA0B,WAAW,MAAM,EAAE;AACzD,qBAAiB,KAAK,OAAO,QAAQ;AAAA,EACtC;AACA,QAAM,SAAS,OAAO;AAAA,IACrB,OAAO,QAAQ,YAAY,EAAE,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM;AACxD,UAAI,MAAM,oBAAoB;AAC7B,eACC,oBAAoB,IAAI,MAAM,kBAAkB,KAChD,eAAe,MAAM,kBAAkB,MAAM;AAAA,MAE/C;AACA,aAAO,kBAAkB,IAAI,MAAM;AAAA,IACpC,CAAC;AAAA,EACF;AACA,SAAO;AAAA,IACN;AAAA,IACA,eAAe,OAAO,KAAK,MAAM,EAAE,WAAW,OAAO,KAAK,YAAY,EAAE;AAAA,IACxE;AAAA,EACD;AACD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var source_monitor_exports = {};
|
|
20
|
+
__export(source_monitor_exports, {
|
|
21
|
+
DEFAULTS: () => DEFAULTS,
|
|
22
|
+
SourceMonitor: () => SourceMonitor,
|
|
23
|
+
parseStoredModel: () => parseStoredModel
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(source_monitor_exports);
|
|
26
|
+
var import_detectors = require("./detectors");
|
|
27
|
+
var import_advanced_detectors = require("./advanced-detectors");
|
|
28
|
+
var import_contextual_model = require("./model/contextual-model");
|
|
29
|
+
var import_temporal_model = require("./model/temporal-model");
|
|
30
|
+
var import_statistics = require("./model/statistics");
|
|
31
|
+
var import_scoring = require("./scoring");
|
|
32
|
+
const DEFAULTS = {
|
|
33
|
+
minimumSamples: 30,
|
|
34
|
+
bucketMinutes: 60,
|
|
35
|
+
sensitivity: 3.5,
|
|
36
|
+
anomalyThreshold: 70,
|
|
37
|
+
minimumAnomalyDurationMinutes: 5,
|
|
38
|
+
hysteresis: 10,
|
|
39
|
+
stuckDurationMinutes: 120,
|
|
40
|
+
maxHistoryGapMinutes: 360,
|
|
41
|
+
maxContextModels: 32
|
|
42
|
+
};
|
|
43
|
+
class SourceMonitor {
|
|
44
|
+
constructor(settings, data) {
|
|
45
|
+
this.settings = settings;
|
|
46
|
+
var _a, _b;
|
|
47
|
+
const bucketMinutes = (_a = settings.bucketMinutes) != null ? _a : DEFAULTS.bucketMinutes;
|
|
48
|
+
this.valueModel = new import_temporal_model.TemporalModel(bucketMinutes, void 0, data == null ? void 0 : data.value);
|
|
49
|
+
this.rateModel = new import_temporal_model.TemporalModel(bucketMinutes, void 0, data == null ? void 0 : data.rate);
|
|
50
|
+
this.contextualModel = new import_contextual_model.ContextualModel(bucketMinutes, DEFAULTS.maxContextModels, void 0, data == null ? void 0 : data.context);
|
|
51
|
+
this.advanced = new import_advanced_detectors.AdvancedDetectors(data == null ? void 0 : data.changePoint, data == null ? void 0 : data.trend);
|
|
52
|
+
this.lastValue = data == null ? void 0 : data.lastValue;
|
|
53
|
+
this.lastTimestamp = data == null ? void 0 : data.lastTimestamp;
|
|
54
|
+
this.lastContextKey = data == null ? void 0 : data.lastContextKey;
|
|
55
|
+
this.repeatedSince = data == null ? void 0 : data.repeatedSince;
|
|
56
|
+
this.anomalySince = data == null ? void 0 : data.anomalySince;
|
|
57
|
+
this.detected = (_b = data == null ? void 0 : data.detected) != null ? _b : false;
|
|
58
|
+
this.bootstrap = data == null ? void 0 : data.bootstrap;
|
|
59
|
+
}
|
|
60
|
+
valueModel;
|
|
61
|
+
rateModel;
|
|
62
|
+
contextualModel;
|
|
63
|
+
advanced;
|
|
64
|
+
lastValue;
|
|
65
|
+
lastTimestamp;
|
|
66
|
+
lastContextKey;
|
|
67
|
+
repeatedSince;
|
|
68
|
+
anomalySince;
|
|
69
|
+
detected;
|
|
70
|
+
bootstrap;
|
|
71
|
+
observe(value, timestamp, contextKey) {
|
|
72
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
73
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isFinite(timestamp)) {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
const minSamples = (_a = this.settings.minimumSamples) != null ? _a : DEFAULTS.minimumSamples;
|
|
77
|
+
const sensitivity = (_b = this.settings.sensitivity) != null ? _b : DEFAULTS.sensitivity;
|
|
78
|
+
const threshold = (_c = this.settings.anomalyThreshold) != null ? _c : DEFAULTS.anomalyThreshold;
|
|
79
|
+
const timeContext = (_d = this.settings.timeContext) != null ? _d : true;
|
|
80
|
+
const weekdayContext = (_e = this.settings.weekdayContext) != null ? _e : false;
|
|
81
|
+
const hasActiveContext = this.settings.enableContext === true && contextKey !== void 0;
|
|
82
|
+
const contextSampleCountBefore = hasActiveContext ? this.contextualModel.sampleCount(contextKey) : 0;
|
|
83
|
+
const contextIsLearning = hasActiveContext && contextSampleCountBefore < minSamples;
|
|
84
|
+
const standardBaseline = this.valueModel.baseline(timestamp, minSamples, timeContext, weekdayContext);
|
|
85
|
+
const contextBaseline = contextIsLearning ? void 0 : this.settings.enableContext ? this.contextualModel.baseline(contextKey, timestamp, minSamples, timeContext, weekdayContext) : void 0;
|
|
86
|
+
const baseline = contextBaseline != null ? contextBaseline : standardBaseline;
|
|
87
|
+
const baselineScope = contextBaseline ? "context" : contextIsLearning ? "insufficient" : baseline.scope === "global" ? "global" : "time";
|
|
88
|
+
const baselineSampleCount = contextIsLearning ? 0 : baseline.series.count;
|
|
89
|
+
const expected = contextIsLearning ? void 0 : baseline.series.median();
|
|
90
|
+
const contextChanged = this.settings.enableContext === true && contextKey !== this.lastContextKey;
|
|
91
|
+
const rate = contextChanged ? void 0 : this.calculateRate(value, timestamp);
|
|
92
|
+
const results = [];
|
|
93
|
+
if (((_f = this.settings.enableMad) != null ? _f : true) && !contextIsLearning) {
|
|
94
|
+
const result = (0, import_detectors.detectMadDeviation)(value, baseline.series, minSamples, sensitivity);
|
|
95
|
+
if (result) {
|
|
96
|
+
results.push(
|
|
97
|
+
contextBaseline ? {
|
|
98
|
+
...result,
|
|
99
|
+
name: "context",
|
|
100
|
+
reason: "Value is significantly outside the normal range for the current context"
|
|
101
|
+
} : { ...result, reason: valueDeviationReason(baselineScope) }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if ((_g = this.settings.enableRate) != null ? _g : true) {
|
|
106
|
+
const rateBaseline = this.rateModel.baseline(timestamp, minSamples, timeContext, weekdayContext);
|
|
107
|
+
const result = (0, import_detectors.detectRateDeviation)(rate, rateBaseline.series, minSamples, sensitivity);
|
|
108
|
+
if (result) {
|
|
109
|
+
results.push(result);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
this.updateRepeated(value, timestamp);
|
|
113
|
+
if ((_h = this.settings.enableStuck) != null ? _h : false) {
|
|
114
|
+
const result = (0, import_detectors.detectStuck)(
|
|
115
|
+
this.repeatedSince,
|
|
116
|
+
timestamp,
|
|
117
|
+
(_i = this.settings.stuckDurationMinutes) != null ? _i : DEFAULTS.stuckDurationMinutes
|
|
118
|
+
);
|
|
119
|
+
if (result) {
|
|
120
|
+
results.push(result);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const advanced = this.advanced.observe(
|
|
124
|
+
expected === void 0 ? void 0 : value - expected,
|
|
125
|
+
timestamp,
|
|
126
|
+
0,
|
|
127
|
+
baseline.series.mad(),
|
|
128
|
+
this.settings.enableChangePoint === true,
|
|
129
|
+
this.settings.enableTrend === true,
|
|
130
|
+
sensitivity
|
|
131
|
+
);
|
|
132
|
+
if (advanced.changePoint) {
|
|
133
|
+
results.push(advanced.changePoint);
|
|
134
|
+
}
|
|
135
|
+
if (advanced.trend) {
|
|
136
|
+
results.push(advanced.trend);
|
|
137
|
+
}
|
|
138
|
+
const scoring = (0, import_scoring.scoreDetectors)(results);
|
|
139
|
+
this.updatePersistentDetection(scoring.score, timestamp, threshold);
|
|
140
|
+
const isStrongAnomaly = scoring.score >= threshold;
|
|
141
|
+
const learningNewContext = contextIsLearning;
|
|
142
|
+
if (!isStrongAnomaly || advanced.adapt || learningNewContext) {
|
|
143
|
+
this.valueModel.add(value, timestamp, timeContext, weekdayContext);
|
|
144
|
+
if (this.settings.enableContext) {
|
|
145
|
+
this.contextualModel.add(contextKey, value, timestamp, timeContext, weekdayContext);
|
|
146
|
+
}
|
|
147
|
+
if (rate !== void 0) {
|
|
148
|
+
this.rateModel.add(rate, timestamp, timeContext, weekdayContext);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
this.lastValue = value;
|
|
152
|
+
this.lastTimestamp = timestamp;
|
|
153
|
+
this.lastContextKey = this.settings.enableContext === true ? contextKey : void 0;
|
|
154
|
+
const sufficient = this.valueModel.sampleCount >= minSamples;
|
|
155
|
+
const contextSampleCount = hasActiveContext ? this.contextualModel.sampleCount(contextKey) : 0;
|
|
156
|
+
return {
|
|
157
|
+
actual: value,
|
|
158
|
+
expected,
|
|
159
|
+
deviation: expected === void 0 ? void 0 : value - expected,
|
|
160
|
+
score: scoring.score,
|
|
161
|
+
detected: this.detected,
|
|
162
|
+
status: contextIsLearning ? "learning" : sufficient ? "monitoring" : this.valueModel.sampleCount === 0 ? "insufficientData" : "learning",
|
|
163
|
+
reason: contextIsLearning && results.length === 0 ? "Insufficient data for the current context" : scoring.reason,
|
|
164
|
+
sampleCount: this.valueModel.sampleCount,
|
|
165
|
+
lastAnomaly: isStrongAnomaly ? timestamp : void 0,
|
|
166
|
+
baselineScope,
|
|
167
|
+
activeContext: formatContextKey(contextKey),
|
|
168
|
+
baselineSampleCount,
|
|
169
|
+
contextSampleCount
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
toJSON() {
|
|
173
|
+
return {
|
|
174
|
+
schemaVersion: import_statistics.MODEL_SCHEMA_VERSION,
|
|
175
|
+
value: this.valueModel.toJSON(),
|
|
176
|
+
rate: this.rateModel.toJSON(),
|
|
177
|
+
lastValue: this.lastValue,
|
|
178
|
+
lastTimestamp: this.lastTimestamp,
|
|
179
|
+
lastContextKey: this.lastContextKey,
|
|
180
|
+
repeatedSince: this.repeatedSince,
|
|
181
|
+
anomalySince: this.anomalySince,
|
|
182
|
+
detected: this.detected,
|
|
183
|
+
bootstrap: this.bootstrap,
|
|
184
|
+
context: this.contextualModel.toJSON(),
|
|
185
|
+
...this.advanced.toJSON()
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
get hasSufficientData() {
|
|
189
|
+
var _a;
|
|
190
|
+
return this.valueModel.sampleCount >= ((_a = this.settings.minimumSamples) != null ? _a : DEFAULTS.minimumSamples);
|
|
191
|
+
}
|
|
192
|
+
/** Number of retained global value-model samples, capped at the model capacity. */
|
|
193
|
+
get sampleCount() {
|
|
194
|
+
return this.valueModel.sampleCount;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Imports sanitized historical observations into the same temporal and rate models used by live updates.
|
|
198
|
+
*
|
|
199
|
+
* @param samples Ordered, sanitized historical samples.
|
|
200
|
+
* @param provider Configured history adapter instance.
|
|
201
|
+
* @param historySourceId Effective source queried from the history provider.
|
|
202
|
+
* @param start Start timestamp of the requested range.
|
|
203
|
+
* @param end End timestamp of the requested range.
|
|
204
|
+
* @param configKey Import-relevant source configuration.
|
|
205
|
+
*/
|
|
206
|
+
bootstrapFromHistory(samples, provider, historySourceId, start, end, configKey) {
|
|
207
|
+
var _a, _b;
|
|
208
|
+
const retained = this.rejectExtremeOutliers(samples);
|
|
209
|
+
const timeContext = (_a = this.settings.timeContext) != null ? _a : true;
|
|
210
|
+
const weekdayContext = (_b = this.settings.weekdayContext) != null ? _b : false;
|
|
211
|
+
let previous;
|
|
212
|
+
for (const sample of retained) {
|
|
213
|
+
this.valueModel.add(sample.value, sample.timestamp, timeContext, weekdayContext);
|
|
214
|
+
if (previous && sample.timestamp > previous.timestamp) {
|
|
215
|
+
const gap = sample.timestamp - previous.timestamp;
|
|
216
|
+
if (gap <= DEFAULTS.maxHistoryGapMinutes * 6e4) {
|
|
217
|
+
this.rateModel.add(
|
|
218
|
+
(sample.value - previous.value) / (gap / 1e3),
|
|
219
|
+
sample.timestamp,
|
|
220
|
+
timeContext,
|
|
221
|
+
weekdayContext
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
previous = sample;
|
|
226
|
+
}
|
|
227
|
+
if (previous) {
|
|
228
|
+
this.lastValue = previous.value;
|
|
229
|
+
this.lastTimestamp = previous.timestamp;
|
|
230
|
+
this.repeatedSince = void 0;
|
|
231
|
+
}
|
|
232
|
+
this.bootstrap = {
|
|
233
|
+
completed: true,
|
|
234
|
+
provider,
|
|
235
|
+
historySourceId,
|
|
236
|
+
configKey,
|
|
237
|
+
start,
|
|
238
|
+
end,
|
|
239
|
+
importedSamples: retained.length
|
|
240
|
+
};
|
|
241
|
+
return retained.length;
|
|
242
|
+
}
|
|
243
|
+
reset() {
|
|
244
|
+
var _a, _b, _c;
|
|
245
|
+
this.valueModel = new import_temporal_model.TemporalModel((_a = this.settings.bucketMinutes) != null ? _a : DEFAULTS.bucketMinutes);
|
|
246
|
+
this.rateModel = new import_temporal_model.TemporalModel((_b = this.settings.bucketMinutes) != null ? _b : DEFAULTS.bucketMinutes);
|
|
247
|
+
this.contextualModel = new import_contextual_model.ContextualModel(
|
|
248
|
+
(_c = this.settings.bucketMinutes) != null ? _c : DEFAULTS.bucketMinutes,
|
|
249
|
+
DEFAULTS.maxContextModels
|
|
250
|
+
);
|
|
251
|
+
this.advanced = new import_advanced_detectors.AdvancedDetectors();
|
|
252
|
+
this.lastValue = void 0;
|
|
253
|
+
this.lastTimestamp = void 0;
|
|
254
|
+
this.lastContextKey = void 0;
|
|
255
|
+
this.repeatedSince = void 0;
|
|
256
|
+
this.anomalySince = void 0;
|
|
257
|
+
this.detected = false;
|
|
258
|
+
this.bootstrap = void 0;
|
|
259
|
+
}
|
|
260
|
+
/** @param configKey Import-relevant source configuration. */
|
|
261
|
+
bootstrapMatches(configKey) {
|
|
262
|
+
var _a;
|
|
263
|
+
return ((_a = this.bootstrap) == null ? void 0 : _a.completed) === true && this.bootstrap.configKey === configKey;
|
|
264
|
+
}
|
|
265
|
+
rejectExtremeOutliers(samples) {
|
|
266
|
+
var _a;
|
|
267
|
+
if (samples.length < 5) {
|
|
268
|
+
return [...samples];
|
|
269
|
+
}
|
|
270
|
+
const values = samples.map((sample) => sample.value);
|
|
271
|
+
const center = (0, import_statistics.median)(values);
|
|
272
|
+
if (center === void 0) {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
const mad = (_a = (0, import_statistics.median)(values.map((value) => Math.abs(value - center)))) != null ? _a : 0;
|
|
276
|
+
if (mad === 0) {
|
|
277
|
+
return samples.filter((sample) => sample.value === center);
|
|
278
|
+
}
|
|
279
|
+
return samples.filter((sample) => 0.6745 * Math.abs(sample.value - center) / mad <= 7);
|
|
280
|
+
}
|
|
281
|
+
calculateRate(value, timestamp) {
|
|
282
|
+
if (this.lastValue === void 0 || this.lastTimestamp === void 0 || timestamp <= this.lastTimestamp) {
|
|
283
|
+
return void 0;
|
|
284
|
+
}
|
|
285
|
+
return (value - this.lastValue) / ((timestamp - this.lastTimestamp) / 1e3);
|
|
286
|
+
}
|
|
287
|
+
updateRepeated(value, timestamp) {
|
|
288
|
+
if (this.lastValue === void 0 || this.lastValue !== value) {
|
|
289
|
+
this.repeatedSince = timestamp;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
updatePersistentDetection(score, timestamp, threshold) {
|
|
293
|
+
var _a, _b;
|
|
294
|
+
if (this.detected) {
|
|
295
|
+
if (score < Math.max(0, threshold - DEFAULTS.hysteresis)) {
|
|
296
|
+
this.detected = false;
|
|
297
|
+
this.anomalySince = void 0;
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (score < threshold) {
|
|
302
|
+
this.anomalySince = void 0;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
(_a = this.anomalySince) != null ? _a : this.anomalySince = timestamp;
|
|
306
|
+
const requiredDuration = ((_b = this.settings.minimumAnomalyDurationMinutes) != null ? _b : DEFAULTS.minimumAnomalyDurationMinutes) * 6e4;
|
|
307
|
+
if (timestamp - this.anomalySince >= requiredDuration) {
|
|
308
|
+
this.detected = true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function valueDeviationReason(scope) {
|
|
313
|
+
if (scope === "global") {
|
|
314
|
+
return "Value is significantly outside the learned global normal range";
|
|
315
|
+
}
|
|
316
|
+
return "Value is significantly outside the normal range for this time period";
|
|
317
|
+
}
|
|
318
|
+
function formatContextKey(key) {
|
|
319
|
+
if (!key) {
|
|
320
|
+
return "";
|
|
321
|
+
}
|
|
322
|
+
return key.replace(/=boolean:/g, "=").replace(/=string:/g, "=").replace(/=number:([^:|]+):([^|]+)/g, "=$1\u2013$2");
|
|
323
|
+
}
|
|
324
|
+
function parseStoredModel(value) {
|
|
325
|
+
if (typeof value !== "string") {
|
|
326
|
+
return {};
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
const parsed = JSON.parse(value);
|
|
330
|
+
if (!parsed || typeof parsed !== "object") {
|
|
331
|
+
return {};
|
|
332
|
+
}
|
|
333
|
+
return Object.fromEntries(
|
|
334
|
+
Object.entries(parsed).filter(
|
|
335
|
+
([, model]) => !!model && typeof model === "object" && (model.schemaVersion === 1 || model.schemaVersion === import_statistics.MODEL_SCHEMA_VERSION)
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
} catch {
|
|
339
|
+
return {};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
343
|
+
0 && (module.exports = {
|
|
344
|
+
DEFAULTS,
|
|
345
|
+
SourceMonitor,
|
|
346
|
+
parseStoredModel
|
|
347
|
+
});
|
|
348
|
+
//# sourceMappingURL=source-monitor.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/source-monitor.ts"],
|
|
4
|
+
"sourcesContent": ["import { detectMadDeviation, detectRateDeviation, detectStuck, type DetectorResult } from \"./detectors\";\nimport { AdvancedDetectors, type ChangePointData, type TrendData } from \"./advanced-detectors\";\nimport { ContextualModel, type ContextualModelData } from \"./model/contextual-model\";\nimport { TemporalModel, type TemporalModelData } from \"./model/temporal-model\";\nimport { median, MODEL_SCHEMA_VERSION } from \"./model/statistics\";\nimport { scoreDetectors } from \"./scoring\";\nimport type { HistorySample } from \"./history-provider\";\n\nexport const DEFAULTS = {\n\tminimumSamples: 30,\n\tbucketMinutes: 60,\n\tsensitivity: 3.5,\n\tanomalyThreshold: 70,\n\tminimumAnomalyDurationMinutes: 5,\n\thysteresis: 10,\n\tstuckDurationMinutes: 120,\n\tmaxHistoryGapMinutes: 360,\n\tmaxContextModels: 32,\n} as const;\nexport interface ContextStateSettings {\n\tid: string;\n\tbucketWidth?: number;\n}\n\nexport interface SourceSettings {\n\tenabled: boolean;\n\tid: string;\n\tname?: string;\n\tminimumSamples?: number;\n\tbucketMinutes?: number;\n\tsensitivity?: number;\n\tanomalyThreshold?: number;\n\tminimumAnomalyDurationMinutes?: number;\n\tenableMad?: boolean;\n\tenableRate?: boolean;\n\tenableStuck?: boolean;\n\tstuckDurationMinutes?: number;\n\ttimeContext?: boolean;\n\tweekdayContext?: boolean;\n\tinitialTraining?: \"live\" | \"history\";\n\thistoryInstance?: string;\n\ttrainingDays?: number;\n\tmaxHistorySamples?: number;\n\tautoStartMonitoringAfterImport?: boolean;\n\tenableContext?: boolean;\n\tcontextStates?: ContextStateSettings[];\n\tenableChangePoint?: boolean;\n\tenableTrend?: boolean;\n}\n\nexport interface BootstrapMetadata {\n\tcompleted: boolean;\n\tprovider?: string;\n\thistorySourceId?: string;\n\tconfigKey?: string;\n\tstart?: number;\n\tend?: number;\n\timportedSamples: number;\n}\n\nexport interface SourceModelData {\n\tschemaVersion: number;\n\tconfiguredSourceId?: string;\n\tvalue: TemporalModelData;\n\trate: TemporalModelData;\n\tlastValue?: number;\n\tlastTimestamp?: number;\n\tlastContextKey?: string;\n\trepeatedSince?: number;\n\tanomalySince?: number;\n\tdetected?: boolean;\n\tbootstrap?: BootstrapMetadata;\n\tcontext?: ContextualModelData;\n\tchangePoint?: ChangePointData;\n\ttrend?: TrendData;\n}\n\nexport type BaselineScope = \"context\" | \"time\" | \"global\" | \"insufficient\";\n\nexport interface ObservationResult {\n\tactual: number;\n\texpected?: number;\n\tdeviation?: number;\n\tscore: number;\n\tdetected: boolean;\n\tstatus: \"learning\" | \"monitoring\" | \"insufficientData\";\n\treason: string;\n\tsampleCount: number;\n\tlastAnomaly?: number;\n\tbaselineScope: BaselineScope;\n\tactiveContext: string;\n\tbaselineSampleCount: number;\n\tcontextSampleCount: number;\n}\n\n/** Framework-independent lifecycle and learning policy for one source state. */\nexport class SourceMonitor {\n\tprivate valueModel: TemporalModel;\n\tprivate rateModel: TemporalModel;\n\tprivate contextualModel: ContextualModel;\n\tprivate advanced: AdvancedDetectors;\n\tprivate lastValue: number | undefined;\n\tprivate lastTimestamp: number | undefined;\n\tprivate lastContextKey: string | undefined;\n\tprivate repeatedSince: number | undefined;\n\tprivate anomalySince: number | undefined;\n\tprivate detected: boolean;\n\tprivate bootstrap?: BootstrapMetadata;\n\n\tpublic constructor(\n\t\tprivate readonly settings: SourceSettings,\n\t\tdata?: SourceModelData,\n\t) {\n\t\tconst bucketMinutes = settings.bucketMinutes ?? DEFAULTS.bucketMinutes;\n\t\tthis.valueModel = new TemporalModel(bucketMinutes, undefined, data?.value);\n\t\tthis.rateModel = new TemporalModel(bucketMinutes, undefined, data?.rate);\n\t\tthis.contextualModel = new ContextualModel(bucketMinutes, DEFAULTS.maxContextModels, undefined, data?.context);\n\t\tthis.advanced = new AdvancedDetectors(data?.changePoint, data?.trend);\n\t\tthis.lastValue = data?.lastValue;\n\t\tthis.lastTimestamp = data?.lastTimestamp;\n\t\tthis.lastContextKey = data?.lastContextKey;\n\t\tthis.repeatedSince = data?.repeatedSince;\n\t\tthis.anomalySince = data?.anomalySince;\n\t\tthis.detected = data?.detected ?? false;\n\t\tthis.bootstrap = data?.bootstrap;\n\t}\n\n\tpublic observe(value: unknown, timestamp: number, contextKey?: string): ObservationResult | undefined {\n\t\tif (typeof value !== \"number\" || !Number.isFinite(value) || !Number.isFinite(timestamp)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst minSamples = this.settings.minimumSamples ?? DEFAULTS.minimumSamples;\n\t\tconst sensitivity = this.settings.sensitivity ?? DEFAULTS.sensitivity;\n\t\tconst threshold = this.settings.anomalyThreshold ?? DEFAULTS.anomalyThreshold;\n\t\tconst timeContext = this.settings.timeContext ?? true;\n\t\tconst weekdayContext = this.settings.weekdayContext ?? false;\n\t\tconst hasActiveContext = this.settings.enableContext === true && contextKey !== undefined;\n\t\tconst contextSampleCountBefore = hasActiveContext ? this.contextualModel.sampleCount(contextKey) : 0;\n\t\tconst contextIsLearning = hasActiveContext && contextSampleCountBefore < minSamples;\n\t\tconst standardBaseline = this.valueModel.baseline(timestamp, minSamples, timeContext, weekdayContext);\n\t\tconst contextBaseline = contextIsLearning\n\t\t\t? undefined\n\t\t\t: this.settings.enableContext\n\t\t\t\t? this.contextualModel.baseline(contextKey, timestamp, minSamples, timeContext, weekdayContext)\n\t\t\t\t: undefined;\n\t\tconst baseline = contextBaseline ?? standardBaseline;\n\t\tconst baselineScope: BaselineScope = contextBaseline\n\t\t\t? \"context\"\n\t\t\t: contextIsLearning\n\t\t\t\t? \"insufficient\"\n\t\t\t\t: baseline.scope === \"global\"\n\t\t\t\t\t? \"global\"\n\t\t\t\t\t: \"time\";\n\t\tconst baselineSampleCount = contextIsLearning ? 0 : baseline.series.count;\n\t\tconst expected = contextIsLearning ? undefined : baseline.series.median();\n\t\tconst contextChanged = this.settings.enableContext === true && contextKey !== this.lastContextKey;\n\t\tconst rate = contextChanged ? undefined : this.calculateRate(value, timestamp);\n\t\tconst results: DetectorResult[] = [];\n\t\tif ((this.settings.enableMad ?? true) && !contextIsLearning) {\n\t\t\tconst result = detectMadDeviation(value, baseline.series, minSamples, sensitivity);\n\t\t\tif (result) {\n\t\t\t\tresults.push(\n\t\t\t\t\tcontextBaseline\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t...result,\n\t\t\t\t\t\t\t\tname: \"context\",\n\t\t\t\t\t\t\t\treason: \"Value is significantly outside the normal range for the current context\",\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: { ...result, reason: valueDeviationReason(baselineScope) },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tif (this.settings.enableRate ?? true) {\n\t\t\tconst rateBaseline = this.rateModel.baseline(timestamp, minSamples, timeContext, weekdayContext);\n\t\t\tconst result = detectRateDeviation(rate, rateBaseline.series, minSamples, sensitivity);\n\t\t\tif (result) {\n\t\t\t\tresults.push(result);\n\t\t\t}\n\t\t}\n\t\tthis.updateRepeated(value, timestamp);\n\t\tif (this.settings.enableStuck ?? false) {\n\t\t\tconst result = detectStuck(\n\t\t\t\tthis.repeatedSince,\n\t\t\t\ttimestamp,\n\t\t\t\tthis.settings.stuckDurationMinutes ?? DEFAULTS.stuckDurationMinutes,\n\t\t\t);\n\t\t\tif (result) {\n\t\t\t\tresults.push(result);\n\t\t\t}\n\t\t}\n\t\tconst advanced = this.advanced.observe(\n\t\t\texpected === undefined ? undefined : value - expected,\n\t\t\ttimestamp,\n\t\t\t0,\n\t\t\tbaseline.series.mad(),\n\t\t\tthis.settings.enableChangePoint === true,\n\t\t\tthis.settings.enableTrend === true,\n\t\t\tsensitivity,\n\t\t);\n\t\tif (advanced.changePoint) {\n\t\t\tresults.push(advanced.changePoint);\n\t\t}\n\t\tif (advanced.trend) {\n\t\t\tresults.push(advanced.trend);\n\t\t}\n\t\tconst scoring = scoreDetectors(results);\n\t\tthis.updatePersistentDetection(scoring.score, timestamp, threshold);\n\t\tconst isStrongAnomaly = scoring.score >= threshold;\n\t\tconst learningNewContext = contextIsLearning;\n\t\tif (!isStrongAnomaly || advanced.adapt || learningNewContext) {\n\t\t\tthis.valueModel.add(value, timestamp, timeContext, weekdayContext);\n\t\t\tif (this.settings.enableContext) {\n\t\t\t\tthis.contextualModel.add(contextKey, value, timestamp, timeContext, weekdayContext);\n\t\t\t}\n\t\t\tif (rate !== undefined) {\n\t\t\t\tthis.rateModel.add(rate, timestamp, timeContext, weekdayContext);\n\t\t\t}\n\t\t}\n\t\tthis.lastValue = value;\n\t\tthis.lastTimestamp = timestamp;\n\t\tthis.lastContextKey = this.settings.enableContext === true ? contextKey : undefined;\n\t\tconst sufficient = this.valueModel.sampleCount >= minSamples;\n\t\tconst contextSampleCount = hasActiveContext ? this.contextualModel.sampleCount(contextKey) : 0;\n\t\treturn {\n\t\t\tactual: value,\n\t\t\texpected,\n\t\t\tdeviation: expected === undefined ? undefined : value - expected,\n\t\t\tscore: scoring.score,\n\t\t\tdetected: this.detected,\n\t\t\tstatus: contextIsLearning\n\t\t\t\t? \"learning\"\n\t\t\t\t: sufficient\n\t\t\t\t\t? \"monitoring\"\n\t\t\t\t\t: this.valueModel.sampleCount === 0\n\t\t\t\t\t\t? \"insufficientData\"\n\t\t\t\t\t\t: \"learning\",\n\t\t\treason:\n\t\t\t\tcontextIsLearning && results.length === 0\n\t\t\t\t\t? \"Insufficient data for the current context\"\n\t\t\t\t\t: scoring.reason,\n\t\t\tsampleCount: this.valueModel.sampleCount,\n\t\t\tlastAnomaly: isStrongAnomaly ? timestamp : undefined,\n\t\t\tbaselineScope,\n\t\t\tactiveContext: formatContextKey(contextKey),\n\t\t\tbaselineSampleCount,\n\t\t\tcontextSampleCount,\n\t\t};\n\t}\n\n\tpublic toJSON(): SourceModelData {\n\t\treturn {\n\t\t\tschemaVersion: MODEL_SCHEMA_VERSION,\n\t\t\tvalue: this.valueModel.toJSON(),\n\t\t\trate: this.rateModel.toJSON(),\n\t\t\tlastValue: this.lastValue,\n\t\t\tlastTimestamp: this.lastTimestamp,\n\t\t\tlastContextKey: this.lastContextKey,\n\t\t\trepeatedSince: this.repeatedSince,\n\t\t\tanomalySince: this.anomalySince,\n\t\t\tdetected: this.detected,\n\t\t\tbootstrap: this.bootstrap,\n\t\t\tcontext: this.contextualModel.toJSON(),\n\t\t\t...this.advanced.toJSON(),\n\t\t};\n\t}\n\n\tpublic get hasSufficientData(): boolean {\n\t\treturn this.valueModel.sampleCount >= (this.settings.minimumSamples ?? DEFAULTS.minimumSamples);\n\t}\n\n\t/** Number of retained global value-model samples, capped at the model capacity. */\n\tpublic get sampleCount(): number {\n\t\treturn this.valueModel.sampleCount;\n\t}\n\n\t/**\n\t * Imports sanitized historical observations into the same temporal and rate models used by live updates.\n\t *\n\t * @param samples Ordered, sanitized historical samples.\n\t * @param provider Configured history adapter instance.\n\t * @param historySourceId Effective source queried from the history provider.\n\t * @param start Start timestamp of the requested range.\n\t * @param end End timestamp of the requested range.\n\t * @param configKey Import-relevant source configuration.\n\t */\n\tpublic bootstrapFromHistory(\n\t\tsamples: readonly HistorySample[],\n\t\tprovider: string,\n\t\thistorySourceId: string,\n\t\tstart: number,\n\t\tend: number,\n\t\tconfigKey: string,\n\t): number {\n\t\tconst retained = this.rejectExtremeOutliers(samples);\n\t\tconst timeContext = this.settings.timeContext ?? true;\n\t\tconst weekdayContext = this.settings.weekdayContext ?? false;\n\t\tlet previous: HistorySample | undefined;\n\t\tfor (const sample of retained) {\n\t\t\tthis.valueModel.add(sample.value, sample.timestamp, timeContext, weekdayContext);\n\t\t\tif (previous && sample.timestamp > previous.timestamp) {\n\t\t\t\tconst gap = sample.timestamp - previous.timestamp;\n\t\t\t\tif (gap <= DEFAULTS.maxHistoryGapMinutes * 60_000) {\n\t\t\t\t\tthis.rateModel.add(\n\t\t\t\t\t\t(sample.value - previous.value) / (gap / 1000),\n\t\t\t\t\t\tsample.timestamp,\n\t\t\t\t\t\ttimeContext,\n\t\t\t\t\t\tweekdayContext,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tprevious = sample;\n\t\t}\n\t\tif (previous) {\n\t\t\tthis.lastValue = previous.value;\n\t\t\tthis.lastTimestamp = previous.timestamp;\n\t\t\t// Historical gaps do not prove that the source was stuck. Start this detector with live updates only.\n\t\t\tthis.repeatedSince = undefined;\n\t\t}\n\t\tthis.bootstrap = {\n\t\t\tcompleted: true,\n\t\t\tprovider,\n\t\t\thistorySourceId,\n\t\t\tconfigKey,\n\t\t\tstart,\n\t\t\tend,\n\t\t\timportedSamples: retained.length,\n\t\t};\n\t\treturn retained.length;\n\t}\n\n\tpublic reset(): void {\n\t\tthis.valueModel = new TemporalModel(this.settings.bucketMinutes ?? DEFAULTS.bucketMinutes);\n\t\tthis.rateModel = new TemporalModel(this.settings.bucketMinutes ?? DEFAULTS.bucketMinutes);\n\t\tthis.contextualModel = new ContextualModel(\n\t\t\tthis.settings.bucketMinutes ?? DEFAULTS.bucketMinutes,\n\t\t\tDEFAULTS.maxContextModels,\n\t\t);\n\t\tthis.advanced = new AdvancedDetectors();\n\t\tthis.lastValue = undefined;\n\t\tthis.lastTimestamp = undefined;\n\t\tthis.lastContextKey = undefined;\n\t\tthis.repeatedSince = undefined;\n\t\tthis.anomalySince = undefined;\n\t\tthis.detected = false;\n\t\tthis.bootstrap = undefined;\n\t}\n\n\t/** @param configKey Import-relevant source configuration. */\n\tpublic bootstrapMatches(configKey: string): boolean {\n\t\treturn this.bootstrap?.completed === true && this.bootstrap.configKey === configKey;\n\t}\n\n\tprivate rejectExtremeOutliers(samples: readonly HistorySample[]): HistorySample[] {\n\t\tif (samples.length < 5) {\n\t\t\treturn [...samples];\n\t\t}\n\t\tconst values = samples.map(sample => sample.value);\n\t\tconst center = median(values);\n\t\tif (center === undefined) {\n\t\t\treturn [];\n\t\t}\n\t\tconst mad = median(values.map(value => Math.abs(value - center))) ?? 0;\n\t\tif (mad === 0) {\n\t\t\treturn samples.filter(sample => sample.value === center);\n\t\t}\n\t\treturn samples.filter(sample => (0.6745 * Math.abs(sample.value - center)) / mad <= 7);\n\t}\n\n\tprivate calculateRate(value: number, timestamp: number): number | undefined {\n\t\tif (this.lastValue === undefined || this.lastTimestamp === undefined || timestamp <= this.lastTimestamp) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn (value - this.lastValue) / ((timestamp - this.lastTimestamp) / 1000);\n\t}\n\n\tprivate updateRepeated(value: number, timestamp: number): void {\n\t\tif (this.lastValue === undefined || this.lastValue !== value) {\n\t\t\tthis.repeatedSince = timestamp;\n\t\t}\n\t}\n\n\tprivate updatePersistentDetection(score: number, timestamp: number, threshold: number): void {\n\t\tif (this.detected) {\n\t\t\tif (score < Math.max(0, threshold - DEFAULTS.hysteresis)) {\n\t\t\t\tthis.detected = false;\n\t\t\t\tthis.anomalySince = undefined;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (score < threshold) {\n\t\t\tthis.anomalySince = undefined;\n\t\t\treturn;\n\t\t}\n\t\tthis.anomalySince ??= timestamp;\n\t\tconst requiredDuration =\n\t\t\t(this.settings.minimumAnomalyDurationMinutes ?? DEFAULTS.minimumAnomalyDurationMinutes) * 60_000;\n\t\tif (timestamp - this.anomalySince >= requiredDuration) {\n\t\t\tthis.detected = true;\n\t\t}\n\t}\n}\n\n/**\n * Builds the value-deviation reason for the selected non-context baseline.\n *\n * @param scope Actual baseline scope.\n */\nfunction valueDeviationReason(scope: BaselineScope): string {\n\tif (scope === \"global\") {\n\t\treturn \"Value is significantly outside the learned global normal range\";\n\t}\n\treturn \"Value is significantly outside the normal range for this time period\";\n}\n\n/**\n * Converts the internal normalized context key to a stable readable diagnostic value.\n *\n * @param key Internal normalized context key.\n */\nfunction formatContextKey(key: string | undefined): string {\n\tif (!key) {\n\t\treturn \"\";\n\t}\n\treturn key\n\t\t.replace(/=boolean:/g, \"=\")\n\t\t.replace(/=string:/g, \"=\")\n\t\t.replace(/=number:([^:|]+):([^|]+)/g, \"=$1\u2013$2\");\n}\n\nexport function parseStoredModel(value: unknown): Record<string, SourceModelData> {\n\tif (typeof value !== \"string\") {\n\t\treturn {};\n\t}\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(value);\n\t\tif (!parsed || typeof parsed !== \"object\") {\n\t\t\treturn {};\n\t\t}\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(parsed as Record<string, unknown>).filter(\n\t\t\t\t([, model]) =>\n\t\t\t\t\t!!model &&\n\t\t\t\t\ttypeof model === \"object\" &&\n\t\t\t\t\t((model as SourceModelData).schemaVersion === 1 ||\n\t\t\t\t\t\t(model as SourceModelData).schemaVersion === MODEL_SCHEMA_VERSION),\n\t\t\t),\n\t\t) as Record<string, SourceModelData>;\n\t} catch {\n\t\treturn {};\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|