@mongrov/analytics 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -10
- package/UPGRADING.md +456 -0
- package/dist/core/device-events.d.ts +14 -0
- package/dist/core/device-events.d.ts.map +1 -0
- package/dist/core/device-events.js +13 -0
- package/dist/core/device-events.js.map +1 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/extensions.d.ts +21 -4
- package/dist/core/extensions.d.ts.map +1 -1
- package/dist/core/extensions.js +21 -4
- package/dist/core/extensions.js.map +1 -1
- package/dist/core/factory.d.ts.map +1 -1
- package/dist/core/factory.js +125 -23
- package/dist/core/factory.js.map +1 -1
- package/dist/core/hooks.js +1 -1
- package/dist/core/hooks.js.map +1 -1
- package/dist/core/index.d.ts +13 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +18 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/insight.d.ts +44 -0
- package/dist/core/insight.d.ts.map +1 -0
- package/dist/core/insight.js +62 -0
- package/dist/core/insight.js.map +1 -0
- package/dist/core/metric_metadata.d.ts +55 -0
- package/dist/core/metric_metadata.d.ts.map +1 -1
- package/dist/core/metric_metadata.js +35 -7
- package/dist/core/metric_metadata.js.map +1 -1
- package/dist/core/migrations.d.ts.map +1 -1
- package/dist/core/migrations.js +103 -2
- package/dist/core/migrations.js.map +1 -1
- package/dist/core/retention.d.ts +44 -13
- package/dist/core/retention.d.ts.map +1 -1
- package/dist/core/retention.js +49 -22
- package/dist/core/retention.js.map +1 -1
- package/dist/core/sampling.d.ts +70 -0
- package/dist/core/sampling.d.ts.map +1 -0
- package/dist/core/sampling.js +107 -0
- package/dist/core/sampling.js.map +1 -0
- package/dist/core/schemas.d.ts +73 -2
- package/dist/core/schemas.d.ts.map +1 -1
- package/dist/core/schemas.js +160 -5
- package/dist/core/schemas.js.map +1 -1
- package/dist/core/table_metadata.d.ts.map +1 -1
- package/dist/core/table_metadata.js +3 -0
- package/dist/core/table_metadata.js.map +1 -1
- package/dist/core/types.d.ts +45 -10
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/warehouse.d.ts +28 -0
- package/dist/core/warehouse.d.ts.map +1 -1
- package/dist/core/warehouse.js +44 -0
- package/dist/core/warehouse.js.map +1 -1
- package/dist/core/watermark-cache.d.ts +75 -0
- package/dist/core/watermark-cache.d.ts.map +1 -0
- package/dist/core/watermark-cache.js +0 -0
- package/dist/core/watermark-cache.js.map +1 -0
- package/dist/rules/__fakes__/fakeEngine.d.ts +2 -0
- package/dist/rules/__fakes__/fakeEngine.d.ts.map +1 -1
- package/dist/rules/__fakes__/fakeEngine.js +4 -0
- package/dist/rules/__fakes__/fakeEngine.js.map +1 -1
- package/dist/rules/baseline-reader.d.ts +55 -0
- package/dist/rules/baseline-reader.d.ts.map +1 -0
- package/dist/rules/baseline-reader.js +111 -0
- package/dist/rules/baseline-reader.js.map +1 -0
- package/dist/rules/compiler.d.ts +36 -1
- package/dist/rules/compiler.d.ts.map +1 -1
- package/dist/rules/compiler.js +185 -39
- package/dist/rules/compiler.js.map +1 -1
- package/dist/rules/defaults/luminx.d.ts.map +1 -1
- package/dist/rules/defaults/luminx.js +8 -0
- package/dist/rules/defaults/luminx.js.map +1 -1
- package/dist/rules/defaults/viva.d.ts.map +1 -1
- package/dist/rules/defaults/viva.js +7 -0
- package/dist/rules/defaults/viva.js.map +1 -1
- package/dist/rules/defaults/yogaring.d.ts.map +1 -1
- package/dist/rules/defaults/yogaring.js +7 -0
- package/dist/rules/defaults/yogaring.js.map +1 -1
- package/dist/rules/defaults/ziva.d.ts.map +1 -1
- package/dist/rules/defaults/ziva.js +100 -24
- package/dist/rules/defaults/ziva.js.map +1 -1
- package/dist/rules/evaluator.d.ts +14 -3
- package/dist/rules/evaluator.d.ts.map +1 -1
- package/dist/rules/evaluator.js +157 -5
- package/dist/rules/evaluator.js.map +1 -1
- package/dist/rules/factory.d.ts.map +1 -1
- package/dist/rules/factory.js +5 -1
- package/dist/rules/factory.js.map +1 -1
- package/dist/rules/index.d.ts +2 -1
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +2 -1
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/schema.d.ts +75 -4
- package/dist/rules/schema.d.ts.map +1 -1
- package/dist/rules/schema.js +44 -0
- package/dist/rules/schema.js.map +1 -1
- package/dist/rules/types.d.ts +16 -1
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/validator.d.ts.map +1 -1
- package/dist/rules/validator.js +89 -0
- package/dist/rules/validator.js.map +1 -1
- package/dist/sync/baseline-compute.d.ts +63 -0
- package/dist/sync/baseline-compute.d.ts.map +1 -0
- package/dist/sync/baseline-compute.js +211 -0
- package/dist/sync/baseline-compute.js.map +1 -0
- package/dist/sync/events.d.ts +19 -5
- package/dist/sync/events.d.ts.map +1 -1
- package/dist/sync/events.js +17 -0
- package/dist/sync/events.js.map +1 -1
- package/dist/sync/factory.d.ts +54 -8
- package/dist/sync/factory.d.ts.map +1 -1
- package/dist/sync/factory.js +145 -46
- package/dist/sync/factory.js.map +1 -1
- package/dist/sync/fetcher.d.ts +15 -2
- package/dist/sync/fetcher.d.ts.map +1 -1
- package/dist/sync/fetcher.js +43 -7
- package/dist/sync/fetcher.js.map +1 -1
- package/dist/sync/flusher.d.ts +52 -2
- package/dist/sync/flusher.d.ts.map +1 -1
- package/dist/sync/flusher.js +78 -9
- package/dist/sync/flusher.js.map +1 -1
- package/dist/sync/index.d.ts +3 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/mapper/activity.d.ts.map +1 -1
- package/dist/sync/mapper/activity.js +2 -1
- package/dist/sync/mapper/activity.js.map +1 -1
- package/dist/sync/mapper/firmware.d.ts +3 -12
- package/dist/sync/mapper/firmware.d.ts.map +1 -1
- package/dist/sync/mapper/firmware.js +2 -8
- package/dist/sync/mapper/firmware.js.map +1 -1
- package/dist/sync/mapper/ring-config.d.ts +76 -28
- package/dist/sync/mapper/ring-config.d.ts.map +1 -1
- package/dist/sync/mapper/ring-config.js +118 -35
- package/dist/sync/mapper/ring-config.js.map +1 -1
- package/dist/sync/mapper/schema.d.ts +669 -0
- package/dist/sync/mapper/schema.d.ts.map +1 -0
- package/dist/sync/mapper/schema.js +160 -0
- package/dist/sync/mapper/schema.js.map +1 -0
- package/dist/sync/mapper/sleep.d.ts +58 -13
- package/dist/sync/mapper/sleep.d.ts.map +1 -1
- package/dist/sync/mapper/sleep.js +141 -44
- package/dist/sync/mapper/sleep.js.map +1 -1
- package/dist/sync/mapper/types.d.ts +118 -29
- package/dist/sync/mapper/types.d.ts.map +1 -1
- package/dist/sync/pending_closes.js +2 -2
- package/dist/sync/pending_closes.js.map +1 -1
- package/dist/sync/pusher.js +2 -2
- package/dist/sync/pusher.js.map +1 -1
- package/dist/sync/scheduler.d.ts +7 -0
- package/dist/sync/scheduler.d.ts.map +1 -1
- package/dist/sync/scheduler.js +14 -0
- package/dist/sync/scheduler.js.map +1 -1
- package/dist/tools/__fakes__/engine.d.ts +2 -0
- package/dist/tools/__fakes__/engine.d.ts.map +1 -1
- package/dist/tools/__fakes__/engine.js +10 -0
- package/dist/tools/__fakes__/engine.js.map +1 -1
- package/dist/tools/authorize.d.ts +19 -12
- package/dist/tools/authorize.d.ts.map +1 -1
- package/dist/tools/authorize.js +31 -40
- package/dist/tools/authorize.js.map +1 -1
- package/dist/tools/factory.d.ts +2 -0
- package/dist/tools/factory.d.ts.map +1 -1
- package/dist/tools/factory.js +12 -1
- package/dist/tools/factory.js.map +1 -1
- package/dist/tools/formatters/copy-guidelines.d.ts +57 -0
- package/dist/tools/formatters/copy-guidelines.d.ts.map +1 -0
- package/dist/tools/formatters/copy-guidelines.js +110 -0
- package/dist/tools/formatters/copy-guidelines.js.map +1 -0
- package/dist/tools/formatters/index.d.ts +14 -0
- package/dist/tools/formatters/index.d.ts.map +1 -0
- package/dist/tools/formatters/index.js +14 -0
- package/dist/tools/formatters/index.js.map +1 -0
- package/dist/tools/formatters/spo2.d.ts +21 -0
- package/dist/tools/formatters/spo2.d.ts.map +1 -0
- package/dist/tools/formatters/spo2.js +79 -0
- package/dist/tools/formatters/spo2.js.map +1 -0
- package/dist/tools/{formatters.d.ts → formatters/text.d.ts} +1 -1
- package/dist/tools/formatters/text.d.ts.map +1 -0
- package/dist/tools/{formatters.js → formatters/text.js} +1 -1
- package/dist/tools/formatters/text.js.map +1 -0
- package/dist/tools/impls/activity.d.ts +2 -2
- package/dist/tools/impls/activity.js +7 -3
- package/dist/tools/impls/activity.js.map +1 -1
- package/dist/tools/impls/anomaly.d.ts +2 -2
- package/dist/tools/impls/anomaly.js +8 -4
- package/dist/tools/impls/anomaly.js.map +1 -1
- package/dist/tools/impls/compare.d.ts +2 -2
- package/dist/tools/impls/compare.js +8 -4
- package/dist/tools/impls/compare.js.map +1 -1
- package/dist/tools/impls/hrv.d.ts +2 -2
- package/dist/tools/impls/hrv.js +6 -2
- package/dist/tools/impls/hrv.js.map +1 -1
- package/dist/tools/impls/insights.d.ts +4 -4
- package/dist/tools/impls/insights.d.ts.map +1 -1
- package/dist/tools/impls/insights.js +10 -3
- package/dist/tools/impls/insights.js.map +1 -1
- package/dist/tools/impls/sleep.d.ts +2 -2
- package/dist/tools/impls/sleep.js +6 -2
- package/dist/tools/impls/sleep.js.map +1 -1
- package/dist/tools/impls/spo2.d.ts +52 -0
- package/dist/tools/impls/spo2.d.ts.map +1 -0
- package/dist/tools/impls/spo2.js +65 -0
- package/dist/tools/impls/spo2.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp/guard.d.ts +28 -11
- package/dist/tools/mcp/guard.d.ts.map +1 -1
- package/dist/tools/mcp/guard.js +45 -15
- package/dist/tools/mcp/guard.js.map +1 -1
- package/dist/tools/mcp/index.d.ts +1 -1
- package/dist/tools/mcp/index.d.ts.map +1 -1
- package/dist/tools/mcp/index.js +1 -1
- package/dist/tools/mcp/index.js.map +1 -1
- package/dist/tools/mcp/server.d.ts.map +1 -1
- package/dist/tools/mcp/server.js +4 -0
- package/dist/tools/mcp/server.js.map +1 -1
- package/dist/tools/mcp/transports/http.d.ts.map +1 -1
- package/dist/tools/mcp/transports/http.js +4 -0
- package/dist/tools/mcp/transports/http.js.map +1 -1
- package/package.json +8 -5
- package/dist/tools/formatters.d.ts.map +0 -1
- package/dist/tools/formatters.js.map +0 -1
|
@@ -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;
|
|
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"}
|
package/dist/tools/factory.js
CHANGED
|
@@ -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|
|
|
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,
|
|
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=
|
|
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"}
|
|
@@ -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
|
|
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
|
|
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":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;
|
|
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
|
}>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { formatBytes, popStddev } from '../formatters';
|
|
2
|
+
import { assertNoBanTerms, formatBytes, popStddev } from '../formatters';
|
|
3
3
|
export const detectAnomalyInputSchema = z.object({
|
|
4
4
|
userId: z.string(),
|
|
5
5
|
metric: z.enum(['hrv_ms', 'sleep_total_minutes', 'activity_steps']),
|
|
@@ -9,7 +9,7 @@ export const detectAnomalyInputSchema = z.object({
|
|
|
9
9
|
const METRIC_SPECS = {
|
|
10
10
|
hrv_ms: {
|
|
11
11
|
sql: lookbackDays => `SELECT date_trunc('day', ts)::VARCHAR AS day, AVG(hrv_ms)::DOUBLE AS value
|
|
12
|
-
FROM
|
|
12
|
+
FROM v_hrv
|
|
13
13
|
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
14
14
|
AND hrv_ms IS NOT NULL
|
|
15
15
|
AND ts >= now() - INTERVAL (${lookbackDays}) DAY
|
|
@@ -19,7 +19,7 @@ const METRIC_SPECS = {
|
|
|
19
19
|
},
|
|
20
20
|
sleep_total_minutes: {
|
|
21
21
|
sql: lookbackDays => `SELECT night_of::VARCHAR AS day, SUM(total_minutes)::DOUBLE AS value
|
|
22
|
-
FROM
|
|
22
|
+
FROM v_sleep_session
|
|
23
23
|
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
24
24
|
AND ts_start >= now() - INTERVAL (${lookbackDays}) DAY
|
|
25
25
|
GROUP BY night_of ORDER BY night_of`,
|
|
@@ -28,7 +28,7 @@ const METRIC_SPECS = {
|
|
|
28
28
|
},
|
|
29
29
|
activity_steps: {
|
|
30
30
|
sql: lookbackDays => `SELECT date_trunc('day', ts)::VARCHAR AS day, SUM(steps)::DOUBLE AS value
|
|
31
|
-
FROM
|
|
31
|
+
FROM v_activity
|
|
32
32
|
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
33
33
|
AND ts >= now() - INTERVAL (${lookbackDays}) DAY
|
|
34
34
|
GROUP BY 1 ORDER BY 1`,
|
|
@@ -65,6 +65,10 @@ export const detectAnomaly = async (input, ctx) => {
|
|
|
65
65
|
return finalize(text, outliers.length);
|
|
66
66
|
};
|
|
67
67
|
function finalize(text, rowCount) {
|
|
68
|
+
// principle 37 — every formatter's return path is guarded, not
|
|
69
|
+
// just the SpO2 one. Tool text lands in an LLM's context, and the
|
|
70
|
+
// model repeats whatever register it finds there.
|
|
71
|
+
assertNoBanTerms(text, 'detectAnomaly');
|
|
68
72
|
return { text, rowCount, bytes: formatBytes(text) };
|
|
69
73
|
}
|
|
70
74
|
//# sourceMappingURL=anomaly.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anomaly.js","sourceRoot":"","sources":["../../../src/tools/impls/anomaly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"anomaly.js","sourceRoot":"","sources":["../../../src/tools/impls/anomaly.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAGxE,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,qBAAqB,EAAE,gBAAgB,CAAC,CAAC;IACnE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC7C,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;CACrD,CAAC,CAAA;AAeF,MAAM,YAAY,GAAqD;IACrE,MAAM,EAAE;QACN,GAAG,EAAE,YAAY,CAAC,EAAE,CAClB;;;;uCAIiC,YAAY;6BACtB;QACzB,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,KAAK;KACb;IACD,mBAAmB,EAAE;QACnB,GAAG,EAAE,YAAY,CAAC,EAAE,CAClB;;;6CAGuC,YAAY;2CACd;QACvC,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,aAAa;KACrB;IACD,cAAc,EAAE;QACd,GAAG,EAAE,YAAY,CAAC,EAAE,CAClB;;;uCAGiC,YAAY;6BACtB;QACzB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,UAAU;KAClB;CACF,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAiC,KAAK,EAC9D,KAAK,EACL,GAAG,EACH,EAAE;IACF,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACvC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,OAAO,CAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE;QAC1E,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;KACvB,CAAC,CAAA;IAEF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,QAAQ,CACb,MAAM,IAAI,CAAC,KAAK,sBAAsB,KAAK,CAAC,YAAY,QAAQ,EAChE,CAAC,CACF,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IACrC,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,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,eAAe,GAAG,MAAM,CAAA;IAE3C,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IAElE,MAAM,WAAW,GACb,QAAQ,CAAC,MAAM,KAAK,CAAC;QACrB,CAAC,CAAC,yBAAyB;QAC3B,CAAC,CAAC,QAAQ;aACL,GAAG,CACF,CAAC,CAAC,EAAE,CACF,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG;cAC9C,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,aAAa,CAC7D;aACA,IAAI,CAAC,IAAI,CAAC,CAAA;IAEnB,MAAM,IAAI,GACN,GAAG,IAAI,CAAC,KAAK,oBAAoB,KAAK,CAAC,YAAY,KAAK;UACxD,aAAa,KAAK,CAAC,eAAe,OAAO;UACzC,oBAAoB,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI;UACnD,UAAU,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI;UAC3C,GAAG,WAAW,EAAE,CAAA;IAEpB,OAAO,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;AACxC,CAAC,CAAA;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB;IAC9C,+DAA+D;IAC/D,kEAAkE;IAClE,kDAAkD;IAClD,gBAAgB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;IACvC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAA;AACrD,CAAC"}
|
|
@@ -6,13 +6,13 @@ export declare const compareTrendInputSchema: z.ZodObject<{
|
|
|
6
6
|
currentWindowDays: z.ZodNumber;
|
|
7
7
|
priorWindowDays: 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
|
currentWindowDays: number;
|
|
12
12
|
priorWindowDays: 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
|
currentWindowDays: number;
|
|
17
17
|
priorWindowDays: number;
|
|
18
18
|
}>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { deltaPct, formatBytes } from '../formatters';
|
|
2
|
+
import { assertNoBanTerms, deltaPct, formatBytes } from '../formatters';
|
|
3
3
|
export const compareTrendInputSchema = z.object({
|
|
4
4
|
userId: z.string(),
|
|
5
5
|
metric: z.enum(['hrv_ms', 'sleep_total_minutes', 'activity_steps']),
|
|
@@ -9,7 +9,7 @@ export const compareTrendInputSchema = z.object({
|
|
|
9
9
|
const METRIC_SPECS = {
|
|
10
10
|
hrv_ms: {
|
|
11
11
|
sql: (windowDays, offsetDays) => `SELECT AVG(hrv_ms)::DOUBLE AS value
|
|
12
|
-
FROM
|
|
12
|
+
FROM v_hrv
|
|
13
13
|
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
14
14
|
AND hrv_ms IS NOT NULL
|
|
15
15
|
AND ts >= now() - INTERVAL (${windowDays + offsetDays}) DAY
|
|
@@ -19,7 +19,7 @@ const METRIC_SPECS = {
|
|
|
19
19
|
},
|
|
20
20
|
sleep_total_minutes: {
|
|
21
21
|
sql: (windowDays, offsetDays) => `SELECT AVG(total_minutes)::DOUBLE AS value
|
|
22
|
-
FROM
|
|
22
|
+
FROM v_sleep_session
|
|
23
23
|
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
24
24
|
AND ts_start >= now() - INTERVAL (${windowDays + offsetDays}) DAY
|
|
25
25
|
AND ts_start < now() - INTERVAL (${offsetDays}) DAY`,
|
|
@@ -29,7 +29,7 @@ const METRIC_SPECS = {
|
|
|
29
29
|
activity_steps: {
|
|
30
30
|
sql: (windowDays, offsetDays) => `SELECT AVG(daily_steps)::DOUBLE AS value FROM (
|
|
31
31
|
SELECT date_trunc('day', ts) AS day, SUM(steps) AS daily_steps
|
|
32
|
-
FROM
|
|
32
|
+
FROM v_activity
|
|
33
33
|
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
34
34
|
AND ts >= now() - INTERVAL (${windowDays + offsetDays}) DAY
|
|
35
35
|
AND ts < now() - INTERVAL (${offsetDays}) DAY
|
|
@@ -63,6 +63,10 @@ function fmt(v) {
|
|
|
63
63
|
return v == null ? 'n/a' : v.toFixed(1);
|
|
64
64
|
}
|
|
65
65
|
function finalize(text, rowCount) {
|
|
66
|
+
// principle 37 — every formatter's return path is guarded, not
|
|
67
|
+
// just the SpO2 one. Tool text lands in an LLM's context, and the
|
|
68
|
+
// model repeats whatever register it finds there.
|
|
69
|
+
assertNoBanTerms(text, 'compareTrend');
|
|
66
70
|
return { text, rowCount, bytes: formatBytes(text) };
|
|
67
71
|
}
|
|
68
72
|
//# sourceMappingURL=compare.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/tools/impls/compare.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/tools/impls/compare.ts"],"names":[],"mappings":"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"}
|