@mongrov/analytics 0.23.2 → 0.25.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/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/migrations.d.ts.map +1 -1
- package/dist/core/migrations.js +35 -0
- package/dist/core/migrations.js.map +1 -1
- package/dist/core/reading-quality.d.ts +90 -0
- package/dist/core/reading-quality.d.ts.map +1 -0
- package/dist/core/reading-quality.js +247 -0
- package/dist/core/reading-quality.js.map +1 -0
- package/dist/core/schemas.d.ts.map +1 -1
- package/dist/core/schemas.js +5 -2
- package/dist/core/schemas.js.map +1 -1
- package/dist/core/warehouse.d.ts.map +1 -1
- package/dist/core/warehouse.js +21 -0
- package/dist/core/warehouse.js.map +1 -1
- package/dist/rules/compiler.d.ts.map +1 -1
- package/dist/rules/compiler.js +4 -1
- package/dist/rules/compiler.js.map +1 -1
- package/dist/sync/baseline-compute.d.ts.map +1 -1
- package/dist/sync/baseline-compute.js +4 -1
- package/dist/sync/baseline-compute.js.map +1 -1
- package/dist/sync/factory.d.ts +7 -0
- package/dist/sync/factory.d.ts.map +1 -1
- package/dist/sync/factory.js +31 -1
- package/dist/sync/factory.js.map +1 -1
- package/dist/sync/index.d.ts +2 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/mapper/firmware.d.ts.map +1 -1
- package/dist/sync/mapper/firmware.js +7 -5
- package/dist/sync/mapper/firmware.js.map +1 -1
- package/dist/sync/mapper/schema.d.ts +29 -36
- package/dist/sync/mapper/schema.d.ts.map +1 -1
- package/dist/sync/mapper/schema.js +11 -12
- package/dist/sync/mapper/schema.js.map +1 -1
- package/dist/sync/mapper/sleep.d.ts +50 -43
- package/dist/sync/mapper/sleep.d.ts.map +1 -1
- package/dist/sync/mapper/sleep.js +99 -138
- package/dist/sync/mapper/sleep.js.map +1 -1
- package/dist/sync/mapper/types.d.ts +17 -18
- package/dist/sync/mapper/types.d.ts.map +1 -1
- package/dist/sync/sleep-correction/classify.d.ts +86 -0
- package/dist/sync/sleep-correction/classify.d.ts.map +1 -0
- package/dist/sync/sleep-correction/classify.js +247 -0
- package/dist/sync/sleep-correction/classify.js.map +1 -0
- package/dist/sync/sleep-correction/correct.d.ts +46 -0
- package/dist/sync/sleep-correction/correct.d.ts.map +1 -0
- package/dist/sync/sleep-correction/correct.js +60 -0
- package/dist/sync/sleep-correction/correct.js.map +1 -0
- package/dist/sync/sleep-correction/index.d.ts +8 -0
- package/dist/sync/sleep-correction/index.d.ts.map +1 -0
- package/dist/sync/sleep-correction/index.js +8 -0
- package/dist/sync/sleep-correction/index.js.map +1 -0
- package/dist/sync/sleep-correction/night.d.ts +26 -0
- package/dist/sync/sleep-correction/night.d.ts.map +1 -0
- package/dist/sync/sleep-correction/night.js +48 -0
- package/dist/sync/sleep-correction/night.js.map +1 -0
- package/dist/sync/sleep-correction/orchestrate.d.ts +50 -0
- package/dist/sync/sleep-correction/orchestrate.d.ts.map +1 -0
- package/dist/sync/sleep-correction/orchestrate.js +57 -0
- package/dist/sync/sleep-correction/orchestrate.js.map +1 -0
- package/dist/sync/sleep-correction/settle.d.ts +24 -0
- package/dist/sync/sleep-correction/settle.d.ts.map +1 -0
- package/dist/sync/sleep-correction/settle.js +59 -0
- package/dist/sync/sleep-correction/settle.js.map +1 -0
- package/dist/sync/sleep-correction/sql.d.ts +44 -0
- package/dist/sync/sleep-correction/sql.d.ts.map +1 -0
- package/dist/sync/sleep-correction/sql.js +329 -0
- package/dist/sync/sleep-correction/sql.js.map +1 -0
- package/dist/sync/sleep-correction/staging.d.ts +32 -0
- package/dist/sync/sleep-correction/staging.d.ts.map +1 -0
- package/dist/sync/sleep-correction/staging.js +51 -0
- package/dist/sync/sleep-correction/staging.js.map +1 -0
- package/dist/sync/sleep-derive.d.ts +55 -0
- package/dist/sync/sleep-derive.d.ts.map +1 -0
- package/dist/sync/sleep-derive.js +180 -0
- package/dist/sync/sleep-derive.js.map +1 -0
- package/dist/tools/impls/anomaly.js +1 -1
- package/dist/tools/impls/compare.js +1 -1
- package/dist/tools/impls/hrv.js +1 -1
- package/dist/tools/impls/spo2.js +1 -1
- package/dist/tools/impls/temperature.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derived sleep — the correction step every batch cycle runs before
|
|
3
|
+
* `batch:complete` (sleep-correction §3).
|
|
4
|
+
*
|
|
5
|
+
* `sleep_raw` is written like any sensor table. Sessions and stages are not:
|
|
6
|
+
* they are derived from it by the validated correction pipeline, night by
|
|
7
|
+
* night, and written back into the SAME batch, so a `context: 'asleep'` rule
|
|
8
|
+
* woken by `batch:complete` sees corrected sleep, never raw minutes.
|
|
9
|
+
*
|
|
10
|
+
* Raw rows can arrive by three flush paths (scheduled cycle, manual flush,
|
|
11
|
+
* size/age triggers), so the deriver does not hang off any one flush: it
|
|
12
|
+
* notes every enqueued `sleep_raw` range, and the next batch cycle corrects
|
|
13
|
+
* all pending nights after its flushes land.
|
|
14
|
+
*
|
|
15
|
+
* Night replace (principle 66 as amended): a corrected night's END moves as
|
|
16
|
+
* later data arrives, and the principle-25 id moves with it, so a night is
|
|
17
|
+
* DELETEd locally before its new rows are written. Nights that come back with
|
|
18
|
+
* no primary block are left alone — replacing a night with nothing on a
|
|
19
|
+
* partial sync would erase sleep the next sync restores.
|
|
20
|
+
*
|
|
21
|
+
* Local only. Rows already pushed to a remote catalog are not rewritten; that
|
|
22
|
+
* needs a server-side answer before cloud sync is switched on.
|
|
23
|
+
*/
|
|
24
|
+
import type { HybridDuckDB } from '../core/engine';
|
|
25
|
+
import type { SensorBuffer } from './buffer';
|
|
26
|
+
import type { BatchFlusher, FlushReason } from './flusher';
|
|
27
|
+
import type { SchedulerLogger } from './scheduler';
|
|
28
|
+
export interface SleepDeriverDeps {
|
|
29
|
+
engine: HybridDuckDB;
|
|
30
|
+
buffer: SensorBuffer;
|
|
31
|
+
flusher: BatchFlusher;
|
|
32
|
+
/** The user's IANA zone (the factory's `resolveTimezone`). */
|
|
33
|
+
resolveTimezone: (userId: string) => Promise<string>;
|
|
34
|
+
/** Baseline lookback. v3.1 production: 30 days. */
|
|
35
|
+
lookbackDays?: number;
|
|
36
|
+
now?: () => number;
|
|
37
|
+
logger?: SchedulerLogger;
|
|
38
|
+
localCatalog?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface SleepDeriver {
|
|
41
|
+
/** Record an enqueued `sleep_raw` batch so the next cycle corrects its nights. */
|
|
42
|
+
noteRaw: (batch: {
|
|
43
|
+
brand: string;
|
|
44
|
+
familyId: string;
|
|
45
|
+
userId: string;
|
|
46
|
+
deviceId: string;
|
|
47
|
+
rows: ReadonlyArray<Record<string, unknown>>;
|
|
48
|
+
}) => void;
|
|
49
|
+
/** (user, device) ranges still waiting for a cycle. */
|
|
50
|
+
pendingCount: () => number;
|
|
51
|
+
/** Correct every pending range and write its nights into `batchId`. Never throws. */
|
|
52
|
+
derive: (batchId: string, reason: FlushReason) => Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export declare function createSleepDeriver(deps: SleepDeriverDeps): SleepDeriver;
|
|
55
|
+
//# sourceMappingURL=sleep-derive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep-derive.d.ts","sourceRoot":"","sources":["../../src/sync/sleep-derive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAiBlD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,YAAY,CAAA;IACpB,OAAO,EAAE,YAAY,CAAA;IACrB,8DAA8D;IAC9D,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACpD,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,OAAO,EAAE,CAAC,KAAK,EAAE;QACf,KAAK,EAAE,MAAM,CAAA;QACb,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;KAC7C,KAAK,IAAI,CAAA;IACV,uDAAuD;IACvD,YAAY,EAAE,MAAM,MAAM,CAAA;IAC1B,qFAAqF;IACrF,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChE;AA8CD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,YAAY,CAuHvE"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derived sleep — the correction step every batch cycle runs before
|
|
3
|
+
* `batch:complete` (sleep-correction §3).
|
|
4
|
+
*
|
|
5
|
+
* `sleep_raw` is written like any sensor table. Sessions and stages are not:
|
|
6
|
+
* they are derived from it by the validated correction pipeline, night by
|
|
7
|
+
* night, and written back into the SAME batch, so a `context: 'asleep'` rule
|
|
8
|
+
* woken by `batch:complete` sees corrected sleep, never raw minutes.
|
|
9
|
+
*
|
|
10
|
+
* Raw rows can arrive by three flush paths (scheduled cycle, manual flush,
|
|
11
|
+
* size/age triggers), so the deriver does not hang off any one flush: it
|
|
12
|
+
* notes every enqueued `sleep_raw` range, and the next batch cycle corrects
|
|
13
|
+
* all pending nights after its flushes land.
|
|
14
|
+
*
|
|
15
|
+
* Night replace (principle 66 as amended): a corrected night's END moves as
|
|
16
|
+
* later data arrives, and the principle-25 id moves with it, so a night is
|
|
17
|
+
* DELETEd locally before its new rows are written. Nights that come back with
|
|
18
|
+
* no primary block are left alone — replacing a night with nothing on a
|
|
19
|
+
* partial sync would erase sleep the next sync restores.
|
|
20
|
+
*
|
|
21
|
+
* Local only. Rows already pushed to a remote catalog are not rewritten; that
|
|
22
|
+
* needs a server-side answer before cloud sync is switched on.
|
|
23
|
+
*/
|
|
24
|
+
import { sessionsFromCorrected } from './mapper/sleep';
|
|
25
|
+
import { computeNightOf } from './mapper/time';
|
|
26
|
+
import { correctSleepNights, nightsForEpochs } from './sleep-correction/orchestrate';
|
|
27
|
+
import { stagingViewsSql } from './sleep-correction/staging';
|
|
28
|
+
const SIX_HOURS = 6 * 3600;
|
|
29
|
+
function epochOf(ts) {
|
|
30
|
+
if (ts instanceof Date)
|
|
31
|
+
return Math.floor(ts.getTime() / 1000);
|
|
32
|
+
if (typeof ts === 'number')
|
|
33
|
+
return Math.floor(ts > 1e12 ? ts / 1000 : ts);
|
|
34
|
+
if (typeof ts === 'string') {
|
|
35
|
+
const ms = Date.parse(ts.includes('T') ? ts : `${ts.replace(' ', 'T')}Z`);
|
|
36
|
+
return Number.isFinite(ms) ? Math.floor(ms / 1000) : null;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/** The `night_of` value a session starting inside this window is stored with. */
|
|
41
|
+
function storedNightOf(windowStart, timeZone) {
|
|
42
|
+
return computeNightOf(new Date((windowStart + 60) * 1000), timeZone).toISOString().slice(0, 10);
|
|
43
|
+
}
|
|
44
|
+
/** Every 6pm→6pm night between two instants, inclusive. */
|
|
45
|
+
function nightsBetween(minEpoch, maxEpoch, timeZone) {
|
|
46
|
+
const probes = [];
|
|
47
|
+
for (let e = minEpoch; e < maxEpoch; e += SIX_HOURS)
|
|
48
|
+
probes.push(e);
|
|
49
|
+
probes.push(maxEpoch);
|
|
50
|
+
return nightsForEpochs(probes, timeZone);
|
|
51
|
+
}
|
|
52
|
+
function DELETE_STAGES_SQL(catalog) {
|
|
53
|
+
return `
|
|
54
|
+
DELETE FROM ${catalog}.main.sleep_stage
|
|
55
|
+
WHERE session_id IN (
|
|
56
|
+
SELECT session_id FROM ${catalog}.main.sleep_session
|
|
57
|
+
WHERE brand = $brand AND family_id = $family_id AND user_id = $user_id
|
|
58
|
+
AND device_id = $device_id AND night_of = CAST($night AS DATE)
|
|
59
|
+
)`;
|
|
60
|
+
}
|
|
61
|
+
function DELETE_SESSIONS_SQL(catalog) {
|
|
62
|
+
return `
|
|
63
|
+
DELETE FROM ${catalog}.main.sleep_session
|
|
64
|
+
WHERE brand = $brand AND family_id = $family_id AND user_id = $user_id
|
|
65
|
+
AND device_id = $device_id AND night_of = CAST($night AS DATE)`;
|
|
66
|
+
}
|
|
67
|
+
export function createSleepDeriver(deps) {
|
|
68
|
+
const pending = new Map();
|
|
69
|
+
const catalog = deps.localCatalog ?? 'memory';
|
|
70
|
+
const now = deps.now ?? (() => Date.now());
|
|
71
|
+
const lookbackDays = deps.lookbackDays ?? 30;
|
|
72
|
+
async function replaceNight(ctx, night) {
|
|
73
|
+
const params = {
|
|
74
|
+
brand: ctx.brand,
|
|
75
|
+
family_id: ctx.familyId,
|
|
76
|
+
user_id: ctx.userId,
|
|
77
|
+
device_id: ctx.deviceId,
|
|
78
|
+
night,
|
|
79
|
+
};
|
|
80
|
+
await deps.engine.execute(DELETE_STAGES_SQL(catalog), params);
|
|
81
|
+
await deps.engine.execute(DELETE_SESSIONS_SQL(catalog), params);
|
|
82
|
+
}
|
|
83
|
+
async function deriveRange(p) {
|
|
84
|
+
const timeZone = await deps.resolveTimezone(p.userId);
|
|
85
|
+
const ctx = {
|
|
86
|
+
brand: p.brand,
|
|
87
|
+
familyId: p.familyId,
|
|
88
|
+
userId: p.userId,
|
|
89
|
+
deviceId: p.deviceId,
|
|
90
|
+
userTimezone: timeZone,
|
|
91
|
+
};
|
|
92
|
+
for (const sql of stagingViewsSql({ ...ctx, localCatalog: catalog }))
|
|
93
|
+
await deps.engine.execute(sql);
|
|
94
|
+
const result = await correctSleepNights(deps.engine, {
|
|
95
|
+
nights: nightsBetween(p.minEpoch, p.maxEpoch, timeZone),
|
|
96
|
+
timeZone,
|
|
97
|
+
cutoffEpoch: Math.floor(now() / 1000) - lookbackDays * 86400,
|
|
98
|
+
});
|
|
99
|
+
const sessions = [];
|
|
100
|
+
const stages = [];
|
|
101
|
+
for (const n of result.nights) {
|
|
102
|
+
if (n.status === 'failed')
|
|
103
|
+
deps.logger?.warn('sync.sleep-derive: night failed; previous rows kept', { night: n.night, err: n.error });
|
|
104
|
+
if (n.status !== 'ok' || !n.rows)
|
|
105
|
+
continue;
|
|
106
|
+
const out = sessionsFromCorrected(n.rows, ctx, { settleMin: n.settleMin });
|
|
107
|
+
if (out.sleep_session.length === 0)
|
|
108
|
+
continue;
|
|
109
|
+
// Two conventions meet here. v3.1 names a night by its MORNING date
|
|
110
|
+
// (window Jun 17 18:00 → Jun 18 18:00 is "2026-06-18"); `night_of` is
|
|
111
|
+
// the mapper's evening date, stored as the UTC date of `computeNightOf`.
|
|
112
|
+
// Deleting by `n.night` would wipe the FOLLOWING night — so the key is
|
|
113
|
+
// derived exactly as the writer derives it, from inside the window.
|
|
114
|
+
const nights = new Set([
|
|
115
|
+
storedNightOf(n.windowStart, timeZone),
|
|
116
|
+
...out.sleep_session.map(s => s.night_of.toISOString().slice(0, 10)),
|
|
117
|
+
]);
|
|
118
|
+
for (const night of nights)
|
|
119
|
+
await replaceNight(ctx, night);
|
|
120
|
+
sessions.push(...out.sleep_session);
|
|
121
|
+
stages.push(...out.sleep_stage);
|
|
122
|
+
}
|
|
123
|
+
return { sessions, stages };
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
noteRaw(batch) {
|
|
127
|
+
let min = Number.POSITIVE_INFINITY;
|
|
128
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
129
|
+
for (const row of batch.rows) {
|
|
130
|
+
const e = epochOf(row.ts);
|
|
131
|
+
if (e == null)
|
|
132
|
+
continue;
|
|
133
|
+
if (e < min)
|
|
134
|
+
min = e;
|
|
135
|
+
if (e > max)
|
|
136
|
+
max = e;
|
|
137
|
+
}
|
|
138
|
+
if (!Number.isFinite(min))
|
|
139
|
+
return;
|
|
140
|
+
const key = `${batch.brand}|${batch.familyId}|${batch.userId}|${batch.deviceId}`;
|
|
141
|
+
const prev = pending.get(key);
|
|
142
|
+
pending.set(key, {
|
|
143
|
+
brand: batch.brand,
|
|
144
|
+
familyId: batch.familyId,
|
|
145
|
+
userId: batch.userId,
|
|
146
|
+
deviceId: batch.deviceId,
|
|
147
|
+
minEpoch: prev ? Math.min(prev.minEpoch, min) : min,
|
|
148
|
+
maxEpoch: prev ? Math.max(prev.maxEpoch, max) : max,
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
pendingCount: () => pending.size,
|
|
152
|
+
async derive(batchId, reason) {
|
|
153
|
+
if (pending.size === 0)
|
|
154
|
+
return;
|
|
155
|
+
const ranges = [...pending.values()];
|
|
156
|
+
pending.clear();
|
|
157
|
+
for (const p of ranges) {
|
|
158
|
+
try {
|
|
159
|
+
const { sessions, stages } = await deriveRange(p);
|
|
160
|
+
const tenant = { brand: p.brand, familyId: p.familyId, userId: p.userId, deviceId: p.deviceId };
|
|
161
|
+
if (sessions.length > 0)
|
|
162
|
+
await deps.buffer.push({ table: 'sleep_session', ...tenant, rows: sessions });
|
|
163
|
+
if (stages.length > 0)
|
|
164
|
+
await deps.buffer.push({ table: 'sleep_stage', ...tenant, rows: stages });
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
deps.logger?.warn('sync.sleep-derive: correction failed; raw sleep kept, nights unchanged', {
|
|
168
|
+
userId: p.userId,
|
|
169
|
+
deviceId: p.deviceId,
|
|
170
|
+
err: err instanceof Error ? err.message : String(err),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Land the derived rows inside this batch, so batch:complete follows them.
|
|
175
|
+
await deps.flusher.flush('sleep_session', reason, batchId);
|
|
176
|
+
await deps.flusher.flush('sleep_stage', reason, batchId);
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=sleep-derive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep-derive.js","sourceRoot":"","sources":["../../src/sync/sleep-derive.ts"],"names":[],"mappings":"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"}
|
|
@@ -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_clean
|
|
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
|
|
@@ -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_clean
|
|
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
|
package/dist/tools/impls/hrv.js
CHANGED
|
@@ -6,7 +6,7 @@ export const getHRVInputSchema = z.object({
|
|
|
6
6
|
});
|
|
7
7
|
export const getHRV = async (input, ctx) => {
|
|
8
8
|
const rows = await ctx.analytics.execute(`SELECT date_trunc('day', ts)::VARCHAR AS day, AVG(hrv_ms)::DOUBLE AS avg_hrv
|
|
9
|
-
FROM
|
|
9
|
+
FROM v_hrv_clean
|
|
10
10
|
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
11
11
|
AND ts >= now() - INTERVAL (CAST($days AS BIGINT)) DAY
|
|
12
12
|
AND hrv_ms IS NOT NULL
|
package/dist/tools/impls/spo2.js
CHANGED
|
@@ -39,7 +39,7 @@ export const getSpO2 = async (input, ctx) => {
|
|
|
39
39
|
AVG(m.spo2)::DOUBLE AS avg_spo2,
|
|
40
40
|
MIN(m.spo2)::DOUBLE AS min_spo2,
|
|
41
41
|
COUNT(*) FILTER (WHERE m.spo2 < ${LOW_MOMENT_THRESHOLD})::INTEGER AS low_moment_count
|
|
42
|
-
FROM
|
|
42
|
+
FROM v_spo2_clean m
|
|
43
43
|
INNER JOIN v_sleep_session s
|
|
44
44
|
ON s.user_id = m.user_id
|
|
45
45
|
AND s.brand = m.brand
|
|
@@ -25,7 +25,7 @@ export const getTemperature = async (input, ctx) => {
|
|
|
25
25
|
AVG(temp_c)::DOUBLE AS avg_temp,
|
|
26
26
|
MAX(temp_c)::DOUBLE AS hi_temp,
|
|
27
27
|
MIN(temp_c)::DOUBLE AS lo_temp
|
|
28
|
-
FROM
|
|
28
|
+
FROM v_temperature_clean
|
|
29
29
|
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
30
30
|
AND ts >= now() - INTERVAL (CAST($days AS BIGINT)) DAY
|
|
31
31
|
AND temp_c IS NOT NULL
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongrov/analytics",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"description": "DuckDB + R2 Iceberg local analytics for @mongrov apps",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
],
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@modelcontextprotocol/sdk": ">=1.20.0",
|
|
64
|
-
"@mongrov/types": "^0.
|
|
64
|
+
"@mongrov/types": "^0.10.0",
|
|
65
65
|
"ai": ">=4",
|
|
66
66
|
"react": ">=19",
|
|
67
67
|
"react-native": ">=0.81",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"vitest": "^2.1.9",
|
|
109
109
|
"zod": "^3.23.8",
|
|
110
110
|
"@mongrov/db": "0.3.0",
|
|
111
|
-
"@mongrov/types": "0.
|
|
111
|
+
"@mongrov/types": "0.10.0"
|
|
112
112
|
},
|
|
113
113
|
"scripts": {
|
|
114
114
|
"build": "tsc -p tsconfig.build.json",
|