margin-ts 0.6.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 +78 -0
- package/dist/anomaly.d.ts +34 -0
- package/dist/anomaly.d.ts.map +1 -0
- package/dist/anomaly.js +91 -0
- package/dist/anomaly.js.map +1 -0
- package/dist/confidence.d.ts +16 -0
- package/dist/confidence.d.ts.map +1 -0
- package/dist/confidence.js +41 -0
- package/dist/confidence.js.map +1 -0
- package/dist/drift.d.ts +36 -0
- package/dist/drift.d.ts.map +1 -0
- package/dist/drift.js +141 -0
- package/dist/drift.js.map +1 -0
- package/dist/health.d.ts +26 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +54 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/observation.d.ts +59 -0
- package/dist/observation.d.ts.map +1 -0
- package/dist/observation.js +147 -0
- package/dist/observation.js.map +1 -0
- package/package.json +29 -0
- package/src/anomaly.ts +111 -0
- package/src/confidence.ts +37 -0
- package/src/drift.ts +160 -0
- package/src/health.ts +66 -0
- package/src/index.ts +33 -0
- package/src/observation.ts +201 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cope Labs LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# margin
|
|
2
|
+
|
|
3
|
+
**Typed health classification for systems that measure things.**
|
|
4
|
+
|
|
5
|
+
TypeScript port of the [Python margin library](https://github.com/sethc5/margin). Zero dependencies.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Parser, createThresholds, expressionToString } from 'margin-ts';
|
|
9
|
+
|
|
10
|
+
const parser = new Parser(
|
|
11
|
+
{ throughput: 500, errorRate: 0.002 },
|
|
12
|
+
createThresholds(400, 150),
|
|
13
|
+
{ errorRate: createThresholds(0.01, 0.10, false) },
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const expr = parser.parse({ throughput: 480, errorRate: 0.03 });
|
|
17
|
+
console.log(expressionToString(expr));
|
|
18
|
+
// [throughput:INTACT(-0.04σ)] [errorRate:DEGRADED(-14.00σ)]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Throughput and error rate on the same scale. One is higher-is-better, the other is lower-is-better. Both classified correctly. Sigma-normalised so you can compare them.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install margin-ts
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
A number comes in. Margin gives it:
|
|
32
|
+
|
|
33
|
+
- **Health** — INTACT / DEGRADED / ABLATED / RECOVERING / OOD
|
|
34
|
+
- **Polarity** — higher-is-better or lower-is-better, handled correctly everywhere
|
|
35
|
+
- **Sigma** — dimensionless deviation from baseline, always positive = healthier
|
|
36
|
+
- **Confidence** — CERTAIN / HIGH / MODERATE / LOW / INDETERMINATE
|
|
37
|
+
- **Drift** — trajectory: STABLE / DRIFTING / ACCELERATING / DECELERATING / REVERTING / OSCILLATING
|
|
38
|
+
- **Anomaly** — outlier: EXPECTED / UNUSUAL / ANOMALOUS / NOVEL
|
|
39
|
+
|
|
40
|
+
## Drift detection
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { classifyDrift, DriftState } from 'margin-ts';
|
|
44
|
+
|
|
45
|
+
const dc = classifyDrift(observations);
|
|
46
|
+
dc.state; // DriftState.DRIFTING
|
|
47
|
+
dc.direction; // DriftDirection.WORSENING
|
|
48
|
+
dc.rate; // slope per second (polarity-normalised)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Anomaly detection
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { classifyAnomaly, AnomalyState } from 'margin-ts';
|
|
55
|
+
|
|
56
|
+
const ac = classifyAnomaly(currentValue, referenceValues, { component: 'cpu' });
|
|
57
|
+
ac.state; // AnomalyState.NOVEL
|
|
58
|
+
ac.zScore; // how many σ from historical mean
|
|
59
|
+
ac.isNovel; // true if outside historical range
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API parity with Python
|
|
63
|
+
|
|
64
|
+
The TypeScript port mirrors the Python library's core types:
|
|
65
|
+
|
|
66
|
+
| Python | TypeScript |
|
|
67
|
+
|--------|-----------|
|
|
68
|
+
| `Health.INTACT` | `Health.INTACT` |
|
|
69
|
+
| `Thresholds(intact=80, ablated=30)` | `createThresholds(80, 30)` |
|
|
70
|
+
| `classify(value, confidence, thresholds)` | `classify(value, confidence, thresholds)` |
|
|
71
|
+
| `Parser(baselines, thresholds)` | `new Parser(baselines, thresholds)` |
|
|
72
|
+
| `expr.to_string()` | `expressionToString(expr)` |
|
|
73
|
+
| `classify_drift(observations)` | `classifyDrift(observations)` |
|
|
74
|
+
| `classify_anomaly(value, ref)` | `classifyAnomaly(value, ref)` |
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT — Copyright (c) 2026 Cope Labs LLC
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anomaly detection: typed states for statistical outliers.
|
|
3
|
+
*
|
|
4
|
+
* Health says "is it good?", Drift says "is it changing?",
|
|
5
|
+
* Anomaly says "is it normal?"
|
|
6
|
+
*/
|
|
7
|
+
import { Confidence } from './confidence.js';
|
|
8
|
+
export declare enum AnomalyState {
|
|
9
|
+
EXPECTED = "EXPECTED",
|
|
10
|
+
UNUSUAL = "UNUSUAL",
|
|
11
|
+
ANOMALOUS = "ANOMALOUS",
|
|
12
|
+
NOVEL = "NOVEL"
|
|
13
|
+
}
|
|
14
|
+
export declare const ANOMALY_SEVERITY: Record<AnomalyState, number>;
|
|
15
|
+
export interface AnomalyClassification {
|
|
16
|
+
component: string;
|
|
17
|
+
state: AnomalyState;
|
|
18
|
+
zScore: number;
|
|
19
|
+
historicalMean: number;
|
|
20
|
+
historicalStd: number;
|
|
21
|
+
historicalMin: number;
|
|
22
|
+
historicalMax: number;
|
|
23
|
+
isNovel: boolean;
|
|
24
|
+
confidence: Confidence;
|
|
25
|
+
nReference: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function classifyAnomaly(value: number, reference: number[], options?: {
|
|
28
|
+
component?: string;
|
|
29
|
+
unusualThreshold?: number;
|
|
30
|
+
anomalousThreshold?: number;
|
|
31
|
+
novelMargin?: number;
|
|
32
|
+
minReference?: number;
|
|
33
|
+
}): AnomalyClassification | null;
|
|
34
|
+
//# sourceMappingURL=anomaly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anomaly.d.ts","sourceRoot":"","sources":["../src/anomaly.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,oBAAY,YAAY;IACtB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,SAAS,cAAc;IACvB,KAAK,UAAU;CAChB;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAKzD,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAmBD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,qBAAqB,GAAG,IAAI,CA+C9B"}
|
package/dist/anomaly.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Anomaly detection: typed states for statistical outliers.
|
|
4
|
+
*
|
|
5
|
+
* Health says "is it good?", Drift says "is it changing?",
|
|
6
|
+
* Anomaly says "is it normal?"
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ANOMALY_SEVERITY = exports.AnomalyState = void 0;
|
|
10
|
+
exports.classifyAnomaly = classifyAnomaly;
|
|
11
|
+
const confidence_js_1 = require("./confidence.js");
|
|
12
|
+
var AnomalyState;
|
|
13
|
+
(function (AnomalyState) {
|
|
14
|
+
AnomalyState["EXPECTED"] = "EXPECTED";
|
|
15
|
+
AnomalyState["UNUSUAL"] = "UNUSUAL";
|
|
16
|
+
AnomalyState["ANOMALOUS"] = "ANOMALOUS";
|
|
17
|
+
AnomalyState["NOVEL"] = "NOVEL";
|
|
18
|
+
})(AnomalyState || (exports.AnomalyState = AnomalyState = {}));
|
|
19
|
+
exports.ANOMALY_SEVERITY = {
|
|
20
|
+
[AnomalyState.EXPECTED]: 0,
|
|
21
|
+
[AnomalyState.UNUSUAL]: 1,
|
|
22
|
+
[AnomalyState.ANOMALOUS]: 2,
|
|
23
|
+
[AnomalyState.NOVEL]: 3,
|
|
24
|
+
};
|
|
25
|
+
function mean(xs) {
|
|
26
|
+
return xs.reduce((a, b) => a + b, 0) / xs.length;
|
|
27
|
+
}
|
|
28
|
+
function std(xs, m) {
|
|
29
|
+
if (xs.length < 2)
|
|
30
|
+
return 0;
|
|
31
|
+
const variance = xs.reduce((a, x) => a + (x - m) ** 2, 0) / (xs.length - 1);
|
|
32
|
+
return Math.sqrt(variance);
|
|
33
|
+
}
|
|
34
|
+
function confidenceFromN(n) {
|
|
35
|
+
if (n >= 30)
|
|
36
|
+
return confidence_js_1.Confidence.HIGH;
|
|
37
|
+
if (n >= 10)
|
|
38
|
+
return confidence_js_1.Confidence.MODERATE;
|
|
39
|
+
if (n >= 3)
|
|
40
|
+
return confidence_js_1.Confidence.LOW;
|
|
41
|
+
return confidence_js_1.Confidence.INDETERMINATE;
|
|
42
|
+
}
|
|
43
|
+
function classifyAnomaly(value, reference, options = {}) {
|
|
44
|
+
const component = options.component ?? '';
|
|
45
|
+
const unusualThreshold = options.unusualThreshold ?? 2.0;
|
|
46
|
+
const anomalousThreshold = options.anomalousThreshold ?? 3.0;
|
|
47
|
+
const novelMargin = options.novelMargin ?? 0.1;
|
|
48
|
+
const minReference = options.minReference ?? 3;
|
|
49
|
+
if (reference.length < minReference)
|
|
50
|
+
return null;
|
|
51
|
+
const refMean = mean(reference);
|
|
52
|
+
const refStd = std(reference, refMean);
|
|
53
|
+
const refMin = Math.min(...reference);
|
|
54
|
+
const refMax = Math.max(...reference);
|
|
55
|
+
const n = reference.length;
|
|
56
|
+
// Z-score
|
|
57
|
+
let z;
|
|
58
|
+
if (refStd > 0) {
|
|
59
|
+
z = (value - refMean) / refStd;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
z = value === refMean ? 0 : (value > refMean ? Infinity : -Infinity);
|
|
63
|
+
}
|
|
64
|
+
// Novelty check
|
|
65
|
+
const refRange = refMax - refMin;
|
|
66
|
+
const margin = refRange > 0 ? Math.max(refRange * novelMargin, refStd) : refStd * 2;
|
|
67
|
+
const isNovel = value < refMin - margin || value > refMax + margin;
|
|
68
|
+
// Classification
|
|
69
|
+
let state;
|
|
70
|
+
if (isNovel)
|
|
71
|
+
state = AnomalyState.NOVEL;
|
|
72
|
+
else if (Math.abs(z) >= anomalousThreshold)
|
|
73
|
+
state = AnomalyState.ANOMALOUS;
|
|
74
|
+
else if (Math.abs(z) >= unusualThreshold)
|
|
75
|
+
state = AnomalyState.UNUSUAL;
|
|
76
|
+
else
|
|
77
|
+
state = AnomalyState.EXPECTED;
|
|
78
|
+
return {
|
|
79
|
+
component,
|
|
80
|
+
state,
|
|
81
|
+
zScore: z,
|
|
82
|
+
historicalMean: refMean,
|
|
83
|
+
historicalStd: refStd,
|
|
84
|
+
historicalMin: refMin,
|
|
85
|
+
historicalMax: refMax,
|
|
86
|
+
isNovel,
|
|
87
|
+
confidence: confidenceFromN(n),
|
|
88
|
+
nReference: n,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=anomaly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anomaly.js","sourceRoot":"","sources":["../src/anomaly.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confidence tiers for uncertain comparisons.
|
|
3
|
+
* Replaces binary booleans with graded confidence levels.
|
|
4
|
+
*/
|
|
5
|
+
export declare enum Confidence {
|
|
6
|
+
CERTAIN = "certain",
|
|
7
|
+
HIGH = "high",
|
|
8
|
+
MODERATE = "moderate",
|
|
9
|
+
LOW = "low",
|
|
10
|
+
INDETERMINATE = "indeterminate"
|
|
11
|
+
}
|
|
12
|
+
export declare function confidenceRank(c: Confidence): number;
|
|
13
|
+
export declare function confidenceGte(a: Confidence, b: Confidence): boolean;
|
|
14
|
+
export declare function confidenceLt(a: Confidence, b: Confidence): boolean;
|
|
15
|
+
export declare function minConfidence(...confs: Confidence[]): Confidence;
|
|
16
|
+
//# sourceMappingURL=confidence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confidence.d.ts","sourceRoot":"","sources":["../src/confidence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,oBAAY,UAAU;IACpB,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,GAAG,QAAQ;IACX,aAAa,kBAAkB;CAChC;AAUD,wBAAgB,cAAc,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAEpD;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAEnE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAElE;AAED,wBAAgB,aAAa,CAAC,GAAG,KAAK,EAAE,UAAU,EAAE,GAAG,UAAU,CAGhE"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Confidence tiers for uncertain comparisons.
|
|
4
|
+
* Replaces binary booleans with graded confidence levels.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Confidence = void 0;
|
|
8
|
+
exports.confidenceRank = confidenceRank;
|
|
9
|
+
exports.confidenceGte = confidenceGte;
|
|
10
|
+
exports.confidenceLt = confidenceLt;
|
|
11
|
+
exports.minConfidence = minConfidence;
|
|
12
|
+
var Confidence;
|
|
13
|
+
(function (Confidence) {
|
|
14
|
+
Confidence["CERTAIN"] = "certain";
|
|
15
|
+
Confidence["HIGH"] = "high";
|
|
16
|
+
Confidence["MODERATE"] = "moderate";
|
|
17
|
+
Confidence["LOW"] = "low";
|
|
18
|
+
Confidence["INDETERMINATE"] = "indeterminate";
|
|
19
|
+
})(Confidence || (exports.Confidence = Confidence = {}));
|
|
20
|
+
const RANK = {
|
|
21
|
+
[Confidence.CERTAIN]: 4,
|
|
22
|
+
[Confidence.HIGH]: 3,
|
|
23
|
+
[Confidence.MODERATE]: 2,
|
|
24
|
+
[Confidence.LOW]: 1,
|
|
25
|
+
[Confidence.INDETERMINATE]: 0,
|
|
26
|
+
};
|
|
27
|
+
function confidenceRank(c) {
|
|
28
|
+
return RANK[c];
|
|
29
|
+
}
|
|
30
|
+
function confidenceGte(a, b) {
|
|
31
|
+
return RANK[a] >= RANK[b];
|
|
32
|
+
}
|
|
33
|
+
function confidenceLt(a, b) {
|
|
34
|
+
return RANK[a] < RANK[b];
|
|
35
|
+
}
|
|
36
|
+
function minConfidence(...confs) {
|
|
37
|
+
if (confs.length === 0)
|
|
38
|
+
return Confidence.INDETERMINATE;
|
|
39
|
+
return confs.reduce((a, b) => (RANK[a] <= RANK[b] ? a : b));
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=confidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confidence.js","sourceRoot":"","sources":["../src/confidence.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAkBH,wCAEC;AAED,sCAEC;AAED,oCAEC;AAED,sCAGC;AA/BD,IAAY,UAMX;AAND,WAAY,UAAU;IACpB,iCAAmB,CAAA;IACnB,2BAAa,CAAA;IACb,mCAAqB,CAAA;IACrB,yBAAW,CAAA;IACX,6CAA+B,CAAA;AACjC,CAAC,EANW,UAAU,0BAAV,UAAU,QAMrB;AAED,MAAM,IAAI,GAA+B;IACvC,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;IACvB,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;IACpB,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;IACxB,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;IACnB,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;CAC9B,CAAC;AAEF,SAAgB,cAAc,CAAC,CAAa;IAC1C,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,SAAgB,aAAa,CAAC,CAAa,EAAE,CAAa;IACxD,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,SAAgB,YAAY,CAAC,CAAa,EAAE,CAAa;IACvD,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED,SAAgB,aAAa,CAAC,GAAG,KAAmB;IAClD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,UAAU,CAAC,aAAa,CAAC;IACxD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC"}
|
package/dist/drift.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drift classification: typed states for value trajectories.
|
|
3
|
+
*
|
|
4
|
+
* Health tells you WHERE a value is. Drift tells you WHERE IT'S HEADED.
|
|
5
|
+
*/
|
|
6
|
+
import { Confidence } from './confidence.js';
|
|
7
|
+
import { Observation } from './observation.js';
|
|
8
|
+
export declare enum DriftState {
|
|
9
|
+
STABLE = "STABLE",
|
|
10
|
+
DRIFTING = "DRIFTING",
|
|
11
|
+
ACCELERATING = "ACCELERATING",
|
|
12
|
+
DECELERATING = "DECELERATING",
|
|
13
|
+
REVERTING = "REVERTING",
|
|
14
|
+
OSCILLATING = "OSCILLATING"
|
|
15
|
+
}
|
|
16
|
+
export declare enum DriftDirection {
|
|
17
|
+
IMPROVING = "IMPROVING",
|
|
18
|
+
WORSENING = "WORSENING",
|
|
19
|
+
NEUTRAL = "NEUTRAL"
|
|
20
|
+
}
|
|
21
|
+
export interface DriftClassification {
|
|
22
|
+
component: string;
|
|
23
|
+
state: DriftState;
|
|
24
|
+
direction: DriftDirection;
|
|
25
|
+
rate: number;
|
|
26
|
+
acceleration: number;
|
|
27
|
+
rSquared: number;
|
|
28
|
+
confidence: Confidence;
|
|
29
|
+
nSamples: number;
|
|
30
|
+
windowSeconds: number;
|
|
31
|
+
}
|
|
32
|
+
export declare function classifyDrift(observations: Observation[], options?: {
|
|
33
|
+
minSamples?: number;
|
|
34
|
+
noiseThreshold?: number;
|
|
35
|
+
}): DriftClassification | null;
|
|
36
|
+
//# sourceMappingURL=drift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift.d.ts","sourceRoot":"","sources":["../src/drift.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,oBAAY,UAAU;IACpB,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,YAAY,iBAAiB;IAC7B,YAAY,iBAAiB;IAC7B,SAAS,cAAc;IACvB,WAAW,gBAAgB;CAC5B;AAED,oBAAY,cAAc;IACxB,SAAS,cAAc;IACvB,SAAS,cAAc;IACvB,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,cAAc,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAmDD,wBAAgB,aAAa,CAC3B,YAAY,EAAE,WAAW,EAAE,EAC3B,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7D,mBAAmB,GAAG,IAAI,CAuE5B"}
|
package/dist/drift.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Drift classification: typed states for value trajectories.
|
|
4
|
+
*
|
|
5
|
+
* Health tells you WHERE a value is. Drift tells you WHERE IT'S HEADED.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.DriftDirection = exports.DriftState = void 0;
|
|
9
|
+
exports.classifyDrift = classifyDrift;
|
|
10
|
+
const confidence_js_1 = require("./confidence.js");
|
|
11
|
+
var DriftState;
|
|
12
|
+
(function (DriftState) {
|
|
13
|
+
DriftState["STABLE"] = "STABLE";
|
|
14
|
+
DriftState["DRIFTING"] = "DRIFTING";
|
|
15
|
+
DriftState["ACCELERATING"] = "ACCELERATING";
|
|
16
|
+
DriftState["DECELERATING"] = "DECELERATING";
|
|
17
|
+
DriftState["REVERTING"] = "REVERTING";
|
|
18
|
+
DriftState["OSCILLATING"] = "OSCILLATING";
|
|
19
|
+
})(DriftState || (exports.DriftState = DriftState = {}));
|
|
20
|
+
var DriftDirection;
|
|
21
|
+
(function (DriftDirection) {
|
|
22
|
+
DriftDirection["IMPROVING"] = "IMPROVING";
|
|
23
|
+
DriftDirection["WORSENING"] = "WORSENING";
|
|
24
|
+
DriftDirection["NEUTRAL"] = "NEUTRAL";
|
|
25
|
+
})(DriftDirection || (exports.DriftDirection = DriftDirection = {}));
|
|
26
|
+
// -----------------------------------------------------------------------
|
|
27
|
+
// Linear regression
|
|
28
|
+
// -----------------------------------------------------------------------
|
|
29
|
+
function linreg(xs, ys) {
|
|
30
|
+
const n = xs.length;
|
|
31
|
+
const mx = xs.reduce((a, b) => a + b, 0) / n;
|
|
32
|
+
const my = ys.reduce((a, b) => a + b, 0) / n;
|
|
33
|
+
let ssxx = 0, ssyy = 0, ssxy = 0;
|
|
34
|
+
for (let i = 0; i < n; i++) {
|
|
35
|
+
ssxx += (xs[i] - mx) ** 2;
|
|
36
|
+
ssyy += (ys[i] - my) ** 2;
|
|
37
|
+
ssxy += (xs[i] - mx) * (ys[i] - my);
|
|
38
|
+
}
|
|
39
|
+
if (ssxx === 0)
|
|
40
|
+
return { slope: 0, intercept: my, rSq: 0, se: 0 };
|
|
41
|
+
const slope = ssxy / ssxx;
|
|
42
|
+
const intercept = my - slope * mx;
|
|
43
|
+
let ssRes = 0;
|
|
44
|
+
for (let i = 0; i < n; i++)
|
|
45
|
+
ssRes += (ys[i] - (intercept + slope * xs[i])) ** 2;
|
|
46
|
+
const rSq = ssyy > 0 ? Math.max(1 - ssRes / ssyy, 0) : 0;
|
|
47
|
+
const se = ssxx > 0 ? Math.sqrt(ssRes / Math.max(n - 2, 1) / ssxx) : 0;
|
|
48
|
+
return { slope, intercept, rSq, se };
|
|
49
|
+
}
|
|
50
|
+
// -----------------------------------------------------------------------
|
|
51
|
+
// Oscillation detection
|
|
52
|
+
// -----------------------------------------------------------------------
|
|
53
|
+
function isOscillating(ys, residuals) {
|
|
54
|
+
if (ys.length < 5)
|
|
55
|
+
return false;
|
|
56
|
+
const maxRes = Math.max(...residuals.map(Math.abs));
|
|
57
|
+
const tol = 0.01 * maxRes;
|
|
58
|
+
let crossings = 0;
|
|
59
|
+
for (let i = 1; i < residuals.length; i++) {
|
|
60
|
+
if (residuals[i - 1] * residuals[i] < 0 &&
|
|
61
|
+
Math.abs(residuals[i - 1]) > tol && Math.abs(residuals[i]) > tol) {
|
|
62
|
+
crossings++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const amplitude = Math.max(...ys) - Math.min(...ys);
|
|
66
|
+
const meanAbs = ys.reduce((a, b) => a + Math.abs(b), 0) / ys.length;
|
|
67
|
+
const relAmp = amplitude / Math.max(meanAbs, 1e-10);
|
|
68
|
+
return crossings >= 2 && relAmp > 0.02;
|
|
69
|
+
}
|
|
70
|
+
// -----------------------------------------------------------------------
|
|
71
|
+
// Main classification
|
|
72
|
+
// -----------------------------------------------------------------------
|
|
73
|
+
function classifyDrift(observations, options = {}) {
|
|
74
|
+
const minSamples = options.minSamples ?? 3;
|
|
75
|
+
const noiseThreshold = options.noiseThreshold ?? 1.5;
|
|
76
|
+
const timed = observations
|
|
77
|
+
.filter(o => o.measuredAt != null)
|
|
78
|
+
.sort((a, b) => a.measuredAt.getTime() - b.measuredAt.getTime());
|
|
79
|
+
if (timed.length < minSamples)
|
|
80
|
+
return null;
|
|
81
|
+
const name = timed[0].name;
|
|
82
|
+
const higherIsBetter = timed[0].higherIsBetter;
|
|
83
|
+
const baseline = timed[0].baseline;
|
|
84
|
+
const t0 = timed[0].measuredAt.getTime();
|
|
85
|
+
const xs = timed.map(o => (o.measuredAt.getTime() - t0) / 1000);
|
|
86
|
+
const ys = timed.map(o => o.value);
|
|
87
|
+
const n = xs.length;
|
|
88
|
+
const window = xs[n - 1] - xs[0];
|
|
89
|
+
if (window === 0)
|
|
90
|
+
return null;
|
|
91
|
+
const { slope, intercept, rSq, se } = linreg(xs, ys);
|
|
92
|
+
const residuals = ys.map((y, i) => y - (intercept + slope * xs[i]));
|
|
93
|
+
const normSlope = higherIsBetter ? slope : -slope;
|
|
94
|
+
// Slope significance
|
|
95
|
+
const slopeSignificant = se === 0 ? Math.abs(slope) > 0 : Math.abs(slope) > noiseThreshold * se;
|
|
96
|
+
let state;
|
|
97
|
+
let direction;
|
|
98
|
+
if (!slopeSignificant) {
|
|
99
|
+
if (isOscillating(ys, residuals)) {
|
|
100
|
+
state = DriftState.OSCILLATING;
|
|
101
|
+
direction = DriftDirection.NEUTRAL;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
state = DriftState.STABLE;
|
|
105
|
+
direction = DriftDirection.NEUTRAL;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
direction = normSlope > 0 ? DriftDirection.IMPROVING : DriftDirection.WORSENING;
|
|
110
|
+
const firstVal = ys[0];
|
|
111
|
+
const currentVal = ys[n - 1];
|
|
112
|
+
const wasUnhealthy = higherIsBetter ? firstVal < baseline : firstVal > baseline;
|
|
113
|
+
const nowCloser = Math.abs(currentVal - baseline) < Math.abs(firstVal - baseline);
|
|
114
|
+
if (wasUnhealthy && nowCloser) {
|
|
115
|
+
state = DriftState.REVERTING;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
state = DriftState.DRIFTING;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Confidence
|
|
122
|
+
let confidence;
|
|
123
|
+
if (n >= 10 && rSq > 0.8)
|
|
124
|
+
confidence = confidence_js_1.Confidence.HIGH;
|
|
125
|
+
else if (n >= 5 && rSq > 0.5)
|
|
126
|
+
confidence = confidence_js_1.Confidence.MODERATE;
|
|
127
|
+
else
|
|
128
|
+
confidence = confidence_js_1.Confidence.LOW;
|
|
129
|
+
return {
|
|
130
|
+
component: name,
|
|
131
|
+
state,
|
|
132
|
+
direction,
|
|
133
|
+
rate: normSlope,
|
|
134
|
+
acceleration: 0,
|
|
135
|
+
rSquared: rSq,
|
|
136
|
+
confidence,
|
|
137
|
+
nSamples: n,
|
|
138
|
+
windowSeconds: window,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=drift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift.js","sourceRoot":"","sources":["../src/drift.ts"],"names":[],"mappings":"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"}
|
package/dist/health.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health classification: typed states for any monitored component.
|
|
3
|
+
*
|
|
4
|
+
* Maps a scalar measurement against thresholds into a typed health predicate.
|
|
5
|
+
* Supports both polarities: higher_is_better and lower_is_better.
|
|
6
|
+
*/
|
|
7
|
+
import { Confidence } from './confidence.js';
|
|
8
|
+
export declare enum Health {
|
|
9
|
+
INTACT = "INTACT",
|
|
10
|
+
DEGRADED = "DEGRADED",
|
|
11
|
+
ABLATED = "ABLATED",
|
|
12
|
+
RECOVERING = "RECOVERING",
|
|
13
|
+
OOD = "OOD"
|
|
14
|
+
}
|
|
15
|
+
export declare const SEVERITY: Record<Health, number>;
|
|
16
|
+
export interface Thresholds {
|
|
17
|
+
intact: number;
|
|
18
|
+
ablated: number;
|
|
19
|
+
higherIsBetter?: boolean;
|
|
20
|
+
activeMin?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function createThresholds(intact: number, ablated: number, higherIsBetter?: boolean, activeMin?: number): Thresholds;
|
|
23
|
+
export declare function isIntact(value: number, t: Thresholds): boolean;
|
|
24
|
+
export declare function isAblated(value: number, t: Thresholds): boolean;
|
|
25
|
+
export declare function classify(value: number, confidence: Confidence, thresholds: Thresholds, correcting?: boolean): Health;
|
|
26
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,oBAAY,MAAM;IAChB,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,UAAU,eAAe;IACzB,GAAG,QAAQ;CACZ;AAED,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAM3C,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,cAAc,UAAO,EACrB,SAAS,SAAO,GACf,UAAU,CAQZ;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAE9D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAE/D;AAED,wBAAgB,QAAQ,CACtB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,UAAU,EACtB,UAAU,UAAQ,GACjB,MAAM,CAKR"}
|
package/dist/health.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Health classification: typed states for any monitored component.
|
|
4
|
+
*
|
|
5
|
+
* Maps a scalar measurement against thresholds into a typed health predicate.
|
|
6
|
+
* Supports both polarities: higher_is_better and lower_is_better.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.SEVERITY = exports.Health = void 0;
|
|
10
|
+
exports.createThresholds = createThresholds;
|
|
11
|
+
exports.isIntact = isIntact;
|
|
12
|
+
exports.isAblated = isAblated;
|
|
13
|
+
exports.classify = classify;
|
|
14
|
+
const confidence_js_1 = require("./confidence.js");
|
|
15
|
+
var Health;
|
|
16
|
+
(function (Health) {
|
|
17
|
+
Health["INTACT"] = "INTACT";
|
|
18
|
+
Health["DEGRADED"] = "DEGRADED";
|
|
19
|
+
Health["ABLATED"] = "ABLATED";
|
|
20
|
+
Health["RECOVERING"] = "RECOVERING";
|
|
21
|
+
Health["OOD"] = "OOD";
|
|
22
|
+
})(Health || (exports.Health = Health = {}));
|
|
23
|
+
exports.SEVERITY = {
|
|
24
|
+
[Health.INTACT]: 0,
|
|
25
|
+
[Health.RECOVERING]: 1,
|
|
26
|
+
[Health.DEGRADED]: 2,
|
|
27
|
+
[Health.ABLATED]: 3,
|
|
28
|
+
[Health.OOD]: 4,
|
|
29
|
+
};
|
|
30
|
+
function createThresholds(intact, ablated, higherIsBetter = true, activeMin = 0.05) {
|
|
31
|
+
if (higherIsBetter && ablated > intact) {
|
|
32
|
+
throw new Error(`higherIsBetter but ablated (${ablated}) > intact (${intact})`);
|
|
33
|
+
}
|
|
34
|
+
if (!higherIsBetter && ablated < intact) {
|
|
35
|
+
throw new Error(`lowerIsBetter but ablated (${ablated}) < intact (${intact})`);
|
|
36
|
+
}
|
|
37
|
+
return { intact, ablated, higherIsBetter, activeMin };
|
|
38
|
+
}
|
|
39
|
+
function isIntact(value, t) {
|
|
40
|
+
return t.higherIsBetter !== false ? value >= t.intact : value <= t.intact;
|
|
41
|
+
}
|
|
42
|
+
function isAblated(value, t) {
|
|
43
|
+
return t.higherIsBetter !== false ? value < t.ablated : value > t.ablated;
|
|
44
|
+
}
|
|
45
|
+
function classify(value, confidence, thresholds, correcting = false) {
|
|
46
|
+
if (confidence === confidence_js_1.Confidence.INDETERMINATE)
|
|
47
|
+
return Health.OOD;
|
|
48
|
+
if (isIntact(value, thresholds))
|
|
49
|
+
return Health.INTACT;
|
|
50
|
+
if (isAblated(value, thresholds))
|
|
51
|
+
return correcting ? Health.RECOVERING : Health.ABLATED;
|
|
52
|
+
return correcting ? Health.RECOVERING : Health.DEGRADED;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AA2BH,4CAaC;AAED,4BAEC;AAED,8BAEC;AAED,4BAUC;AA1DD,mDAA6C;AAE7C,IAAY,MAMX;AAND,WAAY,MAAM;IAChB,2BAAiB,CAAA;IACjB,+BAAqB,CAAA;IACrB,6BAAmB,CAAA;IACnB,mCAAyB,CAAA;IACzB,qBAAW,CAAA;AACb,CAAC,EANW,MAAM,sBAAN,MAAM,QAMjB;AAEY,QAAA,QAAQ,GAA2B;IAC9C,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IAClB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;IACtB,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;IACpB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IACnB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;CAChB,CAAC;AASF,SAAgB,gBAAgB,CAC9B,MAAc,EACd,OAAe,EACf,cAAc,GAAG,IAAI,EACrB,SAAS,GAAG,IAAI;IAEhB,IAAI,cAAc,IAAI,OAAO,GAAG,MAAM,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,+BAA+B,OAAO,eAAe,MAAM,GAAG,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC,cAAc,IAAI,OAAO,GAAG,MAAM,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,8BAA8B,OAAO,eAAe,MAAM,GAAG,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC;AACxD,CAAC;AAED,SAAgB,QAAQ,CAAC,KAAa,EAAE,CAAa;IACnD,OAAO,CAAC,CAAC,cAAc,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;AAC5E,CAAC;AAED,SAAgB,SAAS,CAAC,KAAa,EAAE,CAAa;IACpD,OAAO,CAAC,CAAC,cAAc,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC;AAC5E,CAAC;AAED,SAAgB,QAAQ,CACtB,KAAa,EACb,UAAsB,EACtB,UAAsB,EACtB,UAAU,GAAG,KAAK;IAElB,IAAI,UAAU,KAAK,0BAAU,CAAC,aAAa;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IAC/D,IAAI,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC;IACtD,IAAI,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IACzF,OAAO,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;AAC1D,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* margin — Typed health classification for systems that measure things.
|
|
3
|
+
*
|
|
4
|
+
* TypeScript port of the Python margin library.
|
|
5
|
+
* Zero dependencies. Pure TypeScript.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2026 Cope Labs LLC. MIT License.
|
|
8
|
+
*/
|
|
9
|
+
export { Confidence, confidenceRank, confidenceGte, confidenceLt, minConfidence } from './confidence.js';
|
|
10
|
+
export { Health, SEVERITY, Thresholds, createThresholds, isIntact, isAblated, classify, } from './health.js';
|
|
11
|
+
export { Op, Observation, Correction, Expression, observationSigma, observationToAtom, observationToDict, observationFromDict, correctionIsActive, createExpression, healthOf, degraded, expressionToString, expressionToDict, Parser, } from './observation.js';
|
|
12
|
+
export { DriftState, DriftDirection, DriftClassification, classifyDrift, } from './drift.js';
|
|
13
|
+
export { AnomalyState, ANOMALY_SEVERITY, AnomalyClassification, classifyAnomaly, } from './anomaly.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEzG,OAAO,EACL,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,gBAAgB,EAC9C,QAAQ,EAAE,SAAS,EAAE,QAAQ,GAC9B,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EACvC,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAC3E,kBAAkB,EAClB,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAC1E,MAAM,GACP,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,UAAU,EAAE,cAAc,EAAE,mBAAmB,EAC/C,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,YAAY,EAAE,gBAAgB,EAAE,qBAAqB,EACrD,eAAe,GAChB,MAAM,cAAc,CAAC"}
|