@mongrov/analytics 0.23.1 → 0.24.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/migrations.d.ts.map +1 -1
- package/dist/core/migrations.js +35 -0
- package/dist/core/migrations.js.map +1 -1
- 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/rules/compiler.d.ts.map +1 -1
- package/dist/rules/compiler.js +37 -16
- package/dist/rules/compiler.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 -118
- 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/package.json +4 -4
|
@@ -18,12 +18,11 @@ import { mapBattery } from './battery';
|
|
|
18
18
|
import { mapHeartRate } from './heart-rate';
|
|
19
19
|
import { mapHrv } from './hrv';
|
|
20
20
|
import { mapRingConfig } from './ring-config';
|
|
21
|
-
import {
|
|
21
|
+
import { mapSleepRaw } from './sleep';
|
|
22
22
|
import { mapSpo2 } from './spo2';
|
|
23
23
|
import { mapTemperature } from './temperature';
|
|
24
24
|
export function mapFirmwareExport(fw, ctx, opts = {}) {
|
|
25
25
|
const activity = mapActivity(fw.activitydetails ?? [], ctx);
|
|
26
|
-
const sleep = reconstructSleepSessions(fw.sleep_processed ?? [], ctx);
|
|
27
26
|
const config = mapRingConfigIfPossible(fw, ctx, opts);
|
|
28
27
|
return {
|
|
29
28
|
hrv: mapHrv(fw.hrv_table ?? [], ctx),
|
|
@@ -32,9 +31,12 @@ export function mapFirmwareExport(fw, ctx, opts = {}) {
|
|
|
32
31
|
temperature: mapTemperature(fw.temperature_table ?? [], ctx),
|
|
33
32
|
activity: activity.activity,
|
|
34
33
|
activity_bucket: activity.activity_bucket,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
// Raw firmware minutes only. Sessions and stages are DERIVED — the sync
|
|
35
|
+
// factory runs the sleep-correction pipeline over `sleep_raw` after the
|
|
36
|
+
// batch flushes and writes them night by night (sleep-correction §3).
|
|
37
|
+
sleep_session: [],
|
|
38
|
+
sleep_stage: [],
|
|
39
|
+
sleep_raw: mapSleepRaw(fw.sleep ?? [], ctx),
|
|
38
40
|
// Battery moved off the generic event stream in 0.6.0 (fix B2);
|
|
39
41
|
// `device_event` stays in the batch shape for future event types.
|
|
40
42
|
device_event: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firmware.js","sourceRoot":"","sources":["../../../src/sync/mapper/firmware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"firmware.js","sourceRoot":"","sources":["../../../src/sync/mapper/firmware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAe9C,MAAM,UAAU,iBAAiB,CAC/B,EAAkB,EAClB,GAAkB,EAClB,OAA2B,EAAE;IAE7B,MAAM,QAAQ,GAAG,WAAW,CAAC,EAAE,CAAC,eAAe,IAAI,EAAE,EAAE,GAAG,CAAC,CAAA;IAC3D,MAAM,MAAM,GAAG,uBAAuB,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,CAAA;IAErD,OAAO;QACL,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,SAAS,IAAI,EAAE,EAAE,GAAG,CAAC;QACpC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC,SAAS,IAAI,EAAE,EAAE,GAAG,CAAC;QACjD,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,CAAC;QACjC,WAAW,EAAE,cAAc,CAAC,EAAE,CAAC,iBAAiB,IAAI,EAAE,EAAE,GAAG,CAAC;QAC5D,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,eAAe,EAAE,QAAQ,CAAC,eAAe;QACzC,wEAAwE;QACxE,wEAAwE;QACxE,sEAAsE;QACtE,aAAa,EAAE,EAAE;QACjB,WAAW,EAAE,EAAE;QACf,SAAS,EAAE,WAAW,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG,CAAC;QAC3C,gEAAgE;QAChE,kEAAkE;QAClE,YAAY,EAAE,EAAE;QAChB,cAAc,EAAE,UAAU,CAAC,EAAE,CAAC,aAAa,IAAI,EAAE,EAAE,GAAG,CAAC;QACvD,aAAa,EAAE,MAAM,CAAC,OAAO;QAC7B,oBAAoB,EAAE,MAAM,CAAC,MAAM;KACpC,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,uBAAuB,CAC9B,EAAkB,EAClB,GAAkB,EAClB,IAAwB;IAExB,sEAAsE;IACtE,+DAA+D;IAC/D,OAAO,aAAa,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,uBAAuB,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE;QACpE,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;KAC5C,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -78,35 +78,33 @@ export declare const firmwareActivityRowSchema: z.ZodObject<{
|
|
|
78
78
|
arraySteps: z.ZodArray<z.ZodNumber, "many">;
|
|
79
79
|
}, "strict", z.ZodTypeAny, {
|
|
80
80
|
calories: number;
|
|
81
|
-
timestamp: string;
|
|
82
81
|
step: number;
|
|
82
|
+
timestamp: string;
|
|
83
83
|
distance: number;
|
|
84
84
|
arraySteps: number[];
|
|
85
85
|
}, {
|
|
86
86
|
calories: number;
|
|
87
|
-
timestamp: string;
|
|
88
87
|
step: number;
|
|
88
|
+
timestamp: string;
|
|
89
89
|
distance: number;
|
|
90
90
|
arraySteps: number[];
|
|
91
91
|
}>;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
end: z.ZodString;
|
|
95
|
-
block_type: z.ZodString;
|
|
96
|
-
confidence: z.ZodNumber;
|
|
92
|
+
/** Raw firmware sleep sample (types 0.10.0); `quality` is the firmware code. */
|
|
93
|
+
export declare const firmwareSleepRawRowSchema: z.ZodObject<{
|
|
97
94
|
timestamp: z.ZodString;
|
|
95
|
+
quality: z.ZodNumber;
|
|
96
|
+
start: z.ZodString;
|
|
97
|
+
unitLength: z.ZodNumber;
|
|
98
98
|
}, "strict", z.ZodTypeAny, {
|
|
99
99
|
start: string;
|
|
100
|
+
quality: number;
|
|
101
|
+
unitLength: number;
|
|
100
102
|
timestamp: string;
|
|
101
|
-
end: string;
|
|
102
|
-
block_type: string;
|
|
103
|
-
confidence: number;
|
|
104
103
|
}, {
|
|
105
104
|
start: string;
|
|
105
|
+
quality: number;
|
|
106
|
+
unitLength: number;
|
|
106
107
|
timestamp: string;
|
|
107
|
-
end: string;
|
|
108
|
-
block_type: string;
|
|
109
|
-
confidence: number;
|
|
110
108
|
}>;
|
|
111
109
|
export declare const firmwareBatteryRowSchema: z.ZodObject<{
|
|
112
110
|
timestamp: z.ZodString;
|
|
@@ -389,35 +387,32 @@ export declare const firmwareExportSchema: z.ZodObject<{
|
|
|
389
387
|
arraySteps: z.ZodArray<z.ZodNumber, "many">;
|
|
390
388
|
}, "strict", z.ZodTypeAny, {
|
|
391
389
|
calories: number;
|
|
392
|
-
timestamp: string;
|
|
393
390
|
step: number;
|
|
391
|
+
timestamp: string;
|
|
394
392
|
distance: number;
|
|
395
393
|
arraySteps: number[];
|
|
396
394
|
}, {
|
|
397
395
|
calories: number;
|
|
398
|
-
timestamp: string;
|
|
399
396
|
step: number;
|
|
397
|
+
timestamp: string;
|
|
400
398
|
distance: number;
|
|
401
399
|
arraySteps: number[];
|
|
402
400
|
}>, "many">;
|
|
403
|
-
|
|
404
|
-
start: z.ZodString;
|
|
405
|
-
end: z.ZodString;
|
|
406
|
-
block_type: z.ZodString;
|
|
407
|
-
confidence: z.ZodNumber;
|
|
401
|
+
sleep: z.ZodArray<z.ZodObject<{
|
|
408
402
|
timestamp: z.ZodString;
|
|
403
|
+
quality: z.ZodNumber;
|
|
404
|
+
start: z.ZodString;
|
|
405
|
+
unitLength: z.ZodNumber;
|
|
409
406
|
}, "strict", z.ZodTypeAny, {
|
|
410
407
|
start: string;
|
|
408
|
+
quality: number;
|
|
409
|
+
unitLength: number;
|
|
411
410
|
timestamp: string;
|
|
412
|
-
end: string;
|
|
413
|
-
block_type: string;
|
|
414
|
-
confidence: number;
|
|
415
411
|
}, {
|
|
416
412
|
start: string;
|
|
413
|
+
quality: number;
|
|
414
|
+
unitLength: number;
|
|
417
415
|
timestamp: string;
|
|
418
|
-
end: string;
|
|
419
|
-
block_type: string;
|
|
420
|
-
confidence: number;
|
|
421
416
|
}>, "many">;
|
|
422
417
|
battery_table: z.ZodArray<z.ZodObject<{
|
|
423
418
|
timestamp: z.ZodString;
|
|
@@ -561,17 +556,16 @@ export declare const firmwareExportSchema: z.ZodObject<{
|
|
|
561
556
|
}[];
|
|
562
557
|
activitydetails: {
|
|
563
558
|
calories: number;
|
|
564
|
-
timestamp: string;
|
|
565
559
|
step: number;
|
|
560
|
+
timestamp: string;
|
|
566
561
|
distance: number;
|
|
567
562
|
arraySteps: number[];
|
|
568
563
|
}[];
|
|
569
|
-
|
|
564
|
+
sleep: {
|
|
570
565
|
start: string;
|
|
566
|
+
quality: number;
|
|
567
|
+
unitLength: number;
|
|
571
568
|
timestamp: string;
|
|
572
|
-
end: string;
|
|
573
|
-
block_type: string;
|
|
574
|
-
confidence: number;
|
|
575
569
|
}[];
|
|
576
570
|
battery_table: {
|
|
577
571
|
timestamp: string;
|
|
@@ -622,17 +616,16 @@ export declare const firmwareExportSchema: z.ZodObject<{
|
|
|
622
616
|
}[];
|
|
623
617
|
activitydetails: {
|
|
624
618
|
calories: number;
|
|
625
|
-
timestamp: string;
|
|
626
619
|
step: number;
|
|
620
|
+
timestamp: string;
|
|
627
621
|
distance: number;
|
|
628
622
|
arraySteps: number[];
|
|
629
623
|
}[];
|
|
630
|
-
|
|
624
|
+
sleep: {
|
|
631
625
|
start: string;
|
|
626
|
+
quality: number;
|
|
627
|
+
unitLength: number;
|
|
632
628
|
timestamp: string;
|
|
633
|
-
end: string;
|
|
634
|
-
block_type: string;
|
|
635
|
-
confidence: number;
|
|
636
629
|
}[];
|
|
637
630
|
battery_table: {
|
|
638
631
|
timestamp: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/sync/mapper/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAQvB,eAAO,MAAM,mBAAmB;;;;;;;;;EAGrB,CAAA;AAEX,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;EAQtB,CAAA;AAEX,eAAO,MAAM,qBAAqB;;;;;;;;;EAGvB,CAAA;AAEX,eAAO,MAAM,qBAAqB;;;;;;;;;EAGvB,CAAA;AAEX,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;EAM3B,CAAA;AAEX,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/sync/mapper/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAQvB,eAAO,MAAM,mBAAmB;;;;;;;;;EAGrB,CAAA;AAEX,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;EAQtB,CAAA;AAEX,eAAO,MAAM,qBAAqB;;;;;;;;;EAGvB,CAAA;AAEX,eAAO,MAAM,qBAAqB;;;;;;;;;EAGvB,CAAA;AAEX,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;EAM3B,CAAA;AAEX,gFAAgF;AAChF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;EAK3B,CAAA;AAEX,eAAO,MAAM,wBAAwB;;;;;;;;;EAG1B,CAAA;AAEX,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;EAQxB,CAAA;AAEX;;;;GAIG;AACH,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAShC,CAAA;AAEX,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAE1B,CAAA;AAEX,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYtB,CAAA;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AASvE;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAqDxD,CAAA"}
|
|
@@ -45,12 +45,12 @@ export const firmwareActivityRowSchema = z.object({
|
|
|
45
45
|
distance: z.number(),
|
|
46
46
|
arraySteps: z.array(z.number()).length(10),
|
|
47
47
|
}).strict();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end: firmwareTimestamp,
|
|
51
|
-
block_type: z.string(),
|
|
52
|
-
confidence: z.number(),
|
|
48
|
+
/** Raw firmware sleep sample (types 0.10.0); `quality` is the firmware code. */
|
|
49
|
+
export const firmwareSleepRawRowSchema = z.object({
|
|
53
50
|
timestamp: firmwareTimestamp,
|
|
51
|
+
quality: z.number().int(),
|
|
52
|
+
start: firmwareTimestamp,
|
|
53
|
+
unitLength: z.number().int().positive(),
|
|
54
54
|
}).strict();
|
|
55
55
|
export const firmwareBatteryRowSchema = z.object({
|
|
56
56
|
timestamp: firmwareTimestamp,
|
|
@@ -92,7 +92,7 @@ export const firmwareExportSchema = z.object({
|
|
|
92
92
|
spo2: z.array(firmwareSpO2RowSchema),
|
|
93
93
|
temperature_table: z.array(firmwareTempRowSchema),
|
|
94
94
|
activitydetails: z.array(firmwareActivityRowSchema),
|
|
95
|
-
|
|
95
|
+
sleep: z.array(firmwareSleepRawRowSchema),
|
|
96
96
|
battery_table: z.array(firmwareBatteryRowSchema),
|
|
97
97
|
ring: firmwareRingConfigSchema,
|
|
98
98
|
}).strict();
|
|
@@ -127,12 +127,11 @@ export const FIRMWARE_EXPORT_KEY_PATHS = new Set([
|
|
|
127
127
|
'activitydetails[].calories',
|
|
128
128
|
'activitydetails[].distance',
|
|
129
129
|
'activitydetails[].arraySteps',
|
|
130
|
-
'
|
|
131
|
-
'
|
|
132
|
-
'
|
|
133
|
-
'
|
|
134
|
-
'
|
|
135
|
-
'sleep_processed[].timestamp',
|
|
130
|
+
'sleep',
|
|
131
|
+
'sleep[].timestamp',
|
|
132
|
+
'sleep[].quality',
|
|
133
|
+
'sleep[].start',
|
|
134
|
+
'sleep[].unitLength',
|
|
136
135
|
'battery_table',
|
|
137
136
|
'battery_table[].timestamp',
|
|
138
137
|
'battery_table[].battery',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/sync/mapper/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,gFAAgF;AAChF,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CACxC,mDAAmD,EACnD,mDAAmD,CACpD,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,SAAS,EAAE,iBAAiB;IAC5B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;CACrB,CAAC,CAAC,MAAM,EAAE,CAAA;AAEX,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,SAAS,EAAE,iBAAiB;IAC5B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAC,MAAM,EAAE,CAAA;AAEX,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,SAAS,EAAE,iBAAiB;IAC5B,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE;CAC9B,CAAC,CAAC,MAAM,EAAE,CAAA;AAEX,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,SAAS,EAAE,iBAAiB;IAC5B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;CACxB,CAAC,CAAC,MAAM,EAAE,CAAA;AAEX,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,SAAS,EAAE,iBAAiB;IAC5B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;CAC3C,CAAC,CAAC,MAAM,EAAE,CAAA;AAEX,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/sync/mapper/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,59 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sleep
|
|
2
|
+
* Sleep mapping (sleep-correction §3).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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.
|
|
4
|
+
* Two halves that run at different times:
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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` = `fnv1a32hex(device_id | user_id | ts_session_start |
|
|
19
|
-
* ts_session_end)` (principle 25, amended 2026-08-14) — fully
|
|
20
|
-
* deterministic, so the same night mapped twice yields the same id.
|
|
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.
|
|
6
|
+
* - `mapSleepRaw`, at ingest: every raw firmware sample → `sleep_raw`,
|
|
7
|
+
* verbatim. `quality` stays the FIRMWARE code (1 deep / 2 light / 3 rem /
|
|
8
|
+
* 5 awake, other codes kept). Nothing is classified here — `sleep_raw` is
|
|
9
|
+
* the reprocessing source the correction pipeline reads.
|
|
10
|
+
* - `sessionsFromCorrected`, after the batch flushes: one night's Phase 3
|
|
11
|
+
* output from `../sleep-correction` → `sleep_session` + `sleep_stage`.
|
|
30
12
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
13
|
+
* Until 0.24.0 the mapper built sessions straight from app-classified blocks
|
|
14
|
+
* (`sleep_processed`) — an admission filter on `primary`, a per-block `end`
|
|
15
|
+
* read as the session end, and a guessed stage map. All three were wrong on
|
|
16
|
+
* device (zivaone_app#77, and the stage swap found 2026-09-10). Sessions now
|
|
17
|
+
* come only from the validated correction pipeline.
|
|
18
|
+
*
|
|
19
|
+
* Principle 20: the firmware `quality` code is translated to the DDL's
|
|
20
|
+
* `stage` code here, and nowhere else. The two enums share integers and mean
|
|
21
|
+
* different things — `FIRMWARE_QUALITY_TO_STAGE` is the one crossing point.
|
|
33
22
|
*/
|
|
34
|
-
import type {
|
|
23
|
+
import type { ClassifiedRow } from '../sleep-correction/classify';
|
|
24
|
+
import type { FirmwareSleepRawRow, MapperContext, SleepRawRow, SleepSessionRow, SleepStageRow } from './types';
|
|
35
25
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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.
|
|
26
|
+
* `sleep_stage.stage` codes (DDL: `1=awake, 2=light, 3=deep, 5=rem`). Sparse
|
|
27
|
+
* on purpose (no 4) to match the DDL comment verbatim.
|
|
47
28
|
*/
|
|
48
29
|
export declare const SLEEP_STAGE_CODES: Readonly<Record<string, number>>;
|
|
49
30
|
/** Reverse lookup for the stage-minute accumulator + diagnostics. */
|
|
50
31
|
export declare const SLEEP_STAGE_NAMES: Readonly<Record<number, string>>;
|
|
51
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Firmware `quality` → DDL `stage`. The firmware says 1 deep / 2 light /
|
|
34
|
+
* 3 rem / 5 awake (ziva_app v3.1; the vendor header is silent); the DDL says
|
|
35
|
+
* 1 awake / 2 light / 3 deep / 5 rem. Copying a predicate across the two
|
|
36
|
+
* computes deep from awake with no error — this map is the only translation.
|
|
37
|
+
*/
|
|
38
|
+
export declare const FIRMWARE_QUALITY_TO_STAGE: Readonly<Record<number, number>>;
|
|
39
|
+
/** Raw firmware samples → `sleep_raw`, verbatim. */
|
|
40
|
+
export declare function mapSleepRaw(rows: readonly FirmwareSleepRawRow[], ctx: MapperContext): SleepRawRow[];
|
|
41
|
+
export interface SessionsFromCorrectedResult {
|
|
52
42
|
sleep_session: SleepSessionRow[];
|
|
53
43
|
sleep_stage: SleepStageRow[];
|
|
54
|
-
sleep_raw: SleepRawRow[];
|
|
55
44
|
}
|
|
56
|
-
|
|
45
|
+
/**
|
|
46
|
+
* One night's corrected rows → sessions + stages.
|
|
47
|
+
*
|
|
48
|
+
* - Only `block_type === 'primary'` rows count (R-A: every primary minute,
|
|
49
|
+
* whatever its confidence — confidence is provenance, never a gate).
|
|
50
|
+
* - Sessions are Phase 2's stitched sessions, identified by `start`. A night
|
|
51
|
+
* is normally one; a gap between 20 and 30 minutes inside the primary
|
|
52
|
+
* block leaves two.
|
|
53
|
+
* - `ts_end` is the last primary minute plus its width; `total_minutes` is
|
|
54
|
+
* the primary minutes actually present (awake minutes included).
|
|
55
|
+
* - `settle_min` belongs to the night, measured from bed, so it goes on the
|
|
56
|
+
* session that starts at bed — the first. `recovered_min` is per session.
|
|
57
|
+
* - `session_id` is principle 25's deterministic hash; the caller replaces
|
|
58
|
+
* the whole night before writing (principle 66 as amended), because a
|
|
59
|
+
* corrected END moves as later data arrives and the id moves with it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function sessionsFromCorrected(rows: readonly ClassifiedRow[], ctx: MapperContext, night?: {
|
|
62
|
+
settleMin?: number | null;
|
|
63
|
+
}): SessionsFromCorrectedResult;
|
|
57
64
|
/**
|
|
58
65
|
* Session id (principle 25, as amended 2026-08-14):
|
|
59
66
|
* `fnv1a32hex(device_id | user_id | ts_session_start | ts_session_end)`
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,eAAe,EACf,aAAa,EACd,MAAM,SAAS,CAAA;AAIhB;;;GAGG;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;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAKrE,CAAA;AAEF,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,mBAAmB,EAAE,EAAE,GAAG,EAAE,aAAa,GAAG,WAAW,EAAE,CAWnG;AAED,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,eAAe,EAAE,CAAA;IAChC,WAAW,EAAE,aAAa,EAAE,CAAA;CAC7B;AAKD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,SAAS,aAAa,EAAE,EAC9B,GAAG,EAAE,aAAa,EAClB,KAAK,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,GACxC,2BAA2B,CAkE7B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,aAAa,EAClB,cAAc,EAAE,IAAI,EACpB,YAAY,EAAE,IAAI,GACjB,MAAM,CAKR;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD"}
|
|
@@ -1,52 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sleep
|
|
2
|
+
* Sleep mapping (sleep-correction §3).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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.
|
|
4
|
+
* Two halves that run at different times:
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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` = `fnv1a32hex(device_id | user_id | ts_session_start |
|
|
19
|
-
* ts_session_end)` (principle 25, amended 2026-08-14) — fully
|
|
20
|
-
* deterministic, so the same night mapped twice yields the same id.
|
|
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.
|
|
6
|
+
* - `mapSleepRaw`, at ingest: every raw firmware sample → `sleep_raw`,
|
|
7
|
+
* verbatim. `quality` stays the FIRMWARE code (1 deep / 2 light / 3 rem /
|
|
8
|
+
* 5 awake, other codes kept). Nothing is classified here — `sleep_raw` is
|
|
9
|
+
* the reprocessing source the correction pipeline reads.
|
|
10
|
+
* - `sessionsFromCorrected`, after the batch flushes: one night's Phase 3
|
|
11
|
+
* output from `../sleep-correction` → `sleep_session` + `sleep_stage`.
|
|
30
12
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
13
|
+
* Until 0.24.0 the mapper built sessions straight from app-classified blocks
|
|
14
|
+
* (`sleep_processed`) — an admission filter on `primary`, a per-block `end`
|
|
15
|
+
* read as the session end, and a guessed stage map. All three were wrong on
|
|
16
|
+
* device (zivaone_app#77, and the stage swap found 2026-09-10). Sessions now
|
|
17
|
+
* come only from the validated correction pipeline.
|
|
18
|
+
*
|
|
19
|
+
* Principle 20: the firmware `quality` code is translated to the DDL's
|
|
20
|
+
* `stage` code here, and nowhere else. The two enums share integers and mean
|
|
21
|
+
* different things — `FIRMWARE_QUALITY_TO_STAGE` is the one crossing point.
|
|
33
22
|
*/
|
|
23
|
+
import { parseDateStr } from '../sleep-correction/classify';
|
|
34
24
|
import { computeNightOf, parseTimestamp } from './time';
|
|
35
|
-
const MINUTE_MS = 60000;
|
|
36
|
-
const PRIMARY = 'primary';
|
|
37
|
-
const CONFIDENCE_FLOOR = 0.7;
|
|
38
|
-
/**
|
|
39
|
-
* Block width in minutes when the firmware revision omits `unit_length`.
|
|
40
|
-
* Firmware doc §Sleep processing: blocks are 1-min wide by convention.
|
|
41
|
-
*/
|
|
42
|
-
export const DEFAULT_BLOCK_MINUTES = 1;
|
|
43
25
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* `primary` is deliberately absent: it marks the session envelope, not a
|
|
48
|
-
* sleep stage. The enum is intentionally sparse (no 4) to match the DDL
|
|
49
|
-
* comment verbatim.
|
|
26
|
+
* `sleep_stage.stage` codes (DDL: `1=awake, 2=light, 3=deep, 5=rem`). Sparse
|
|
27
|
+
* on purpose (no 4) to match the DDL comment verbatim.
|
|
50
28
|
*/
|
|
51
29
|
export const SLEEP_STAGE_CODES = Object.freeze({
|
|
52
30
|
awake: 1,
|
|
@@ -61,88 +39,89 @@ export const SLEEP_STAGE_NAMES = Object.freeze({
|
|
|
61
39
|
3: 'deep',
|
|
62
40
|
5: 'rem',
|
|
63
41
|
});
|
|
64
|
-
/** Per-block width, honouring a firmware-supplied `unit_length`. */
|
|
65
|
-
function blockMinutes(row) {
|
|
66
|
-
return typeof row.unit_length === 'number' && row.unit_length > 0
|
|
67
|
-
? row.unit_length
|
|
68
|
-
: DEFAULT_BLOCK_MINUTES;
|
|
69
|
-
}
|
|
70
42
|
/**
|
|
71
|
-
* `
|
|
72
|
-
*
|
|
73
|
-
*
|
|
43
|
+
* Firmware `quality` → DDL `stage`. The firmware says 1 deep / 2 light /
|
|
44
|
+
* 3 rem / 5 awake (ziva_app v3.1; the vendor header is silent); the DDL says
|
|
45
|
+
* 1 awake / 2 light / 3 deep / 5 rem. Copying a predicate across the two
|
|
46
|
+
* computes deep from awake with no error — this map is the only translation.
|
|
74
47
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
48
|
+
export const FIRMWARE_QUALITY_TO_STAGE = Object.freeze({
|
|
49
|
+
1: SLEEP_STAGE_CODES.deep,
|
|
50
|
+
2: SLEEP_STAGE_CODES.light,
|
|
51
|
+
3: SLEEP_STAGE_CODES.rem,
|
|
52
|
+
5: SLEEP_STAGE_CODES.awake,
|
|
53
|
+
});
|
|
54
|
+
/** Raw firmware samples → `sleep_raw`, verbatim. */
|
|
55
|
+
export function mapSleepRaw(rows, ctx) {
|
|
56
|
+
return rows.map(r => ({
|
|
57
|
+
ts: parseTimestamp(r.timestamp),
|
|
58
|
+
ts_session_start: parseTimestamp(r.start),
|
|
59
|
+
brand: ctx.brand,
|
|
60
|
+
family_id: ctx.familyId,
|
|
61
|
+
user_id: ctx.userId,
|
|
62
|
+
device_id: ctx.deviceId,
|
|
63
|
+
quality: r.quality,
|
|
64
|
+
unit_length: typeof r.unitLength === 'number' ? r.unitLength : null,
|
|
65
|
+
}));
|
|
79
66
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
67
|
+
/** Width of one Phase 2/3 row. v3.1 emits every row at `unitLength` 1. */
|
|
68
|
+
const ROW_SECONDS = 60;
|
|
69
|
+
/**
|
|
70
|
+
* One night's corrected rows → sessions + stages.
|
|
71
|
+
*
|
|
72
|
+
* - Only `block_type === 'primary'` rows count (R-A: every primary minute,
|
|
73
|
+
* whatever its confidence — confidence is provenance, never a gate).
|
|
74
|
+
* - Sessions are Phase 2's stitched sessions, identified by `start`. A night
|
|
75
|
+
* is normally one; a gap between 20 and 30 minutes inside the primary
|
|
76
|
+
* block leaves two.
|
|
77
|
+
* - `ts_end` is the last primary minute plus its width; `total_minutes` is
|
|
78
|
+
* the primary minutes actually present (awake minutes included).
|
|
79
|
+
* - `settle_min` belongs to the night, measured from bed, so it goes on the
|
|
80
|
+
* session that starts at bed — the first. `recovered_min` is per session.
|
|
81
|
+
* - `session_id` is principle 25's deterministic hash; the caller replaces
|
|
82
|
+
* the whole night before writing (principle 66 as amended), because a
|
|
83
|
+
* corrected END moves as later data arrives and the id moves with it.
|
|
84
|
+
*/
|
|
85
|
+
export function sessionsFromCorrected(rows, ctx, night = {}) {
|
|
99
86
|
const bySession = new Map();
|
|
100
|
-
for (const
|
|
101
|
-
|
|
87
|
+
for (const r of rows) {
|
|
88
|
+
if (r.block_type !== 'primary')
|
|
89
|
+
continue;
|
|
90
|
+
const bucket = bySession.get(r.start);
|
|
102
91
|
if (bucket)
|
|
103
|
-
bucket.push(
|
|
92
|
+
bucket.push(r);
|
|
104
93
|
else
|
|
105
|
-
bySession.set(
|
|
94
|
+
bySession.set(r.start, [r]);
|
|
106
95
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// nothing — it is the session end instant, so total is a plain delta.
|
|
115
|
-
const tsStart = parseTimestamp(startKey);
|
|
116
|
-
const tsEnd = parseTimestamp(blocks[0].end);
|
|
117
|
-
const totalMinutes = Math.max(0, Math.round((tsEnd.getTime() - tsStart.getTime()) / MINUTE_MS));
|
|
118
|
-
const nightOf = computeNightOf(tsStart, ctx.userTimezone);
|
|
96
|
+
const groups = Array.from(bySession.values(), g => g.map(r => ({ r, e: parseDateStr(r.date) })).sort((a, b) => a.e - b.e))
|
|
97
|
+
.sort((a, b) => a[0].e - b[0].e);
|
|
98
|
+
const sleep_session = [];
|
|
99
|
+
const sleep_stage = [];
|
|
100
|
+
groups.forEach((group, index) => {
|
|
101
|
+
const tsStart = new Date(group[0].e * 1000);
|
|
102
|
+
const tsEnd = new Date((group[group.length - 1].e + ROW_SECONDS) * 1000);
|
|
119
103
|
const sessionId = makeSessionId(ctx, tsStart, tsEnd);
|
|
120
|
-
|
|
121
|
-
const stageMinutes = {
|
|
122
|
-
awake: 0,
|
|
123
|
-
light: 0,
|
|
124
|
-
deep: 0,
|
|
125
|
-
rem: 0,
|
|
126
|
-
};
|
|
104
|
+
const minutes = { awake: 0, light: 0, deep: 0, rem: 0 };
|
|
127
105
|
let confidenceSum = 0;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
106
|
+
let recovered = 0;
|
|
107
|
+
for (const { r, e } of group) {
|
|
108
|
+
confidenceSum += r.confidence;
|
|
109
|
+
if (r.source === 'envelope' || r.source === 'gap')
|
|
110
|
+
recovered++;
|
|
111
|
+
const stage = FIRMWARE_QUALITY_TO_STAGE[r.quality];
|
|
112
|
+
if (stage === undefined)
|
|
113
|
+
continue; // a code the pipeline passed but the DDL has no stage for
|
|
114
|
+
minutes[SLEEP_STAGE_NAMES[stage]]++;
|
|
137
115
|
sleep_stage.push({
|
|
138
|
-
ts:
|
|
116
|
+
ts: new Date(e * 1000),
|
|
139
117
|
brand: ctx.brand,
|
|
140
118
|
family_id: ctx.familyId,
|
|
141
119
|
user_id: ctx.userId,
|
|
142
120
|
device_id: ctx.deviceId,
|
|
143
121
|
session_id: sessionId,
|
|
144
|
-
stage
|
|
145
|
-
confidence:
|
|
122
|
+
stage,
|
|
123
|
+
confidence: r.confidence,
|
|
124
|
+
source: r.source,
|
|
146
125
|
});
|
|
147
126
|
}
|
|
148
127
|
sleep_session.push({
|
|
@@ -153,16 +132,18 @@ export function reconstructSleepSessions(rows, ctx) {
|
|
|
153
132
|
session_id: sessionId,
|
|
154
133
|
ts_start: tsStart,
|
|
155
134
|
ts_end: tsEnd,
|
|
156
|
-
total_minutes:
|
|
157
|
-
deep_minutes:
|
|
158
|
-
rem_minutes:
|
|
159
|
-
light_minutes:
|
|
160
|
-
awake_minutes:
|
|
161
|
-
avg_confidence:
|
|
162
|
-
night_of:
|
|
135
|
+
total_minutes: group.length,
|
|
136
|
+
deep_minutes: minutes.deep,
|
|
137
|
+
rem_minutes: minutes.rem,
|
|
138
|
+
light_minutes: minutes.light,
|
|
139
|
+
awake_minutes: minutes.awake,
|
|
140
|
+
avg_confidence: confidenceSum / group.length,
|
|
141
|
+
night_of: computeNightOf(tsStart, ctx.userTimezone),
|
|
142
|
+
settle_min: index === 0 ? (night.settleMin ?? null) : null,
|
|
143
|
+
recovered_min: recovered,
|
|
163
144
|
});
|
|
164
|
-
}
|
|
165
|
-
return { sleep_session, sleep_stage
|
|
145
|
+
});
|
|
146
|
+
return { sleep_session, sleep_stage };
|
|
166
147
|
}
|
|
167
148
|
/**
|
|
168
149
|
* Session id (principle 25, as amended 2026-08-14):
|