@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
@@ -3,10 +3,20 @@
3
3
  *
4
4
  * `familyScopeAuthorize` — grants access iff `args.userId` is either
5
5
  * the requester themselves or another member of the requester's
6
- * family. When `config.familyMembersProvider` is supplied (the
7
- * factory path since T-11), membership resolves via that provider;
8
- * otherwise the legacy SQL path against a `family_member` table is
9
- * used (kept for standalone hook usage and backwards compat).
6
+ * family.
7
+ *
8
+ * Membership always resolves through the engine's configured
9
+ * `FamilyMembersProvider` (principle 39 "Rules engine and AI tools
10
+ * share the same source of truth"): an explicitly-passed
11
+ * `config.familyMembersProvider` wins, otherwise `analytics
12
+ * .getFamilyMembers()` delegates to the one wired at `createAnalytics`.
13
+ *
14
+ * There is deliberately no SQL fallback. The spec's illustrative
15
+ * `SELECT 1 FROM family_member ...` referenced a table that no DDL in
16
+ * this package ever creates, so that path could only ever throw and
17
+ * fail closed — silently denying every cross-member query on any
18
+ * install that didn't pass a provider. Membership lives in the RxDB
19
+ * Family doc, not in DuckDB.
10
20
  *
11
21
  * Both hooks fail closed on any error — provider throws, engine
12
22
  * throws, missing/malformed args — return `false` rather than
@@ -21,22 +31,14 @@ export function familyScopeAuthorize(analytics, config = {}) {
21
31
  if (targetUserId === ctx.requesterUserId) {
22
32
  return true;
23
33
  }
24
- if (config.familyMembersProvider) {
25
- try {
26
- const members = await config.familyMembersProvider({
34
+ try {
35
+ const members = config.familyMembersProvider
36
+ ? await config.familyMembersProvider({
27
37
  brand: ctx.brand,
28
38
  familyId: ctx.familyId,
29
- });
30
- return members.includes(targetUserId);
31
- }
32
- catch {
33
- return false;
34
- }
35
- }
36
- try {
37
- const rows = await analytics.execute('SELECT 1 AS one FROM family_member '
38
- + 'WHERE family_id = $familyId AND user_id = $userId LIMIT 1', { familyId: ctx.familyId, userId: targetUserId });
39
- return rows.length > 0;
39
+ })
40
+ : await analytics.getFamilyMembers();
41
+ return members.includes(targetUserId);
40
42
  }
41
43
  catch {
42
44
  return false;
@@ -44,14 +46,11 @@ export function familyScopeAuthorize(analytics, config = {}) {
44
46
  };
45
47
  }
46
48
  /**
47
- * `orgScopeAuthorize` — parity with family scope but against an
48
- * `org_member` table. Orgs are not first-class in v0.1.0; the ctx
49
- * `familyId` is treated as the org id until org membership is wired
50
- * through `RequestContext`.
51
- *
52
- * When `familyMembersProvider` is supplied, the same provider is
53
- * consulted (v0.1.0 shim — a dedicated `orgMembersProvider` will
54
- * split out once orgs land).
49
+ * `orgScopeAuthorize` — parity with family scope. Orgs are not
50
+ * first-class in v0.1.0; the ctx `familyId` is treated as the org id
51
+ * until org membership is wired through `RequestContext`, and the same
52
+ * membership provider is consulted (v0.1.0 shim — a dedicated
53
+ * `orgMembersProvider` splits out once orgs land).
55
54
  */
56
55
  export function orgScopeAuthorize(analytics, config = {}) {
57
56
  return async (_toolName, args, ctx) => {
@@ -62,22 +61,14 @@ export function orgScopeAuthorize(analytics, config = {}) {
62
61
  if (targetUserId === ctx.requesterUserId) {
63
62
  return true;
64
63
  }
65
- if (config.familyMembersProvider) {
66
- try {
67
- const members = await config.familyMembersProvider({
64
+ try {
65
+ const members = config.familyMembersProvider
66
+ ? await config.familyMembersProvider({
68
67
  brand: ctx.brand,
69
68
  familyId: ctx.familyId,
70
- });
71
- return members.includes(targetUserId);
72
- }
73
- catch {
74
- return false;
75
- }
76
- }
77
- try {
78
- const rows = await analytics.execute('SELECT 1 AS one FROM org_member '
79
- + 'WHERE org_id = $orgId AND user_id = $userId LIMIT 1', { orgId: ctx.familyId, userId: targetUserId });
80
- return rows.length > 0;
69
+ })
70
+ : await analytics.getFamilyMembers();
71
+ return members.includes(targetUserId);
81
72
  }
82
73
  catch {
83
74
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/tools/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAaH,MAAM,UAAU,oBAAoB,CAClC,SAA0B,EAC1B,SAA0B,EAAE;IAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAA;QAChC,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClE,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,YAAY,KAAK,GAAG,CAAC,eAAe,EAAE,CAAC;YACzC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,MAAM,CAAC,qBAAqB,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC;oBACjD,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;iBACvB,CAAC,CAAA;gBACF,OAAO,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;YACvC,CAAC;YACD,MAAM,CAAC;gBACL,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAClC,qCAAqC;kBACnC,2DAA2D,EAC7D,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,CACjD,CAAA;YACD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;QACxB,CAAC;QACD,MAAM,CAAC;YACL,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAA0B,EAC1B,SAA0B,EAAE;IAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAA;QAChC,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClE,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,YAAY,KAAK,GAAG,CAAC,eAAe,EAAE,CAAC;YACzC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,MAAM,CAAC,qBAAqB,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC;oBACjD,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;iBACvB,CAAC,CAAA;gBACF,OAAO,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;YACvC,CAAC;YACD,MAAM,CAAC;gBACL,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAClC,kCAAkC;kBAChC,qDAAqD,EACvD,EAAE,KAAK,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,CAC9C,CAAA;YACD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;QACxB,CAAC;QACD,MAAM,CAAC;YACL,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/tools/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAaH,MAAM,UAAU,oBAAoB,CAClC,SAA0B,EAC1B,SAA0B,EAAE;IAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAA;QAChC,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClE,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,YAAY,KAAK,GAAG,CAAC,eAAe,EAAE,CAAC;YACzC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,CAAC,qBAAqB;gBAC1C,CAAC,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC;oBACjC,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;iBACvB,CAAC;gBACJ,CAAC,CAAC,MAAM,SAAS,CAAC,gBAAgB,EAAE,CAAA;YACtC,OAAO,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;QACvC,CAAC;QACD,MAAM,CAAC;YACL,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAA0B,EAC1B,SAA0B,EAAE;IAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAA;QAChC,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClE,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,YAAY,KAAK,GAAG,CAAC,eAAe,EAAE,CAAC;YACzC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,CAAC,qBAAqB;gBAC1C,CAAC,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC;oBACjC,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;iBACvB,CAAC;gBACJ,CAAC,CAAC,MAAM,SAAS,CAAC,gBAAgB,EAAE,CAAA;YACtC,OAAO,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;QACvC,CAAC;QACD,MAAM,CAAC;YACL,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
@@ -23,6 +23,7 @@ import { type GetActivityTotalInput } from './impls/activity';
23
23
  import { type DetectAnomalyInput } from './impls/anomaly';
24
24
  import { type CompareTrendInput } from './impls/compare';
25
25
  import { type GetHRVInput } from './impls/hrv';
26
+ import { type GetSpO2Input } from './impls/spo2';
26
27
  import { type GetInsightsInput } from './impls/insights';
27
28
  import { type GetSleepSummaryInput } from './impls/sleep';
28
29
  export interface AnalyticsToolMap {
@@ -31,6 +32,7 @@ export interface AnalyticsToolMap {
31
32
  getActivityTotal: ReturnType<typeof makeTool<GetActivityTotalInput>>;
32
33
  compareTrend: ReturnType<typeof makeTool<CompareTrendInput>>;
33
34
  detectAnomaly: ReturnType<typeof makeTool<DetectAnomalyInput>>;
35
+ getSpO2: ReturnType<typeof makeTool<GetSpO2Input>>;
34
36
  getInsights: ReturnType<typeof makeTool<GetInsightsInput>>;
35
37
  }
36
38
  export interface AnalyticsToolsHandle {
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/tools/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,OAAO,KAAK,EACV,oBAAoB,EACpB,WAAW,EAEX,WAAW,EACZ,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAEjC,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AACzE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,eAAe,CAAA;AAoBtB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAA;IAChD,eAAe,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAA;IAClE,gBAAgB,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,qBAAqB,CAAC,CAAC,CAAA;IACpE,YAAY,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAA;IAC5D,aAAa,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAA;IAC9D,WAAW,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAA;CAC3D;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,CAAA;IACvB,UAAU,EAAE,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7C,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,oBAAoB,GAC3B,oBAAoB,CAiGtB"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/tools/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,OAAO,KAAK,EACV,oBAAoB,EACpB,WAAW,EAEX,WAAW,EACZ,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAEjC,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AACzE,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAC7E,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,eAAe,CAAA;AAuBtB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAA;IAChD,eAAe,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAA;IAClE,gBAAgB,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,qBAAqB,CAAC,CAAC,CAAA;IACpE,YAAY,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAA;IAC5D,aAAa,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAA;IAC9D,OAAO,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAA;IAClD,WAAW,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAA;CAC3D;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,CAAA;IACvB,UAAU,EAAE,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7C,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,oBAAoB,GAC3B,oBAAoB,CAwGtB"}
@@ -26,6 +26,7 @@ import { getActivityTotal, getActivityTotalInputSchema, } from './impls/activity
26
26
  import { detectAnomaly, detectAnomalyInputSchema, } from './impls/anomaly';
27
27
  import { compareTrend, compareTrendInputSchema, } from './impls/compare';
28
28
  import { getHRV, getHRVInputSchema } from './impls/hrv';
29
+ import { getSpO2, getSpO2InputSchema } from './impls/spo2';
29
30
  import { getInsights, getInsightsInputSchema, } from './impls/insights';
30
31
  import { getSleepSummary, getSleepSummaryInputSchema, } from './impls/sleep';
31
32
  const DESCRIPTIONS = {
@@ -42,8 +43,11 @@ const DESCRIPTIONS = {
42
43
  + 'in a user\'s metric over the last lookbackDays. Args: userId, metric '
43
44
  + '(hrv_ms|sleep_total_minutes|activity_steps), lookbackDays (7..90), '
44
45
  + 'stddevThreshold (1..4, default 2).',
46
+ getSpO2: 'Return nightly blood-oxygen averages during sleep for a user, '
47
+ + 'with how many brief low moments occurred each night and how it '
48
+ + 'compares to their usual range. Args: userId, days (1..90).',
45
49
  getInsights: 'Return recent AI-generated insights for a user. Args: userId, '
46
- + 'days (1..30, default 7), optional severity (info|warn|critical).',
50
+ + 'days (1..30, default 7), optional severity (info|warn|urgent).',
47
51
  };
48
52
  export function createAnalyticsTools(config) {
49
53
  const budget = {
@@ -80,6 +84,13 @@ export function createAnalyticsTools(config) {
80
84
  clock: config.clock,
81
85
  };
82
86
  const tools = {
87
+ getSpO2: makeTool({
88
+ ...shared,
89
+ name: 'getSpO2',
90
+ description: DESCRIPTIONS.getSpO2,
91
+ inputSchema: getSpO2InputSchema,
92
+ impl: getSpO2,
93
+ }),
83
94
  getHRV: makeTool({
84
95
  ...shared,
85
96
  name: 'getHRV',
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/tools/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAoB,MAAM,cAAc,CAAA;AAClE,OAAO,EACL,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,SAAS,CAAA;AAOhB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAEjC,OAAO,EACL,gBAAgB,EAChB,2BAA2B,GAE5B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,aAAa,EACb,wBAAwB,GAEzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,YAAY,EACZ,uBAAuB,GAExB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAoB,MAAM,aAAa,CAAA;AACzE,OAAO,EACL,WAAW,EACX,sBAAsB,GAEvB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,eAAe,EACf,0BAA0B,GAE3B,MAAM,eAAe,CAAA;AAEtB,MAAM,YAAY,GAAG;IACnB,MAAM,EAAE,oEAAoE;UACxE,mDAAmD;IACvD,eAAe,EAAE,0DAA0D;UACvE,mDAAmD;IACvD,gBAAgB,EAAE,2DAA2D;UACzE,mDAAmD;IACvD,YAAY,EAAE,6DAA6D;UACvE,8DAA8D;UAC9D,kDAAkD;IACtD,aAAa,EAAE,+DAA+D;UAC1E,uEAAuE;UACvE,qEAAqE;UACrE,oCAAoC;IACxC,WAAW,EAAE,gEAAgE;UACzE,kEAAkE;CAC9D,CAAA;AAkBV,MAAM,UAAU,oBAAoB,CAClC,MAA4B;IAE5B,MAAM,MAAM,GAAiB;QAC3B,GAAG,qBAAqB;QACxB,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC;KAC/B,CAAA;IAED,MAAM,WAAW,GACb,MAAM,CAAC,SAAS,KAAK,KAAK;QAC1B,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,iBAAiB,CAAC;YAChB,MAAM,EAAE,MAAM,CAAC,SAAS,IAAI,kBAAkB;YAC9C,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CAAA;IAER,MAAM,SAAS,GACX,MAAM,CAAC,SAAS;WACf,oBAAoB,CAAC,MAAM,CAAC,SAAS,EAAE;YACxC,qBAAqB,EAAE,MAAM,CAAC,qBAAqB;SACpD,CAAC,CAAA;IAEJ,MAAM,KAAK,GAAG,iBAAiB,CAAC;QAC9B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,IAAI,IAAI;QACtC,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS;QAClC,eAAe,EAAE,MAAM,CAAC,KAAK,EAAE,eAAe;QAC9C,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC,CAAA;IAEF,IAAI,UAAU,GAAuB,IAAI,CAAA;IACzC,MAAM,WAAW,GAAG,GAAuB,EAAE,CAAC,UAAU,CAAA;IAExD,MAAM,MAAM,GAAG;QACb,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS;QACT,WAAW;QACX,KAAK;QACL,MAAM;QACN,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,WAAW;QACX,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAA;IAED,MAAM,KAAK,GAAqB;QAC9B,MAAM,EAAE,QAAQ,CAAC;YACf,GAAG,MAAM;YACT,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,YAAY,CAAC,MAAM;YAChC,WAAW,EAAE,iBAAiB;YAC9B,IAAI,EAAE,MAAM;SACb,CAAC;QACF,eAAe,EAAE,QAAQ,CAAC;YACxB,GAAG,MAAM;YACT,IAAI,EAAE,iBAAiB;YACvB,WAAW,EAAE,YAAY,CAAC,eAAe;YACzC,WAAW,EAAE,0BAA0B;YACvC,IAAI,EAAE,eAAe;SACtB,CAAC;QACF,gBAAgB,EAAE,QAAQ,CAAC;YACzB,GAAG,MAAM;YACT,IAAI,EAAE,kBAAkB;YACxB,WAAW,EAAE,YAAY,CAAC,gBAAgB;YAC1C,WAAW,EAAE,2BAA2B;YACxC,IAAI,EAAE,gBAAgB;SACvB,CAAC;QACF,YAAY,EAAE,QAAQ,CAAC;YACrB,GAAG,MAAM;YACT,IAAI,EAAE,cAAc;YACpB,WAAW,EAAE,YAAY,CAAC,YAAY;YACtC,WAAW,EAAE,uBAAuB;YACpC,IAAI,EAAE,YAAY;SACnB,CAAC;QACF,aAAa,EAAE,QAAQ,CAAC;YACtB,GAAG,MAAM;YACT,IAAI,EAAE,eAAe;YACrB,WAAW,EAAE,YAAY,CAAC,aAAa;YACvC,WAAW,EAAE,wBAAwB;YACrC,IAAI,EAAE,aAAa;SACpB,CAAC;QACF,WAAW,EAAE,QAAQ,CAAC;YACpB,GAAG,MAAM;YACT,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE,YAAY,CAAC,WAAW;YACrC,WAAW,EAAE,sBAAsB;YACnC,IAAI,EAAE,WAAW;SAClB,CAAC;KACH,CAAA;IAED,OAAO;QACL,KAAK;QACL,UAAU,CAAC,GAAG;YACZ,UAAU,GAAG,GAAG,CAAA;QAClB,CAAC;QACD,WAAW,EAAE,KAAK;QAClB,KAAK,CAAC,KAAK;YACT,MAAM,KAAK,CAAC,KAAK,EAAE,CAAA;QACrB,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/tools/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * User-language guardrails for tool output (Sprint 5 §5 / T-29, Ziva #2,
3
+ * principle 37).
4
+ *
5
+ * Tool output goes into an LLM's context, and the LLM will parrot whatever
6
+ * register it finds there. If a formatter says "2 desaturation events", the
7
+ * assistant tells the user they had two desaturation events — which is
8
+ * clinical language Ziva is not licensed to speak and the user did not ask
9
+ * for. The fix is upstream of the model: never put the word in the context.
10
+ *
11
+ * This is a runtime guard, not a lint rule, because the failure mode is a
12
+ * formatter interpolating a value into a template that happens to read
13
+ * clinically. `assertNoBanTerms` runs on every formatter's return path and
14
+ * throws — a tool call that fails loudly is recoverable; one that quietly
15
+ * teaches the model to talk like a doctor is not.
16
+ */
17
+ /**
18
+ * Vocabulary that must never reach the model.
19
+ *
20
+ * Matching is prefix-based on word boundaries, so listing `desaturation`
21
+ * also catches `desaturations` and `desaturating`. Inflections are still
22
+ * listed explicitly where they read differently, for documentation value.
23
+ */
24
+ export declare const BANNED_MEDICAL_VOCABULARY: readonly ["desaturation", "desaturated", "desaturating", "hypoxia", "hypoxic", "hypoxemia", "apnea", "apneic", "arrhythmia", "arrhythmic", "bradycardia", "tachycardia", "ischemia", "ischemic"];
25
+ export type BannedTerm = (typeof BANNED_MEDICAL_VOCABULARY)[number];
26
+ /**
27
+ * What to write instead. Keys are the clinical phrasing a formatter author
28
+ * might reach for; values are the register Ziva actually speaks.
29
+ *
30
+ * Ordered longest-first at use time so multi-word phrases win over their
31
+ * constituent words.
32
+ */
33
+ export declare const PREFERRED_LANGUAGE: Record<string, string>;
34
+ /** Thrown when a formatter emits banned vocabulary. */
35
+ export declare class FormatterCopyError extends Error {
36
+ readonly formatterName: string;
37
+ readonly found: string[];
38
+ constructor(formatterName: string, found: string[]);
39
+ }
40
+ /** Every banned term present in `text`, in list order. Empty when clean. */
41
+ export declare function findBanTerms(text: string): string[];
42
+ /**
43
+ * Guardrail called from every formatter's return path.
44
+ *
45
+ * @throws `FormatterCopyError` when any banned term appears.
46
+ */
47
+ export declare function assertNoBanTerms(text: string, formatterName: string): void;
48
+ /**
49
+ * Best-effort rewrite of clinical phrasing into Ziva's register.
50
+ *
51
+ * A convenience for formatter authors, NOT a substitute for the guard —
52
+ * it only knows the phrases in `PREFERRED_LANGUAGE`, so
53
+ * `assertNoBanTerms` still runs afterwards and still throws on anything
54
+ * it missed.
55
+ */
56
+ export declare function applyPreferredLanguage(text: string): string;
57
+ //# sourceMappingURL=copy-guidelines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-guidelines.d.ts","sourceRoot":"","sources":["../../../src/tools/formatters/copy-guidelines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,kMAe5B,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAYrD,CAAA;AAED,uDAAuD;AACvD,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;gBAEZ,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;CAUnD;AAWD,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAK1E;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAY3D"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * User-language guardrails for tool output (Sprint 5 §5 / T-29, Ziva #2,
3
+ * principle 37).
4
+ *
5
+ * Tool output goes into an LLM's context, and the LLM will parrot whatever
6
+ * register it finds there. If a formatter says "2 desaturation events", the
7
+ * assistant tells the user they had two desaturation events — which is
8
+ * clinical language Ziva is not licensed to speak and the user did not ask
9
+ * for. The fix is upstream of the model: never put the word in the context.
10
+ *
11
+ * This is a runtime guard, not a lint rule, because the failure mode is a
12
+ * formatter interpolating a value into a template that happens to read
13
+ * clinically. `assertNoBanTerms` runs on every formatter's return path and
14
+ * throws — a tool call that fails loudly is recoverable; one that quietly
15
+ * teaches the model to talk like a doctor is not.
16
+ */
17
+ /**
18
+ * Vocabulary that must never reach the model.
19
+ *
20
+ * Matching is prefix-based on word boundaries, so listing `desaturation`
21
+ * also catches `desaturations` and `desaturating`. Inflections are still
22
+ * listed explicitly where they read differently, for documentation value.
23
+ */
24
+ export const BANNED_MEDICAL_VOCABULARY = [
25
+ 'desaturation',
26
+ 'desaturated',
27
+ 'desaturating',
28
+ 'hypoxia',
29
+ 'hypoxic',
30
+ 'hypoxemia',
31
+ 'apnea',
32
+ 'apneic',
33
+ 'arrhythmia',
34
+ 'arrhythmic',
35
+ 'bradycardia',
36
+ 'tachycardia',
37
+ 'ischemia',
38
+ 'ischemic',
39
+ ];
40
+ /**
41
+ * What to write instead. Keys are the clinical phrasing a formatter author
42
+ * might reach for; values are the register Ziva actually speaks.
43
+ *
44
+ * Ordered longest-first at use time so multi-word phrases win over their
45
+ * constituent words.
46
+ */
47
+ export const PREFERRED_LANGUAGE = {
48
+ 'oxygen desaturation': 'oxygen dip',
49
+ 'desaturation event': 'brief low moment',
50
+ 'desaturation events': 'brief low moments',
51
+ 'desaturations': 'brief low moments',
52
+ 'desaturation': 'brief low moment',
53
+ 'hypoxemia': 'low oxygen',
54
+ 'hypoxia': 'low oxygen',
55
+ 'apnea': 'pauses in breathing',
56
+ 'bradycardia': 'slow heart rate',
57
+ 'tachycardia': 'fast heart rate',
58
+ 'arrhythmia': 'irregular heartbeat',
59
+ };
60
+ /** Thrown when a formatter emits banned vocabulary. */
61
+ export class FormatterCopyError extends Error {
62
+ constructor(formatterName, found) {
63
+ super(`Formatter '${formatterName}' emitted banned medical vocabulary: `
64
+ + `${found.join(', ')}. Use PREFERRED_LANGUAGE or rewrite — this text `
65
+ + `goes into an LLM's context and the model will repeat it.`);
66
+ this.name = 'FormatterCopyError';
67
+ this.formatterName = formatterName;
68
+ this.found = found;
69
+ }
70
+ }
71
+ /**
72
+ * Compiled matchers. Leading `\b` only: a trailing boundary would let
73
+ * `desaturations` slip past a `desaturation` entry, and over-blocking is
74
+ * the right bias for a guardrail on health copy.
75
+ */
76
+ const MATCHERS = BANNED_MEDICAL_VOCABULARY.map(term => ({ term, re: new RegExp(`\\b${term}`, 'iu') }));
77
+ /** Every banned term present in `text`, in list order. Empty when clean. */
78
+ export function findBanTerms(text) {
79
+ return MATCHERS.filter(m => m.re.test(text)).map(m => m.term);
80
+ }
81
+ /**
82
+ * Guardrail called from every formatter's return path.
83
+ *
84
+ * @throws `FormatterCopyError` when any banned term appears.
85
+ */
86
+ export function assertNoBanTerms(text, formatterName) {
87
+ const found = findBanTerms(text);
88
+ if (found.length > 0) {
89
+ throw new FormatterCopyError(formatterName, found);
90
+ }
91
+ }
92
+ /**
93
+ * Best-effort rewrite of clinical phrasing into Ziva's register.
94
+ *
95
+ * A convenience for formatter authors, NOT a substitute for the guard —
96
+ * it only knows the phrases in `PREFERRED_LANGUAGE`, so
97
+ * `assertNoBanTerms` still runs afterwards and still throws on anything
98
+ * it missed.
99
+ */
100
+ export function applyPreferredLanguage(text) {
101
+ // Longest first, so 'oxygen desaturation' is replaced before
102
+ // 'desaturation' can consume half of it.
103
+ const phrases = Object.keys(PREFERRED_LANGUAGE).sort((a, b) => b.length - a.length);
104
+ let out = text;
105
+ for (const phrase of phrases) {
106
+ out = out.replace(new RegExp(`\\b${phrase}\\b`, 'giu'), PREFERRED_LANGUAGE[phrase]);
107
+ }
108
+ return out;
109
+ }
110
+ //# sourceMappingURL=copy-guidelines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-guidelines.js","sourceRoot":"","sources":["../../../src/tools/formatters/copy-guidelines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,cAAc;IACd,aAAa;IACb,cAAc;IACd,SAAS;IACT,SAAS;IACT,WAAW;IACX,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,aAAa;IACb,UAAU;IACV,UAAU;CACF,CAAA;AAIV;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,qBAAqB,EAAE,YAAY;IACnC,oBAAoB,EAAE,kBAAkB;IACxC,qBAAqB,EAAE,mBAAmB;IAC1C,eAAe,EAAE,mBAAmB;IACpC,cAAc,EAAE,kBAAkB;IAClC,WAAW,EAAE,YAAY;IACzB,SAAS,EAAE,YAAY;IACvB,OAAO,EAAE,qBAAqB;IAC9B,aAAa,EAAE,iBAAiB;IAChC,aAAa,EAAE,iBAAiB;IAChC,YAAY,EAAE,qBAAqB;CACpC,CAAA;AAED,uDAAuD;AACvD,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAI3C,YAAY,aAAqB,EAAE,KAAe;QAChD,KAAK,CACH,cAAc,aAAa,uCAAuC;cAChE,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,kDAAkD;cACrE,0DAA0D,CAC7D,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAA;QAChC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAA;QAClC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,QAAQ,GAAmC,yBAAyB,CAAC,GAAG,CAC5E,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CACvD,CAAA;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;AAC/D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,aAAqB;IAClE,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IAChC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,kBAAkB,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;IACpD,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,6DAA6D;IAC7D,yCAAyC;IACzC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAA;IACnF,IAAI,GAAG,GAAG,IAAI,CAAA;IACd,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,GAAG,GAAG,GAAG,CAAC,OAAO,CACf,IAAI,MAAM,CAAC,MAAM,MAAM,KAAK,EAAE,KAAK,CAAC,EACpC,kBAAkB,CAAC,MAAM,CAAC,CAC3B,CAAA;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Formatter surface for `@mongrov/analytics/tools`.
3
+ *
4
+ * Two concerns, kept separate:
5
+ *
6
+ * - `text.ts` — mechanical string/number helpers (byte counts, deltas).
7
+ * - `copy-guidelines.ts` — the user-language guardrail (principle 37).
8
+ * Every formatter calls `assertNoBanTerms` on its return path.
9
+ *
10
+ * Re-exported here so call sites keep importing from `'../formatters'`.
11
+ */
12
+ export { deltaPct, formatBytes, popStddev } from './text';
13
+ export { applyPreferredLanguage, assertNoBanTerms, BANNED_MEDICAL_VOCABULARY, type BannedTerm, findBanTerms, FormatterCopyError, PREFERRED_LANGUAGE, } from './copy-guidelines';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/formatters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,yBAAyB,EACzB,KAAK,UAAU,EACf,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Formatter surface for `@mongrov/analytics/tools`.
3
+ *
4
+ * Two concerns, kept separate:
5
+ *
6
+ * - `text.ts` — mechanical string/number helpers (byte counts, deltas).
7
+ * - `copy-guidelines.ts` — the user-language guardrail (principle 37).
8
+ * Every formatter calls `assertNoBanTerms` on its return path.
9
+ *
10
+ * Re-exported here so call sites keep importing from `'../formatters'`.
11
+ */
12
+ export { deltaPct, formatBytes, popStddev } from './text';
13
+ export { applyPreferredLanguage, assertNoBanTerms, BANNED_MEDICAL_VOCABULARY, findBanTerms, FormatterCopyError, PREFERRED_LANGUAGE, } from './copy-guidelines';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/formatters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,yBAAyB,EAEzB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `getSpO2` formatter (Sprint 5 T-31, Ziva #2).
3
+ *
4
+ * The reference implementation for principle 37. The underlying data has a
5
+ * field counting readings below 90% during sleep; the clinical word for
6
+ * that is on the banned list, so this formatter has to say what happened
7
+ * without reaching for it:
8
+ *
9
+ * "A couple of brief low moments during sleep."
10
+ *
11
+ * not
12
+ *
13
+ * "2 desaturation events. minSpo2=87."
14
+ *
15
+ * The second version is not just colder — it is the version the assistant
16
+ * repeats back to the user, which is how clinical vocabulary leaks into a
17
+ * consumer wellness product.
18
+ */
19
+ import type { SpO2BaselineRow, SpO2NightRow } from '../impls/spo2';
20
+ export declare function formatSpO2(nights: readonly SpO2NightRow[], baseline: SpO2BaselineRow | null, days: number): string;
21
+ //# sourceMappingURL=spo2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spo2.d.ts","sourceRoot":"","sources":["../../../src/tools/formatters/spo2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAmClE,wBAAgB,UAAU,CACxB,MAAM,EAAE,SAAS,YAAY,EAAE,EAC/B,QAAQ,EAAE,eAAe,GAAG,IAAI,EAChC,IAAI,EAAE,MAAM,GACX,MAAM,CAqCR"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `getSpO2` formatter (Sprint 5 T-31, Ziva #2).
3
+ *
4
+ * The reference implementation for principle 37. The underlying data has a
5
+ * field counting readings below 90% during sleep; the clinical word for
6
+ * that is on the banned list, so this formatter has to say what happened
7
+ * without reaching for it:
8
+ *
9
+ * "A couple of brief low moments during sleep."
10
+ *
11
+ * not
12
+ *
13
+ * "2 desaturation events. minSpo2=87."
14
+ *
15
+ * The second version is not just colder — it is the version the assistant
16
+ * repeats back to the user, which is how clinical vocabulary leaks into a
17
+ * consumer wellness product.
18
+ */
19
+ import { assertNoBanTerms } from './copy-guidelines';
20
+ const FORMATTER_NAME = 'getSpO2';
21
+ /**
22
+ * Small counts read better as words, and vague is honest here — the exact
23
+ * count of dips is not a number the user should be doing arithmetic on.
24
+ */
25
+ function describeLowMoments(count) {
26
+ if (count === 0)
27
+ return 'No brief low moments';
28
+ if (count === 1)
29
+ return 'One brief low moment';
30
+ if (count === 2)
31
+ return 'A couple of brief low moments';
32
+ if (count <= 5)
33
+ return 'A few brief low moments';
34
+ return 'Several brief low moments';
35
+ }
36
+ /** "in your usual range" needs a usual range to compare against. */
37
+ function describeRange(avg, baseline) {
38
+ if (!baseline) {
39
+ // Below 20 days of data. Saying nothing is better than implying a
40
+ // reference range we do not have.
41
+ return 'Still learning your usual range.';
42
+ }
43
+ const lo = Math.round(baseline.p10);
44
+ const hi = Math.round(baseline.p50 + (baseline.p50 - baseline.p10));
45
+ const within = avg >= baseline.p10;
46
+ return within
47
+ ? `Overall in your usual range (typically ${lo}–${hi}%).`
48
+ : `A little below your usual range (typically ${lo}–${hi}%).`;
49
+ }
50
+ export function formatSpO2(nights, baseline, days) {
51
+ if (nights.length === 0) {
52
+ const empty = days === 1
53
+ ? 'No sleep readings for last night yet.'
54
+ : `No sleep readings in the last ${days} days yet.`;
55
+ assertNoBanTerms(empty, FORMATTER_NAME);
56
+ return empty;
57
+ }
58
+ const latest = nights[nights.length - 1];
59
+ const lines = [];
60
+ if (nights.length === 1) {
61
+ lines.push('Last night:');
62
+ }
63
+ else {
64
+ lines.push(`Last ${nights.length} nights:`);
65
+ }
66
+ lines.push(` Average oxygen: ${Math.round(latest.avg_spo2)}%. `
67
+ + `${describeLowMoments(latest.low_moment_count)} during sleep.`);
68
+ if (nights.length > 1) {
69
+ const avgAll = nights.reduce((a, n) => a + n.avg_spo2, 0) / nights.length;
70
+ lines.push(` Across those nights: ${Math.round(avgAll)}% on average.`);
71
+ }
72
+ lines.push(` ${describeRange(latest.avg_spo2, baseline)}`);
73
+ const text = lines.join('\n');
74
+ // Runtime guard, not just a test: a future edit that interpolates a
75
+ // clinical phrase fails the tool call rather than teaching the model.
76
+ assertNoBanTerms(text, FORMATTER_NAME);
77
+ return text;
78
+ }
79
+ //# sourceMappingURL=spo2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spo2.js","sourceRoot":"","sources":["../../../src/tools/formatters/spo2.ts"],"names":[],"mappings":"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"}
@@ -14,4 +14,4 @@ export declare function formatBytes(s: string): number;
14
14
  export declare function deltaPct(current: number, prior: number): string;
15
15
  /** Population stddev; returns 0 when the sample is empty. */
16
16
  export declare function popStddev(values: number[]): number;
17
- //# sourceMappingURL=formatters.d.ts.map
17
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/tools/formatters/text.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/D;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAMlD"}
@@ -27,4 +27,4 @@ export function popStddev(values) {
27
27
  const variance = values.reduce((a, v) => a + (v - mean) ** 2, 0) / values.length;
28
28
  return Math.sqrt(variance);
29
29
  }
30
- //# sourceMappingURL=formatters.js.map
30
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/tools/formatters/text.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAA;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,KAAa;IACrD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC7B,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAA;IAC7C,OAAO,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAA;AACnD,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,SAAS,CAAC,MAAgB;IACxC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACjC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAA;IAC9D,MAAM,QAAQ,GACV,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAA;IACnE,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAC5B,CAAC"}
@@ -4,11 +4,11 @@ export declare const getActivityTotalInputSchema: z.ZodObject<{
4
4
  userId: z.ZodString;
5
5
  days: z.ZodNumber;
6
6
  }, "strip", z.ZodTypeAny, {
7
- days: number;
8
7
  userId: string;
9
- }, {
10
8
  days: number;
9
+ }, {
11
10
  userId: string;
11
+ days: number;
12
12
  }>;
13
13
  export type GetActivityTotalInput = z.infer<typeof getActivityTotalInputSchema>;
14
14
  export declare const getActivityTotal: ToolImpl<GetActivityTotalInput>;
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { formatBytes } from '../formatters';
2
+ import { assertNoBanTerms, formatBytes } from '../formatters';
3
3
  export const getActivityTotalInputSchema = z.object({
4
4
  userId: z.string(),
5
5
  days: z.number().int().min(1).max(90),
@@ -12,14 +12,14 @@ export const getActivityTotal = async (input, ctx) => {
12
12
  days: input.days,
13
13
  };
14
14
  const steps = await ctx.analytics.execute(`SELECT date_trunc('day', ts)::VARCHAR AS day, SUM(steps)::INTEGER AS steps
15
- FROM activity
15
+ FROM v_activity
16
16
  WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
17
17
  AND ts >= now() - INTERVAL ($days) DAY
18
18
  GROUP BY 1 ORDER BY 1`, params);
19
19
  const buckets = await ctx.analytics.execute(`SELECT date_trunc('day', ts)::VARCHAR AS day,
20
20
  SUM(calories)::DOUBLE AS calories,
21
21
  SUM(distance_km)::DOUBLE AS distance_km
22
- FROM activity_bucket
22
+ FROM v_activity_bucket
23
23
  WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
24
24
  AND ts >= now() - INTERVAL ($days) DAY
25
25
  GROUP BY 1 ORDER BY 1`, params);
@@ -56,6 +56,10 @@ export const getActivityTotal = async (input, ctx) => {
56
56
  return finalize(text, orderedDays.length);
57
57
  };
58
58
  function finalize(text, rowCount) {
59
+ // principle 37 — every formatter's return path is guarded, not
60
+ // just the SpO2 one. Tool text lands in an LLM's context, and the
61
+ // model repeats whatever register it finds there.
62
+ assertNoBanTerms(text, 'getActivityTotal');
59
63
  return { text, rowCount, bytes: formatBytes(text) };
60
64
  }
61
65
  //# sourceMappingURL=activity.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"activity.js","sourceRoot":"","sources":["../../../src/tools/impls/activity.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"activity.js","sourceRoot":"","sources":["../../../src/tools/impls/activity.ts"],"names":[],"mappings":"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"}
@@ -6,13 +6,13 @@ export declare const detectAnomalyInputSchema: z.ZodObject<{
6
6
  lookbackDays: z.ZodNumber;
7
7
  stddevThreshold: z.ZodDefault<z.ZodNumber>;
8
8
  }, "strip", z.ZodTypeAny, {
9
- metric: "hrv_ms" | "activity_steps" | "sleep_total_minutes";
10
9
  userId: string;
10
+ metric: "hrv_ms" | "activity_steps" | "sleep_total_minutes";
11
11
  lookbackDays: number;
12
12
  stddevThreshold: number;
13
13
  }, {
14
- metric: "hrv_ms" | "activity_steps" | "sleep_total_minutes";
15
14
  userId: string;
15
+ metric: "hrv_ms" | "activity_steps" | "sleep_total_minutes";
16
16
  lookbackDays: number;
17
17
  stddevThreshold?: number | undefined;
18
18
  }>;