@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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod contract for the firmware export payload (principle 60 — firmware
|
|
3
|
+
* fixture governance).
|
|
4
|
+
*
|
|
5
|
+
* Models `FirmwareExport` from `./types.ts` EXACTLY, with `.strict()` on
|
|
6
|
+
* every object so unknown keys fail parsing. This is the CI gate: when the
|
|
7
|
+
* firmware team lands a new versioned fixture, any key the mapper doesn't
|
|
8
|
+
* know about surfaces as a test failure — a PR-blocking event that forces a
|
|
9
|
+
* deliberate schema + mapper update instead of a silent drop.
|
|
10
|
+
*
|
|
11
|
+
* `FIRMWARE_EXPORT_KEY_PATHS` is the same contract expressed as a key-path
|
|
12
|
+
* set (`heartrate`, `heartrate[].timestamp`, ...) for the fixture key-diff
|
|
13
|
+
* test in `__tests__/schema.test.ts`. Keep both in lockstep with
|
|
14
|
+
* `FirmwareExport` — the key-diff test asserts the fixture corpus exercises
|
|
15
|
+
* exactly this set.
|
|
16
|
+
*/
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
/** Firmware timestamp: `"YYYY.MM.DD HH:MM:SS"` UTC wall-clock (see time.ts). */
|
|
19
|
+
const firmwareTimestamp = z.string().regex(/^\d{4}\.\d{1,2}\.\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}$/, 'expected firmware "YYYY.MM.DD HH:MM:SS" timestamp');
|
|
20
|
+
export const firmwareHRRowSchema = z.object({
|
|
21
|
+
timestamp: firmwareTimestamp,
|
|
22
|
+
singleHR: z.number(),
|
|
23
|
+
}).strict();
|
|
24
|
+
export const firmwareHRVRowSchema = z.object({
|
|
25
|
+
timestamp: firmwareTimestamp,
|
|
26
|
+
hrv: z.number().optional(),
|
|
27
|
+
stress: z.number().optional(),
|
|
28
|
+
heartRate: z.number().optional(),
|
|
29
|
+
systolicBP: z.number().optional(),
|
|
30
|
+
diastolicBP: z.number().optional(),
|
|
31
|
+
vascularAging: z.number().optional(),
|
|
32
|
+
}).strict();
|
|
33
|
+
export const firmwareSpO2RowSchema = z.object({
|
|
34
|
+
timestamp: firmwareTimestamp,
|
|
35
|
+
automaticSpo2Data: z.number(),
|
|
36
|
+
}).strict();
|
|
37
|
+
export const firmwareTempRowSchema = z.object({
|
|
38
|
+
timestamp: firmwareTimestamp,
|
|
39
|
+
temperature: z.number(),
|
|
40
|
+
}).strict();
|
|
41
|
+
export const firmwareActivityRowSchema = z.object({
|
|
42
|
+
timestamp: firmwareTimestamp,
|
|
43
|
+
step: z.number(),
|
|
44
|
+
calories: z.number(),
|
|
45
|
+
distance: z.number(),
|
|
46
|
+
arraySteps: z.array(z.number()).length(10),
|
|
47
|
+
}).strict();
|
|
48
|
+
export const firmwareSleepRowSchema = z.object({
|
|
49
|
+
start: firmwareTimestamp,
|
|
50
|
+
end: firmwareTimestamp,
|
|
51
|
+
block_type: z.string(),
|
|
52
|
+
confidence: z.number(),
|
|
53
|
+
timestamp: firmwareTimestamp,
|
|
54
|
+
}).strict();
|
|
55
|
+
export const firmwareBatteryRowSchema = z.object({
|
|
56
|
+
timestamp: firmwareTimestamp,
|
|
57
|
+
battery: z.number(),
|
|
58
|
+
}).strict();
|
|
59
|
+
export const firmwareWeekdaysSchema = z.object({
|
|
60
|
+
sunday: z.boolean(),
|
|
61
|
+
monday: z.boolean(),
|
|
62
|
+
Tuesday: z.boolean(),
|
|
63
|
+
Wednesday: z.boolean(),
|
|
64
|
+
Thursday: z.boolean(),
|
|
65
|
+
Friday: z.boolean(),
|
|
66
|
+
Saturday: z.boolean(),
|
|
67
|
+
}).strict();
|
|
68
|
+
/**
|
|
69
|
+
* Mirrors `AutomaticMonitoring_J2301A` from the JStyle SDK. `dataType` is
|
|
70
|
+
* the firmware enum (1=heartRate, 2=spo2, 3=temperature, 4=HRV) and is
|
|
71
|
+
* translated away at the mapper boundary — see `mapper/ring-config.ts`.
|
|
72
|
+
*/
|
|
73
|
+
export const firmwareMonitoringWindowSchema = z.object({
|
|
74
|
+
dataType: z.number().int(),
|
|
75
|
+
intervalTime: z.number().int().nonnegative(),
|
|
76
|
+
startTime_Hour: z.number().int(),
|
|
77
|
+
startTime_Minutes: z.number().int(),
|
|
78
|
+
endTime_Hour: z.number().int(),
|
|
79
|
+
endTime_Minutes: z.number().int(),
|
|
80
|
+
weeks: firmwareWeekdaysSchema,
|
|
81
|
+
mode: z.number().int().optional(),
|
|
82
|
+
}).strict();
|
|
83
|
+
export const firmwareRingConfigSchema = z.object({
|
|
84
|
+
automaticMonitoringData: z.array(firmwareMonitoringWindowSchema),
|
|
85
|
+
}).strict();
|
|
86
|
+
export const firmwareExportSchema = z.object({
|
|
87
|
+
// Fixture files carry a human-readable `$comment`; real device exports
|
|
88
|
+
// may omit it. Not part of `FirmwareExport` — stripped before mapping.
|
|
89
|
+
$comment: z.string().optional(),
|
|
90
|
+
heartrate: z.array(firmwareHRRowSchema),
|
|
91
|
+
hrv_table: z.array(firmwareHRVRowSchema),
|
|
92
|
+
spo2: z.array(firmwareSpO2RowSchema),
|
|
93
|
+
temperature_table: z.array(firmwareTempRowSchema),
|
|
94
|
+
activitydetails: z.array(firmwareActivityRowSchema),
|
|
95
|
+
sleep_processed: z.array(firmwareSleepRowSchema),
|
|
96
|
+
battery_table: z.array(firmwareBatteryRowSchema),
|
|
97
|
+
ring: firmwareRingConfigSchema,
|
|
98
|
+
}).strict();
|
|
99
|
+
const _assertParsedMatchesType = true;
|
|
100
|
+
void _assertParsedMatchesType;
|
|
101
|
+
/**
|
|
102
|
+
* Every key-path the schema knows about. Array elements are flattened as
|
|
103
|
+
* `section[].key`. Used by the fixture key-diff test (principle 60).
|
|
104
|
+
*/
|
|
105
|
+
export const FIRMWARE_EXPORT_KEY_PATHS = new Set([
|
|
106
|
+
'$comment',
|
|
107
|
+
'heartrate',
|
|
108
|
+
'heartrate[].timestamp',
|
|
109
|
+
'heartrate[].singleHR',
|
|
110
|
+
'hrv_table',
|
|
111
|
+
'hrv_table[].timestamp',
|
|
112
|
+
'hrv_table[].hrv',
|
|
113
|
+
'hrv_table[].stress',
|
|
114
|
+
'hrv_table[].heartRate',
|
|
115
|
+
'hrv_table[].systolicBP',
|
|
116
|
+
'hrv_table[].diastolicBP',
|
|
117
|
+
'hrv_table[].vascularAging',
|
|
118
|
+
'spo2',
|
|
119
|
+
'spo2[].timestamp',
|
|
120
|
+
'spo2[].automaticSpo2Data',
|
|
121
|
+
'temperature_table',
|
|
122
|
+
'temperature_table[].timestamp',
|
|
123
|
+
'temperature_table[].temperature',
|
|
124
|
+
'activitydetails',
|
|
125
|
+
'activitydetails[].timestamp',
|
|
126
|
+
'activitydetails[].step',
|
|
127
|
+
'activitydetails[].calories',
|
|
128
|
+
'activitydetails[].distance',
|
|
129
|
+
'activitydetails[].arraySteps',
|
|
130
|
+
'sleep_processed',
|
|
131
|
+
'sleep_processed[].start',
|
|
132
|
+
'sleep_processed[].end',
|
|
133
|
+
'sleep_processed[].block_type',
|
|
134
|
+
'sleep_processed[].confidence',
|
|
135
|
+
'sleep_processed[].timestamp',
|
|
136
|
+
'battery_table',
|
|
137
|
+
'battery_table[].timestamp',
|
|
138
|
+
'battery_table[].battery',
|
|
139
|
+
'ring',
|
|
140
|
+
'ring.automaticMonitoringData',
|
|
141
|
+
// AutomaticMonitoring_J2301A (JStyle SDK). `dataType` is the firmware
|
|
142
|
+
// enum — 1=heartRate, 2=spo2, 3=temperature, 4=HRV — translated away at
|
|
143
|
+
// the mapper boundary so it never reaches a warehouse column.
|
|
144
|
+
'ring.automaticMonitoringData[].dataType',
|
|
145
|
+
'ring.automaticMonitoringData[].intervalTime',
|
|
146
|
+
'ring.automaticMonitoringData[].startTime_Hour',
|
|
147
|
+
'ring.automaticMonitoringData[].startTime_Minutes',
|
|
148
|
+
'ring.automaticMonitoringData[].endTime_Hour',
|
|
149
|
+
'ring.automaticMonitoringData[].endTime_Minutes',
|
|
150
|
+
'ring.automaticMonitoringData[].mode',
|
|
151
|
+
'ring.automaticMonitoringData[].weeks',
|
|
152
|
+
'ring.automaticMonitoringData[].weeks.sunday',
|
|
153
|
+
'ring.automaticMonitoringData[].weeks.monday',
|
|
154
|
+
'ring.automaticMonitoringData[].weeks.Tuesday',
|
|
155
|
+
'ring.automaticMonitoringData[].weeks.Wednesday',
|
|
156
|
+
'ring.automaticMonitoringData[].weeks.Thursday',
|
|
157
|
+
'ring.automaticMonitoringData[].weeks.Friday',
|
|
158
|
+
'ring.automaticMonitoringData[].weeks.Saturday',
|
|
159
|
+
]);
|
|
160
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/sync/mapper/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,31 +2,76 @@
|
|
|
2
2
|
* Sleep reconstruction mapper (T-06).
|
|
3
3
|
*
|
|
4
4
|
* Firmware `sleep_processed` emits per-block rows tagged with a `start`
|
|
5
|
-
* (session start), `end
|
|
6
|
-
* `confidence` (0..1), and per-block `timestamp` (block instant).
|
|
7
|
-
* blocks share the same `start` when they belong to the same
|
|
5
|
+
* (session start), `end` (session end), `block_type` (primary / light / deep /
|
|
6
|
+
* rem / awake), `confidence` (0..1), and per-block `timestamp` (block instant).
|
|
7
|
+
* Multiple blocks share the same `start` when they belong to the same session.
|
|
8
8
|
*
|
|
9
9
|
* Reconstruction rules (spec.md §Firmware mapper + tasks.md T-06):
|
|
10
10
|
* - Group rows by `start`.
|
|
11
11
|
* - A session is included if AT LEAST ONE block in the group has
|
|
12
12
|
* `block_type === 'primary'` AND `confidence >= 0.7`. This filters
|
|
13
13
|
* day-time naps and low-confidence noise.
|
|
14
|
-
* - Session boundaries
|
|
15
|
-
* `
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* -
|
|
22
|
-
*
|
|
23
|
-
*
|
|
14
|
+
* - Session boundaries come from the firmware's own envelope fields:
|
|
15
|
+
* `ts_start = parse(start)`, `ts_end = parse(end)`. `total_minutes` is
|
|
16
|
+
* their difference. Deriving the envelope from block instants would
|
|
17
|
+
* under-report any session whose first/last minutes went unclassified.
|
|
18
|
+
* - `session_id` = `nanoid(24) + '_' + fnv1a32hex(device_id | user_id |
|
|
19
|
+
* ts_session_start)` (principle 25, locked) — collision-safe even with
|
|
20
|
+
* corrupted timestamps.
|
|
21
|
+
* - `night_of` = 6pm-6pm rule applied to `ts_start` (see `time.ts`).
|
|
22
|
+
* - Stages: one row per classified block, carrying `session_id` and the
|
|
23
|
+
* DDL's integer `stage` code. `primary` is the session envelope marker,
|
|
24
|
+
* not a stage, so it produces no `sleep_stage` row — the DDL's stage
|
|
25
|
+
* enum (1=awake, 2=light, 3=deep, 5=rem) has no code for it. Unknown
|
|
26
|
+
* block types are likewise skipped; nothing is lost, because...
|
|
27
|
+
* - Raw: every input row is preserved in `sleep_raw` regardless of the
|
|
28
|
+
* primary/confidence filter, flattened onto the DDL's columns, so
|
|
29
|
+
* downstream reprocessing can revisit sessions with new heuristics.
|
|
30
|
+
*
|
|
31
|
+
* Principle 20 boundary: firmware `block_type` strings are translated to our
|
|
32
|
+
* integer codes here and never reach the warehouse.
|
|
24
33
|
*/
|
|
25
34
|
import type { FirmwareSleepRow, MapperContext, SleepRawRow, SleepSessionRow, SleepStageRow } from './types';
|
|
35
|
+
/**
|
|
36
|
+
* Block width in minutes when the firmware revision omits `unit_length`.
|
|
37
|
+
* Firmware doc §Sleep processing: blocks are 1-min wide by convention.
|
|
38
|
+
*/
|
|
39
|
+
export declare const DEFAULT_BLOCK_MINUTES = 1;
|
|
40
|
+
/**
|
|
41
|
+
* Firmware `block_type` → `sleep_stage.stage` code (spec §Table schema:
|
|
42
|
+
* `stage SMALLINT NOT NULL -- 1=awake, 2=light, 3=deep, 5=rem`).
|
|
43
|
+
*
|
|
44
|
+
* `primary` is deliberately absent: it marks the session envelope, not a
|
|
45
|
+
* sleep stage. The enum is intentionally sparse (no 4) to match the DDL
|
|
46
|
+
* comment verbatim.
|
|
47
|
+
*/
|
|
48
|
+
export declare const SLEEP_STAGE_CODES: Readonly<Record<string, number>>;
|
|
49
|
+
/** Reverse lookup for the stage-minute accumulator + diagnostics. */
|
|
50
|
+
export declare const SLEEP_STAGE_NAMES: Readonly<Record<number, string>>;
|
|
26
51
|
export interface ReconstructSleepResult {
|
|
27
52
|
sleep_session: SleepSessionRow[];
|
|
28
53
|
sleep_stage: SleepStageRow[];
|
|
29
54
|
sleep_raw: SleepRawRow[];
|
|
30
55
|
}
|
|
31
56
|
export declare function reconstructSleepSessions(rows: readonly FirmwareSleepRow[], ctx: MapperContext): ReconstructSleepResult;
|
|
57
|
+
/**
|
|
58
|
+
* Session id components (principle 25, locked):
|
|
59
|
+
* `nanoid(24) + '_' + fnv1a32hex(device_id | user_id | ts_session_start)`
|
|
60
|
+
*
|
|
61
|
+
* The random prefix makes ids collision-safe even when the firmware emits
|
|
62
|
+
* corrupted/duplicated timestamps; the hash suffix is deterministic over the
|
|
63
|
+
* identifying tuple so ids remain diagnosable (same device+user+start ⇒
|
|
64
|
+
* same suffix). The random source is injectable via `ctx.idGenerator` to
|
|
65
|
+
* keep the mapper testable — the suffix is always deterministic.
|
|
66
|
+
*/
|
|
67
|
+
export declare function sessionIdComponents(ctx: MapperContext, tsSessionStart: Date): {
|
|
68
|
+
random: string;
|
|
69
|
+
hash: string;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* FNV-1a 32-bit, hex-encoded (8 lowercase hex chars). Implemented inline —
|
|
73
|
+
* a dependency for 10 lines of arithmetic is not worth the supply-chain
|
|
74
|
+
* surface, and the mapper only needs a stable non-cryptographic digest.
|
|
75
|
+
*/
|
|
76
|
+
export declare function fnv1a32hex(input: string): string;
|
|
32
77
|
//# sourceMappingURL=sleep.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../../src/sync/mapper/sleep.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../../src/sync/mapper/sleep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,eAAe,EACf,aAAa,EACd,MAAM,SAAS,CAAA;AAMhB;;;GAGG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAA;AAEtC;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK7D,CAAA;AAEF,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK7D,CAAA;AAEF,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,eAAe,EAAE,CAAA;IAChC,WAAW,EAAE,aAAa,EAAE,CAAA;IAC5B,SAAS,EAAE,WAAW,EAAE,CAAA;CACzB;AAoBD,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,SAAS,gBAAgB,EAAE,EACjC,GAAG,EAAE,aAAa,GACjB,sBAAsB,CAgGxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,aAAa,EAClB,cAAc,EAAE,IAAI,GACnB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAMlC;AAOD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD"}
|
|
@@ -2,43 +2,98 @@
|
|
|
2
2
|
* Sleep reconstruction mapper (T-06).
|
|
3
3
|
*
|
|
4
4
|
* Firmware `sleep_processed` emits per-block rows tagged with a `start`
|
|
5
|
-
* (session start), `end
|
|
6
|
-
* `confidence` (0..1), and per-block `timestamp` (block instant).
|
|
7
|
-
* blocks share the same `start` when they belong to the same
|
|
5
|
+
* (session start), `end` (session end), `block_type` (primary / light / deep /
|
|
6
|
+
* rem / awake), `confidence` (0..1), and per-block `timestamp` (block instant).
|
|
7
|
+
* Multiple blocks share the same `start` when they belong to the same session.
|
|
8
8
|
*
|
|
9
9
|
* Reconstruction rules (spec.md §Firmware mapper + tasks.md T-06):
|
|
10
10
|
* - Group rows by `start`.
|
|
11
11
|
* - A session is included if AT LEAST ONE block in the group has
|
|
12
12
|
* `block_type === 'primary'` AND `confidence >= 0.7`. This filters
|
|
13
13
|
* day-time naps and low-confidence noise.
|
|
14
|
-
* - Session boundaries
|
|
15
|
-
* `
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* -
|
|
22
|
-
*
|
|
23
|
-
*
|
|
14
|
+
* - Session boundaries come from the firmware's own envelope fields:
|
|
15
|
+
* `ts_start = parse(start)`, `ts_end = parse(end)`. `total_minutes` is
|
|
16
|
+
* their difference. Deriving the envelope from block instants would
|
|
17
|
+
* under-report any session whose first/last minutes went unclassified.
|
|
18
|
+
* - `session_id` = `nanoid(24) + '_' + fnv1a32hex(device_id | user_id |
|
|
19
|
+
* ts_session_start)` (principle 25, locked) — collision-safe even with
|
|
20
|
+
* corrupted timestamps.
|
|
21
|
+
* - `night_of` = 6pm-6pm rule applied to `ts_start` (see `time.ts`).
|
|
22
|
+
* - Stages: one row per classified block, carrying `session_id` and the
|
|
23
|
+
* DDL's integer `stage` code. `primary` is the session envelope marker,
|
|
24
|
+
* not a stage, so it produces no `sleep_stage` row — the DDL's stage
|
|
25
|
+
* enum (1=awake, 2=light, 3=deep, 5=rem) has no code for it. Unknown
|
|
26
|
+
* block types are likewise skipped; nothing is lost, because...
|
|
27
|
+
* - Raw: every input row is preserved in `sleep_raw` regardless of the
|
|
28
|
+
* primary/confidence filter, flattened onto the DDL's columns, so
|
|
29
|
+
* downstream reprocessing can revisit sessions with new heuristics.
|
|
30
|
+
*
|
|
31
|
+
* Principle 20 boundary: firmware `block_type` strings are translated to our
|
|
32
|
+
* integer codes here and never reach the warehouse.
|
|
24
33
|
*/
|
|
34
|
+
import { nanoid } from 'nanoid';
|
|
25
35
|
import { computeNightOf, parseTimestamp } from './time';
|
|
26
36
|
const MINUTE_MS = 60000;
|
|
27
37
|
const PRIMARY = 'primary';
|
|
28
38
|
const CONFIDENCE_FLOOR = 0.7;
|
|
39
|
+
/**
|
|
40
|
+
* Block width in minutes when the firmware revision omits `unit_length`.
|
|
41
|
+
* Firmware doc §Sleep processing: blocks are 1-min wide by convention.
|
|
42
|
+
*/
|
|
43
|
+
export const DEFAULT_BLOCK_MINUTES = 1;
|
|
44
|
+
/**
|
|
45
|
+
* Firmware `block_type` → `sleep_stage.stage` code (spec §Table schema:
|
|
46
|
+
* `stage SMALLINT NOT NULL -- 1=awake, 2=light, 3=deep, 5=rem`).
|
|
47
|
+
*
|
|
48
|
+
* `primary` is deliberately absent: it marks the session envelope, not a
|
|
49
|
+
* sleep stage. The enum is intentionally sparse (no 4) to match the DDL
|
|
50
|
+
* comment verbatim.
|
|
51
|
+
*/
|
|
52
|
+
export const SLEEP_STAGE_CODES = Object.freeze({
|
|
53
|
+
awake: 1,
|
|
54
|
+
light: 2,
|
|
55
|
+
deep: 3,
|
|
56
|
+
rem: 5,
|
|
57
|
+
});
|
|
58
|
+
/** Reverse lookup for the stage-minute accumulator + diagnostics. */
|
|
59
|
+
export const SLEEP_STAGE_NAMES = Object.freeze({
|
|
60
|
+
1: 'awake',
|
|
61
|
+
2: 'light',
|
|
62
|
+
3: 'deep',
|
|
63
|
+
5: 'rem',
|
|
64
|
+
});
|
|
65
|
+
/** Per-block width, honouring a firmware-supplied `unit_length`. */
|
|
66
|
+
function blockMinutes(row) {
|
|
67
|
+
return typeof row.unit_length === 'number' && row.unit_length > 0
|
|
68
|
+
? row.unit_length
|
|
69
|
+
: DEFAULT_BLOCK_MINUTES;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* `sleep_raw.quality` is NOT NULL. Firmware revisions that carry a native
|
|
73
|
+
* `quality` pass it through verbatim; older revisions derive it from the
|
|
74
|
+
* block confidence (0..1 → 0..100) so the column is always populated.
|
|
75
|
+
*/
|
|
76
|
+
function rawQuality(row) {
|
|
77
|
+
return typeof row.quality === 'number'
|
|
78
|
+
? row.quality
|
|
79
|
+
: Math.round(row.confidence * 100);
|
|
80
|
+
}
|
|
29
81
|
export function reconstructSleepSessions(rows, ctx) {
|
|
30
82
|
const sleep_session = [];
|
|
31
83
|
const sleep_stage = [];
|
|
32
84
|
const sleep_raw = [];
|
|
33
|
-
// Raw pass —
|
|
85
|
+
// Raw pass — every input row preserved for reprocessing, including rows
|
|
86
|
+
// from sessions the primary/confidence filter drops.
|
|
34
87
|
for (const row of rows) {
|
|
35
88
|
sleep_raw.push({
|
|
36
89
|
ts: parseTimestamp(row.timestamp),
|
|
90
|
+
ts_session_start: parseTimestamp(row.start),
|
|
37
91
|
brand: ctx.brand,
|
|
38
92
|
family_id: ctx.familyId,
|
|
39
93
|
user_id: ctx.userId,
|
|
40
94
|
device_id: ctx.deviceId,
|
|
41
|
-
|
|
95
|
+
quality: rawQuality(row),
|
|
96
|
+
unit_length: typeof row.unit_length === 'number' ? row.unit_length : null,
|
|
42
97
|
});
|
|
43
98
|
}
|
|
44
99
|
// Group by `start`.
|
|
@@ -56,48 +111,90 @@ export function reconstructSleepSessions(rows, ctx) {
|
|
|
56
111
|
const hasQualifyingPrimary = blocks.some(b => b.block_type === PRIMARY && b.confidence >= CONFIDENCE_FLOOR);
|
|
57
112
|
if (!hasQualifyingPrimary)
|
|
58
113
|
continue;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const nightOf = computeNightOf(
|
|
65
|
-
const sessionId = makeSessionId(ctx
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
114
|
+
// Envelope from the firmware's own session fields. `end` is exclusive of
|
|
115
|
+
// nothing — it is the session end instant, so total is a plain delta.
|
|
116
|
+
const tsStart = parseTimestamp(startKey);
|
|
117
|
+
const tsEnd = parseTimestamp(blocks[0].end);
|
|
118
|
+
const totalMinutes = Math.max(0, Math.round((tsEnd.getTime() - tsStart.getTime()) / MINUTE_MS));
|
|
119
|
+
const nightOf = computeNightOf(tsStart, ctx.userTimezone);
|
|
120
|
+
const sessionId = makeSessionId(ctx, tsStart);
|
|
121
|
+
// Stage rows + per-stage minute accumulation in one pass.
|
|
122
|
+
const stageMinutes = {
|
|
123
|
+
awake: 0,
|
|
124
|
+
light: 0,
|
|
125
|
+
deep: 0,
|
|
126
|
+
rem: 0,
|
|
127
|
+
};
|
|
128
|
+
let confidenceSum = 0;
|
|
129
|
+
for (const b of blocks) {
|
|
130
|
+
confidenceSum += b.confidence;
|
|
131
|
+
const code = SLEEP_STAGE_CODES[b.block_type];
|
|
132
|
+
if (code === undefined) {
|
|
133
|
+
// `primary` (envelope marker) and any unrecognised firmware block
|
|
134
|
+
// type. Not a stage — preserved in sleep_raw, skipped here.
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
stageMinutes[SLEEP_STAGE_NAMES[code]] += blockMinutes(b);
|
|
79
138
|
sleep_stage.push({
|
|
80
|
-
ts:
|
|
139
|
+
ts: parseTimestamp(b.timestamp),
|
|
81
140
|
brand: ctx.brand,
|
|
82
141
|
family_id: ctx.familyId,
|
|
83
142
|
user_id: ctx.userId,
|
|
84
143
|
device_id: ctx.deviceId,
|
|
85
144
|
session_id: sessionId,
|
|
86
|
-
stage:
|
|
145
|
+
stage: code,
|
|
87
146
|
confidence: b.confidence,
|
|
88
147
|
});
|
|
89
148
|
}
|
|
90
|
-
|
|
91
|
-
|
|
149
|
+
sleep_session.push({
|
|
150
|
+
brand: ctx.brand,
|
|
151
|
+
family_id: ctx.familyId,
|
|
152
|
+
user_id: ctx.userId,
|
|
153
|
+
device_id: ctx.deviceId,
|
|
154
|
+
session_id: sessionId,
|
|
155
|
+
ts_start: tsStart,
|
|
156
|
+
ts_end: tsEnd,
|
|
157
|
+
total_minutes: totalMinutes,
|
|
158
|
+
deep_minutes: stageMinutes.deep,
|
|
159
|
+
rem_minutes: stageMinutes.rem,
|
|
160
|
+
light_minutes: stageMinutes.light,
|
|
161
|
+
awake_minutes: stageMinutes.awake,
|
|
162
|
+
avg_confidence: blocks.length > 0 ? confidenceSum / blocks.length : null,
|
|
163
|
+
night_of: nightOf,
|
|
164
|
+
});
|
|
92
165
|
}
|
|
93
166
|
return { sleep_session, sleep_stage, sleep_raw };
|
|
94
167
|
}
|
|
95
168
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
169
|
+
* Session id components (principle 25, locked):
|
|
170
|
+
* `nanoid(24) + '_' + fnv1a32hex(device_id | user_id | ts_session_start)`
|
|
171
|
+
*
|
|
172
|
+
* The random prefix makes ids collision-safe even when the firmware emits
|
|
173
|
+
* corrupted/duplicated timestamps; the hash suffix is deterministic over the
|
|
174
|
+
* identifying tuple so ids remain diagnosable (same device+user+start ⇒
|
|
175
|
+
* same suffix). The random source is injectable via `ctx.idGenerator` to
|
|
176
|
+
* keep the mapper testable — the suffix is always deterministic.
|
|
177
|
+
*/
|
|
178
|
+
export function sessionIdComponents(ctx, tsSessionStart) {
|
|
179
|
+
const random = ctx.idGenerator ? ctx.idGenerator() : nanoid(24);
|
|
180
|
+
const hash = fnv1a32hex(`${ctx.deviceId}|${ctx.userId}|${tsSessionStart.toISOString()}`);
|
|
181
|
+
return { random, hash };
|
|
182
|
+
}
|
|
183
|
+
function makeSessionId(ctx, startTs) {
|
|
184
|
+
const { random, hash } = sessionIdComponents(ctx, startTs);
|
|
185
|
+
return `${random}_${hash}`;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* FNV-1a 32-bit, hex-encoded (8 lowercase hex chars). Implemented inline —
|
|
189
|
+
* a dependency for 10 lines of arithmetic is not worth the supply-chain
|
|
190
|
+
* surface, and the mapper only needs a stable non-cryptographic digest.
|
|
99
191
|
*/
|
|
100
|
-
function
|
|
101
|
-
|
|
192
|
+
export function fnv1a32hex(input) {
|
|
193
|
+
let h = 0x811C9DC5;
|
|
194
|
+
for (let i = 0; i < input.length; i++) {
|
|
195
|
+
h ^= input.charCodeAt(i);
|
|
196
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
197
|
+
}
|
|
198
|
+
return h.toString(16).padStart(8, '0');
|
|
102
199
|
}
|
|
103
200
|
//# sourceMappingURL=sleep.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../../src/sync/mapper/sleep.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../../src/sync/mapper/sleep.ts"],"names":[],"mappings":"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"}
|