@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.
Files changed (224) hide show
  1. package/README.md +23 -10
  2. package/UPGRADING.md +370 -0
  3. package/dist/core/device-events.d.ts +14 -0
  4. package/dist/core/device-events.d.ts.map +1 -0
  5. package/dist/core/device-events.js +13 -0
  6. package/dist/core/device-events.js.map +1 -0
  7. package/dist/core/errors.d.ts +1 -1
  8. package/dist/core/errors.d.ts.map +1 -1
  9. package/dist/core/errors.js.map +1 -1
  10. package/dist/core/extensions.d.ts +21 -4
  11. package/dist/core/extensions.d.ts.map +1 -1
  12. package/dist/core/extensions.js +21 -4
  13. package/dist/core/extensions.js.map +1 -1
  14. package/dist/core/factory.d.ts.map +1 -1
  15. package/dist/core/factory.js +124 -18
  16. package/dist/core/factory.js.map +1 -1
  17. package/dist/core/hooks.js +1 -1
  18. package/dist/core/hooks.js.map +1 -1
  19. package/dist/core/index.d.ts +11 -1
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/core/index.js +14 -0
  22. package/dist/core/index.js.map +1 -1
  23. package/dist/core/insight.d.ts +44 -0
  24. package/dist/core/insight.d.ts.map +1 -0
  25. package/dist/core/insight.js +62 -0
  26. package/dist/core/insight.js.map +1 -0
  27. package/dist/core/machine.d.ts.map +1 -1
  28. package/dist/core/machine.js +9 -1
  29. package/dist/core/machine.js.map +1 -1
  30. package/dist/core/metric_metadata.d.ts +57 -2
  31. package/dist/core/metric_metadata.d.ts.map +1 -1
  32. package/dist/core/metric_metadata.js +36 -8
  33. package/dist/core/metric_metadata.js.map +1 -1
  34. package/dist/core/migrations.d.ts +6 -0
  35. package/dist/core/migrations.d.ts.map +1 -1
  36. package/dist/core/migrations.js +92 -4
  37. package/dist/core/migrations.js.map +1 -1
  38. package/dist/core/retention.d.ts +44 -13
  39. package/dist/core/retention.d.ts.map +1 -1
  40. package/dist/core/retention.js +50 -22
  41. package/dist/core/retention.js.map +1 -1
  42. package/dist/core/schemas.d.ts +74 -3
  43. package/dist/core/schemas.d.ts.map +1 -1
  44. package/dist/core/schemas.js +168 -5
  45. package/dist/core/schemas.js.map +1 -1
  46. package/dist/core/table_metadata.d.ts.map +1 -1
  47. package/dist/core/table_metadata.js +4 -0
  48. package/dist/core/table_metadata.js.map +1 -1
  49. package/dist/core/types.d.ts +45 -10
  50. package/dist/core/types.d.ts.map +1 -1
  51. package/dist/core/warehouse.d.ts +28 -0
  52. package/dist/core/warehouse.d.ts.map +1 -1
  53. package/dist/core/warehouse.js +44 -0
  54. package/dist/core/warehouse.js.map +1 -1
  55. package/dist/core/watermark-cache.d.ts +75 -0
  56. package/dist/core/watermark-cache.d.ts.map +1 -0
  57. package/dist/core/watermark-cache.js +0 -0
  58. package/dist/core/watermark-cache.js.map +1 -0
  59. package/dist/rules/__fakes__/fakeEngine.d.ts +2 -0
  60. package/dist/rules/__fakes__/fakeEngine.d.ts.map +1 -1
  61. package/dist/rules/__fakes__/fakeEngine.js +4 -0
  62. package/dist/rules/__fakes__/fakeEngine.js.map +1 -1
  63. package/dist/rules/baseline-reader.d.ts +55 -0
  64. package/dist/rules/baseline-reader.d.ts.map +1 -0
  65. package/dist/rules/baseline-reader.js +111 -0
  66. package/dist/rules/baseline-reader.js.map +1 -0
  67. package/dist/rules/compiler.d.ts +36 -1
  68. package/dist/rules/compiler.d.ts.map +1 -1
  69. package/dist/rules/compiler.js +185 -39
  70. package/dist/rules/compiler.js.map +1 -1
  71. package/dist/rules/defaults/luminx.d.ts.map +1 -1
  72. package/dist/rules/defaults/luminx.js +8 -0
  73. package/dist/rules/defaults/luminx.js.map +1 -1
  74. package/dist/rules/defaults/viva.d.ts.map +1 -1
  75. package/dist/rules/defaults/viva.js +7 -0
  76. package/dist/rules/defaults/viva.js.map +1 -1
  77. package/dist/rules/defaults/yogaring.d.ts.map +1 -1
  78. package/dist/rules/defaults/yogaring.js +7 -0
  79. package/dist/rules/defaults/yogaring.js.map +1 -1
  80. package/dist/rules/defaults/ziva.d.ts.map +1 -1
  81. package/dist/rules/defaults/ziva.js +100 -24
  82. package/dist/rules/defaults/ziva.js.map +1 -1
  83. package/dist/rules/evaluator.d.ts +14 -3
  84. package/dist/rules/evaluator.d.ts.map +1 -1
  85. package/dist/rules/evaluator.js +157 -5
  86. package/dist/rules/evaluator.js.map +1 -1
  87. package/dist/rules/factory.d.ts.map +1 -1
  88. package/dist/rules/factory.js +5 -1
  89. package/dist/rules/factory.js.map +1 -1
  90. package/dist/rules/index.d.ts +2 -1
  91. package/dist/rules/index.d.ts.map +1 -1
  92. package/dist/rules/index.js +2 -1
  93. package/dist/rules/index.js.map +1 -1
  94. package/dist/rules/schema.d.ts +78 -7
  95. package/dist/rules/schema.d.ts.map +1 -1
  96. package/dist/rules/schema.js +44 -0
  97. package/dist/rules/schema.js.map +1 -1
  98. package/dist/rules/types.d.ts +16 -1
  99. package/dist/rules/types.d.ts.map +1 -1
  100. package/dist/rules/validator.d.ts.map +1 -1
  101. package/dist/rules/validator.js +56 -0
  102. package/dist/rules/validator.js.map +1 -1
  103. package/dist/sync/baseline-compute.d.ts +63 -0
  104. package/dist/sync/baseline-compute.d.ts.map +1 -0
  105. package/dist/sync/baseline-compute.js +211 -0
  106. package/dist/sync/baseline-compute.js.map +1 -0
  107. package/dist/sync/events.d.ts +19 -5
  108. package/dist/sync/events.d.ts.map +1 -1
  109. package/dist/sync/events.js +17 -0
  110. package/dist/sync/events.js.map +1 -1
  111. package/dist/sync/factory.d.ts +25 -4
  112. package/dist/sync/factory.d.ts.map +1 -1
  113. package/dist/sync/factory.js +109 -15
  114. package/dist/sync/factory.js.map +1 -1
  115. package/dist/sync/fetcher.d.ts +15 -2
  116. package/dist/sync/fetcher.d.ts.map +1 -1
  117. package/dist/sync/fetcher.js +59 -11
  118. package/dist/sync/fetcher.js.map +1 -1
  119. package/dist/sync/flusher.d.ts +52 -2
  120. package/dist/sync/flusher.d.ts.map +1 -1
  121. package/dist/sync/flusher.js +78 -9
  122. package/dist/sync/flusher.js.map +1 -1
  123. package/dist/sync/index.d.ts +4 -2
  124. package/dist/sync/index.d.ts.map +1 -1
  125. package/dist/sync/index.js +1 -0
  126. package/dist/sync/index.js.map +1 -1
  127. package/dist/sync/mapper/activity.d.ts.map +1 -1
  128. package/dist/sync/mapper/activity.js +2 -1
  129. package/dist/sync/mapper/activity.js.map +1 -1
  130. package/dist/sync/mapper/battery.d.ts +14 -7
  131. package/dist/sync/mapper/battery.d.ts.map +1 -1
  132. package/dist/sync/mapper/battery.js +13 -7
  133. package/dist/sync/mapper/battery.js.map +1 -1
  134. package/dist/sync/mapper/firmware.d.ts.map +1 -1
  135. package/dist/sync/mapper/firmware.js +4 -1
  136. package/dist/sync/mapper/firmware.js.map +1 -1
  137. package/dist/sync/mapper/ring-config.js +1 -1
  138. package/dist/sync/mapper/ring-config.js.map +1 -1
  139. package/dist/sync/mapper/schema.d.ts +411 -0
  140. package/dist/sync/mapper/schema.d.ts.map +1 -0
  141. package/dist/sync/mapper/schema.js +128 -0
  142. package/dist/sync/mapper/schema.js.map +1 -0
  143. package/dist/sync/mapper/sleep.d.ts +58 -13
  144. package/dist/sync/mapper/sleep.d.ts.map +1 -1
  145. package/dist/sync/mapper/sleep.js +141 -44
  146. package/dist/sync/mapper/sleep.js.map +1 -1
  147. package/dist/sync/mapper/types.d.ts +75 -10
  148. package/dist/sync/mapper/types.d.ts.map +1 -1
  149. package/dist/sync/scheduler.d.ts +7 -0
  150. package/dist/sync/scheduler.d.ts.map +1 -1
  151. package/dist/sync/scheduler.js +14 -0
  152. package/dist/sync/scheduler.js.map +1 -1
  153. package/dist/tools/__fakes__/engine.d.ts +2 -0
  154. package/dist/tools/__fakes__/engine.d.ts.map +1 -1
  155. package/dist/tools/__fakes__/engine.js +10 -0
  156. package/dist/tools/__fakes__/engine.js.map +1 -1
  157. package/dist/tools/authorize.d.ts +19 -12
  158. package/dist/tools/authorize.d.ts.map +1 -1
  159. package/dist/tools/authorize.js +31 -40
  160. package/dist/tools/authorize.js.map +1 -1
  161. package/dist/tools/factory.d.ts +2 -0
  162. package/dist/tools/factory.d.ts.map +1 -1
  163. package/dist/tools/factory.js +12 -1
  164. package/dist/tools/factory.js.map +1 -1
  165. package/dist/tools/formatters/copy-guidelines.d.ts +57 -0
  166. package/dist/tools/formatters/copy-guidelines.d.ts.map +1 -0
  167. package/dist/tools/formatters/copy-guidelines.js +110 -0
  168. package/dist/tools/formatters/copy-guidelines.js.map +1 -0
  169. package/dist/tools/formatters/index.d.ts +14 -0
  170. package/dist/tools/formatters/index.d.ts.map +1 -0
  171. package/dist/tools/formatters/index.js +14 -0
  172. package/dist/tools/formatters/index.js.map +1 -0
  173. package/dist/tools/formatters/spo2.d.ts +21 -0
  174. package/dist/tools/formatters/spo2.d.ts.map +1 -0
  175. package/dist/tools/formatters/spo2.js +79 -0
  176. package/dist/tools/formatters/spo2.js.map +1 -0
  177. package/dist/tools/{formatters.d.ts → formatters/text.d.ts} +1 -1
  178. package/dist/tools/formatters/text.d.ts.map +1 -0
  179. package/dist/tools/{formatters.js → formatters/text.js} +1 -1
  180. package/dist/tools/formatters/text.js.map +1 -0
  181. package/dist/tools/impls/activity.d.ts +2 -2
  182. package/dist/tools/impls/activity.js +7 -3
  183. package/dist/tools/impls/activity.js.map +1 -1
  184. package/dist/tools/impls/anomaly.d.ts +2 -2
  185. package/dist/tools/impls/anomaly.js +8 -4
  186. package/dist/tools/impls/anomaly.js.map +1 -1
  187. package/dist/tools/impls/compare.d.ts +2 -2
  188. package/dist/tools/impls/compare.js +8 -4
  189. package/dist/tools/impls/compare.js.map +1 -1
  190. package/dist/tools/impls/hrv.d.ts +2 -2
  191. package/dist/tools/impls/hrv.js +6 -2
  192. package/dist/tools/impls/hrv.js.map +1 -1
  193. package/dist/tools/impls/insights.d.ts +4 -4
  194. package/dist/tools/impls/insights.d.ts.map +1 -1
  195. package/dist/tools/impls/insights.js +10 -3
  196. package/dist/tools/impls/insights.js.map +1 -1
  197. package/dist/tools/impls/sleep.d.ts +2 -2
  198. package/dist/tools/impls/sleep.js +6 -2
  199. package/dist/tools/impls/sleep.js.map +1 -1
  200. package/dist/tools/impls/spo2.d.ts +52 -0
  201. package/dist/tools/impls/spo2.d.ts.map +1 -0
  202. package/dist/tools/impls/spo2.js +65 -0
  203. package/dist/tools/impls/spo2.js.map +1 -0
  204. package/dist/tools/index.d.ts +1 -0
  205. package/dist/tools/index.d.ts.map +1 -1
  206. package/dist/tools/index.js +4 -0
  207. package/dist/tools/index.js.map +1 -1
  208. package/dist/tools/mcp/guard.d.ts +28 -11
  209. package/dist/tools/mcp/guard.d.ts.map +1 -1
  210. package/dist/tools/mcp/guard.js +45 -15
  211. package/dist/tools/mcp/guard.js.map +1 -1
  212. package/dist/tools/mcp/index.d.ts +1 -1
  213. package/dist/tools/mcp/index.d.ts.map +1 -1
  214. package/dist/tools/mcp/index.js +1 -1
  215. package/dist/tools/mcp/index.js.map +1 -1
  216. package/dist/tools/mcp/server.d.ts.map +1 -1
  217. package/dist/tools/mcp/server.js +4 -0
  218. package/dist/tools/mcp/server.js.map +1 -1
  219. package/dist/tools/mcp/transports/http.d.ts.map +1 -1
  220. package/dist/tools/mcp/transports/http.js +4 -0
  221. package/dist/tools/mcp/transports/http.js.map +1 -1
  222. package/package.json +8 -5
  223. package/dist/tools/formatters.d.ts.map +0 -1
  224. package/dist/tools/formatters.js.map +0 -1
