@mongrov/analytics 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) 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 +125 -23
  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/metric_metadata.d.ts +55 -0
  28. package/dist/core/metric_metadata.d.ts.map +1 -1
  29. package/dist/core/metric_metadata.js +35 -7
  30. package/dist/core/metric_metadata.js.map +1 -1
  31. package/dist/core/migrations.d.ts.map +1 -1
  32. package/dist/core/migrations.js +64 -2
  33. package/dist/core/migrations.js.map +1 -1
  34. package/dist/core/retention.d.ts +44 -13
  35. package/dist/core/retention.d.ts.map +1 -1
  36. package/dist/core/retention.js +49 -22
  37. package/dist/core/retention.js.map +1 -1
  38. package/dist/core/schemas.d.ts +73 -2
  39. package/dist/core/schemas.d.ts.map +1 -1
  40. package/dist/core/schemas.js +154 -3
  41. package/dist/core/schemas.js.map +1 -1
  42. package/dist/core/table_metadata.d.ts.map +1 -1
  43. package/dist/core/table_metadata.js +3 -0
  44. package/dist/core/table_metadata.js.map +1 -1
  45. package/dist/core/types.d.ts +45 -10
  46. package/dist/core/types.d.ts.map +1 -1
  47. package/dist/core/warehouse.d.ts +28 -0
  48. package/dist/core/warehouse.d.ts.map +1 -1
  49. package/dist/core/warehouse.js +44 -0
  50. package/dist/core/warehouse.js.map +1 -1
  51. package/dist/core/watermark-cache.d.ts +75 -0
  52. package/dist/core/watermark-cache.d.ts.map +1 -0
  53. package/dist/core/watermark-cache.js +0 -0
  54. package/dist/core/watermark-cache.js.map +1 -0
  55. package/dist/rules/__fakes__/fakeEngine.d.ts +2 -0
  56. package/dist/rules/__fakes__/fakeEngine.d.ts.map +1 -1
  57. package/dist/rules/__fakes__/fakeEngine.js +4 -0
  58. package/dist/rules/__fakes__/fakeEngine.js.map +1 -1
  59. package/dist/rules/baseline-reader.d.ts +55 -0
  60. package/dist/rules/baseline-reader.d.ts.map +1 -0
  61. package/dist/rules/baseline-reader.js +111 -0
  62. package/dist/rules/baseline-reader.js.map +1 -0
  63. package/dist/rules/compiler.d.ts +36 -1
  64. package/dist/rules/compiler.d.ts.map +1 -1
  65. package/dist/rules/compiler.js +185 -39
  66. package/dist/rules/compiler.js.map +1 -1
  67. package/dist/rules/defaults/luminx.d.ts.map +1 -1
  68. package/dist/rules/defaults/luminx.js +8 -0
  69. package/dist/rules/defaults/luminx.js.map +1 -1
  70. package/dist/rules/defaults/viva.d.ts.map +1 -1
  71. package/dist/rules/defaults/viva.js +7 -0
  72. package/dist/rules/defaults/viva.js.map +1 -1
  73. package/dist/rules/defaults/yogaring.d.ts.map +1 -1
  74. package/dist/rules/defaults/yogaring.js +7 -0
  75. package/dist/rules/defaults/yogaring.js.map +1 -1
  76. package/dist/rules/defaults/ziva.d.ts.map +1 -1
  77. package/dist/rules/defaults/ziva.js +100 -24
  78. package/dist/rules/defaults/ziva.js.map +1 -1
  79. package/dist/rules/evaluator.d.ts +14 -3
  80. package/dist/rules/evaluator.d.ts.map +1 -1
  81. package/dist/rules/evaluator.js +157 -5
  82. package/dist/rules/evaluator.js.map +1 -1
  83. package/dist/rules/factory.d.ts.map +1 -1
  84. package/dist/rules/factory.js +5 -1
  85. package/dist/rules/factory.js.map +1 -1
  86. package/dist/rules/index.d.ts +2 -1
  87. package/dist/rules/index.d.ts.map +1 -1
  88. package/dist/rules/index.js +2 -1
  89. package/dist/rules/index.js.map +1 -1
  90. package/dist/rules/schema.d.ts +75 -4
  91. package/dist/rules/schema.d.ts.map +1 -1
  92. package/dist/rules/schema.js +44 -0
  93. package/dist/rules/schema.js.map +1 -1
  94. package/dist/rules/types.d.ts +16 -1
  95. package/dist/rules/types.d.ts.map +1 -1
  96. package/dist/rules/validator.d.ts.map +1 -1
  97. package/dist/rules/validator.js +56 -0
  98. package/dist/rules/validator.js.map +1 -1
  99. package/dist/sync/baseline-compute.d.ts +63 -0
  100. package/dist/sync/baseline-compute.d.ts.map +1 -0
  101. package/dist/sync/baseline-compute.js +211 -0
  102. package/dist/sync/baseline-compute.js.map +1 -0
  103. package/dist/sync/events.d.ts +19 -5
  104. package/dist/sync/events.d.ts.map +1 -1
  105. package/dist/sync/events.js +17 -0
  106. package/dist/sync/events.js.map +1 -1
  107. package/dist/sync/factory.d.ts +25 -4
  108. package/dist/sync/factory.d.ts.map +1 -1
  109. package/dist/sync/factory.js +108 -15
  110. package/dist/sync/factory.js.map +1 -1
  111. package/dist/sync/fetcher.d.ts +15 -2
  112. package/dist/sync/fetcher.d.ts.map +1 -1
  113. package/dist/sync/fetcher.js +43 -7
  114. package/dist/sync/fetcher.js.map +1 -1
  115. package/dist/sync/flusher.d.ts +52 -2
  116. package/dist/sync/flusher.d.ts.map +1 -1
  117. package/dist/sync/flusher.js +78 -9
  118. package/dist/sync/flusher.js.map +1 -1
  119. package/dist/sync/index.d.ts +3 -1
  120. package/dist/sync/index.d.ts.map +1 -1
  121. package/dist/sync/index.js +1 -0
  122. package/dist/sync/index.js.map +1 -1
  123. package/dist/sync/mapper/activity.d.ts.map +1 -1
  124. package/dist/sync/mapper/activity.js +2 -1
  125. package/dist/sync/mapper/activity.js.map +1 -1
  126. package/dist/sync/mapper/ring-config.js +1 -1
  127. package/dist/sync/mapper/ring-config.js.map +1 -1
  128. package/dist/sync/mapper/schema.d.ts +411 -0
  129. package/dist/sync/mapper/schema.d.ts.map +1 -0
  130. package/dist/sync/mapper/schema.js +128 -0
  131. package/dist/sync/mapper/schema.js.map +1 -0
  132. package/dist/sync/mapper/sleep.d.ts +58 -13
  133. package/dist/sync/mapper/sleep.d.ts.map +1 -1
  134. package/dist/sync/mapper/sleep.js +141 -44
  135. package/dist/sync/mapper/sleep.js.map +1 -1
  136. package/dist/sync/mapper/types.d.ts +66 -10
  137. package/dist/sync/mapper/types.d.ts.map +1 -1
  138. package/dist/sync/scheduler.d.ts +7 -0
  139. package/dist/sync/scheduler.d.ts.map +1 -1
  140. package/dist/sync/scheduler.js +14 -0
  141. package/dist/sync/scheduler.js.map +1 -1
  142. package/dist/tools/__fakes__/engine.d.ts +2 -0
  143. package/dist/tools/__fakes__/engine.d.ts.map +1 -1
  144. package/dist/tools/__fakes__/engine.js +10 -0
  145. package/dist/tools/__fakes__/engine.js.map +1 -1
  146. package/dist/tools/authorize.d.ts +19 -12
  147. package/dist/tools/authorize.d.ts.map +1 -1
  148. package/dist/tools/authorize.js +31 -40
  149. package/dist/tools/authorize.js.map +1 -1
  150. package/dist/tools/factory.d.ts +2 -0
  151. package/dist/tools/factory.d.ts.map +1 -1
  152. package/dist/tools/factory.js +12 -1
  153. package/dist/tools/factory.js.map +1 -1
  154. package/dist/tools/formatters/copy-guidelines.d.ts +57 -0
  155. package/dist/tools/formatters/copy-guidelines.d.ts.map +1 -0
  156. package/dist/tools/formatters/copy-guidelines.js +110 -0
  157. package/dist/tools/formatters/copy-guidelines.js.map +1 -0
  158. package/dist/tools/formatters/index.d.ts +14 -0
  159. package/dist/tools/formatters/index.d.ts.map +1 -0
  160. package/dist/tools/formatters/index.js +14 -0
  161. package/dist/tools/formatters/index.js.map +1 -0
  162. package/dist/tools/formatters/spo2.d.ts +21 -0
  163. package/dist/tools/formatters/spo2.d.ts.map +1 -0
  164. package/dist/tools/formatters/spo2.js +79 -0
  165. package/dist/tools/formatters/spo2.js.map +1 -0
  166. package/dist/tools/{formatters.d.ts → formatters/text.d.ts} +1 -1
  167. package/dist/tools/formatters/text.d.ts.map +1 -0
  168. package/dist/tools/{formatters.js → formatters/text.js} +1 -1
  169. package/dist/tools/formatters/text.js.map +1 -0
  170. package/dist/tools/impls/activity.d.ts +2 -2
  171. package/dist/tools/impls/activity.js +7 -3
  172. package/dist/tools/impls/activity.js.map +1 -1
  173. package/dist/tools/impls/anomaly.d.ts +2 -2
  174. package/dist/tools/impls/anomaly.js +8 -4
  175. package/dist/tools/impls/anomaly.js.map +1 -1
  176. package/dist/tools/impls/compare.d.ts +2 -2
  177. package/dist/tools/impls/compare.js +8 -4
  178. package/dist/tools/impls/compare.js.map +1 -1
  179. package/dist/tools/impls/hrv.d.ts +2 -2
  180. package/dist/tools/impls/hrv.js +6 -2
  181. package/dist/tools/impls/hrv.js.map +1 -1
  182. package/dist/tools/impls/insights.d.ts +4 -4
  183. package/dist/tools/impls/insights.d.ts.map +1 -1
  184. package/dist/tools/impls/insights.js +10 -3
  185. package/dist/tools/impls/insights.js.map +1 -1
  186. package/dist/tools/impls/sleep.d.ts +2 -2
  187. package/dist/tools/impls/sleep.js +6 -2
  188. package/dist/tools/impls/sleep.js.map +1 -1
  189. package/dist/tools/impls/spo2.d.ts +52 -0
  190. package/dist/tools/impls/spo2.d.ts.map +1 -0
  191. package/dist/tools/impls/spo2.js +65 -0
  192. package/dist/tools/impls/spo2.js.map +1 -0
  193. package/dist/tools/index.d.ts +1 -0
  194. package/dist/tools/index.d.ts.map +1 -1
  195. package/dist/tools/index.js +4 -0
  196. package/dist/tools/index.js.map +1 -1
  197. package/dist/tools/mcp/guard.d.ts +28 -11
  198. package/dist/tools/mcp/guard.d.ts.map +1 -1
  199. package/dist/tools/mcp/guard.js +45 -15
  200. package/dist/tools/mcp/guard.js.map +1 -1
  201. package/dist/tools/mcp/index.d.ts +1 -1
  202. package/dist/tools/mcp/index.d.ts.map +1 -1
  203. package/dist/tools/mcp/index.js +1 -1
  204. package/dist/tools/mcp/index.js.map +1 -1
  205. package/dist/tools/mcp/server.d.ts.map +1 -1
  206. package/dist/tools/mcp/server.js +4 -0
  207. package/dist/tools/mcp/server.js.map +1 -1
  208. package/dist/tools/mcp/transports/http.d.ts.map +1 -1
  209. package/dist/tools/mcp/transports/http.js +4 -0
  210. package/dist/tools/mcp/transports/http.js.map +1 -1
  211. package/package.json +8 -5
  212. package/dist/tools/formatters.d.ts.map +0 -1
  213. package/dist/tools/formatters.js.map +0 -1
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Baseline reader (Sprint 5 T-24).
3
+ *
4
+ * Rules with `baseline_percent` / `baseline_stddev` targets need the user's
5
+ * usual range. The scheduled job in `analytics-sync/baseline-compute.ts`
6
+ * writes it to `user_baseline` after each sync cycle — but a rule can fire
7
+ * before the first compute has run, so this reader falls back to computing
8
+ * on read.
9
+ *
10
+ * The fallback uses the **same day-first pattern** as the scheduled job.
11
+ * That consistency is the whole point of the module: if the on-read path
12
+ * quantiled raw readings while the scheduled path quantiled daily values,
13
+ * the same rule would fire or not depending purely on whether a sync cycle
14
+ * had happened yet — a heisenbug in a health alert.
15
+ *
16
+ * Results are cached for the duration of one evaluation batch. Within a
17
+ * batch, every rule x user combination reads a consistent baseline;
18
+ * across batches, fresh reads pick up the latest compute.
19
+ */
20
+ import { type BaselineWindowDays, type MetricId } from '../core/metric_metadata';
21
+ import type { AnalyticsEngine } from '../core/types';
22
+ import type { RulesLogger } from './types';
23
+ export interface Baseline {
24
+ p05: number | null;
25
+ p10: number | null;
26
+ p50: number | null;
27
+ p90: number | null;
28
+ p95: number | null;
29
+ mean: number | null;
30
+ stddev: number | null;
31
+ /** Number of DAYS the baseline is built from, never readings. */
32
+ sampleCount: number;
33
+ /** True when this came from an on-read compute, not the stored table. */
34
+ computedOnRead: boolean;
35
+ }
36
+ export interface BaselineReaderConfig {
37
+ analytics: AnalyticsEngine;
38
+ logger?: RulesLogger;
39
+ /**
40
+ * IANA zone for the on-read fallback's day bucketing. Only the fallback
41
+ * needs it — stored rows were already bucketed at compute time.
42
+ */
43
+ timezone?: string;
44
+ }
45
+ export interface BaselineReader {
46
+ /** Null when the user has fewer than `BASELINE_MIN_DAYS` days of data. */
47
+ getBaseline(userId: string, metric: MetricId, windowDays: BaselineWindowDays, ctx: {
48
+ brand: string;
49
+ familyId: string;
50
+ }): Promise<Baseline | null>;
51
+ /** Drop the per-batch cache. Called at the start of each evaluation pass. */
52
+ resetCache(): void;
53
+ }
54
+ export declare function createBaselineReader(config: BaselineReaderConfig): BaselineReader;
55
+ //# sourceMappingURL=baseline-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline-reader.d.ts","sourceRoot":"","sources":["../../src/rules/baseline-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE1C,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,eAAe,CAAA;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,WAAW,CACT,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,EAChB,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACvC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IAC3B,6EAA6E;IAC7E,UAAU,IAAI,IAAI,CAAA;CACnB;AAaD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,oBAAoB,GAC3B,cAAc,CAwGhB"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Baseline reader (Sprint 5 T-24).
3
+ *
4
+ * Rules with `baseline_percent` / `baseline_stddev` targets need the user's
5
+ * usual range. The scheduled job in `analytics-sync/baseline-compute.ts`
6
+ * writes it to `user_baseline` after each sync cycle — but a rule can fire
7
+ * before the first compute has run, so this reader falls back to computing
8
+ * on read.
9
+ *
10
+ * The fallback uses the **same day-first pattern** as the scheduled job.
11
+ * That consistency is the whole point of the module: if the on-read path
12
+ * quantiled raw readings while the scheduled path quantiled daily values,
13
+ * the same rule would fire or not depending purely on whether a sync cycle
14
+ * had happened yet — a heisenbug in a health alert.
15
+ *
16
+ * Results are cached for the duration of one evaluation batch. Within a
17
+ * batch, every rule x user combination reads a consistent baseline;
18
+ * across batches, fresh reads pick up the latest compute.
19
+ */
20
+ import { buildBaselineSql } from '../sync/baseline-compute';
21
+ import { BASELINE_MIN_DAYS, } from '../core/metric_metadata';
22
+ export function createBaselineReader(config) {
23
+ const { analytics, logger } = config;
24
+ const cache = new Map();
25
+ function key(userId, metric, windowDays, brand, familyId) {
26
+ return `${brand} ${familyId} ${userId} ${metric} ${windowDays}`;
27
+ }
28
+ async function readStored(userId, metric, windowDays, ctx) {
29
+ const rows = await analytics.execute(`SELECT p05, p10, p50, p90, p95, mean, stddev, sample_count
30
+ FROM user_baseline
31
+ WHERE brand = $brand AND family_id = $familyId AND user_id = $userId
32
+ AND metric = $metric AND window_days = $windowDays
33
+ LIMIT 1`, {
34
+ brand: ctx.brand,
35
+ familyId: ctx.familyId,
36
+ userId,
37
+ metric,
38
+ windowDays,
39
+ });
40
+ const row = rows[0];
41
+ if (!row || row.sample_count === null || row.sample_count < BASELINE_MIN_DAYS) {
42
+ return null;
43
+ }
44
+ return { ...toBaseline(row), computedOnRead: false };
45
+ }
46
+ async function computeOnRead(userId, metric, windowDays, ctx) {
47
+ // Identical SQL to the scheduled job — imported, not reimplemented, so
48
+ // the two paths cannot drift.
49
+ const rows = await analytics.execute(buildBaselineSql(metric, windowDays), {
50
+ userId,
51
+ brand: ctx.brand,
52
+ familyId: ctx.familyId,
53
+ tz: config.timezone ?? 'UTC',
54
+ windowDays,
55
+ });
56
+ const row = rows[0];
57
+ if (!row || row.sample_count === null || row.sample_count < BASELINE_MIN_DAYS) {
58
+ return null;
59
+ }
60
+ return { ...toBaseline(row), computedOnRead: true };
61
+ }
62
+ return {
63
+ resetCache() {
64
+ cache.clear();
65
+ },
66
+ async getBaseline(userId, metric, windowDays, ctx) {
67
+ const k = key(userId, metric, windowDays, ctx.brand, ctx.familyId);
68
+ if (cache.has(k))
69
+ return cache.get(k) ?? null;
70
+ let result = null;
71
+ try {
72
+ result = await readStored(userId, metric, windowDays, ctx);
73
+ if (result === null) {
74
+ result = await computeOnRead(userId, metric, windowDays, ctx);
75
+ if (result !== null) {
76
+ logger?.debug('rules.baseline: stored row absent, computed on read', {
77
+ userId, metric, windowDays,
78
+ });
79
+ }
80
+ }
81
+ }
82
+ catch (err) {
83
+ // A missing baseline is a normal state (new user). A failed read is
84
+ // not, but it must not take down the evaluation pass — the rule
85
+ // simply doesn't fire this cycle.
86
+ logger?.warn('rules.baseline: read failed', {
87
+ userId,
88
+ metric,
89
+ windowDays,
90
+ err: err instanceof Error ? err.message : String(err),
91
+ });
92
+ result = null;
93
+ }
94
+ cache.set(k, result);
95
+ return result;
96
+ },
97
+ };
98
+ }
99
+ function toBaseline(row) {
100
+ return {
101
+ p05: row.p05,
102
+ p10: row.p10,
103
+ p50: row.p50,
104
+ p90: row.p90,
105
+ p95: row.p95,
106
+ mean: row.mean,
107
+ stddev: row.stddev,
108
+ sampleCount: row.sample_count,
109
+ };
110
+ }
111
+ //# sourceMappingURL=baseline-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline-reader.js","sourceRoot":"","sources":["../../src/rules/baseline-reader.ts"],"names":[],"mappings":"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"}
@@ -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;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAGL,KAAK,IAAI,EAIV,MAAM,UAAU,CAAA;AAEjB,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AA4DD,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,CA0CpD"}
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"}
@@ -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"}