@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
package/dist/rules/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* consumers don't need to pull the Zod schema module unless they
|
|
6
6
|
* validate rules themselves.
|
|
7
7
|
*/
|
|
8
|
-
import type { AnalyticsEngine, FamilyMembersProvider, KVStore, Unsubscribe } from '../core/types';
|
|
8
|
+
import type { AnalyticsEngine, EventBus, FamilyMembersProvider, KVStore, Unsubscribe } from '../core/types';
|
|
9
9
|
import type { MetricId } from '../core/metric_metadata';
|
|
10
10
|
import type { TableName } from '../core/schemas';
|
|
11
11
|
import type { Rule } from './schema';
|
|
@@ -35,6 +35,14 @@ export interface CompiledRule {
|
|
|
35
35
|
/** Statically-known params. Runtime binds `$userId`/`$brand`/`$familyId` on top. */
|
|
36
36
|
params: Record<string, string | number>;
|
|
37
37
|
description: string;
|
|
38
|
+
/**
|
|
39
|
+
* KVStore key the evaluator reads to bind `$userSettingValue`
|
|
40
|
+
* (Sprint 5 T-19/T-23). Present only for `target.type: 'user_setting'`.
|
|
41
|
+
* Full key is `analytics:{userId}:{userSettingKey}`.
|
|
42
|
+
*/
|
|
43
|
+
userSettingKey?: string;
|
|
44
|
+
/** Fallback threshold when the user has never set the value. */
|
|
45
|
+
userSettingDefault?: number;
|
|
38
46
|
}
|
|
39
47
|
/** Structured violation delivered to `on('violation', handler)` subscribers. */
|
|
40
48
|
export interface RuleViolation {
|
|
@@ -66,6 +74,13 @@ export interface RulesEngineConfig {
|
|
|
66
74
|
brand: string;
|
|
67
75
|
/** Family id for `onSchedule` — v0.1.0 single-user apps pass the userId. */
|
|
68
76
|
familyId: string;
|
|
77
|
+
/**
|
|
78
|
+
* Optional app event bus (structural type from core — no runtime dep on
|
|
79
|
+
* @mongrov/data-access). When set, the evaluator emits
|
|
80
|
+
* `threshold:violation` + `insight:insert` after persisting a violation
|
|
81
|
+
* to the `insight` table (principle 35 fire-and-forget).
|
|
82
|
+
*/
|
|
83
|
+
eventBus?: EventBus;
|
|
69
84
|
logger?: RulesLogger;
|
|
70
85
|
clock?: Clock;
|
|
71
86
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACrB,OAAO,EACP,WAAW,EACZ,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAEpC,YAAY,EAAE,WAAW,EAAE,CAAA;AAE3B,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAA;AAEvD;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,cAAc,EAAE,SAAS,EAAE,CAAA;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,QAAQ,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;IACvC,WAAW,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/rules/validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,KAAK,IAAI,EAAuB,KAAK,MAAM,EAAW,MAAM,UAAU,CAAA;AA4B/E;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,CAS5D;AAgBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/rules/validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,KAAK,IAAI,EAAuB,KAAK,MAAM,EAAW,MAAM,UAAU,CAAA;AA4B/E;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,CAS5D;AAgBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CA2BnE;AAiED,8EAA8E;AAC9E,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAOpE"}
|
package/dist/rules/validator.js
CHANGED
|
@@ -65,6 +65,8 @@ function samplingLabel(metric) {
|
|
|
65
65
|
* When rawSql is present with `exposureOverride: true`, emits a warn via `logger`.
|
|
66
66
|
*/
|
|
67
67
|
export function validateRule(rule, logger) {
|
|
68
|
+
validateConsecutive(rule);
|
|
69
|
+
validateContext(rule);
|
|
68
70
|
const allowed = allowedWindowsFor(rule.metric);
|
|
69
71
|
if (!allowed.includes(rule.window)) {
|
|
70
72
|
throw new RuleValidationError(`Rule ${rule.id}: metric ${rule.metric} sampled ${samplingLabel(rule.metric)}. `
|
|
@@ -84,6 +86,60 @@ export function validateRule(rule, logger) {
|
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* T-21 — `consecutive x effective_sampling_minutes` must fit the window.
|
|
91
|
+
*
|
|
92
|
+
* A rule asking for 3 consecutive HRV samples (60-min cadence) inside a 1h
|
|
93
|
+
* window is unsatisfiable: the window holds at most one sample, so the
|
|
94
|
+
* rule can never fire and the author gets silence instead of an error.
|
|
95
|
+
*
|
|
96
|
+
* `effective` sampling comes from `device_config.interval_minutes` at eval
|
|
97
|
+
* time when a row exists (principle 22); at register time only the
|
|
98
|
+
* `metric_metadata` fallback is known, so this check uses the fallback and
|
|
99
|
+
* is deliberately permissive — a denser real device only makes a rule MORE
|
|
100
|
+
* satisfiable, never less.
|
|
101
|
+
*/
|
|
102
|
+
function validateConsecutive(rule) {
|
|
103
|
+
const n = rule.consecutive;
|
|
104
|
+
if (n === undefined || n <= 1)
|
|
105
|
+
return;
|
|
106
|
+
// Baseline targets resolve per-window, not per-sample; the compiler has
|
|
107
|
+
// no correct query shape for the combination.
|
|
108
|
+
if (rule.target.type === 'baseline_percent' || rule.target.type === 'baseline_stddev') {
|
|
109
|
+
throw new RuleValidationError(`Rule ${rule.id}: consecutive is not supported with target.type `
|
|
110
|
+
+ `'${rule.target.type}' — baseline targets resolve per-window, not `
|
|
111
|
+
+ `per-sample. Use absolute or user_setting, or drop consecutive.`);
|
|
112
|
+
}
|
|
113
|
+
const cadence = METRIC_METADATA[rule.metric].sampling_minutes;
|
|
114
|
+
if (cadence === 'per_session') {
|
|
115
|
+
// One value per night; "consecutive samples" means consecutive nights,
|
|
116
|
+
// which the window already bounds.
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const needed = n * cadence;
|
|
120
|
+
const available = WINDOW_MINUTES[rule.window];
|
|
121
|
+
if (needed > available) {
|
|
122
|
+
throw new RuleValidationError(`Rule ${rule.id}: consecutive ${n} x ${cadence}min sampling needs `
|
|
123
|
+
+ `${needed}min but window ${rule.window} is only ${available}min. `
|
|
124
|
+
+ `The rule could never fire. Widen the window or lower consecutive.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* T-21 — context compatibility.
|
|
129
|
+
*
|
|
130
|
+
* `context: 'asleep'` JOINs `v_sleep_session` on the metric's `ts`, so a
|
|
131
|
+
* metric whose own time axis IS the sleep session (sleep_total_minutes)
|
|
132
|
+
* would self-join meaninglessly.
|
|
133
|
+
*/
|
|
134
|
+
function validateContext(rule) {
|
|
135
|
+
if (rule.context === 'any')
|
|
136
|
+
return;
|
|
137
|
+
const table = METRIC_METADATA[rule.metric].table;
|
|
138
|
+
if (table === 'sleep_session') {
|
|
139
|
+
throw new RuleValidationError(`Rule ${rule.id}: context '${rule.context}' is redundant for metric `
|
|
140
|
+
+ `${rule.metric} — it is already a per-session measure. Use context 'any'.`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
87
143
|
/** Scan a rawSql body for whole-word references to collected-only columns. */
|
|
88
144
|
export function referencedCollectedOnlyColumns(sql) {
|
|
89
145
|
const hits = new Set();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/rules/validator.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/rules/validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Day-first baseline compute (Sprint 5 §7 / T-13 / T-14, principle 27).
|
|
3
|
+
*
|
|
4
|
+
* Populates `user_baseline`, the shared "usual range" that rules, AI tools,
|
|
5
|
+
* and screens all read. One row per (brand, family, user, metric, window).
|
|
6
|
+
*
|
|
7
|
+
* ## Why day-first
|
|
8
|
+
*
|
|
9
|
+
* The naive implementation quantiles raw readings. That is wrong, and
|
|
10
|
+
* wrong in a way that looks plausible: SpO₂ sampled every 30 minutes dips
|
|
11
|
+
* during deep sleep on most nights, so a raw p10 across 30 days reports the
|
|
12
|
+
* *dip* (~87%) as the bottom of the user's usual range. It isn't — it's a
|
|
13
|
+
* normal nightly event that happens every day. The user's day-to-day
|
|
14
|
+
* variation is much narrower.
|
|
15
|
+
*
|
|
16
|
+
* So: collapse each local day to ONE value, then quantile across days. A
|
|
17
|
+
* fixture of `95, 95, 87, 95, 95` repeated for 30 days gives p10 ≈ 92.6
|
|
18
|
+
* day-first versus 87 raw — and 92.6 is the honest answer.
|
|
19
|
+
*
|
|
20
|
+
* This is also why `sample_count` counts DAYS. Fifteen days of 30-minute
|
|
21
|
+
* sampling is 720 readings but still only 15 days of evidence, and must not
|
|
22
|
+
* populate a baseline.
|
|
23
|
+
*/
|
|
24
|
+
import { type BaselineWindowDays, type MetricId } from '../core/metric_metadata';
|
|
25
|
+
import type { AnalyticsEngine, EventBus } from '../core/types';
|
|
26
|
+
import type { SchedulerLogger } from './scheduler';
|
|
27
|
+
export interface BaselineComputeContext {
|
|
28
|
+
brand: string;
|
|
29
|
+
familyId: string;
|
|
30
|
+
userId: string;
|
|
31
|
+
/** IANA zone. Day boundaries are local, so this decides the buckets. */
|
|
32
|
+
userTimezone: string;
|
|
33
|
+
}
|
|
34
|
+
export interface BaselineComputeConfig {
|
|
35
|
+
analytics: AnalyticsEngine;
|
|
36
|
+
eventBus?: EventBus;
|
|
37
|
+
logger?: SchedulerLogger;
|
|
38
|
+
/** Override for tests. Defaults to every metric declaring an aggregate. */
|
|
39
|
+
metrics?: readonly MetricId[];
|
|
40
|
+
/** Override for tests. Defaults to 7 / 30 / 90. */
|
|
41
|
+
windows?: readonly BaselineWindowDays[];
|
|
42
|
+
}
|
|
43
|
+
export interface BaselineComputeResult {
|
|
44
|
+
computed: number;
|
|
45
|
+
skipped: number;
|
|
46
|
+
failed: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build the day-first aggregate SQL for one (metric, window).
|
|
50
|
+
*
|
|
51
|
+
* Exported for snapshot tests — the shape of this query IS the correctness
|
|
52
|
+
* property, so it is worth asserting directly rather than only through
|
|
53
|
+
* results.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildBaselineSql(metric: MetricId, windowDays: BaselineWindowDays): string;
|
|
56
|
+
export interface BaselineComputer {
|
|
57
|
+
/** Compute one (metric, window). Returns true when a row was written. */
|
|
58
|
+
computeOne(metric: MetricId, windowDays: BaselineWindowDays, ctx: BaselineComputeContext): Promise<boolean>;
|
|
59
|
+
/** Compute every configured metric x window for one user. */
|
|
60
|
+
computeAll(ctx: BaselineComputeContext): Promise<BaselineComputeResult>;
|
|
61
|
+
}
|
|
62
|
+
export declare function createBaselineComputer(config: BaselineComputeConfig): BaselineComputer;
|
|
63
|
+
//# sourceMappingURL=baseline-compute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline-compute.d.ts","sourceRoot":"","sources":["../../src/sync/baseline-compute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAIL,KAAK,kBAAkB,EAGvB,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAclD,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,eAAe,CAAA;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAA;IAC7B,mDAAmD;IACnD,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,QAAQ,EAChB,UAAU,EAAE,kBAAkB,GAC7B,MAAM,CAoDR;AA4CD,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,UAAU,CACR,MAAM,EAAE,QAAQ,EAChB,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE,sBAAsB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAAA;IACnB,6DAA6D;IAC7D,UAAU,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAA;CACxE;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,qBAAqB,GAC5B,gBAAgB,CAmGlB"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Day-first baseline compute (Sprint 5 §7 / T-13 / T-14, principle 27).
|
|
3
|
+
*
|
|
4
|
+
* Populates `user_baseline`, the shared "usual range" that rules, AI tools,
|
|
5
|
+
* and screens all read. One row per (brand, family, user, metric, window).
|
|
6
|
+
*
|
|
7
|
+
* ## Why day-first
|
|
8
|
+
*
|
|
9
|
+
* The naive implementation quantiles raw readings. That is wrong, and
|
|
10
|
+
* wrong in a way that looks plausible: SpO₂ sampled every 30 minutes dips
|
|
11
|
+
* during deep sleep on most nights, so a raw p10 across 30 days reports the
|
|
12
|
+
* *dip* (~87%) as the bottom of the user's usual range. It isn't — it's a
|
|
13
|
+
* normal nightly event that happens every day. The user's day-to-day
|
|
14
|
+
* variation is much narrower.
|
|
15
|
+
*
|
|
16
|
+
* So: collapse each local day to ONE value, then quantile across days. A
|
|
17
|
+
* fixture of `95, 95, 87, 95, 95` repeated for 30 days gives p10 ≈ 92.6
|
|
18
|
+
* day-first versus 87 raw — and 92.6 is the honest answer.
|
|
19
|
+
*
|
|
20
|
+
* This is also why `sample_count` counts DAYS. Fifteen days of 30-minute
|
|
21
|
+
* sampling is 720 readings but still only 15 days of evidence, and must not
|
|
22
|
+
* populate a baseline.
|
|
23
|
+
*/
|
|
24
|
+
import { baselineAggregateFor, BASELINE_MIN_DAYS, BASELINE_WINDOW_DAYS, getBaselineMetricIds, METRIC_METADATA, } from '../core/metric_metadata';
|
|
25
|
+
/**
|
|
26
|
+
* Build the day-first aggregate SQL for one (metric, window).
|
|
27
|
+
*
|
|
28
|
+
* Exported for snapshot tests — the shape of this query IS the correctness
|
|
29
|
+
* property, so it is worth asserting directly rather than only through
|
|
30
|
+
* results.
|
|
31
|
+
*/
|
|
32
|
+
export function buildBaselineSql(metric, windowDays) {
|
|
33
|
+
const meta = METRIC_METADATA[metric];
|
|
34
|
+
const aggregate = baselineAggregateFor(metric);
|
|
35
|
+
const column = meta.column;
|
|
36
|
+
const view = `v_${meta.table}`;
|
|
37
|
+
// DuckDB cannot bind a parameter inside an INTERVAL literal, but it can
|
|
38
|
+
// multiply a static unit interval by a bound integer. Same trick the
|
|
39
|
+
// rules compiler uses.
|
|
40
|
+
const windowBind = `(INTERVAL 1 DAY) * $windowDays`;
|
|
41
|
+
let dailySelect;
|
|
42
|
+
let tsColumn;
|
|
43
|
+
if (aggregate === 'session') {
|
|
44
|
+
// Sleep is already attributed to a local night by the mapper's 6pm-6pm
|
|
45
|
+
// rule, so `night_of` is a better day key than re-deriving one from a
|
|
46
|
+
// timestamp — it is DST-correct by construction.
|
|
47
|
+
//
|
|
48
|
+
// NOTE: this groups, where the Sprint 5 design sketch skipped the
|
|
49
|
+
// GROUP BY for session metrics. Skipping it makes `count(*)` count
|
|
50
|
+
// SESSIONS, so a user with two sessions a night would satisfy the
|
|
51
|
+
// 20-day minimum after 10 nights — contradicting T-14. Summing per
|
|
52
|
+
// night also gives the right daily value: total sleep that night.
|
|
53
|
+
dailySelect = `night_of AS day, sum(${column}) AS daily_value`;
|
|
54
|
+
tsColumn = 'ts_start';
|
|
55
|
+
return `
|
|
56
|
+
WITH daily_values AS (
|
|
57
|
+
SELECT ${dailySelect}
|
|
58
|
+
FROM ${view}
|
|
59
|
+
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
60
|
+
AND ${tsColumn} > now() - ${windowBind}
|
|
61
|
+
GROUP BY night_of
|
|
62
|
+
)
|
|
63
|
+
${quantileSelect()}
|
|
64
|
+
`.trim();
|
|
65
|
+
}
|
|
66
|
+
const fn = aggregate === 'sum' ? 'sum' : 'avg';
|
|
67
|
+
tsColumn = 'ts';
|
|
68
|
+
dailySelect
|
|
69
|
+
= `date_trunc('day', timezone($tz, ${tsColumn})) AS day, ${fn}(${column}) AS daily_value`;
|
|
70
|
+
return `
|
|
71
|
+
WITH daily_values AS (
|
|
72
|
+
SELECT ${dailySelect}
|
|
73
|
+
FROM ${view}
|
|
74
|
+
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
75
|
+
AND ${tsColumn} > now() - ${windowBind}
|
|
76
|
+
GROUP BY day
|
|
77
|
+
)
|
|
78
|
+
${quantileSelect()}
|
|
79
|
+
`.trim();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The quantile pass, identical across aggregate types.
|
|
83
|
+
*
|
|
84
|
+
* `HAVING count(*) >= n` rather than `HAVING sample_count >= n`: the alias
|
|
85
|
+
* is not guaranteed visible in HAVING across DuckDB versions, and getting
|
|
86
|
+
* this wrong would silently write under-evidenced baselines.
|
|
87
|
+
*/
|
|
88
|
+
function quantileSelect() {
|
|
89
|
+
return `SELECT
|
|
90
|
+
quantile_cont(daily_value, 0.05) AS p05,
|
|
91
|
+
quantile_cont(daily_value, 0.10) AS p10,
|
|
92
|
+
quantile_cont(daily_value, 0.50) AS p50,
|
|
93
|
+
quantile_cont(daily_value, 0.90) AS p90,
|
|
94
|
+
quantile_cont(daily_value, 0.95) AS p95,
|
|
95
|
+
avg(daily_value) AS mean,
|
|
96
|
+
stddev_samp(daily_value) AS stddev,
|
|
97
|
+
count(*) AS sample_count
|
|
98
|
+
FROM daily_values
|
|
99
|
+
HAVING count(*) >= ${BASELINE_MIN_DAYS}`;
|
|
100
|
+
}
|
|
101
|
+
/** UPSERT — recompute replaces the previous row for the same key. */
|
|
102
|
+
const UPSERT_SQL = `
|
|
103
|
+
INSERT INTO user_baseline (
|
|
104
|
+
brand, family_id, user_id, metric, window_days,
|
|
105
|
+
p05, p10, p50, p90, p95, mean, stddev, sample_count, computed_at
|
|
106
|
+
) VALUES (
|
|
107
|
+
$brand, $familyId, $userId, $metric, $windowDays,
|
|
108
|
+
$p05, $p10, $p50, $p90, $p95, $mean, $stddev, $sampleCount, now()
|
|
109
|
+
)
|
|
110
|
+
ON CONFLICT (brand, family_id, user_id, metric, window_days) DO UPDATE SET
|
|
111
|
+
p05 = excluded.p05,
|
|
112
|
+
p10 = excluded.p10,
|
|
113
|
+
p50 = excluded.p50,
|
|
114
|
+
p90 = excluded.p90,
|
|
115
|
+
p95 = excluded.p95,
|
|
116
|
+
mean = excluded.mean,
|
|
117
|
+
stddev = excluded.stddev,
|
|
118
|
+
sample_count = excluded.sample_count,
|
|
119
|
+
computed_at = excluded.computed_at
|
|
120
|
+
`.trim();
|
|
121
|
+
export function createBaselineComputer(config) {
|
|
122
|
+
const { analytics, eventBus, logger } = config;
|
|
123
|
+
const metrics = config.metrics ?? getBaselineMetricIds();
|
|
124
|
+
const windows = config.windows ?? BASELINE_WINDOW_DAYS;
|
|
125
|
+
async function computeOne(metric, windowDays, ctx) {
|
|
126
|
+
const sql = buildBaselineSql(metric, windowDays);
|
|
127
|
+
const rows = await analytics.execute(sql, {
|
|
128
|
+
userId: ctx.userId,
|
|
129
|
+
brand: ctx.brand,
|
|
130
|
+
familyId: ctx.familyId,
|
|
131
|
+
tz: ctx.userTimezone,
|
|
132
|
+
windowDays,
|
|
133
|
+
});
|
|
134
|
+
// HAVING filtered it out — fewer than BASELINE_MIN_DAYS distinct days.
|
|
135
|
+
// Not an error: a new user simply has no baseline yet, and consumers
|
|
136
|
+
// render "still learning" rather than a wrong range.
|
|
137
|
+
const row = rows[0];
|
|
138
|
+
if (!row || row.sample_count === null || row.sample_count < BASELINE_MIN_DAYS) {
|
|
139
|
+
logger?.debug('baseline: insufficient days, row not written', {
|
|
140
|
+
metric,
|
|
141
|
+
windowDays,
|
|
142
|
+
userId: ctx.userId,
|
|
143
|
+
sampleCount: row?.sample_count ?? 0,
|
|
144
|
+
required: BASELINE_MIN_DAYS,
|
|
145
|
+
});
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
await analytics.execute(UPSERT_SQL, {
|
|
149
|
+
brand: ctx.brand,
|
|
150
|
+
familyId: ctx.familyId,
|
|
151
|
+
userId: ctx.userId,
|
|
152
|
+
metric,
|
|
153
|
+
windowDays,
|
|
154
|
+
p05: row.p05,
|
|
155
|
+
p10: row.p10,
|
|
156
|
+
p50: row.p50,
|
|
157
|
+
p90: row.p90,
|
|
158
|
+
p95: row.p95,
|
|
159
|
+
mean: row.mean,
|
|
160
|
+
stddev: row.stddev,
|
|
161
|
+
sampleCount: row.sample_count,
|
|
162
|
+
});
|
|
163
|
+
// Registry queries (`spo2.compareBaseline`, `spo2.baselineMaturity`)
|
|
164
|
+
// invalidate on this.
|
|
165
|
+
eventBus?.emit('user_baseline:updated', {
|
|
166
|
+
userId: ctx.userId,
|
|
167
|
+
metric,
|
|
168
|
+
windowDays,
|
|
169
|
+
sampleCount: row.sample_count,
|
|
170
|
+
computedAt: new Date().toISOString(),
|
|
171
|
+
});
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
computeOne,
|
|
176
|
+
async computeAll(ctx) {
|
|
177
|
+
let computed = 0;
|
|
178
|
+
let skipped = 0;
|
|
179
|
+
let failed = 0;
|
|
180
|
+
for (const metric of metrics) {
|
|
181
|
+
for (const windowDays of windows) {
|
|
182
|
+
try {
|
|
183
|
+
const written = await computeOne(metric, windowDays, ctx);
|
|
184
|
+
if (written)
|
|
185
|
+
computed += 1;
|
|
186
|
+
else
|
|
187
|
+
skipped += 1;
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
// One metric's failure must not abort the other twenty.
|
|
191
|
+
failed += 1;
|
|
192
|
+
logger?.warn('baseline: compute failed', {
|
|
193
|
+
metric,
|
|
194
|
+
windowDays,
|
|
195
|
+
userId: ctx.userId,
|
|
196
|
+
err: err instanceof Error ? err.message : String(err),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
logger?.debug('baseline: compute cycle complete', {
|
|
202
|
+
userId: ctx.userId,
|
|
203
|
+
computed,
|
|
204
|
+
skipped,
|
|
205
|
+
failed,
|
|
206
|
+
});
|
|
207
|
+
return { computed, skipped, failed };
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=baseline-compute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline-compute.js","sourceRoot":"","sources":["../../src/sync/baseline-compute.ts"],"names":[],"mappings":"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"}
|
package/dist/sync/events.d.ts
CHANGED
|
@@ -9,15 +9,20 @@
|
|
|
9
9
|
* The `EventBus` is injected by the app (typically `@mongrov/core`'s event bus).
|
|
10
10
|
* Consumers use these events to invalidate caches / re-run subscriptions.
|
|
11
11
|
*/
|
|
12
|
+
import type { EventBus as AnalyticsEventBus } from '../core/types';
|
|
12
13
|
import type { FlushedEvent, SyncEmitter } from './flusher';
|
|
13
14
|
import type { PushResult } from './pusher';
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* The emit-only half of the package's `EventBus` contract.
|
|
17
|
+
*
|
|
18
|
+
* Derived from `core/types.ts` rather than redeclared: the sync layer only
|
|
19
|
+
* ever publishes, so requiring `subscribe` / `subscribePattern` here would
|
|
20
|
+
* force every host (React Query cache, mitt, EventEmitter, custom fan-out)
|
|
21
|
+
* to implement surface it never uses. Deriving keeps the two in step — a
|
|
22
|
+
* change to the core `emit` signature surfaces here instead of silently
|
|
23
|
+
* producing two incompatible buses in one package.
|
|
17
24
|
*/
|
|
18
|
-
export
|
|
19
|
-
emit: (name: string, payload?: unknown) => void;
|
|
20
|
-
}
|
|
25
|
+
export type EventBus = Pick<AnalyticsEventBus, 'emit'>;
|
|
21
26
|
export interface FlushInsertPayload {
|
|
22
27
|
table: string;
|
|
23
28
|
rowsFlushed: number;
|
|
@@ -27,6 +32,15 @@ export interface SyncCompletePayload {
|
|
|
27
32
|
table: string;
|
|
28
33
|
rowsPushed: number;
|
|
29
34
|
}
|
|
35
|
+
/** Payload of the `batch:complete` bus event (Sprint 5 T-12). */
|
|
36
|
+
export interface BatchCompletePayload {
|
|
37
|
+
batchId: string;
|
|
38
|
+
affectedTables: string[];
|
|
39
|
+
affectedUserIds: string[];
|
|
40
|
+
brand?: string;
|
|
41
|
+
familyId?: string;
|
|
42
|
+
rowCounts: Record<string, number>;
|
|
43
|
+
}
|
|
30
44
|
/**
|
|
31
45
|
* Adapts an `EventBus` into a `SyncEmitter` (as consumed by `BatchFlusher.emit`).
|
|
32
46
|
* Only successful flushes produce an `${table}:insert` event; failures pass
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/sync/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/sync/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C;;;;;;;;;GASG;AACH,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAA;AAEtD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,GAAG,WAAW,CA4B1D;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAA;AAEtD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,WAAW,CAUzD;AAED;;;GAGG;AACH,YAAY,EAAE,YAAY,EAAE,CAAA"}
|
package/dist/sync/events.js
CHANGED
|
@@ -24,6 +24,23 @@ export function bindFlushEvents(bus) {
|
|
|
24
24
|
reason: event.payload.reason,
|
|
25
25
|
};
|
|
26
26
|
bus.emit(`${event.payload.table}:insert`, payload);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (event.type === 'batch-complete') {
|
|
30
|
+
// Both event families are emitted, and they serve different
|
|
31
|
+
// consumers: `{table}:insert` drives cache invalidation, which wants
|
|
32
|
+
// to refresh as early as possible, while `batch:complete` drives rule
|
|
33
|
+
// evaluation, which must not run until every table has landed
|
|
34
|
+
// (Sprint 5 item (a)).
|
|
35
|
+
const payload = {
|
|
36
|
+
batchId: event.payload.batchId,
|
|
37
|
+
affectedTables: event.payload.affectedTables,
|
|
38
|
+
affectedUserIds: event.payload.affectedUserIds,
|
|
39
|
+
brand: event.payload.brand,
|
|
40
|
+
familyId: event.payload.familyId,
|
|
41
|
+
rowCounts: event.payload.rowCounts,
|
|
42
|
+
};
|
|
43
|
+
bus.emit('batch:complete', payload);
|
|
27
44
|
}
|
|
28
45
|
};
|
|
29
46
|
}
|
package/dist/sync/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/sync/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/sync/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAuCH;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAa;IAC3C,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAuB;gBAClC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;gBAC1B,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,WAAW;gBACtC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM;aAC7B,CAAA;YACD,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,SAAS,EAAE,OAAO,CAAC,CAAA;YAClD,OAAM;QACR,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;YACpC,4DAA4D;YAC5D,qEAAqE;YACrE,sEAAsE;YACtE,8DAA8D;YAC9D,uBAAuB;YACvB,MAAM,OAAO,GAAyB;gBACpC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO;gBAC9B,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,cAAc;gBAC5C,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,eAAe;gBAC9C,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;gBAC1B,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ;gBAChC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS;aACnC,CAAA;YACD,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAA;QACrC,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAID;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,GAAa;IAC1C,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,IAAI,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YACvC,MAAM,OAAO,GAAwB;gBACnC,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,UAAU,EAAE,MAAM,CAAC,UAAU;aAC9B,CAAA;YACD,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,gBAAgB,EAAE,OAAO,CAAC,CAAA;QACpD,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
|
package/dist/sync/factory.d.ts
CHANGED
|
@@ -50,12 +50,33 @@ export interface CreateSyncManagerConfig {
|
|
|
50
50
|
taskName?: string;
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
|
-
* Optional rules engine wired into the flush pipeline.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* Optional rules engine wired into the flush pipeline. Evaluation is
|
|
54
|
+
* driven by `batch:complete` (see `strictBatchOrdering`). Fire-and-forget:
|
|
55
|
+
* the evaluator swallows its own errors, so we never block a sync cycle
|
|
56
|
+
* on rules.
|
|
57
57
|
*/
|
|
58
58
|
rulesEngine?: RulesEngine;
|
|
59
|
+
/**
|
|
60
|
+
* When true (default), rules evaluate once per batch on `batch:complete`
|
|
61
|
+
* rather than per table on each `{table}:insert` (Sprint 5 item (a)).
|
|
62
|
+
*
|
|
63
|
+
* This is the fix for a real race: a `context: 'asleep'` rule JOINs
|
|
64
|
+
* `v_sleep_session`, and firing it the moment `spo2` flushes lets it
|
|
65
|
+
* evaluate against a night whose sleep rows are still buffered. Set
|
|
66
|
+
* false only for a consumer that wants lower evaluation latency and
|
|
67
|
+
* ships no context-JOIN or consecutive rules.
|
|
68
|
+
*/
|
|
69
|
+
strictBatchOrdering?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve a user's IANA timezone for day-first baseline bucketing
|
|
72
|
+
* (Sprint 5 §7). `User.timezone` lives in auth/RxDB, so it has to be
|
|
73
|
+
* injected rather than read here. Falls back to the device zone when
|
|
74
|
+
* omitted or when it resolves undefined — wrong-but-close beats
|
|
75
|
+
* refusing to compute a baseline at all.
|
|
76
|
+
*/
|
|
77
|
+
userTimezoneProvider?: (userId: string) => Promise<string | undefined>;
|
|
78
|
+
/** Disable the per-cycle baseline recompute (Sprint 5 T-15). */
|
|
79
|
+
computeBaselines?: boolean;
|
|
59
80
|
logger?: SchedulerLogger;
|
|
60
81
|
/**
|
|
61
82
|
* Consumer-provided translation between firmware ring-config semantics
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/sync/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/sync/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAI5E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAI/C,OAAO,KAAK,EAIV,oBAAoB,EACrB,MAAM,gBAAgB,CAAA;AAIvB,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,eAAe,EAGrB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAc,WAAW,EAAkC,MAAM,WAAW,CAAA;AAExF,OAAO,KAAK,EAAe,cAAc,EAAe,MAAM,SAAS,CAAA;AAIvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAyCjD,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,eAAe,CAAA;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,aAAa,CAAA;IAClB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;IAC9C,cAAc,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,cAAc,CAAC,EAAE,kBAAkB,CAAA;IACnC,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,QAAQ,CAAC,EAAE;QACT,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,MAAM,CAAC,EAAE,cAAc,CAAA;KACxB,CAAA;IACD,SAAS,CAAC,EAAE;QACV,gBAAgB,CAAC,EAAE,OAAO,CAAA;QAC1B,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IACD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;;;;;;OASG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACtE,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;CAC5C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,WAAW,CAqT9E"}
|