@@ -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 rawColumn = meta.column;
84
- const table = sanitizeIdent(rawTable);
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 filter = `WHERE user_id = $userId AND brand = $brand AND family_id = $familyId `
92
- + `AND ${ts} > NOW() - ${interval}`;
93
- const agg = aggExpr(rule.aggregation, column, ts);
94
- const params = {};
95
- const description = `${rule.metric} ${rule.aggregation} over ${rule.window} ${rule.compare} ${describeTarget(rule.target)}`;
96
- const { sql, params: extraParams } = buildForTarget({
97
- table,
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
- Object.assign(params, extraParams);
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 { table, ts, column, agg, filter, target, compare } = args;
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
- FROM ${table}
129
- ${filter}
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
- FROM ${table}
143
- ${filter}
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(${column}) AS mean
158
- FROM ${table}
159
- WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
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
- FROM ${table}
165
- ${filter}
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(${column}) AS mean, stddev_pop(${column}) AS sd
176
- FROM ${table}
177
- WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
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
- FROM ${table}
183
- ${filter}
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":"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"}
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;AA6DrC,eAAO,MAAM,cAAc,EAAE,IAAI,EAA2C,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDZ,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAW,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA"}
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;AA0CrC,eAAO,MAAM,YAAY,EAAE,IAAI,EAAyC,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCZ,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAW,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA"}
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;AA0CrC,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAA6C,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"}
@@ -3,6 +3,13 @@
3
3
  */
4
4
  import { parseCatalog } from './loader';
5
5
  const TOML = `
6
+ # YogaRing brand default rules (placeholder set for v0.1.0).
7
+ #
8
+ # YogaRing focuses on session-adjacent metrics; v0.1.0 ships a stress
9
+ # nudge + a low-activity nudge derived from the shared metric surface.
10
+ #
11
+ # See \`yogaring.ts\` — this file is the source of truth.
12
+
6
13
  [[rule]]
7
14
  id = "yogaring.stress-elevated"
8
15
  brand = "yogaring"
@@ -1 +1 @@
1
- {"version":3,"file":"yogaring.js","sourceRoot":"","sources":["../../../src/rules/defaults/yogaring.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCZ,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAW,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAA"}
1
+ {"version":3,"file":"yogaring.js","sourceRoot":"","sources":["../../../src/rules/defaults/yogaring.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6CZ,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAW,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ziva.d.ts","sourceRoot":"","sources":["../../../src/rules/defaults/ziva.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAgFrC,eAAO,MAAM,YAAY,EAAE,IAAI,EAAyC,CAAA"}
1
+ {"version":3,"file":"ziva.d.ts","sourceRoot":"","sources":["../../../src/rules/defaults/ziva.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AA4JrC,eAAO,MAAM,YAAY,EAAE,IAAI,EAAyC,CAAA"}
@@ -7,11 +7,26 @@
7
7
  */
8
8
  import { parseCatalog } from './loader';
9
9
  const TOML = `
10
+ # Ziva brand default rules.
11
+ #
12
+ # Copied verbatim into \`ziva.ts\` as an inline template string. Do NOT edit
13
+ # the wrapper file directly — treat this .toml as the source of truth.
14
+ #
15
+ # Rules cover the core Ziva health surface: HRV drop, sleep deprivation,
16
+ # elevated stress, and low weekly activity. Verbatim from
17
+ # \`.specifica/features/analytics-rules/spec.md\` §Brand defaults.
18
+ #
19
+ # Two of these deliberately use relative targets rather than absolutes:
20
+ # \`baseline_percent\` (HRV) and \`baseline_stddev\` (stress). A fixed HRV
21
+ # threshold is meaningless across users — 30 ms is alarming for one person
22
+ # and unremarkable for another — so the shipped catalog exercises the
23
+ # compiler's baseline paths, not just the absolute path.
24
+
10
25
  [[rule]]
11
26
  id = "ziva.hrv-drop-30"
12
27
  brand = "ziva"
13
- name = "HRV drop below 30 ms"
14
- description = "Average HRV over the last 24h fell below 30 ms."
28
+ name = "Sudden HRV drop"
29
+ description = "HRV dropped 30% below 7-day baseline"
15
30
  metric = "hrv_ms"
16
31
  window = "24h"
17
32
  aggregation = "avg"
@@ -19,65 +34,126 @@ compare = "less_than"
19
34
  severity = "warn"
20
35
 
21
36
  [rule.target]
22
- type = "absolute"
23
- value = 30
37
+ type = "baseline_percent"
38
+ windowDays = 7
39
+ percent = 70
24
40
 
25
41
  [rule.throttle]
26
- minGapMinutes = 360
27
- maxPerDay = 2
42
+ minGapMinutes = 720
43
+ maxPerDay = 1
28
44
 
29
45
  [[rule]]
30
- id = "ziva.sleep-deprivation-3d"
46
+ id = "ziva.sleep-deprivation-3"
31
47
  brand = "ziva"
32
- name = "Sleep deprivation"
33
- description = "Average nightly sleep across 3 nights below 360 minutes."
48
+ name = "Three nights short sleep"
49
+ description = "Average nightly sleep across 3 nights below 300 minutes."
34
50
  metric = "sleep_total_minutes"
35
51
  window = "3d"
36
52
  aggregation = "avg"
37
53
  compare = "less_than"
38
- severity = "warn"
54
+ severity = "info"
39
55
 
40
56
  [rule.target]
41
57
  type = "absolute"
42
- value = 360
58
+ value = 300
43
59
 
44
60
  [rule.throttle]
45
- minGapMinutes = 720
61
+ minGapMinutes = 1440
46
62
  maxPerDay = 1
47
63
 
48
64
  [[rule]]
49
- id = "ziva.stress-elevated"
65
+ id = "ziva.stress-elevated-day"
50
66
  brand = "ziva"
51
- name = "Elevated stress"
52
- description = "Average stress score over 6h rose above 70."
67
+ name = "Elevated stress today"
68
+ description = "Daily average stress rose 1.5 standard deviations above the 14-day baseline."
53
69
  metric = "stress"
54
- window = "6h"
70
+ window = "24h"
55
71
  aggregation = "avg"
56
72
  compare = "greater_than"
57
73
  severity = "info"
58
74
 
75
+ [rule.target]
76
+ type = "baseline_stddev"
77
+ windowDays = 14
78
+ stddevs = 1.5
79
+
80
+ [rule.throttle]
81
+ minGapMinutes = 1440
82
+ maxPerDay = 1
83
+
84
+ [[rule]]
85
+ id = "ziva.low-activity-week"
86
+ brand = "ziva"
87
+ name = "Low activity all week"
88
+ description = "Total steps across the last 7 days below 20,000."
89
+ metric = "activity_steps"
90
+ window = "7d"
91
+ aggregation = "sum"
92
+ compare = "less_than"
93
+ severity = "info"
94
+
59
95
  [rule.target]
60
96
  type = "absolute"
61
- value = 70
97
+ value = 20000
62
98
 
63
99
  [rule.throttle]
64
- minGapMinutes = 120
100
+ minGapMinutes = 10080
101
+ maxPerDay = 1
102
+
103
+ # ─────────────────────────────────────────────────────────────────────────
104
+ # SpO₂ — two rules that coexist deliberately (Sprint 5 §4, Ziva #1).
105
+ #
106
+ # A is the LOUD one: the alert the user configured in the ⚙ sheet, firing
107
+ # on a single reading below their own safe level, day or night.
108
+ # B is a SECONDARY pattern insight: a sustained nightly dip, which is a
109
+ # different observation and belongs in the feed rather than as an alert.
110
+ # They fire independently and hold independent throttle state.
111
+ # ─────────────────────────────────────────────────────────────────────────
112
+
113
+ [[rule]]
114
+ id = "ziva.spo2-safe-level"
115
+ brand = "ziva"
116
+ name = "SpO₂ below your safe level"
117
+ description = "Alerts on any single reading below your configured safe level"
118
+ metric = "spo2"
119
+ # 24h, NOT 1h. Rules evaluate at batch:complete, and a morning sync
120
+ # delivers ~8 hours of overnight readings in one batch. A wall-clock 1h
121
+ # window would silently miss anything older than an hour before eval —
122
+ # including the exact overnight crossings the "day or night" promise
123
+ # exists for. 24h covers the batch delivery envelope; the throttle below
124
+ # prevents duplicate fires from re-scanned data.
125
+ window = "24h"
126
+ aggregation = "min"
127
+ compare = "less_than"
128
+ context = "any"
129
+ consecutive = 1
130
+ severity = "warn"
131
+
132
+ [rule.target]
133
+ type = "user_setting"
134
+ key = "user:spo2SafeLevel"
135
+ defaultValue = 90
136
+
137
+ [rule.throttle]
138
+ minGapMinutes = 60
65
139
  maxPerDay = 3
66
140
 
67
141
  [[rule]]
68
- id = "ziva.activity-low-24h"
142
+ id = "ziva.spo2-desaturation-asleep"
69
143
  brand = "ziva"
70
- name = "Low activity"
71
- description = "Daily step count is below 3000."
72
- metric = "activity_steps"
144
+ name = "Nightly low SpO₂ pattern"
145
+ description = "Pattern of low SpO₂ during sleep — 3 consecutive samples below 88"
146
+ metric = "spo2"
73
147
  window = "24h"
74
- aggregation = "sum"
148
+ aggregation = "min"
75
149
  compare = "less_than"
150
+ context = "asleep"
151
+ consecutive = 3
76
152
  severity = "info"
77
153
 
78
154
  [rule.target]
79
155
  type = "absolute"
80
- value = 3000
156
+ value = 88
81
157
 
82
158
  [rule.throttle]
83
159
  minGapMinutes = 720
@@ -1 +1 @@
1
- {"version":3,"file":"ziva.js","sourceRoot":"","sources":["../../../src/rules/defaults/ziva.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4EZ,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAW,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA"}
1
+ {"version":3,"file":"ziva.js","sourceRoot":"","sources":["../../../src/rules/defaults/ziva.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwJZ,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAW,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA"}