@mongrov/analytics 0.6.0 → 0.7.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/README.md +23 -10
- package/UPGRADING.md +370 -0
- package/dist/core/device-events.d.ts +14 -0
- package/dist/core/device-events.d.ts.map +1 -0
- package/dist/core/device-events.js +13 -0
- package/dist/core/device-events.js.map +1 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/extensions.d.ts +21 -4
- package/dist/core/extensions.d.ts.map +1 -1
- package/dist/core/extensions.js +21 -4
- package/dist/core/extensions.js.map +1 -1
- package/dist/core/factory.d.ts.map +1 -1
- package/dist/core/factory.js +125 -23
- package/dist/core/factory.js.map +1 -1
- package/dist/core/hooks.js +1 -1
- package/dist/core/hooks.js.map +1 -1
- package/dist/core/index.d.ts +11 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/insight.d.ts +44 -0
- package/dist/core/insight.d.ts.map +1 -0
- package/dist/core/insight.js +62 -0
- package/dist/core/insight.js.map +1 -0
- package/dist/core/metric_metadata.d.ts +55 -0
- package/dist/core/metric_metadata.d.ts.map +1 -1
- package/dist/core/metric_metadata.js +35 -7
- package/dist/core/metric_metadata.js.map +1 -1
- package/dist/core/migrations.d.ts.map +1 -1
- package/dist/core/migrations.js +64 -2
- package/dist/core/migrations.js.map +1 -1
- package/dist/core/retention.d.ts +44 -13
- package/dist/core/retention.d.ts.map +1 -1
- package/dist/core/retention.js +49 -22
- package/dist/core/retention.js.map +1 -1
- package/dist/core/schemas.d.ts +73 -2
- package/dist/core/schemas.d.ts.map +1 -1
- package/dist/core/schemas.js +154 -3
- package/dist/core/schemas.js.map +1 -1
- package/dist/core/table_metadata.d.ts.map +1 -1
- package/dist/core/table_metadata.js +3 -0
- package/dist/core/table_metadata.js.map +1 -1
- package/dist/core/types.d.ts +45 -10
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/warehouse.d.ts +28 -0
- package/dist/core/warehouse.d.ts.map +1 -1
- package/dist/core/warehouse.js +44 -0
- package/dist/core/warehouse.js.map +1 -1
- package/dist/core/watermark-cache.d.ts +75 -0
- package/dist/core/watermark-cache.d.ts.map +1 -0
- package/dist/core/watermark-cache.js +0 -0
- package/dist/core/watermark-cache.js.map +1 -0
- package/dist/rules/__fakes__/fakeEngine.d.ts +2 -0
- package/dist/rules/__fakes__/fakeEngine.d.ts.map +1 -1
- package/dist/rules/__fakes__/fakeEngine.js +4 -0
- package/dist/rules/__fakes__/fakeEngine.js.map +1 -1
- package/dist/rules/baseline-reader.d.ts +55 -0
- package/dist/rules/baseline-reader.d.ts.map +1 -0
- package/dist/rules/baseline-reader.js +111 -0
- package/dist/rules/baseline-reader.js.map +1 -0
- package/dist/rules/compiler.d.ts +36 -1
- package/dist/rules/compiler.d.ts.map +1 -1
- package/dist/rules/compiler.js +185 -39
- package/dist/rules/compiler.js.map +1 -1
- package/dist/rules/defaults/luminx.d.ts.map +1 -1
- package/dist/rules/defaults/luminx.js +8 -0
- package/dist/rules/defaults/luminx.js.map +1 -1
- package/dist/rules/defaults/viva.d.ts.map +1 -1
- package/dist/rules/defaults/viva.js +7 -0
- package/dist/rules/defaults/viva.js.map +1 -1
- package/dist/rules/defaults/yogaring.d.ts.map +1 -1
- package/dist/rules/defaults/yogaring.js +7 -0
- package/dist/rules/defaults/yogaring.js.map +1 -1
- package/dist/rules/defaults/ziva.d.ts.map +1 -1
- package/dist/rules/defaults/ziva.js +100 -24
- package/dist/rules/defaults/ziva.js.map +1 -1
- package/dist/rules/evaluator.d.ts +14 -3
- package/dist/rules/evaluator.d.ts.map +1 -1
- package/dist/rules/evaluator.js +157 -5
- package/dist/rules/evaluator.js.map +1 -1
- package/dist/rules/factory.d.ts.map +1 -1
- package/dist/rules/factory.js +5 -1
- package/dist/rules/factory.js.map +1 -1
- package/dist/rules/index.d.ts +2 -1
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +2 -1
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/schema.d.ts +75 -4
- package/dist/rules/schema.d.ts.map +1 -1
- package/dist/rules/schema.js +44 -0
- package/dist/rules/schema.js.map +1 -1
- package/dist/rules/types.d.ts +16 -1
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/validator.d.ts.map +1 -1
- package/dist/rules/validator.js +56 -0
- package/dist/rules/validator.js.map +1 -1
- package/dist/sync/baseline-compute.d.ts +63 -0
- package/dist/sync/baseline-compute.d.ts.map +1 -0
- package/dist/sync/baseline-compute.js +211 -0
- package/dist/sync/baseline-compute.js.map +1 -0
- package/dist/sync/events.d.ts +19 -5
- package/dist/sync/events.d.ts.map +1 -1
- package/dist/sync/events.js +17 -0
- package/dist/sync/events.js.map +1 -1
- package/dist/sync/factory.d.ts +25 -4
- package/dist/sync/factory.d.ts.map +1 -1
- package/dist/sync/factory.js +108 -15
- package/dist/sync/factory.js.map +1 -1
- package/dist/sync/fetcher.d.ts +15 -2
- package/dist/sync/fetcher.d.ts.map +1 -1
- package/dist/sync/fetcher.js +43 -7
- package/dist/sync/fetcher.js.map +1 -1
- package/dist/sync/flusher.d.ts +52 -2
- package/dist/sync/flusher.d.ts.map +1 -1
- package/dist/sync/flusher.js +78 -9
- package/dist/sync/flusher.js.map +1 -1
- package/dist/sync/index.d.ts +3 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/mapper/activity.d.ts.map +1 -1
- package/dist/sync/mapper/activity.js +2 -1
- package/dist/sync/mapper/activity.js.map +1 -1
- package/dist/sync/mapper/ring-config.js +1 -1
- package/dist/sync/mapper/ring-config.js.map +1 -1
- package/dist/sync/mapper/schema.d.ts +411 -0
- package/dist/sync/mapper/schema.d.ts.map +1 -0
- package/dist/sync/mapper/schema.js +128 -0
- package/dist/sync/mapper/schema.js.map +1 -0
- package/dist/sync/mapper/sleep.d.ts +58 -13
- package/dist/sync/mapper/sleep.d.ts.map +1 -1
- package/dist/sync/mapper/sleep.js +141 -44
- package/dist/sync/mapper/sleep.js.map +1 -1
- package/dist/sync/mapper/types.d.ts +66 -10
- package/dist/sync/mapper/types.d.ts.map +1 -1
- package/dist/sync/scheduler.d.ts +7 -0
- package/dist/sync/scheduler.d.ts.map +1 -1
- package/dist/sync/scheduler.js +14 -0
- package/dist/sync/scheduler.js.map +1 -1
- package/dist/tools/__fakes__/engine.d.ts +2 -0
- package/dist/tools/__fakes__/engine.d.ts.map +1 -1
- package/dist/tools/__fakes__/engine.js +10 -0
- package/dist/tools/__fakes__/engine.js.map +1 -1
- package/dist/tools/authorize.d.ts +19 -12
- package/dist/tools/authorize.d.ts.map +1 -1
- package/dist/tools/authorize.js +31 -40
- package/dist/tools/authorize.js.map +1 -1
- package/dist/tools/factory.d.ts +2 -0
- package/dist/tools/factory.d.ts.map +1 -1
- package/dist/tools/factory.js +12 -1
- package/dist/tools/factory.js.map +1 -1
- package/dist/tools/formatters/copy-guidelines.d.ts +57 -0
- package/dist/tools/formatters/copy-guidelines.d.ts.map +1 -0
- package/dist/tools/formatters/copy-guidelines.js +110 -0
- package/dist/tools/formatters/copy-guidelines.js.map +1 -0
- package/dist/tools/formatters/index.d.ts +14 -0
- package/dist/tools/formatters/index.d.ts.map +1 -0
- package/dist/tools/formatters/index.js +14 -0
- package/dist/tools/formatters/index.js.map +1 -0
- package/dist/tools/formatters/spo2.d.ts +21 -0
- package/dist/tools/formatters/spo2.d.ts.map +1 -0
- package/dist/tools/formatters/spo2.js +79 -0
- package/dist/tools/formatters/spo2.js.map +1 -0
- package/dist/tools/{formatters.d.ts → formatters/text.d.ts} +1 -1
- package/dist/tools/formatters/text.d.ts.map +1 -0
- package/dist/tools/{formatters.js → formatters/text.js} +1 -1
- package/dist/tools/formatters/text.js.map +1 -0
- package/dist/tools/impls/activity.d.ts +2 -2
- package/dist/tools/impls/activity.js +7 -3
- package/dist/tools/impls/activity.js.map +1 -1
- package/dist/tools/impls/anomaly.d.ts +2 -2
- package/dist/tools/impls/anomaly.js +8 -4
- package/dist/tools/impls/anomaly.js.map +1 -1
- package/dist/tools/impls/compare.d.ts +2 -2
- package/dist/tools/impls/compare.js +8 -4
- package/dist/tools/impls/compare.js.map +1 -1
- package/dist/tools/impls/hrv.d.ts +2 -2
- package/dist/tools/impls/hrv.js +6 -2
- package/dist/tools/impls/hrv.js.map +1 -1
- package/dist/tools/impls/insights.d.ts +4 -4
- package/dist/tools/impls/insights.d.ts.map +1 -1
- package/dist/tools/impls/insights.js +10 -3
- package/dist/tools/impls/insights.js.map +1 -1
- package/dist/tools/impls/sleep.d.ts +2 -2
- package/dist/tools/impls/sleep.js +6 -2
- package/dist/tools/impls/sleep.js.map +1 -1
- package/dist/tools/impls/spo2.d.ts +52 -0
- package/dist/tools/impls/spo2.d.ts.map +1 -0
- package/dist/tools/impls/spo2.js +65 -0
- package/dist/tools/impls/spo2.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp/guard.d.ts +28 -11
- package/dist/tools/mcp/guard.d.ts.map +1 -1
- package/dist/tools/mcp/guard.js +45 -15
- package/dist/tools/mcp/guard.js.map +1 -1
- package/dist/tools/mcp/index.d.ts +1 -1
- package/dist/tools/mcp/index.d.ts.map +1 -1
- package/dist/tools/mcp/index.js +1 -1
- package/dist/tools/mcp/index.js.map +1 -1
- package/dist/tools/mcp/server.d.ts.map +1 -1
- package/dist/tools/mcp/server.js +4 -0
- package/dist/tools/mcp/server.js.map +1 -1
- package/dist/tools/mcp/transports/http.d.ts.map +1 -1
- package/dist/tools/mcp/transports/http.js +4 -0
- package/dist/tools/mcp/transports/http.js.map +1 -1
- package/package.json +8 -5
- package/dist/tools/formatters.d.ts.map +0 -1
- package/dist/tools/formatters.js.map +0 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline reader (Sprint 5 T-24).
|
|
3
|
+
*
|
|
4
|
+
* Rules with `baseline_percent` / `baseline_stddev` targets need the user's
|
|
5
|
+
* usual range. The scheduled job in `analytics-sync/baseline-compute.ts`
|
|
6
|
+
* writes it to `user_baseline` after each sync cycle — but a rule can fire
|
|
7
|
+
* before the first compute has run, so this reader falls back to computing
|
|
8
|
+
* on read.
|
|
9
|
+
*
|
|
10
|
+
* The fallback uses the **same day-first pattern** as the scheduled job.
|
|
11
|
+
* That consistency is the whole point of the module: if the on-read path
|
|
12
|
+
* quantiled raw readings while the scheduled path quantiled daily values,
|
|
13
|
+
* the same rule would fire or not depending purely on whether a sync cycle
|
|
14
|
+
* had happened yet — a heisenbug in a health alert.
|
|
15
|
+
*
|
|
16
|
+
* Results are cached for the duration of one evaluation batch. Within a
|
|
17
|
+
* batch, every rule x user combination reads a consistent baseline;
|
|
18
|
+
* across batches, fresh reads pick up the latest compute.
|
|
19
|
+
*/
|
|
20
|
+
import { type BaselineWindowDays, type MetricId } from '../core/metric_metadata';
|
|
21
|
+
import type { AnalyticsEngine } from '../core/types';
|
|
22
|
+
import type { RulesLogger } from './types';
|
|
23
|
+
export interface Baseline {
|
|
24
|
+
p05: number | null;
|
|
25
|
+
p10: number | null;
|
|
26
|
+
p50: number | null;
|
|
27
|
+
p90: number | null;
|
|
28
|
+
p95: number | null;
|
|
29
|
+
mean: number | null;
|
|
30
|
+
stddev: number | null;
|
|
31
|
+
/** Number of DAYS the baseline is built from, never readings. */
|
|
32
|
+
sampleCount: number;
|
|
33
|
+
/** True when this came from an on-read compute, not the stored table. */
|
|
34
|
+
computedOnRead: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface BaselineReaderConfig {
|
|
37
|
+
analytics: AnalyticsEngine;
|
|
38
|
+
logger?: RulesLogger;
|
|
39
|
+
/**
|
|
40
|
+
* IANA zone for the on-read fallback's day bucketing. Only the fallback
|
|
41
|
+
* needs it — stored rows were already bucketed at compute time.
|
|
42
|
+
*/
|
|
43
|
+
timezone?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface BaselineReader {
|
|
46
|
+
/** Null when the user has fewer than `BASELINE_MIN_DAYS` days of data. */
|
|
47
|
+
getBaseline(userId: string, metric: MetricId, windowDays: BaselineWindowDays, ctx: {
|
|
48
|
+
brand: string;
|
|
49
|
+
familyId: string;
|
|
50
|
+
}): Promise<Baseline | null>;
|
|
51
|
+
/** Drop the per-batch cache. Called at the start of each evaluation pass. */
|
|
52
|
+
resetCache(): void;
|
|
53
|
+
}
|
|
54
|
+
export declare function createBaselineReader(config: BaselineReaderConfig): BaselineReader;
|
|
55
|
+
//# sourceMappingURL=baseline-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline-reader.d.ts","sourceRoot":"","sources":["../../src/rules/baseline-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE1C,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,eAAe,CAAA;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,WAAW,CACT,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,EAChB,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACvC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IAC3B,6EAA6E;IAC7E,UAAU,IAAI,IAAI,CAAA;CACnB;AAaD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,oBAAoB,GAC3B,cAAc,CAwGhB"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline reader (Sprint 5 T-24).
|
|
3
|
+
*
|
|
4
|
+
* Rules with `baseline_percent` / `baseline_stddev` targets need the user's
|
|
5
|
+
* usual range. The scheduled job in `analytics-sync/baseline-compute.ts`
|
|
6
|
+
* writes it to `user_baseline` after each sync cycle — but a rule can fire
|
|
7
|
+
* before the first compute has run, so this reader falls back to computing
|
|
8
|
+
* on read.
|
|
9
|
+
*
|
|
10
|
+
* The fallback uses the **same day-first pattern** as the scheduled job.
|
|
11
|
+
* That consistency is the whole point of the module: if the on-read path
|
|
12
|
+
* quantiled raw readings while the scheduled path quantiled daily values,
|
|
13
|
+
* the same rule would fire or not depending purely on whether a sync cycle
|
|
14
|
+
* had happened yet — a heisenbug in a health alert.
|
|
15
|
+
*
|
|
16
|
+
* Results are cached for the duration of one evaluation batch. Within a
|
|
17
|
+
* batch, every rule x user combination reads a consistent baseline;
|
|
18
|
+
* across batches, fresh reads pick up the latest compute.
|
|
19
|
+
*/
|
|
20
|
+
import { buildBaselineSql } from '../sync/baseline-compute';
|
|
21
|
+
import { BASELINE_MIN_DAYS, } from '../core/metric_metadata';
|
|
22
|
+
export function createBaselineReader(config) {
|
|
23
|
+
const { analytics, logger } = config;
|
|
24
|
+
const cache = new Map();
|
|
25
|
+
function key(userId, metric, windowDays, brand, familyId) {
|
|
26
|
+
return `${brand} ${familyId} ${userId} ${metric} ${windowDays}`;
|
|
27
|
+
}
|
|
28
|
+
async function readStored(userId, metric, windowDays, ctx) {
|
|
29
|
+
const rows = await analytics.execute(`SELECT p05, p10, p50, p90, p95, mean, stddev, sample_count
|
|
30
|
+
FROM user_baseline
|
|
31
|
+
WHERE brand = $brand AND family_id = $familyId AND user_id = $userId
|
|
32
|
+
AND metric = $metric AND window_days = $windowDays
|
|
33
|
+
LIMIT 1`, {
|
|
34
|
+
brand: ctx.brand,
|
|
35
|
+
familyId: ctx.familyId,
|
|
36
|
+
userId,
|
|
37
|
+
metric,
|
|
38
|
+
windowDays,
|
|
39
|
+
});
|
|
40
|
+
const row = rows[0];
|
|
41
|
+
if (!row || row.sample_count === null || row.sample_count < BASELINE_MIN_DAYS) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return { ...toBaseline(row), computedOnRead: false };
|
|
45
|
+
}
|
|
46
|
+
async function computeOnRead(userId, metric, windowDays, ctx) {
|
|
47
|
+
// Identical SQL to the scheduled job — imported, not reimplemented, so
|
|
48
|
+
// the two paths cannot drift.
|
|
49
|
+
const rows = await analytics.execute(buildBaselineSql(metric, windowDays), {
|
|
50
|
+
userId,
|
|
51
|
+
brand: ctx.brand,
|
|
52
|
+
familyId: ctx.familyId,
|
|
53
|
+
tz: config.timezone ?? 'UTC',
|
|
54
|
+
windowDays,
|
|
55
|
+
});
|
|
56
|
+
const row = rows[0];
|
|
57
|
+
if (!row || row.sample_count === null || row.sample_count < BASELINE_MIN_DAYS) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return { ...toBaseline(row), computedOnRead: true };
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
resetCache() {
|
|
64
|
+
cache.clear();
|
|
65
|
+
},
|
|
66
|
+
async getBaseline(userId, metric, windowDays, ctx) {
|
|
67
|
+
const k = key(userId, metric, windowDays, ctx.brand, ctx.familyId);
|
|
68
|
+
if (cache.has(k))
|
|
69
|
+
return cache.get(k) ?? null;
|
|
70
|
+
let result = null;
|
|
71
|
+
try {
|
|
72
|
+
result = await readStored(userId, metric, windowDays, ctx);
|
|
73
|
+
if (result === null) {
|
|
74
|
+
result = await computeOnRead(userId, metric, windowDays, ctx);
|
|
75
|
+
if (result !== null) {
|
|
76
|
+
logger?.debug('rules.baseline: stored row absent, computed on read', {
|
|
77
|
+
userId, metric, windowDays,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// A missing baseline is a normal state (new user). A failed read is
|
|
84
|
+
// not, but it must not take down the evaluation pass — the rule
|
|
85
|
+
// simply doesn't fire this cycle.
|
|
86
|
+
logger?.warn('rules.baseline: read failed', {
|
|
87
|
+
userId,
|
|
88
|
+
metric,
|
|
89
|
+
windowDays,
|
|
90
|
+
err: err instanceof Error ? err.message : String(err),
|
|
91
|
+
});
|
|
92
|
+
result = null;
|
|
93
|
+
}
|
|
94
|
+
cache.set(k, result);
|
|
95
|
+
return result;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function toBaseline(row) {
|
|
100
|
+
return {
|
|
101
|
+
p05: row.p05,
|
|
102
|
+
p10: row.p10,
|
|
103
|
+
p50: row.p50,
|
|
104
|
+
p90: row.p90,
|
|
105
|
+
p95: row.p95,
|
|
106
|
+
mean: row.mean,
|
|
107
|
+
stddev: row.stddev,
|
|
108
|
+
sampleCount: row.sample_count,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=baseline-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline-reader.js","sourceRoot":"","sources":["../../src/rules/baseline-reader.ts"],"names":[],"mappings":"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"}
|
package/dist/rules/compiler.d.ts
CHANGED
|
@@ -17,11 +17,46 @@
|
|
|
17
17
|
* observed_value NUMERIC, threshold_value NUMERIC
|
|
18
18
|
* so the evaluator can build `RuleViolation` without dispatching per
|
|
19
19
|
* target type.
|
|
20
|
+
*
|
|
21
|
+
* ## v0.2.0 additions (Sprint 5 §4)
|
|
22
|
+
*
|
|
23
|
+
* - **`context`** (T-17) — an INNER JOIN, not a post-filter. "SpO₂ during
|
|
24
|
+
* sleep" must aggregate over sleep samples only; computing over
|
|
25
|
+
* everything and labelling the result would report a number nobody asked
|
|
26
|
+
* for.
|
|
27
|
+
* - **`consecutive: n`** (T-18) — run-length detection via gaps-and-islands.
|
|
28
|
+
* `n <= 1` stays on the aggregate path, because "any single breach" is
|
|
29
|
+
* already expressible as `aggregation: 'min'` + `less_than`.
|
|
30
|
+
* - **`target.type: 'user_setting'`** (T-19) — emits a `$userSettingValue`
|
|
31
|
+
* placeholder the evaluator binds from KVStore at eval time. The compiled
|
|
32
|
+
* SQL is threshold-agnostic and therefore cacheable across users.
|
|
33
|
+
* - **Views** (T-20) — all generated SQL reads `v_{table}`, never
|
|
34
|
+
* `local.{table}` or `r2.default.{table}` (principle 19).
|
|
20
35
|
*/
|
|
21
36
|
import type { CompiledRule } from './types';
|
|
22
|
-
import { type Rule } from './schema';
|
|
37
|
+
import { type Rule, type RuleContext } from './schema';
|
|
23
38
|
/** Sanitize an identifier for direct interpolation. */
|
|
24
39
|
export declare function sanitizeIdent(raw: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Union-view name for a table (T-20, principle 19). Reading the view is
|
|
42
|
+
* what makes a rule see rows flushed locally but not yet pushed to R2 —
|
|
43
|
+
* i.e. this morning's sync, which is exactly the data a rule fires on.
|
|
44
|
+
*/
|
|
45
|
+
export declare function viewFor(table: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* T-17 — INNER JOIN restricting samples to a physiological context.
|
|
48
|
+
*
|
|
49
|
+
* Aliased `m` for the metric relation throughout. Both joins carry the
|
|
50
|
+
* full tenant triple, not just `user_id`: a join on user alone would match
|
|
51
|
+
* another brand's sleep sessions for the same person on a multi-brand
|
|
52
|
+
* install.
|
|
53
|
+
*/
|
|
54
|
+
export declare function emitContextJoin(context: RuleContext): string;
|
|
55
|
+
/**
|
|
56
|
+
* Placeholder the evaluator binds from KVStore for `user_setting` targets
|
|
57
|
+
* (T-19/T-23). Exported so the evaluator and its tests agree on the name.
|
|
58
|
+
*/
|
|
59
|
+
export declare const USER_SETTING_PARAM = "userSettingValue";
|
|
25
60
|
/** Compile a validated rule to a `CompiledRule`. */
|
|
26
61
|
export declare function compileRule(rule: Rule): CompiledRule;
|
|
27
62
|
//# sourceMappingURL=compiler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../src/rules/compiler.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../src/rules/compiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAGL,KAAK,IAAI,EACT,KAAK,WAAW,EAIjB,MAAM,UAAU,CAAA;AAEjB,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AA6CD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAsB5D;AAiBD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,qBAAqB,CAAA;AAEpD,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,CA8CpD"}
|
package/dist/rules/compiler.js
CHANGED
|
@@ -17,6 +17,21 @@
|
|
|
17
17
|
* observed_value NUMERIC, threshold_value NUMERIC
|
|
18
18
|
* so the evaluator can build `RuleViolation` without dispatching per
|
|
19
19
|
* target type.
|
|
20
|
+
*
|
|
21
|
+
* ## v0.2.0 additions (Sprint 5 §4)
|
|
22
|
+
*
|
|
23
|
+
* - **`context`** (T-17) — an INNER JOIN, not a post-filter. "SpO₂ during
|
|
24
|
+
* sleep" must aggregate over sleep samples only; computing over
|
|
25
|
+
* everything and labelling the result would report a number nobody asked
|
|
26
|
+
* for.
|
|
27
|
+
* - **`consecutive: n`** (T-18) — run-length detection via gaps-and-islands.
|
|
28
|
+
* `n <= 1` stays on the aggregate path, because "any single breach" is
|
|
29
|
+
* already expressible as `aggregation: 'min'` + `less_than`.
|
|
30
|
+
* - **`target.type: 'user_setting'`** (T-19) — emits a `$userSettingValue`
|
|
31
|
+
* placeholder the evaluator binds from KVStore at eval time. The compiled
|
|
32
|
+
* SQL is threshold-agnostic and therefore cacheable across users.
|
|
33
|
+
* - **Views** (T-20) — all generated SQL reads `v_{table}`, never
|
|
34
|
+
* `local.{table}` or `r2.default.{table}` (principle 19).
|
|
20
35
|
*/
|
|
21
36
|
import { METRIC_METADATA } from '../core/metric_metadata';
|
|
22
37
|
import { RuleValidationError, } from './schema';
|
|
@@ -24,6 +39,14 @@ import { RuleValidationError, } from './schema';
|
|
|
24
39
|
export function sanitizeIdent(raw) {
|
|
25
40
|
return raw.replace(/[^A-Za-z0-9_]/g, '_');
|
|
26
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Union-view name for a table (T-20, principle 19). Reading the view is
|
|
44
|
+
* what makes a rule see rows flushed locally but not yet pushed to R2 —
|
|
45
|
+
* i.e. this morning's sync, which is exactly the data a rule fires on.
|
|
46
|
+
*/
|
|
47
|
+
export function viewFor(table) {
|
|
48
|
+
return `v_${sanitizeIdent(table)}`;
|
|
49
|
+
}
|
|
27
50
|
/** Timestamp column per table — `sleep_session` uses `ts_start`. */
|
|
28
51
|
function tsColumn(table) {
|
|
29
52
|
return table === 'sleep_session' ? 'ts_start' : 'ts';
|
|
@@ -63,6 +86,37 @@ function compareClause(compare, lhs, rhs) {
|
|
|
63
86
|
case 'between': throw new Error('between handled per-target');
|
|
64
87
|
}
|
|
65
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* T-17 — INNER JOIN restricting samples to a physiological context.
|
|
91
|
+
*
|
|
92
|
+
* Aliased `m` for the metric relation throughout. Both joins carry the
|
|
93
|
+
* full tenant triple, not just `user_id`: a join on user alone would match
|
|
94
|
+
* another brand's sleep sessions for the same person on a multi-brand
|
|
95
|
+
* install.
|
|
96
|
+
*/
|
|
97
|
+
export function emitContextJoin(context) {
|
|
98
|
+
switch (context) {
|
|
99
|
+
case 'any':
|
|
100
|
+
return '';
|
|
101
|
+
case 'asleep':
|
|
102
|
+
return `
|
|
103
|
+
INNER JOIN ${viewFor('sleep_session')} s
|
|
104
|
+
ON s.user_id = m.user_id
|
|
105
|
+
AND s.brand = m.brand
|
|
106
|
+
AND s.family_id = m.family_id
|
|
107
|
+
AND m.ts BETWEEN s.ts_start AND s.ts_end`;
|
|
108
|
+
case 'resting':
|
|
109
|
+
// Activity rows are 1-minute after unnest, so a sample is "resting"
|
|
110
|
+
// when its own minute recorded zero steps.
|
|
111
|
+
return `
|
|
112
|
+
INNER JOIN ${viewFor('activity')} a
|
|
113
|
+
ON a.user_id = m.user_id
|
|
114
|
+
AND a.brand = m.brand
|
|
115
|
+
AND a.family_id = m.family_id
|
|
116
|
+
AND a.ts = date_trunc('minute', m.ts)
|
|
117
|
+
AND a.steps = 0`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
66
120
|
const ALLOWED_RAW_PLACEHOLDERS = new Set(['userId', 'brand', 'familyId']);
|
|
67
121
|
/** Extract every `$name` placeholder from a SQL string. */
|
|
68
122
|
function placeholdersOf(sql) {
|
|
@@ -76,40 +130,50 @@ function placeholdersOf(sql) {
|
|
|
76
130
|
}
|
|
77
131
|
return out;
|
|
78
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Placeholder the evaluator binds from KVStore for `user_setting` targets
|
|
135
|
+
* (T-19/T-23). Exported so the evaluator and its tests agree on the name.
|
|
136
|
+
*/
|
|
137
|
+
export const USER_SETTING_PARAM = 'userSettingValue';
|
|
79
138
|
/** Compile a validated rule to a `CompiledRule`. */
|
|
80
139
|
export function compileRule(rule) {
|
|
81
140
|
const meta = METRIC_METADATA[rule.metric];
|
|
82
141
|
const rawTable = meta.table;
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const column = sanitizeIdent(rawColumn);
|
|
142
|
+
const column = sanitizeIdent(meta.column);
|
|
143
|
+
const view = viewFor(rawTable);
|
|
86
144
|
if (rule.rawSql) {
|
|
87
145
|
return compileRawSql(rule, rule.rawSql);
|
|
88
146
|
}
|
|
89
147
|
const ts = tsColumn(rawTable);
|
|
90
148
|
const interval = windowInterval(rule.window);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
149
|
+
const join = emitContextJoin(rule.context);
|
|
150
|
+
const description = `${rule.metric} ${rule.aggregation} over ${rule.window} ${rule.compare} `
|
|
151
|
+
+ `${describeTarget(rule.target)}`
|
|
152
|
+
+ (rule.context === 'any' ? '' : ` [${rule.context}]`)
|
|
153
|
+
+ (rule.consecutive && rule.consecutive > 1 ? ` x${rule.consecutive} consecutive` : '');
|
|
154
|
+
const args = {
|
|
155
|
+
view,
|
|
156
|
+
join,
|
|
98
157
|
ts,
|
|
99
158
|
interval,
|
|
100
159
|
column,
|
|
101
|
-
agg,
|
|
102
|
-
filter,
|
|
160
|
+
agg: aggExpr(rule.aggregation, `m.${column}`, `m.${ts}`),
|
|
103
161
|
target: rule.target,
|
|
104
162
|
compare: rule.compare,
|
|
105
|
-
|
|
106
|
-
|
|
163
|
+
consecutive: rule.consecutive,
|
|
164
|
+
};
|
|
165
|
+
const { sql, params } = rule.consecutive && rule.consecutive > 1
|
|
166
|
+
? buildConsecutive(args)
|
|
167
|
+
: buildForTarget(args);
|
|
107
168
|
return {
|
|
108
169
|
ruleId: rule.id,
|
|
109
170
|
metric: rule.metric,
|
|
110
171
|
sql,
|
|
111
172
|
params,
|
|
112
173
|
description,
|
|
174
|
+
/** Evaluator binds `$userSettingValue` from this key when present. */
|
|
175
|
+
userSettingKey: rule.target.type === 'user_setting' ? rule.target.key : undefined,
|
|
176
|
+
userSettingDefault: rule.target.type === 'user_setting' ? rule.target.defaultValue : undefined,
|
|
113
177
|
};
|
|
114
178
|
}
|
|
115
179
|
function describeTarget(target) {
|
|
@@ -118,18 +182,36 @@ function describeTarget(target) {
|
|
|
118
182
|
case 'baseline_percent': return `${target.percent}% of ${target.windowDays}d baseline`;
|
|
119
183
|
case 'baseline_stddev': return `${target.stddevs} stddev over ${target.windowDays}d baseline`;
|
|
120
184
|
case 'range': return `[${target.min}, ${target.max}]`;
|
|
185
|
+
case 'user_setting': return `user setting ${target.key} (default ${target.defaultValue})`;
|
|
121
186
|
}
|
|
122
187
|
}
|
|
188
|
+
/** Tenant + window predicate, shared by both paths. */
|
|
189
|
+
function whereClause(ts, interval) {
|
|
190
|
+
return (`WHERE m.user_id = $userId AND m.brand = $brand AND m.family_id = $familyId\n`
|
|
191
|
+
+ ` AND m.${ts} > NOW() - ${interval}`);
|
|
192
|
+
}
|
|
123
193
|
function buildForTarget(args) {
|
|
124
|
-
const {
|
|
194
|
+
const { view, join, ts, interval, column, agg, target, compare } = args;
|
|
195
|
+
const where = whereClause(ts, interval);
|
|
196
|
+
const from = `FROM ${view} m${join}`;
|
|
125
197
|
if (target.type === 'absolute') {
|
|
126
198
|
const params = { threshold_absolute: target.value };
|
|
127
199
|
const sql = `SELECT ${agg} AS observed_value, $threshold_absolute AS threshold_value
|
|
128
|
-
|
|
129
|
-
${
|
|
200
|
+
${from}
|
|
201
|
+
${where}
|
|
130
202
|
HAVING ${compareClause(compare, 'observed_value', '$threshold_absolute')};`;
|
|
131
203
|
return { sql, params };
|
|
132
204
|
}
|
|
205
|
+
if (target.type === 'user_setting') {
|
|
206
|
+
// T-19: threshold is a bound param resolved at eval time, so the same
|
|
207
|
+
// compiled SQL serves every user in the family and survives the user
|
|
208
|
+
// changing their setting — no cache invalidation needed.
|
|
209
|
+
const sql = `SELECT ${agg} AS observed_value, $${USER_SETTING_PARAM} AS threshold_value
|
|
210
|
+
${from}
|
|
211
|
+
${where}
|
|
212
|
+
HAVING ${compareClause(compare, 'observed_value', `$${USER_SETTING_PARAM}`)};`;
|
|
213
|
+
return { sql, params: {} };
|
|
214
|
+
}
|
|
133
215
|
if (target.type === 'range') {
|
|
134
216
|
const params = { range_min: target.min, range_max: target.max };
|
|
135
217
|
// For range, `between` violates on values OUTSIDE the range; other
|
|
@@ -139,8 +221,8 @@ HAVING ${compareClause(compare, 'observed_value', '$threshold_absolute')};`;
|
|
|
139
221
|
? `NOT (observed_value BETWEEN $range_min AND $range_max)`
|
|
140
222
|
: compareClause(compare, 'observed_value', '$range_min');
|
|
141
223
|
const sql = `SELECT ${agg} AS observed_value, $range_min AS threshold_value
|
|
142
|
-
|
|
143
|
-
${
|
|
224
|
+
${from}
|
|
225
|
+
${where}
|
|
144
226
|
HAVING ${outside};`;
|
|
145
227
|
return { sql, params };
|
|
146
228
|
}
|
|
@@ -148,42 +230,106 @@ HAVING ${outside};`;
|
|
|
148
230
|
// DuckDB can't parametrize inside an INTERVAL literal, but it can
|
|
149
231
|
// multiply a static unit interval by a bound integer.
|
|
150
232
|
const baselineDaysBind = `(INTERVAL 1 DAY) * $baselineDays`;
|
|
233
|
+
const baselineFrom = `FROM ${view} m${join}`;
|
|
234
|
+
const baselineWhere = `WHERE m.user_id = $userId AND m.brand = $brand AND m.family_id = $familyId\n`
|
|
235
|
+
+ ` AND m.${ts} > NOW() - ${baselineDaysBind}`;
|
|
151
236
|
if (target.type === 'baseline_percent') {
|
|
152
|
-
const params = {
|
|
153
|
-
baselineDays: target.windowDays,
|
|
154
|
-
pct: target.percent,
|
|
155
|
-
};
|
|
237
|
+
const params = { baselineDays: target.windowDays, pct: target.percent };
|
|
156
238
|
const sql = `WITH baseline AS (
|
|
157
|
-
SELECT AVG(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
AND ${ts} > NOW() - ${baselineDaysBind}
|
|
239
|
+
SELECT AVG(m.${column}) AS mean
|
|
240
|
+
${baselineFrom}
|
|
241
|
+
${baselineWhere}
|
|
161
242
|
)
|
|
162
243
|
SELECT ${agg} AS observed_value,
|
|
163
244
|
(SELECT mean FROM baseline) * ($pct / 100.0) AS threshold_value
|
|
164
|
-
|
|
165
|
-
${
|
|
245
|
+
${from}
|
|
246
|
+
${where}
|
|
166
247
|
HAVING ${compareClause(compare, 'observed_value', 'threshold_value')};`;
|
|
167
248
|
return { sql, params };
|
|
168
249
|
}
|
|
169
250
|
// baseline_stddev
|
|
170
|
-
const params = {
|
|
171
|
-
baselineDays: target.windowDays,
|
|
172
|
-
stddevs: target.stddevs,
|
|
173
|
-
};
|
|
251
|
+
const params = { baselineDays: target.windowDays, stddevs: target.stddevs };
|
|
174
252
|
const sql = `WITH baseline AS (
|
|
175
|
-
SELECT AVG(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
AND ${ts} > NOW() - ${baselineDaysBind}
|
|
253
|
+
SELECT AVG(m.${column}) AS mean, stddev_pop(m.${column}) AS sd
|
|
254
|
+
${baselineFrom}
|
|
255
|
+
${baselineWhere}
|
|
179
256
|
)
|
|
180
257
|
SELECT ${agg} AS observed_value,
|
|
181
258
|
(SELECT mean + $stddevs * sd FROM baseline) AS threshold_value
|
|
182
|
-
|
|
183
|
-
${
|
|
259
|
+
${from}
|
|
260
|
+
${where}
|
|
184
261
|
HAVING ${compareClause(compare, 'observed_value', 'threshold_value')};`;
|
|
185
262
|
return { sql, params };
|
|
186
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* T-18 — `consecutive: n` via gaps-and-islands.
|
|
266
|
+
*
|
|
267
|
+
* A run of adjacent breaching samples is identified by the classic
|
|
268
|
+
* difference of two row numbers: one over all samples in time order, one
|
|
269
|
+
* partitioned by the breach flag. Within a maximal run of same-flag rows
|
|
270
|
+
* that difference is constant, so it serves as a group key.
|
|
271
|
+
*
|
|
272
|
+
* The rule fires when ANY breaching run reaches length `n`. `observed_value`
|
|
273
|
+
* is the run's extreme in the direction of the comparison — the worst
|
|
274
|
+
* reading in the run, which is what a user-facing card should quote.
|
|
275
|
+
*
|
|
276
|
+
* Only supported for the threshold-style targets. Baseline targets would
|
|
277
|
+
* need the baseline resolved per-sample rather than per-window, which is a
|
|
278
|
+
* different query shape and has no consumer in v0.2.0 — the validator
|
|
279
|
+
* rejects the combination rather than silently emitting something subtly
|
|
280
|
+
* wrong.
|
|
281
|
+
*/
|
|
282
|
+
function buildConsecutive(args) {
|
|
283
|
+
const { view, join, ts, interval, column, target, compare, consecutive } = args;
|
|
284
|
+
const where = whereClause(ts, interval);
|
|
285
|
+
const from = `FROM ${view} m${join}`;
|
|
286
|
+
let thresholdExpr;
|
|
287
|
+
let params = {};
|
|
288
|
+
if (target.type === 'absolute') {
|
|
289
|
+
thresholdExpr = '$threshold_absolute';
|
|
290
|
+
params = { threshold_absolute: target.value };
|
|
291
|
+
}
|
|
292
|
+
else if (target.type === 'user_setting') {
|
|
293
|
+
thresholdExpr = `$${USER_SETTING_PARAM}`;
|
|
294
|
+
}
|
|
295
|
+
else if (target.type === 'range') {
|
|
296
|
+
thresholdExpr = '$range_min';
|
|
297
|
+
params = { range_min: target.min, range_max: target.max };
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
throw new RuleValidationError(`consecutive is not supported with target.type '${target.type}' — `
|
|
301
|
+
+ `baseline targets resolve per-window, not per-sample. Use an absolute `
|
|
302
|
+
+ `or user_setting target, or drop consecutive.`);
|
|
303
|
+
}
|
|
304
|
+
// Worst reading in the run, in the direction of the comparison.
|
|
305
|
+
const extreme = compare === 'greater_than' ? 'MAX' : 'MIN';
|
|
306
|
+
const breach = compare === 'between'
|
|
307
|
+
? `NOT (m.${column} BETWEEN $range_min AND $range_max)`
|
|
308
|
+
: compareClause(compare, `m.${column}`, thresholdExpr);
|
|
309
|
+
const sql = `WITH samples AS (
|
|
310
|
+
SELECT m.${ts} AS ts,
|
|
311
|
+
m.${column} AS value,
|
|
312
|
+
(${breach}) AS breached,
|
|
313
|
+
${thresholdExpr} AS threshold_value
|
|
314
|
+
${from}
|
|
315
|
+
${where}
|
|
316
|
+
),
|
|
317
|
+
runs AS (
|
|
318
|
+
SELECT ts, value, breached, threshold_value,
|
|
319
|
+
ROW_NUMBER() OVER (ORDER BY ts)
|
|
320
|
+
- ROW_NUMBER() OVER (PARTITION BY breached ORDER BY ts) AS run_key
|
|
321
|
+
FROM samples
|
|
322
|
+
)
|
|
323
|
+
SELECT ${extreme}(value) AS observed_value,
|
|
324
|
+
ANY_VALUE(threshold_value) AS threshold_value
|
|
325
|
+
FROM runs
|
|
326
|
+
WHERE breached
|
|
327
|
+
GROUP BY run_key
|
|
328
|
+
HAVING COUNT(*) >= $consecutive
|
|
329
|
+
ORDER BY observed_value ${compare === 'greater_than' ? 'DESC' : 'ASC'}
|
|
330
|
+
LIMIT 1;`;
|
|
331
|
+
return { sql, params: { ...params, consecutive: consecutive } };
|
|
332
|
+
}
|
|
187
333
|
/**
|
|
188
334
|
* Pass-through path with allow-list check. Placeholders must be one of
|
|
189
335
|
* {userId, brand, familyId} or declared in `rule.rawSqlParams`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.js","sourceRoot":"","sources":["../../src/rules/compiler.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"compiler.js","sourceRoot":"","sources":["../../src/rules/compiler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"luminx.d.ts","sourceRoot":"","sources":["../../../src/rules/defaults/luminx.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"luminx.d.ts","sourceRoot":"","sources":["../../../src/rules/defaults/luminx.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAqErC,eAAO,MAAM,cAAc,EAAE,IAAI,EAA2C,CAAA"}
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { parseCatalog } from './loader';
|
|
5
5
|
const TOML = `
|
|
6
|
+
# LuminX brand default rules.
|
|
7
|
+
#
|
|
8
|
+
# LuminX rules are asset-side: battery low, prolonged disconnect, and
|
|
9
|
+
# firmware/status. All three run against the \`device_event\` table, whose
|
|
10
|
+
# metric proxy \`device_battery\` gates evaluation on batch.
|
|
11
|
+
#
|
|
12
|
+
# See \`luminx.ts\` — this file is the source of truth.
|
|
13
|
+
|
|
6
14
|
[[rule]]
|
|
7
15
|
id = "luminx.battery-low"
|
|
8
16
|
brand = "luminx"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"luminx.js","sourceRoot":"","sources":["../../../src/rules/defaults/luminx.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,MAAM,IAAI,GAAG
|
|
1
|
+
{"version":3,"file":"luminx.js","sourceRoot":"","sources":["../../../src/rules/defaults/luminx.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiEZ,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAW,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viva.d.ts","sourceRoot":"","sources":["../../../src/rules/defaults/viva.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"viva.d.ts","sourceRoot":"","sources":["../../../src/rules/defaults/viva.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAiDrC,eAAO,MAAM,YAAY,EAAE,IAAI,EAAyC,CAAA"}
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { parseCatalog } from './loader';
|
|
5
5
|
const TOML = `
|
|
6
|
+
# Viva brand default rules (placeholder set for v0.1.0).
|
|
7
|
+
#
|
|
8
|
+
# Viva is asset-focused; rules mirror the LuminX asset-threshold pattern
|
|
9
|
+
# for battery + connectivity. Refined once product surfaces land.
|
|
10
|
+
#
|
|
11
|
+
# See \`viva.ts\` — this file is the source of truth.
|
|
12
|
+
|
|
6
13
|
[[rule]]
|
|
7
14
|
id = "viva.battery-low"
|
|
8
15
|
brand = "viva"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viva.js","sourceRoot":"","sources":["../../../src/rules/defaults/viva.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,MAAM,IAAI,GAAG
|
|
1
|
+
{"version":3,"file":"viva.js","sourceRoot":"","sources":["../../../src/rules/defaults/viva.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6CZ,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAW,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yogaring.d.ts","sourceRoot":"","sources":["../../../src/rules/defaults/yogaring.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"yogaring.d.ts","sourceRoot":"","sources":["../../../src/rules/defaults/yogaring.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAiDrC,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAA6C,CAAA"}
|