@mongrov/analytics 0.5.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 +124 -18
- 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/machine.d.ts.map +1 -1
- package/dist/core/machine.js +9 -1
- package/dist/core/machine.js.map +1 -1
- package/dist/core/metric_metadata.d.ts +57 -2
- package/dist/core/metric_metadata.d.ts.map +1 -1
- package/dist/core/metric_metadata.js +36 -8
- package/dist/core/metric_metadata.js.map +1 -1
- package/dist/core/migrations.d.ts +6 -0
- package/dist/core/migrations.d.ts.map +1 -1
- package/dist/core/migrations.js +92 -4
- 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 +50 -22
- package/dist/core/retention.js.map +1 -1
- package/dist/core/schemas.d.ts +74 -3
- package/dist/core/schemas.d.ts.map +1 -1
- package/dist/core/schemas.js +168 -5
- package/dist/core/schemas.js.map +1 -1
- package/dist/core/table_metadata.d.ts.map +1 -1
- package/dist/core/table_metadata.js +4 -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 +78 -7
- 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 +109 -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 +59 -11
- 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 +4 -2
- 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/battery.d.ts +14 -7
- package/dist/sync/mapper/battery.d.ts.map +1 -1
- package/dist/sync/mapper/battery.js +13 -7
- package/dist/sync/mapper/battery.js.map +1 -1
- package/dist/sync/mapper/firmware.d.ts.map +1 -1
- package/dist/sync/mapper/firmware.js +4 -1
- package/dist/sync/mapper/firmware.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 +75 -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,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push-watermark cache (Sprint 5 T-07 / item (b)).
|
|
3
|
+
*
|
|
4
|
+
* **Disabled by default, and deliberately so.** Every `v_{table}` scan
|
|
5
|
+
* resolves the push watermark through a correlated subquery against
|
|
6
|
+
* `sync_watermark`. That is presumed cheap — a primary-key lookup against a
|
|
7
|
+
* table with one row per (tenant, table, kind) — but "presumed" is the
|
|
8
|
+
* operative word, and caching a value that gates data visibility is exactly
|
|
9
|
+
* the kind of optimisation that trades a measurable millisecond for an
|
|
10
|
+
* unmeasurable correctness bug.
|
|
11
|
+
*
|
|
12
|
+
* So this ships behind a measurement gate (principles §Open Items,
|
|
13
|
+
* "Watermark cache activation — pilot measurement gate"): the instrument
|
|
14
|
+
* runs always, the cache runs only when p95 query latency for hot registry
|
|
15
|
+
* queries exceeds 20ms in the Ziva pilot and an operator opts in via
|
|
16
|
+
* `AnalyticsConfig.watermarkCache: true`.
|
|
17
|
+
*
|
|
18
|
+
* Staleness is bounded on two axes:
|
|
19
|
+
* - a 5-second TTL, so the worst case is bounded even if invalidation is
|
|
20
|
+
* missed entirely;
|
|
21
|
+
* - explicit invalidation on `{table}:sync_complete`, which is the only
|
|
22
|
+
* event that advances a push watermark.
|
|
23
|
+
*
|
|
24
|
+
* A stale-high watermark would hide freshly-flushed local rows; a
|
|
25
|
+
* stale-low one would double-count rows present in both catalogs. The TTL
|
|
26
|
+
* caps both windows at 5s, which is below the shortest sync cycle.
|
|
27
|
+
*/
|
|
28
|
+
import type { EventBus } from './types';
|
|
29
|
+
/** Cache lifetime. Below the shortest sync cycle, so drift cannot compound. */
|
|
30
|
+
export declare const WATERMARK_CACHE_TTL_MS = 5000;
|
|
31
|
+
export interface WatermarkCacheStats {
|
|
32
|
+
hits: number;
|
|
33
|
+
misses: number;
|
|
34
|
+
invalidations: number;
|
|
35
|
+
expirations: number;
|
|
36
|
+
/** Rolling p50/p95/p99 over observed loader latencies, in ms. */
|
|
37
|
+
latency: {
|
|
38
|
+
p50: number;
|
|
39
|
+
p95: number;
|
|
40
|
+
p99: number;
|
|
41
|
+
samples: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface WatermarkCacheOptions {
|
|
45
|
+
/** Master switch. `false` (default) makes every read a pass-through. */
|
|
46
|
+
enabled?: boolean;
|
|
47
|
+
/** Override the 5s TTL. Tests inject shorter values. */
|
|
48
|
+
ttlMs?: number;
|
|
49
|
+
/** Injectable clock so tests need no timers. */
|
|
50
|
+
now?: () => number;
|
|
51
|
+
/**
|
|
52
|
+
* Bus to subscribe for `{table}:sync_complete`. Omitted → the cache
|
|
53
|
+
* relies on TTL expiry alone.
|
|
54
|
+
*/
|
|
55
|
+
eventBus?: EventBus;
|
|
56
|
+
}
|
|
57
|
+
export interface WatermarkCache {
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a watermark, consulting the cache when enabled.
|
|
60
|
+
*
|
|
61
|
+
* `load` is always the source of truth; the cache only ever elides a
|
|
62
|
+
* call to it. When disabled, this is `load()` plus instrumentation.
|
|
63
|
+
*/
|
|
64
|
+
get(brand: string, familyId: string, table: string, load: () => Promise<Date | null>): Promise<Date | null>;
|
|
65
|
+
/** Drop one table's entry across all tenants. */
|
|
66
|
+
invalidate(table: string): void;
|
|
67
|
+
/** Drop everything — used on attach/detach and brand switch. */
|
|
68
|
+
clear(): void;
|
|
69
|
+
stats(): WatermarkCacheStats;
|
|
70
|
+
/** Detach the event-bus subscription. */
|
|
71
|
+
dispose(): void;
|
|
72
|
+
readonly enabled: boolean;
|
|
73
|
+
}
|
|
74
|
+
export declare function createWatermarkCache(options?: WatermarkCacheOptions): WatermarkCache;
|
|
75
|
+
//# sourceMappingURL=watermark-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watermark-cache.d.ts","sourceRoot":"","sources":["../../src/core/watermark-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,OAAQ,CAAA;AAE3C,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,iEAAiE;IACjE,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CACpE;AAED,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;CACpB;AAsBD,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,GAAG,CACD,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,GAC/B,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IACvB,iDAAiD;IACjD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,gEAAgE;IAChE,KAAK,IAAI,IAAI,CAAA;IACb,KAAK,IAAI,mBAAmB,CAAA;IAC5B,yCAAyC;IACzC,OAAO,IAAI,IAAI,CAAA;IACf,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,qBAA0B,GAClC,cAAc,CA6FhB"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watermark-cache.js","sourceRoot":"","sources":["../../src/core/watermark-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,8 @@ export interface FakeEngine extends AnalyticsEngine {
|
|
|
9
9
|
__setError(err: Error | null): void;
|
|
10
10
|
/** Mutate the reported `state` for tests that exercise the attached-state guard. */
|
|
11
11
|
__setState(state: AnalyticsState): void;
|
|
12
|
+
/** Seed the roster returned by `getFamilyMembers()`. */
|
|
13
|
+
__setFamilyMembers(ids: string[]): void;
|
|
12
14
|
}
|
|
13
15
|
export declare function createFakeEngine(): FakeEngine;
|
|
14
16
|
//# sourceMappingURL=fakeEngine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fakeEngine.d.ts","sourceRoot":"","sources":["../../../src/rules/__fakes__/fakeEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEvE,wDAAwD;AACxD,MAAM,WAAW,UAAW,SAAQ,eAAe;IACjD,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,CAAA;IAC3D,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAClC,UAAU,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,CAAA;IACnC,oFAAoF;IACpF,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAA;CACxC;AAED,wBAAgB,gBAAgB,IAAI,UAAU,
|
|
1
|
+
{"version":3,"file":"fakeEngine.d.ts","sourceRoot":"","sources":["../../../src/rules/__fakes__/fakeEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEvE,wDAAwD;AACxD,MAAM,WAAW,UAAW,SAAQ,eAAe;IACjD,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,CAAA;IAC3D,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAClC,UAAU,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,CAAA;IACnC,oFAAoF;IACpF,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAA;IACvC,wDAAwD;IACxD,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CACxC;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CAsC7C"}
|
|
@@ -2,10 +2,13 @@ export function createFakeEngine() {
|
|
|
2
2
|
let queued = [];
|
|
3
3
|
let queuedErr = null;
|
|
4
4
|
let state = 'attached';
|
|
5
|
+
let familyMembers = [];
|
|
5
6
|
const calls = [];
|
|
6
7
|
const engine = {
|
|
7
8
|
async attach() { },
|
|
8
9
|
async detach() { },
|
|
10
|
+
async getFamilyMembers() { return familyMembers; },
|
|
11
|
+
async dismissInsight() { },
|
|
9
12
|
async execute(sql, params) {
|
|
10
13
|
calls.push({ sql, params: params ?? {} });
|
|
11
14
|
if (queuedErr)
|
|
@@ -30,6 +33,7 @@ export function createFakeEngine() {
|
|
|
30
33
|
__setResult(rows) { queued = rows; queuedErr = null; },
|
|
31
34
|
__setError(err) { queuedErr = err; },
|
|
32
35
|
__setState(s) { state = s; },
|
|
36
|
+
__setFamilyMembers(ids) { familyMembers = ids; },
|
|
33
37
|
};
|
|
34
38
|
return engine;
|
|
35
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fakeEngine.js","sourceRoot":"","sources":["../../../src/rules/__fakes__/fakeEngine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fakeEngine.js","sourceRoot":"","sources":["../../../src/rules/__fakes__/fakeEngine.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,gBAAgB;IAC9B,IAAI,MAAM,GAAc,EAAE,CAAA;IAC1B,IAAI,SAAS,GAAiB,IAAI,CAAA;IAClC,IAAI,KAAK,GAAmB,UAAU,CAAA;IACtC,IAAI,aAAa,GAAa,EAAE,CAAA;IAChC,MAAM,KAAK,GAAuD,EAAE,CAAA;IAEpE,MAAM,MAAM,GAAe;QACzB,KAAK,CAAC,MAAM,KAAI,CAAC;QACjB,KAAK,CAAC,MAAM,KAAI,CAAC;QACjB,KAAK,CAAC,gBAAgB,KAAK,OAAO,aAAa,CAAA,CAAC,CAAC;QACjD,KAAK,CAAC,cAAc,KAAI,CAAC;QACzB,KAAK,CAAC,OAAO,CAAI,GAAW,EAAE,MAAgC;YAC5D,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC,CAAA;YACzC,IAAI,SAAS;gBAAE,MAAM,SAAS,CAAA;YAC9B,OAAO,MAAa,CAAA;QACtB,CAAC;QACD,MAAM;YACJ,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;QACnD,CAAC;QACD,cAAc;YACZ,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;QAC3D,CAAC;QACD,IAAI,KAAK,KAAK,OAAO,KAAK,CAAA,CAAC,CAAC;QAC5B,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,IAAI;QACV,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;QACzB,KAAK,CAAC,YAAY,KAAI,CAAC;QACvB,KAAK,CAAC,aAAa,KAAK,OAAO,IAAI,CAAA,CAAC,CAAC;QACrC,KAAK,CAAC,KAAK,KAAI,CAAC;QAChB,OAAO,EAAE,KAAK;QACd,WAAW,CAAC,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC,SAAS,GAAG,IAAI,CAAA,CAAC,CAAC;QACrD,UAAU,CAAC,GAAG,IAAI,SAAS,GAAG,GAAG,CAAA,CAAC,CAAC;QACnC,UAAU,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAA,CAAC,CAAC;QAC3B,kBAAkB,CAAC,GAAG,IAAI,aAAa,GAAG,GAAG,CAAA,CAAC,CAAC;KAChD,CAAA;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EACL,iBAAiB,GAGlB,MAAM,yBAAyB,CAAA;AAmDhC,MAAM,UAAU,oBAAoB,CAClC,MAA4B;IAE5B,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;IACpC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAA;IAEhD,SAAS,GAAG,CACV,MAAc,EACd,MAAc,EACd,UAAkB,EAClB,KAAa,EACb,QAAgB;QAEhB,OAAO,GAAG,KAAK,IAAI,QAAQ,IAAI,MAAM,IAAI,MAAM,IAAI,UAAU,EAAE,CAAA;IACjE,CAAC;IAED,KAAK,UAAU,UAAU,CACvB,MAAc,EACd,MAAgB,EAChB,UAA8B,EAC9B,GAAwC;QAExC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAClC;;;;eAIS,EACT;YACE,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,MAAM;YACN,MAAM;YACN,UAAU;SACX,CACF,CAAA;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACnB,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,YAAY,KAAK,IAAI,IAAI,GAAG,CAAC,YAAY,GAAG,iBAAiB,EAAE,CAAC;YAC9E,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAA;IACtD,CAAC;IAED,KAAK,UAAU,aAAa,CAC1B,MAAc,EACd,MAAgB,EAChB,UAA8B,EAC9B,GAAwC;QAExC,uEAAuE;QACvE,8BAA8B;QAC9B,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAClC,gBAAgB,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC;YACE,MAAM;YACN,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,EAAE,EAAE,MAAM,CAAC,QAAQ,IAAI,KAAK;YAC5B,UAAU;SACX,CACF,CAAA;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACnB,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,YAAY,KAAK,IAAI,IAAI,GAAG,CAAC,YAAY,GAAG,iBAAiB,EAAE,CAAC;YAC9E,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAA;IACrD,CAAC;IAED,OAAO;QACL,UAAU;YACR,KAAK,CAAC,KAAK,EAAE,CAAA;QACf,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG;YAC/C,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAA;YAClE,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;YAE7C,IAAI,MAAM,GAAoB,IAAI,CAAA;YAClC,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,CAAA;gBAC1D,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;oBACpB,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,CAAA;oBAC7D,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;wBACpB,MAAM,EAAE,KAAK,CAAC,qDAAqD,EAAE;4BACnE,MAAM,EAAE,MAAM,EAAE,UAAU;yBAC3B,CAAC,CAAA;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YACD,OAAO,GAAG,EAAE,CAAC;gBACX,oEAAoE;gBACpE,gEAAgE;gBAChE,kCAAkC;gBAClC,MAAM,EAAE,IAAI,CAAC,6BAA6B,EAAE;oBAC1C,MAAM;oBACN,MAAM;oBACN,UAAU;oBACV,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBACtD,CAAC,CAAA;gBACF,MAAM,GAAG,IAAI,CAAA;YACf,CAAC;YAED,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;YACpB,OAAO,MAAM,CAAA;QACf,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,GAAc;IAChC,OAAO;QACL,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,WAAW,EAAE,GAAG,CAAC,YAAsB;KACxC,CAAA;AACH,CAAC"}
|
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"}
|