@elizaos/plugin-health 2.0.0-beta.1 → 2.0.3-beta.3
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/LICENSE +21 -0
- package/README.md +33 -27
- package/assets/hero.svg +67 -0
- package/package.json +69 -4
- package/dist/actions/index.d.ts +0 -20
- package/dist/actions/index.d.ts.map +0 -1
- package/dist/actions/index.js +0 -5
- package/dist/actions/index.js.map +0 -1
- package/dist/anchors/index.d.ts +0 -19
- package/dist/anchors/index.d.ts.map +0 -1
- package/dist/anchors/index.js +0 -9
- package/dist/anchors/index.js.map +0 -1
- package/dist/connectors/contract-stubs.d.ts +0 -112
- package/dist/connectors/contract-stubs.d.ts.map +0 -1
- package/dist/connectors/contract-stubs.js +0 -1
- package/dist/connectors/contract-stubs.js.map +0 -1
- package/dist/connectors/index.d.ts +0 -28
- package/dist/connectors/index.d.ts.map +0 -1
- package/dist/connectors/index.js +0 -202
- package/dist/connectors/index.js.map +0 -1
- package/dist/contracts/circadian-default.d.ts +0 -15
- package/dist/contracts/circadian-default.d.ts.map +0 -1
- package/dist/contracts/circadian-default.js +0 -30
- package/dist/contracts/circadian-default.js.map +0 -1
- package/dist/contracts/circadian.d.ts +0 -92
- package/dist/contracts/circadian.d.ts.map +0 -1
- package/dist/contracts/circadian.js +0 -14
- package/dist/contracts/circadian.js.map +0 -1
- package/dist/contracts/health.d.ts +0 -9
- package/dist/contracts/health.d.ts.map +0 -1
- package/dist/contracts/health.js +0 -21
- package/dist/contracts/health.js.map +0 -1
- package/dist/contracts/lifeops-connector-degradation.d.ts +0 -9
- package/dist/contracts/lifeops-connector-degradation.d.ts.map +0 -1
- package/dist/contracts/lifeops-connector-degradation.js +0 -17
- package/dist/contracts/lifeops-connector-degradation.js.map +0 -1
- package/dist/contracts/lifeops.d.ts +0 -3123
- package/dist/contracts/lifeops.d.ts.map +0 -1
- package/dist/contracts/lifeops.js +0 -635
- package/dist/contracts/lifeops.js.map +0 -1
- package/dist/contracts/permissions.d.ts +0 -39
- package/dist/contracts/permissions.d.ts.map +0 -1
- package/dist/contracts/permissions.js +0 -1
- package/dist/contracts/permissions.js.map +0 -1
- package/dist/default-packs/bedtime.d.ts +0 -14
- package/dist/default-packs/bedtime.d.ts.map +0 -1
- package/dist/default-packs/bedtime.js +0 -48
- package/dist/default-packs/bedtime.js.map +0 -1
- package/dist/default-packs/contract-stubs.d.ts +0 -161
- package/dist/default-packs/contract-stubs.d.ts.map +0 -1
- package/dist/default-packs/contract-stubs.js +0 -1
- package/dist/default-packs/contract-stubs.js.map +0 -1
- package/dist/default-packs/index.d.ts +0 -18
- package/dist/default-packs/index.d.ts.map +0 -1
- package/dist/default-packs/index.js +0 -39
- package/dist/default-packs/index.js.map +0 -1
- package/dist/default-packs/sleep-recap.d.ts +0 -14
- package/dist/default-packs/sleep-recap.d.ts.map +0 -1
- package/dist/default-packs/sleep-recap.js +0 -51
- package/dist/default-packs/sleep-recap.js.map +0 -1
- package/dist/default-packs/wake-up.d.ts +0 -14
- package/dist/default-packs/wake-up.d.ts.map +0 -1
- package/dist/default-packs/wake-up.js +0 -61
- package/dist/default-packs/wake-up.js.map +0 -1
- package/dist/health-bridge/health-bridge.d.ts +0 -57
- package/dist/health-bridge/health-bridge.d.ts.map +0 -1
- package/dist/health-bridge/health-bridge.js +0 -558
- package/dist/health-bridge/health-bridge.js.map +0 -1
- package/dist/health-bridge/health-connectors.d.ts +0 -23
- package/dist/health-bridge/health-connectors.d.ts.map +0 -1
- package/dist/health-bridge/health-connectors.js +0 -1018
- package/dist/health-bridge/health-connectors.js.map +0 -1
- package/dist/health-bridge/health-oauth.d.ts +0 -62
- package/dist/health-bridge/health-oauth.d.ts.map +0 -1
- package/dist/health-bridge/health-oauth.js +0 -432
- package/dist/health-bridge/health-oauth.js.map +0 -1
- package/dist/health-bridge/health-provider-registry.d.ts +0 -89
- package/dist/health-bridge/health-provider-registry.d.ts.map +0 -1
- package/dist/health-bridge/health-provider-registry.js +0 -141
- package/dist/health-bridge/health-provider-registry.js.map +0 -1
- package/dist/health-bridge/health-records.d.ts +0 -14
- package/dist/health-bridge/health-records.d.ts.map +0 -1
- package/dist/health-bridge/health-records.js +0 -45
- package/dist/health-bridge/health-records.js.map +0 -1
- package/dist/health-bridge/index.d.ts +0 -22
- package/dist/health-bridge/index.d.ts.map +0 -1
- package/dist/health-bridge/index.js +0 -7
- package/dist/health-bridge/index.js.map +0 -1
- package/dist/health-bridge/service-normalize-health.d.ts +0 -3
- package/dist/health-bridge/service-normalize-health.d.ts.map +0 -1
- package/dist/health-bridge/service-normalize-health.js +0 -96
- package/dist/health-bridge/service-normalize-health.js.map +0 -1
- package/dist/index.d.ts +0 -41
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -62
- package/dist/index.js.map +0 -1
- package/dist/screen-time/index.d.ts +0 -23
- package/dist/screen-time/index.d.ts.map +0 -1
- package/dist/screen-time/index.js +0 -1
- package/dist/screen-time/index.js.map +0 -1
- package/dist/sleep/awake-probability.d.ts +0 -11
- package/dist/sleep/awake-probability.d.ts.map +0 -1
- package/dist/sleep/awake-probability.js +0 -163
- package/dist/sleep/awake-probability.js.map +0 -1
- package/dist/sleep/circadian-rules.d.ts +0 -45
- package/dist/sleep/circadian-rules.d.ts.map +0 -1
- package/dist/sleep/circadian-rules.js +0 -258
- package/dist/sleep/circadian-rules.js.map +0 -1
- package/dist/sleep/index.d.ts +0 -21
- package/dist/sleep/index.d.ts.map +0 -1
- package/dist/sleep/index.js +0 -11
- package/dist/sleep/index.js.map +0 -1
- package/dist/sleep/sleep-cycle-dispatch.d.ts +0 -75
- package/dist/sleep/sleep-cycle-dispatch.d.ts.map +0 -1
- package/dist/sleep/sleep-cycle-dispatch.js +0 -102
- package/dist/sleep/sleep-cycle-dispatch.js.map +0 -1
- package/dist/sleep/sleep-cycle.d.ts +0 -38
- package/dist/sleep/sleep-cycle.d.ts.map +0 -1
- package/dist/sleep/sleep-cycle.js +0 -418
- package/dist/sleep/sleep-cycle.js.map +0 -1
- package/dist/sleep/sleep-episode-store.d.ts +0 -25
- package/dist/sleep/sleep-episode-store.d.ts.map +0 -1
- package/dist/sleep/sleep-episode-store.js +0 -69
- package/dist/sleep/sleep-episode-store.js.map +0 -1
- package/dist/sleep/sleep-episode-types.d.ts +0 -38
- package/dist/sleep/sleep-episode-types.d.ts.map +0 -1
- package/dist/sleep/sleep-episode-types.js +0 -14
- package/dist/sleep/sleep-episode-types.js.map +0 -1
- package/dist/sleep/sleep-recap.d.ts +0 -19
- package/dist/sleep/sleep-recap.d.ts.map +0 -1
- package/dist/sleep/sleep-recap.js +0 -1
- package/dist/sleep/sleep-recap.js.map +0 -1
- package/dist/sleep/sleep-regularity.d.ts +0 -19
- package/dist/sleep/sleep-regularity.d.ts.map +0 -1
- package/dist/sleep/sleep-regularity.js +0 -242
- package/dist/sleep/sleep-regularity.js.map +0 -1
- package/dist/sleep/sleep-wake-events.d.ts +0 -58
- package/dist/sleep/sleep-wake-events.d.ts.map +0 -1
- package/dist/sleep/sleep-wake-events.js +0 -135
- package/dist/sleep/sleep-wake-events.js.map +0 -1
- package/dist/sleep/source-reliability.d.ts +0 -38
- package/dist/sleep/source-reliability.d.ts.map +0 -1
- package/dist/sleep/source-reliability.js +0 -62
- package/dist/sleep/source-reliability.js.map +0 -1
- package/dist/util/index.d.ts +0 -10
- package/dist/util/index.d.ts.map +0 -1
- package/dist/util/index.js +0 -3
- package/dist/util/index.js.map +0 -1
- package/dist/util/normalize.d.ts +0 -22
- package/dist/util/normalize.d.ts.map +0 -1
- package/dist/util/normalize.js +0 -62
- package/dist/util/normalize.js.map +0 -1
- package/dist/util/time-util.d.ts +0 -10
- package/dist/util/time-util.d.ts.map +0 -1
- package/dist/util/time-util.js +0 -14
- package/dist/util/time-util.js.map +0 -1
- package/dist/util/time.d.ts +0 -17
- package/dist/util/time.d.ts.map +0 -1
- package/dist/util/time.js +0 -152
- package/dist/util/time.js.map +0 -1
- package/dist/util/token-encryption.d.ts +0 -42
- package/dist/util/token-encryption.d.ts.map +0 -1
- package/dist/util/token-encryption.js +0 -96
- package/dist/util/token-encryption.js.map +0 -1
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sleep recap surfaced to the night-summary check-in prompt.
|
|
3
|
-
*
|
|
4
|
-
* Originally defined in `app-lifeops/src/lifeops/checkin/types.ts`. Moved
|
|
5
|
-
* here in Wave-1 (W1-B) because it is sleep-domain. app-lifeops re-exports
|
|
6
|
-
* `SleepRecap` from its old location for backward compatibility.
|
|
7
|
-
*/
|
|
8
|
-
import type { LifeOpsRegularityClass } from "../contracts/health.js";
|
|
9
|
-
export interface SleepRecap {
|
|
10
|
-
/** Local bedtime hour in [12, 36) (next-day-normalized). Null when baseline insufficient. */
|
|
11
|
-
readonly medianBedtimeLocalHour: number | null;
|
|
12
|
-
/** Median sleep episode duration in minutes. Null when baseline insufficient. */
|
|
13
|
-
readonly medianSleepDurationMin: number | null;
|
|
14
|
-
/** Sleep regularity index in [0, 100]. */
|
|
15
|
-
readonly sri: number;
|
|
16
|
-
/** Classification bucket from `classifyRegularity`. */
|
|
17
|
-
readonly regularityClass: LifeOpsRegularityClass;
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=sleep-recap.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sleep-recap.d.ts","sourceRoot":"","sources":["../../src/sleep/sleep-recap.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,WAAW,UAAU;IACzB,6FAA6F;IAC7F,QAAQ,CAAC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,iFAAiF;IACjF,QAAQ,CAAC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,0CAA0C;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,QAAQ,CAAC,eAAe,EAAE,sBAAsB,CAAC;CAClD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=sleep-recap.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { LifeOpsPersonalBaseline, LifeOpsScheduleRegularity, LifeOpsSleepCycleType } from "../contracts/health.js";
|
|
2
|
-
export interface SleepRegularityEpisodeLike {
|
|
3
|
-
startAt: string;
|
|
4
|
-
endAt: string | null;
|
|
5
|
-
cycleType: LifeOpsSleepCycleType;
|
|
6
|
-
}
|
|
7
|
-
export declare function computeSleepRegularity(args: {
|
|
8
|
-
episodes: readonly SleepRegularityEpisodeLike[];
|
|
9
|
-
timezone: string;
|
|
10
|
-
nowMs: number;
|
|
11
|
-
windowDays?: number;
|
|
12
|
-
}): LifeOpsScheduleRegularity;
|
|
13
|
-
export declare function computePersonalBaseline(args: {
|
|
14
|
-
episodes: readonly SleepRegularityEpisodeLike[];
|
|
15
|
-
timezone: string;
|
|
16
|
-
nowMs: number;
|
|
17
|
-
windowDays?: number;
|
|
18
|
-
}): LifeOpsPersonalBaseline | null;
|
|
19
|
-
//# sourceMappingURL=sleep-regularity.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sleep-regularity.d.ts","sourceRoot":"","sources":["../../src/sleep/sleep-regularity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,yBAAyB,EACzB,qBAAqB,EACtB,MAAM,wBAAwB,CAAC;AAIhC,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,qBAAqB,CAAC;CAClC;AAmJD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,QAAQ,EAAE,SAAS,0BAA0B,EAAE,CAAC;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,yBAAyB,CAoD5B;AAsCD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,QAAQ,EAAE,SAAS,0BAA0B,EAAE,CAAC;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,uBAAuB,GAAG,IAAI,CAiCjC"}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { getZonedDateParts } from "../util/time.js";
|
|
2
|
-
import { parseIsoMs } from "../util/time-util.js";
|
|
3
|
-
function clamp(value, min, max) {
|
|
4
|
-
return Math.min(max, Math.max(min, value));
|
|
5
|
-
}
|
|
6
|
-
function round(value) {
|
|
7
|
-
if (!Number.isFinite(value)) {
|
|
8
|
-
return 0;
|
|
9
|
-
}
|
|
10
|
-
return Math.round(value * 100) / 100;
|
|
11
|
-
}
|
|
12
|
-
function durationMinutes(startMs, endMs) {
|
|
13
|
-
return Math.max(0, Math.round((endMs - startMs) / 6e4));
|
|
14
|
-
}
|
|
15
|
-
function isRegularityEpisode(episode, nowMs) {
|
|
16
|
-
const startMs = parseIsoMs(episode.startAt);
|
|
17
|
-
const endMs = parseIsoMs(episode.endAt);
|
|
18
|
-
if (startMs === null || endMs === null || endMs <= startMs || endMs > nowMs) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
if (episode.cycleType === "nap") {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
return durationMinutes(startMs, endMs) >= 180;
|
|
25
|
-
}
|
|
26
|
-
function localMinuteOfDay(ms, timezone) {
|
|
27
|
-
const parts = getZonedDateParts(new Date(ms), timezone);
|
|
28
|
-
return parts.hour * 60 + parts.minute;
|
|
29
|
-
}
|
|
30
|
-
function circularStddevMinutes(minuteValues) {
|
|
31
|
-
if (minuteValues.length === 0) {
|
|
32
|
-
return 0;
|
|
33
|
-
}
|
|
34
|
-
const angleScale = 2 * Math.PI / (24 * 60);
|
|
35
|
-
let sumSin = 0;
|
|
36
|
-
let sumCos = 0;
|
|
37
|
-
for (const minute of minuteValues) {
|
|
38
|
-
const angle = minute * angleScale;
|
|
39
|
-
sumSin += Math.sin(angle);
|
|
40
|
-
sumCos += Math.cos(angle);
|
|
41
|
-
}
|
|
42
|
-
const meanSin = sumSin / minuteValues.length;
|
|
43
|
-
const meanCos = sumCos / minuteValues.length;
|
|
44
|
-
const meanResultantLength = Math.sqrt(meanSin ** 2 + meanCos ** 2);
|
|
45
|
-
if (meanResultantLength <= 0) {
|
|
46
|
-
return 720;
|
|
47
|
-
}
|
|
48
|
-
const stddevRadians = Math.sqrt(-2 * Math.log(meanResultantLength));
|
|
49
|
-
return round(stddevRadians / angleScale);
|
|
50
|
-
}
|
|
51
|
-
function occupancyVector(args) {
|
|
52
|
-
const totalMinutes = args.windowDays * 24 * 60;
|
|
53
|
-
const windowEndMs = args.nowMs;
|
|
54
|
-
const windowStartMs = windowEndMs - totalMinutes * 6e4;
|
|
55
|
-
const deltas = new Int16Array(totalMinutes + 1);
|
|
56
|
-
for (const episode of args.episodes) {
|
|
57
|
-
const startMs = parseIsoMs(episode.startAt);
|
|
58
|
-
const endMs = parseIsoMs(episode.endAt);
|
|
59
|
-
if (startMs === null || endMs === null) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const clampedStartMs = Math.max(startMs, windowStartMs);
|
|
63
|
-
const clampedEndMs = Math.min(endMs, windowEndMs);
|
|
64
|
-
if (clampedEndMs <= clampedStartMs) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const startIndex = Math.max(
|
|
68
|
-
0,
|
|
69
|
-
Math.floor((clampedStartMs - windowStartMs) / 6e4)
|
|
70
|
-
);
|
|
71
|
-
const endIndex = Math.min(
|
|
72
|
-
totalMinutes,
|
|
73
|
-
Math.ceil((clampedEndMs - windowStartMs) / 6e4)
|
|
74
|
-
);
|
|
75
|
-
deltas[startIndex] += 1;
|
|
76
|
-
deltas[endIndex] -= 1;
|
|
77
|
-
}
|
|
78
|
-
const occupied = new Array(totalMinutes);
|
|
79
|
-
let activeCount = 0;
|
|
80
|
-
for (let index = 0; index < totalMinutes; index += 1) {
|
|
81
|
-
activeCount += deltas[index] ?? 0;
|
|
82
|
-
occupied[index] = activeCount > 0;
|
|
83
|
-
}
|
|
84
|
-
return { occupied, windowStartMs };
|
|
85
|
-
}
|
|
86
|
-
function computeSleepRegularityIndex(args) {
|
|
87
|
-
const totalMinutes = args.windowDays * 24 * 60;
|
|
88
|
-
if (totalMinutes <= 24 * 60) {
|
|
89
|
-
return 0;
|
|
90
|
-
}
|
|
91
|
-
const { occupied } = occupancyVector(args);
|
|
92
|
-
let matches = 0;
|
|
93
|
-
let comparisons = 0;
|
|
94
|
-
const oneDayMinutes = 24 * 60;
|
|
95
|
-
for (let index = 0; index < occupied.length - oneDayMinutes; index += 1) {
|
|
96
|
-
matches += occupied[index] === occupied[index + oneDayMinutes] ? 1 : 0;
|
|
97
|
-
comparisons += 1;
|
|
98
|
-
}
|
|
99
|
-
if (comparisons === 0) {
|
|
100
|
-
return 0;
|
|
101
|
-
}
|
|
102
|
-
const agreement = matches / comparisons;
|
|
103
|
-
return round(clamp(200 * agreement - 100, 0, 100));
|
|
104
|
-
}
|
|
105
|
-
function classifyRegularity(args) {
|
|
106
|
-
if (args.sampleCount < 5) {
|
|
107
|
-
return "insufficient_data";
|
|
108
|
-
}
|
|
109
|
-
const worstStddev = Math.max(args.bedtimeStddevMin, args.wakeStddevMin);
|
|
110
|
-
if (args.sri >= 85 && worstStddev <= 45) {
|
|
111
|
-
return "very_regular";
|
|
112
|
-
}
|
|
113
|
-
if (args.sri >= 70 && worstStddev <= 90) {
|
|
114
|
-
return "regular";
|
|
115
|
-
}
|
|
116
|
-
if (args.sri >= 50) {
|
|
117
|
-
return "irregular";
|
|
118
|
-
}
|
|
119
|
-
return "very_irregular";
|
|
120
|
-
}
|
|
121
|
-
function computeSleepRegularity(args) {
|
|
122
|
-
const windowDays = args.windowDays ?? 28;
|
|
123
|
-
const relevant = args.episodes.filter(
|
|
124
|
-
(episode) => isRegularityEpisode(episode, args.nowMs)
|
|
125
|
-
);
|
|
126
|
-
if (relevant.length === 0) {
|
|
127
|
-
return {
|
|
128
|
-
sri: 0,
|
|
129
|
-
bedtimeStddevMin: 0,
|
|
130
|
-
wakeStddevMin: 0,
|
|
131
|
-
midSleepStddevMin: 0,
|
|
132
|
-
regularityClass: "insufficient_data",
|
|
133
|
-
sampleCount: 0,
|
|
134
|
-
windowDays
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
const bedtimeMinutes = relevant.map(
|
|
138
|
-
(episode) => localMinuteOfDay(Date.parse(episode.startAt), args.timezone)
|
|
139
|
-
);
|
|
140
|
-
const wakeMinutes = relevant.map(
|
|
141
|
-
(episode) => localMinuteOfDay(Date.parse(episode.endAt), args.timezone)
|
|
142
|
-
);
|
|
143
|
-
const midSleepMinutes = relevant.map((episode) => {
|
|
144
|
-
const startMs = Date.parse(episode.startAt);
|
|
145
|
-
const endMs = Date.parse(episode.endAt);
|
|
146
|
-
const midpointMs = startMs + Math.round((endMs - startMs) / 2);
|
|
147
|
-
return localMinuteOfDay(midpointMs, args.timezone);
|
|
148
|
-
});
|
|
149
|
-
const sri = computeSleepRegularityIndex({
|
|
150
|
-
episodes: relevant,
|
|
151
|
-
nowMs: args.nowMs,
|
|
152
|
-
windowDays
|
|
153
|
-
});
|
|
154
|
-
const bedtimeStddevMin = circularStddevMinutes(bedtimeMinutes);
|
|
155
|
-
const wakeStddevMin = circularStddevMinutes(wakeMinutes);
|
|
156
|
-
const midSleepStddevMin = circularStddevMinutes(midSleepMinutes);
|
|
157
|
-
return {
|
|
158
|
-
sri,
|
|
159
|
-
bedtimeStddevMin,
|
|
160
|
-
wakeStddevMin,
|
|
161
|
-
midSleepStddevMin,
|
|
162
|
-
regularityClass: classifyRegularity({
|
|
163
|
-
sampleCount: relevant.length,
|
|
164
|
-
sri,
|
|
165
|
-
bedtimeStddevMin,
|
|
166
|
-
wakeStddevMin
|
|
167
|
-
}),
|
|
168
|
-
sampleCount: relevant.length,
|
|
169
|
-
windowDays
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
const BASELINE_MIN_SAMPLE_COUNT = 5;
|
|
173
|
-
function circularMeanHour(minuteValues) {
|
|
174
|
-
if (minuteValues.length === 0) return null;
|
|
175
|
-
const angleScale = 2 * Math.PI / (24 * 60);
|
|
176
|
-
let sumSin = 0;
|
|
177
|
-
let sumCos = 0;
|
|
178
|
-
for (const minute of minuteValues) {
|
|
179
|
-
const angle = minute * angleScale;
|
|
180
|
-
sumSin += Math.sin(angle);
|
|
181
|
-
sumCos += Math.cos(angle);
|
|
182
|
-
}
|
|
183
|
-
const meanSin = sumSin / minuteValues.length;
|
|
184
|
-
const meanCos = sumCos / minuteValues.length;
|
|
185
|
-
if (meanSin === 0 && meanCos === 0) return null;
|
|
186
|
-
let meanAngle = Math.atan2(meanSin, meanCos);
|
|
187
|
-
if (meanAngle < 0) meanAngle += 2 * Math.PI;
|
|
188
|
-
return round(meanAngle / angleScale / 60);
|
|
189
|
-
}
|
|
190
|
-
function medianNumber(values) {
|
|
191
|
-
if (values.length === 0) return null;
|
|
192
|
-
const sorted = [...values].sort((left, right) => left - right);
|
|
193
|
-
const middle = Math.floor(sorted.length / 2);
|
|
194
|
-
if (sorted.length % 2 === 1) {
|
|
195
|
-
const value = sorted[middle];
|
|
196
|
-
return value === void 0 ? null : round(value);
|
|
197
|
-
}
|
|
198
|
-
const lower = sorted[middle - 1];
|
|
199
|
-
const upper = sorted[middle];
|
|
200
|
-
if (lower === void 0 || upper === void 0) {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
return round((lower + upper) / 2);
|
|
204
|
-
}
|
|
205
|
-
function computePersonalBaseline(args) {
|
|
206
|
-
const windowDays = args.windowDays ?? 28;
|
|
207
|
-
const relevant = args.episodes.filter(
|
|
208
|
-
(episode) => isRegularityEpisode(episode, args.nowMs)
|
|
209
|
-
);
|
|
210
|
-
if (relevant.length < BASELINE_MIN_SAMPLE_COUNT) {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
const bedtimeMinutes = relevant.map(
|
|
214
|
-
(episode) => localMinuteOfDay(Date.parse(episode.startAt), args.timezone)
|
|
215
|
-
);
|
|
216
|
-
const wakeMinutes = relevant.map(
|
|
217
|
-
(episode) => localMinuteOfDay(Date.parse(episode.endAt), args.timezone)
|
|
218
|
-
);
|
|
219
|
-
const durations = relevant.map(
|
|
220
|
-
(episode) => durationMinutes(Date.parse(episode.startAt), Date.parse(episode.endAt))
|
|
221
|
-
);
|
|
222
|
-
const bedtimeMean = circularMeanHour(bedtimeMinutes);
|
|
223
|
-
const wakeMean = circularMeanHour(wakeMinutes);
|
|
224
|
-
if (bedtimeMean === null || wakeMean === null) {
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
const medianBedtimeLocalHour = bedtimeMean < 12 ? round(bedtimeMean + 24) : bedtimeMean;
|
|
228
|
-
return {
|
|
229
|
-
medianWakeLocalHour: wakeMean,
|
|
230
|
-
medianBedtimeLocalHour,
|
|
231
|
-
medianSleepDurationMin: medianNumber(durations) ?? 0,
|
|
232
|
-
bedtimeStddevMin: circularStddevMinutes(bedtimeMinutes),
|
|
233
|
-
wakeStddevMin: circularStddevMinutes(wakeMinutes),
|
|
234
|
-
sampleCount: relevant.length,
|
|
235
|
-
windowDays
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
export {
|
|
239
|
-
computePersonalBaseline,
|
|
240
|
-
computeSleepRegularity
|
|
241
|
-
};
|
|
242
|
-
//# sourceMappingURL=sleep-regularity.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/sleep/sleep-regularity.ts"],"sourcesContent":["import type {\n LifeOpsPersonalBaseline,\n LifeOpsScheduleRegularity,\n LifeOpsSleepCycleType,\n} from \"../contracts/health.js\";\nimport { getZonedDateParts } from \"../util/time.js\";\nimport { parseIsoMs } from \"../util/time-util.js\";\n\nexport interface SleepRegularityEpisodeLike {\n startAt: string;\n endAt: string | null;\n cycleType: LifeOpsSleepCycleType;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction round(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n return Math.round(value * 100) / 100;\n}\n\nfunction durationMinutes(startMs: number, endMs: number): number {\n return Math.max(0, Math.round((endMs - startMs) / 60_000));\n}\n\nfunction isRegularityEpisode(\n episode: SleepRegularityEpisodeLike,\n nowMs: number,\n): episode is SleepRegularityEpisodeLike & { endAt: string } {\n const startMs = parseIsoMs(episode.startAt);\n const endMs = parseIsoMs(episode.endAt);\n if (startMs === null || endMs === null || endMs <= startMs || endMs > nowMs) {\n return false;\n }\n if (episode.cycleType === \"nap\") {\n return false;\n }\n return durationMinutes(startMs, endMs) >= 180;\n}\n\nfunction localMinuteOfDay(ms: number, timezone: string): number {\n const parts = getZonedDateParts(new Date(ms), timezone);\n return parts.hour * 60 + parts.minute;\n}\n\nfunction circularStddevMinutes(minuteValues: readonly number[]): number {\n if (minuteValues.length === 0) {\n return 0;\n }\n const angleScale = (2 * Math.PI) / (24 * 60);\n let sumSin = 0;\n let sumCos = 0;\n for (const minute of minuteValues) {\n const angle = minute * angleScale;\n sumSin += Math.sin(angle);\n sumCos += Math.cos(angle);\n }\n const meanSin = sumSin / minuteValues.length;\n const meanCos = sumCos / minuteValues.length;\n const meanResultantLength = Math.sqrt(meanSin ** 2 + meanCos ** 2);\n if (meanResultantLength <= 0) {\n return 720;\n }\n const stddevRadians = Math.sqrt(-2 * Math.log(meanResultantLength));\n return round(stddevRadians / angleScale);\n}\n\nfunction occupancyVector(args: {\n episodes: Array<SleepRegularityEpisodeLike & { endAt: string }>;\n nowMs: number;\n windowDays: number;\n}): { occupied: boolean[]; windowStartMs: number } {\n const totalMinutes = args.windowDays * 24 * 60;\n const windowEndMs = args.nowMs;\n const windowStartMs = windowEndMs - totalMinutes * 60_000;\n const deltas = new Int16Array(totalMinutes + 1);\n\n for (const episode of args.episodes) {\n const startMs = parseIsoMs(episode.startAt);\n const endMs = parseIsoMs(episode.endAt);\n if (startMs === null || endMs === null) {\n continue;\n }\n const clampedStartMs = Math.max(startMs, windowStartMs);\n const clampedEndMs = Math.min(endMs, windowEndMs);\n if (clampedEndMs <= clampedStartMs) {\n continue;\n }\n const startIndex = Math.max(\n 0,\n Math.floor((clampedStartMs - windowStartMs) / 60_000),\n );\n const endIndex = Math.min(\n totalMinutes,\n Math.ceil((clampedEndMs - windowStartMs) / 60_000),\n );\n deltas[startIndex] += 1;\n deltas[endIndex] -= 1;\n }\n\n const occupied = new Array<boolean>(totalMinutes);\n let activeCount = 0;\n for (let index = 0; index < totalMinutes; index += 1) {\n activeCount += deltas[index] ?? 0;\n occupied[index] = activeCount > 0;\n }\n return { occupied, windowStartMs };\n}\n\nfunction computeSleepRegularityIndex(args: {\n episodes: Array<SleepRegularityEpisodeLike & { endAt: string }>;\n nowMs: number;\n windowDays: number;\n}): number {\n const totalMinutes = args.windowDays * 24 * 60;\n if (totalMinutes <= 24 * 60) {\n return 0;\n }\n const { occupied } = occupancyVector(args);\n let matches = 0;\n let comparisons = 0;\n const oneDayMinutes = 24 * 60;\n for (let index = 0; index < occupied.length - oneDayMinutes; index += 1) {\n matches += occupied[index] === occupied[index + oneDayMinutes] ? 1 : 0;\n comparisons += 1;\n }\n if (comparisons === 0) {\n return 0;\n }\n const agreement = matches / comparisons;\n return round(clamp(200 * agreement - 100, 0, 100));\n}\n\nfunction classifyRegularity(args: {\n sampleCount: number;\n sri: number;\n bedtimeStddevMin: number;\n wakeStddevMin: number;\n}): LifeOpsScheduleRegularity[\"regularityClass\"] {\n if (args.sampleCount < 5) {\n return \"insufficient_data\";\n }\n const worstStddev = Math.max(args.bedtimeStddevMin, args.wakeStddevMin);\n if (args.sri >= 85 && worstStddev <= 45) {\n return \"very_regular\";\n }\n if (args.sri >= 70 && worstStddev <= 90) {\n return \"regular\";\n }\n if (args.sri >= 50) {\n return \"irregular\";\n }\n return \"very_irregular\";\n}\n\nexport function computeSleepRegularity(args: {\n episodes: readonly SleepRegularityEpisodeLike[];\n timezone: string;\n nowMs: number;\n windowDays?: number;\n}): LifeOpsScheduleRegularity {\n const windowDays = args.windowDays ?? 28;\n const relevant = args.episodes.filter((episode) =>\n isRegularityEpisode(episode, args.nowMs),\n );\n\n if (relevant.length === 0) {\n return {\n sri: 0,\n bedtimeStddevMin: 0,\n wakeStddevMin: 0,\n midSleepStddevMin: 0,\n regularityClass: \"insufficient_data\",\n sampleCount: 0,\n windowDays,\n };\n }\n\n const bedtimeMinutes = relevant.map((episode) =>\n localMinuteOfDay(Date.parse(episode.startAt), args.timezone),\n );\n const wakeMinutes = relevant.map((episode) =>\n localMinuteOfDay(Date.parse(episode.endAt), args.timezone),\n );\n const midSleepMinutes = relevant.map((episode) => {\n const startMs = Date.parse(episode.startAt);\n const endMs = Date.parse(episode.endAt);\n const midpointMs = startMs + Math.round((endMs - startMs) / 2);\n return localMinuteOfDay(midpointMs, args.timezone);\n });\n const sri = computeSleepRegularityIndex({\n episodes: relevant,\n nowMs: args.nowMs,\n windowDays,\n });\n const bedtimeStddevMin = circularStddevMinutes(bedtimeMinutes);\n const wakeStddevMin = circularStddevMinutes(wakeMinutes);\n const midSleepStddevMin = circularStddevMinutes(midSleepMinutes);\n return {\n sri,\n bedtimeStddevMin,\n wakeStddevMin,\n midSleepStddevMin,\n regularityClass: classifyRegularity({\n sampleCount: relevant.length,\n sri,\n bedtimeStddevMin,\n wakeStddevMin,\n }),\n sampleCount: relevant.length,\n windowDays,\n };\n}\n\nconst BASELINE_MIN_SAMPLE_COUNT = 5;\n\nfunction circularMeanHour(minuteValues: readonly number[]): number | null {\n if (minuteValues.length === 0) return null;\n const angleScale = (2 * Math.PI) / (24 * 60);\n let sumSin = 0;\n let sumCos = 0;\n for (const minute of minuteValues) {\n const angle = minute * angleScale;\n sumSin += Math.sin(angle);\n sumCos += Math.cos(angle);\n }\n const meanSin = sumSin / minuteValues.length;\n const meanCos = sumCos / minuteValues.length;\n if (meanSin === 0 && meanCos === 0) return null;\n let meanAngle = Math.atan2(meanSin, meanCos);\n if (meanAngle < 0) meanAngle += 2 * Math.PI;\n return round(meanAngle / angleScale / 60);\n}\n\nfunction medianNumber(values: readonly number[]): number | null {\n if (values.length === 0) return null;\n const sorted = [...values].sort((left, right) => left - right);\n const middle = Math.floor(sorted.length / 2);\n if (sorted.length % 2 === 1) {\n const value = sorted[middle];\n return value === undefined ? null : round(value);\n }\n const lower = sorted[middle - 1];\n const upper = sorted[middle];\n if (lower === undefined || upper === undefined) {\n return null;\n }\n return round((lower + upper) / 2);\n}\n\nexport function computePersonalBaseline(args: {\n episodes: readonly SleepRegularityEpisodeLike[];\n timezone: string;\n nowMs: number;\n windowDays?: number;\n}): LifeOpsPersonalBaseline | null {\n const windowDays = args.windowDays ?? 28;\n const relevant = args.episodes.filter((episode) =>\n isRegularityEpisode(episode, args.nowMs),\n );\n if (relevant.length < BASELINE_MIN_SAMPLE_COUNT) {\n return null;\n }\n const bedtimeMinutes = relevant.map((episode) =>\n localMinuteOfDay(Date.parse(episode.startAt), args.timezone),\n );\n const wakeMinutes = relevant.map((episode) =>\n localMinuteOfDay(Date.parse(episode.endAt), args.timezone),\n );\n const durations = relevant.map((episode) =>\n durationMinutes(Date.parse(episode.startAt), Date.parse(episode.endAt)),\n );\n const bedtimeMean = circularMeanHour(bedtimeMinutes);\n const wakeMean = circularMeanHour(wakeMinutes);\n if (bedtimeMean === null || wakeMean === null) {\n return null;\n }\n const medianBedtimeLocalHour =\n bedtimeMean < 12 ? round(bedtimeMean + 24) : bedtimeMean;\n return {\n medianWakeLocalHour: wakeMean,\n medianBedtimeLocalHour,\n medianSleepDurationMin: medianNumber(durations) ?? 0,\n bedtimeStddevMin: circularStddevMinutes(bedtimeMinutes),\n wakeStddevMin: circularStddevMinutes(wakeMinutes),\n sampleCount: relevant.length,\n windowDays,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { LifeOpsCircadianState, LifeOpsEventKind, LifeOpsRegularityClass, LifeOpsScheduleInsight, LifeOpsScheduleSleepStatus, LifeOpsUnclearReason } from "../contracts/health.js";
|
|
2
|
-
/**
|
|
3
|
-
* Structural shape of `LifeOpsScheduleMergedState` (declared in
|
|
4
|
-
* `app-lifeops/src/lifeops/schedule-sync-contracts.ts`) restricted to the
|
|
5
|
-
* fields this module reads. Defined locally to keep plugin-health independent
|
|
6
|
-
* of app-lifeops' schedule-sync repository layer; app-lifeops feeds compatible
|
|
7
|
-
* objects in via the call-site.
|
|
8
|
-
*/
|
|
9
|
-
export interface LifeOpsScheduleMergedStateRecord extends LifeOpsScheduleInsight {
|
|
10
|
-
id: string;
|
|
11
|
-
agentId: string;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Typed payload for every circadian-state event emitted from the scheduler
|
|
15
|
-
* tick. Every field is derived from the merged state record, and must not
|
|
16
|
-
* contain `unknown` / `any` (per repo rule).
|
|
17
|
-
*/
|
|
18
|
-
interface LifeOpsDerivedEventPayload {
|
|
19
|
-
currentStateId: string;
|
|
20
|
-
previousStateId: string | null;
|
|
21
|
-
sleepStatus: LifeOpsScheduleSleepStatus;
|
|
22
|
-
circadianState: LifeOpsCircadianState;
|
|
23
|
-
stateConfidence: number;
|
|
24
|
-
uncertaintyReason: LifeOpsUnclearReason | null;
|
|
25
|
-
regularityClass: LifeOpsRegularityClass;
|
|
26
|
-
wakeAt: string | null;
|
|
27
|
-
bedtimeTargetAt: string | null;
|
|
28
|
-
minutesUntilBedtimeTarget: number | null;
|
|
29
|
-
}
|
|
30
|
-
export interface LifeOpsDerivedEvent {
|
|
31
|
-
id: string;
|
|
32
|
-
kind: Exclude<LifeOpsEventKind, "calendar.event.ended" | "gmail.message.received" | "gmail.thread.needs_response">;
|
|
33
|
-
occurredAt: string;
|
|
34
|
-
confidence: number;
|
|
35
|
-
payload: LifeOpsDerivedEventPayload;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Edge-triggered circadian event derivation per `sleep-wake-spec.md`:
|
|
39
|
-
* - Events fire only on state transitions, never on stable ticks.
|
|
40
|
-
* - `wake.observed` fires on sleeping/napping -> waking transitions.
|
|
41
|
-
* - `wake.confirmed` fires on waking -> awake transitions (paired with sleep.ended).
|
|
42
|
-
* - `sleep.onset_candidate` fires on awake/winding_down -> (onset) transitions
|
|
43
|
-
* when the state machine starts the SLEEP_ONSET_WINDOW. For this interim
|
|
44
|
-
* adapter we fire it alongside sleep.detected since the scorer rewrite
|
|
45
|
-
* (scorer_rewrite todo) is where the dedicated onset candidate state lives.
|
|
46
|
-
* - `sleep.detected` fires on any -> sleeping transition.
|
|
47
|
-
* - `nap.detected` fires on any -> napping transition.
|
|
48
|
-
* - `bedtime.imminent` fires once when minutesUntilBedtimeTarget crosses
|
|
49
|
-
* from >30 to <=30 with a future target.
|
|
50
|
-
* - `regularity.changed` fires on regularityClass transitions.
|
|
51
|
-
*/
|
|
52
|
-
export declare function deriveSleepWakeEvents(args: {
|
|
53
|
-
previous: LifeOpsScheduleMergedStateRecord | null;
|
|
54
|
-
current: LifeOpsScheduleMergedStateRecord;
|
|
55
|
-
now: Date;
|
|
56
|
-
}): LifeOpsDerivedEvent[];
|
|
57
|
-
export {};
|
|
58
|
-
//# sourceMappingURL=sleep-wake-events.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sleep-wake-events.d.ts","sourceRoot":"","sources":["../../src/sleep/sleep-wake-events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,0BAA0B,EAC1B,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;GAMG;AACH,MAAM,WAAW,gCACf,SAAQ,sBAAsB;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,UAAU,0BAA0B;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,0BAA0B,CAAC;IACxC,cAAc,EAAE,qBAAqB,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC/C,eAAe,EAAE,sBAAsB,CAAC;IACxC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,yBAAyB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CACX,gBAAgB,EACd,sBAAsB,GACtB,wBAAwB,GACxB,6BAA6B,CAChC,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,0BAA0B,CAAC;CACrC;AAsCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,gCAAgC,GAAG,IAAI,CAAC;IAClD,OAAO,EAAE,gCAAgC,CAAC;IAC1C,GAAG,EAAE,IAAI,CAAC;CACX,GAAG,mBAAmB,EAAE,CA4IxB"}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { parseIsoMs } from "../util/time-util.js";
|
|
2
|
-
function buildEvent(args) {
|
|
3
|
-
return {
|
|
4
|
-
id: `${args.kind}:${args.current.agentId}:${args.occurredAt}`,
|
|
5
|
-
kind: args.kind,
|
|
6
|
-
occurredAt: args.occurredAt,
|
|
7
|
-
confidence: args.confidence,
|
|
8
|
-
payload: {
|
|
9
|
-
currentStateId: args.current.id,
|
|
10
|
-
previousStateId: args.previous?.id ?? null,
|
|
11
|
-
sleepStatus: args.current.sleepStatus,
|
|
12
|
-
circadianState: args.current.circadianState,
|
|
13
|
-
stateConfidence: args.current.stateConfidence,
|
|
14
|
-
uncertaintyReason: args.current.uncertaintyReason,
|
|
15
|
-
regularityClass: args.current.regularity.regularityClass,
|
|
16
|
-
wakeAt: args.current.wakeAt,
|
|
17
|
-
bedtimeTargetAt: args.current.relativeTime.bedtimeTargetAt,
|
|
18
|
-
minutesUntilBedtimeTarget: args.current.relativeTime.minutesUntilBedtimeTarget
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
function isAsleepState(state) {
|
|
23
|
-
return state === "sleeping" || state === "napping";
|
|
24
|
-
}
|
|
25
|
-
function isAwakeState(state) {
|
|
26
|
-
return state === "awake" || state === "waking";
|
|
27
|
-
}
|
|
28
|
-
function deriveSleepWakeEvents(args) {
|
|
29
|
-
const events = [];
|
|
30
|
-
const current = args.current;
|
|
31
|
-
const previous = args.previous;
|
|
32
|
-
const currentState = current.circadianState;
|
|
33
|
-
const previousState = previous?.circadianState ?? null;
|
|
34
|
-
const stateChanged = previousState !== currentState;
|
|
35
|
-
if (stateChanged) {
|
|
36
|
-
if (currentState === "sleeping" && current.currentSleepStartedAt) {
|
|
37
|
-
events.push(
|
|
38
|
-
buildEvent({
|
|
39
|
-
kind: "lifeops.sleep.onset_candidate",
|
|
40
|
-
occurredAt: current.currentSleepStartedAt,
|
|
41
|
-
confidence: current.sleepConfidence,
|
|
42
|
-
current,
|
|
43
|
-
previous
|
|
44
|
-
})
|
|
45
|
-
);
|
|
46
|
-
events.push(
|
|
47
|
-
buildEvent({
|
|
48
|
-
kind: "lifeops.sleep.detected",
|
|
49
|
-
occurredAt: current.currentSleepStartedAt,
|
|
50
|
-
confidence: current.sleepConfidence,
|
|
51
|
-
current,
|
|
52
|
-
previous
|
|
53
|
-
})
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
if (currentState === "napping" && current.currentSleepStartedAt) {
|
|
57
|
-
events.push(
|
|
58
|
-
buildEvent({
|
|
59
|
-
kind: "lifeops.nap.detected",
|
|
60
|
-
occurredAt: current.currentSleepStartedAt,
|
|
61
|
-
confidence: current.sleepConfidence,
|
|
62
|
-
current,
|
|
63
|
-
previous
|
|
64
|
-
})
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
if (currentState === "waking" && previousState !== null && isAsleepState(previousState) && current.wakeAt) {
|
|
68
|
-
events.push(
|
|
69
|
-
buildEvent({
|
|
70
|
-
kind: "lifeops.wake.observed",
|
|
71
|
-
occurredAt: current.wakeAt,
|
|
72
|
-
confidence: current.awakeProbability.pAwake,
|
|
73
|
-
current,
|
|
74
|
-
previous
|
|
75
|
-
})
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
if (currentState === "awake" && previousState === "waking" && current.wakeAt) {
|
|
79
|
-
events.push(
|
|
80
|
-
buildEvent({
|
|
81
|
-
kind: "lifeops.wake.confirmed",
|
|
82
|
-
occurredAt: current.wakeAt,
|
|
83
|
-
confidence: current.awakeProbability.pAwake,
|
|
84
|
-
current,
|
|
85
|
-
previous
|
|
86
|
-
})
|
|
87
|
-
);
|
|
88
|
-
if (current.lastSleepEndedAt) {
|
|
89
|
-
events.push(
|
|
90
|
-
buildEvent({
|
|
91
|
-
kind: "lifeops.sleep.ended",
|
|
92
|
-
occurredAt: current.lastSleepEndedAt,
|
|
93
|
-
confidence: current.sleepConfidence,
|
|
94
|
-
current,
|
|
95
|
-
previous
|
|
96
|
-
})
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const previousMinutesUntilBedtime = previous?.relativeTime.minutesUntilBedtimeTarget ?? null;
|
|
102
|
-
const currentMinutesUntilBedtime = current.relativeTime.minutesUntilBedtimeTarget;
|
|
103
|
-
const nowInBedtimeWindow = currentMinutesUntilBedtime !== null && currentMinutesUntilBedtime >= 0 && currentMinutesUntilBedtime <= 30;
|
|
104
|
-
const wasOutsideWindow = previousMinutesUntilBedtime === null || previousMinutesUntilBedtime > 30;
|
|
105
|
-
if (nowInBedtimeWindow && wasOutsideWindow && isAwakeState(currentState)) {
|
|
106
|
-
const occurredAtMs = parseIsoMs(current.relativeTime.bedtimeTargetAt);
|
|
107
|
-
events.push(
|
|
108
|
-
buildEvent({
|
|
109
|
-
kind: "lifeops.bedtime.imminent",
|
|
110
|
-
occurredAt: occurredAtMs !== null ? new Date(occurredAtMs).toISOString() : args.now.toISOString(),
|
|
111
|
-
confidence: current.relativeTime.confidence,
|
|
112
|
-
current,
|
|
113
|
-
previous
|
|
114
|
-
})
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
const previousClass = previous?.regularity.regularityClass ?? null;
|
|
118
|
-
const currentClass = current.regularity.regularityClass;
|
|
119
|
-
if (previousClass !== null && previousClass !== currentClass) {
|
|
120
|
-
events.push(
|
|
121
|
-
buildEvent({
|
|
122
|
-
kind: "lifeops.regularity.changed",
|
|
123
|
-
occurredAt: current.inferredAt,
|
|
124
|
-
confidence: current.stateConfidence,
|
|
125
|
-
current,
|
|
126
|
-
previous
|
|
127
|
-
})
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
return events;
|
|
131
|
-
}
|
|
132
|
-
export {
|
|
133
|
-
deriveSleepWakeEvents
|
|
134
|
-
};
|
|
135
|
-
//# sourceMappingURL=sleep-wake-events.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/sleep/sleep-wake-events.ts"],"sourcesContent":["import type {\n LifeOpsCircadianState,\n LifeOpsEventKind,\n LifeOpsRegularityClass,\n LifeOpsScheduleInsight,\n LifeOpsScheduleSleepStatus,\n LifeOpsUnclearReason,\n} from \"../contracts/health.js\";\nimport { parseIsoMs } from \"../util/time-util.js\";\n\n/**\n * Structural shape of `LifeOpsScheduleMergedState` (declared in\n * `app-lifeops/src/lifeops/schedule-sync-contracts.ts`) restricted to the\n * fields this module reads. Defined locally to keep plugin-health independent\n * of app-lifeops' schedule-sync repository layer; app-lifeops feeds compatible\n * objects in via the call-site.\n */\nexport interface LifeOpsScheduleMergedStateRecord\n extends LifeOpsScheduleInsight {\n id: string;\n agentId: string;\n}\n\n/**\n * Typed payload for every circadian-state event emitted from the scheduler\n * tick. Every field is derived from the merged state record, and must not\n * contain `unknown` / `any` (per repo rule).\n */\ninterface LifeOpsDerivedEventPayload {\n currentStateId: string;\n previousStateId: string | null;\n sleepStatus: LifeOpsScheduleSleepStatus;\n circadianState: LifeOpsCircadianState;\n stateConfidence: number;\n uncertaintyReason: LifeOpsUnclearReason | null;\n regularityClass: LifeOpsRegularityClass;\n wakeAt: string | null;\n bedtimeTargetAt: string | null;\n minutesUntilBedtimeTarget: number | null;\n}\n\nexport interface LifeOpsDerivedEvent {\n id: string;\n kind: Exclude<\n LifeOpsEventKind,\n | \"calendar.event.ended\"\n | \"gmail.message.received\"\n | \"gmail.thread.needs_response\"\n >;\n occurredAt: string;\n confidence: number;\n payload: LifeOpsDerivedEventPayload;\n}\n\nfunction buildEvent(args: {\n kind: LifeOpsDerivedEvent[\"kind\"];\n occurredAt: string;\n confidence: number;\n current: LifeOpsScheduleMergedStateRecord;\n previous: LifeOpsScheduleMergedStateRecord | null;\n}): LifeOpsDerivedEvent {\n return {\n id: `${args.kind}:${args.current.agentId}:${args.occurredAt}`,\n kind: args.kind,\n occurredAt: args.occurredAt,\n confidence: args.confidence,\n payload: {\n currentStateId: args.current.id,\n previousStateId: args.previous?.id ?? null,\n sleepStatus: args.current.sleepStatus,\n circadianState: args.current.circadianState,\n stateConfidence: args.current.stateConfidence,\n uncertaintyReason: args.current.uncertaintyReason,\n regularityClass: args.current.regularity.regularityClass,\n wakeAt: args.current.wakeAt,\n bedtimeTargetAt: args.current.relativeTime.bedtimeTargetAt,\n minutesUntilBedtimeTarget:\n args.current.relativeTime.minutesUntilBedtimeTarget,\n },\n };\n}\n\nfunction isAsleepState(state: LifeOpsCircadianState): boolean {\n return state === \"sleeping\" || state === \"napping\";\n}\n\nfunction isAwakeState(state: LifeOpsCircadianState): boolean {\n return state === \"awake\" || state === \"waking\";\n}\n\n/**\n * Edge-triggered circadian event derivation per `sleep-wake-spec.md`:\n * - Events fire only on state transitions, never on stable ticks.\n * - `wake.observed` fires on sleeping/napping -> waking transitions.\n * - `wake.confirmed` fires on waking -> awake transitions (paired with sleep.ended).\n * - `sleep.onset_candidate` fires on awake/winding_down -> (onset) transitions\n * when the state machine starts the SLEEP_ONSET_WINDOW. For this interim\n * adapter we fire it alongside sleep.detected since the scorer rewrite\n * (scorer_rewrite todo) is where the dedicated onset candidate state lives.\n * - `sleep.detected` fires on any -> sleeping transition.\n * - `nap.detected` fires on any -> napping transition.\n * - `bedtime.imminent` fires once when minutesUntilBedtimeTarget crosses\n * from >30 to <=30 with a future target.\n * - `regularity.changed` fires on regularityClass transitions.\n */\nexport function deriveSleepWakeEvents(args: {\n previous: LifeOpsScheduleMergedStateRecord | null;\n current: LifeOpsScheduleMergedStateRecord;\n now: Date;\n}): LifeOpsDerivedEvent[] {\n const events: LifeOpsDerivedEvent[] = [];\n const current = args.current;\n const previous = args.previous;\n const currentState = current.circadianState;\n const previousState = previous?.circadianState ?? null;\n const stateChanged = previousState !== currentState;\n\n if (stateChanged) {\n // sleep.onset_candidate + sleep.detected on any -> sleeping edge\n if (currentState === \"sleeping\" && current.currentSleepStartedAt) {\n events.push(\n buildEvent({\n kind: \"lifeops.sleep.onset_candidate\",\n occurredAt: current.currentSleepStartedAt,\n confidence: current.sleepConfidence,\n current,\n previous,\n }),\n );\n events.push(\n buildEvent({\n kind: \"lifeops.sleep.detected\",\n occurredAt: current.currentSleepStartedAt,\n confidence: current.sleepConfidence,\n current,\n previous,\n }),\n );\n }\n\n // nap.detected on any -> napping edge\n if (currentState === \"napping\" && current.currentSleepStartedAt) {\n events.push(\n buildEvent({\n kind: \"lifeops.nap.detected\",\n occurredAt: current.currentSleepStartedAt,\n confidence: current.sleepConfidence,\n current,\n previous,\n }),\n );\n }\n\n // wake.observed on (sleeping|napping) -> waking edge\n if (\n currentState === \"waking\" &&\n previousState !== null &&\n isAsleepState(previousState) &&\n current.wakeAt\n ) {\n events.push(\n buildEvent({\n kind: \"lifeops.wake.observed\",\n occurredAt: current.wakeAt,\n confidence: current.awakeProbability.pAwake,\n current,\n previous,\n }),\n );\n }\n\n // wake.confirmed + sleep.ended on waking -> awake edge\n if (\n currentState === \"awake\" &&\n previousState === \"waking\" &&\n current.wakeAt\n ) {\n events.push(\n buildEvent({\n kind: \"lifeops.wake.confirmed\",\n occurredAt: current.wakeAt,\n confidence: current.awakeProbability.pAwake,\n current,\n previous,\n }),\n );\n if (current.lastSleepEndedAt) {\n events.push(\n buildEvent({\n kind: \"lifeops.sleep.ended\",\n occurredAt: current.lastSleepEndedAt,\n confidence: current.sleepConfidence,\n current,\n previous,\n }),\n );\n }\n }\n\n // Note: cold-boot (no previous state) does not synthesize any circadian\n // event. The first tick landed in its scored state because the\n // stability-window gate bypassed when priorState was null; the next\n // real transition will emit the appropriate edge-triggered event.\n }\n\n // bedtime.imminent — edge-triggered when minutesUntilBedtimeTarget crosses\n // into the [0, 30] window from above.\n const previousMinutesUntilBedtime =\n previous?.relativeTime.minutesUntilBedtimeTarget ?? null;\n const currentMinutesUntilBedtime =\n current.relativeTime.minutesUntilBedtimeTarget;\n const nowInBedtimeWindow =\n currentMinutesUntilBedtime !== null &&\n currentMinutesUntilBedtime >= 0 &&\n currentMinutesUntilBedtime <= 30;\n const wasOutsideWindow =\n previousMinutesUntilBedtime === null || previousMinutesUntilBedtime > 30;\n if (nowInBedtimeWindow && wasOutsideWindow && isAwakeState(currentState)) {\n const occurredAtMs = parseIsoMs(current.relativeTime.bedtimeTargetAt);\n events.push(\n buildEvent({\n kind: \"lifeops.bedtime.imminent\",\n occurredAt:\n occurredAtMs !== null\n ? new Date(occurredAtMs).toISOString()\n : args.now.toISOString(),\n confidence: current.relativeTime.confidence,\n current,\n previous,\n }),\n );\n }\n\n // regularity.changed — edge-triggered on regularityClass transitions.\n const previousClass = previous?.regularity.regularityClass ?? null;\n const currentClass = current.regularity.regularityClass;\n if (previousClass !== null && previousClass !== currentClass) {\n events.push(\n buildEvent({\n kind: \"lifeops.regularity.changed\",\n occurredAt: current.inferredAt,\n confidence: current.stateConfidence,\n current,\n previous,\n }),\n );\n }\n\n return events;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { LifeOpsActivitySignalSource } from "../contracts/health.js";
|
|
2
|
-
export type LifeOpsReliabilityKey = {
|
|
3
|
-
kind: "manual_override";
|
|
4
|
-
} | {
|
|
5
|
-
kind: "mobile_health";
|
|
6
|
-
permissionGranted: boolean;
|
|
7
|
-
} | {
|
|
8
|
-
kind: "desktop_power";
|
|
9
|
-
transition: "system" | "screen" | "session";
|
|
10
|
-
} | {
|
|
11
|
-
kind: "message_outbound";
|
|
12
|
-
channel: LifeOpsMessageReliabilityChannel;
|
|
13
|
-
} | {
|
|
14
|
-
kind: "message_inbound";
|
|
15
|
-
} | {
|
|
16
|
-
kind: "status_activity";
|
|
17
|
-
} | {
|
|
18
|
-
kind: "desktop_idle";
|
|
19
|
-
source: "iokit_hid" | "cgevent";
|
|
20
|
-
} | {
|
|
21
|
-
kind: "browser_focus";
|
|
22
|
-
} | {
|
|
23
|
-
kind: "device_presence";
|
|
24
|
-
transition: boolean;
|
|
25
|
-
} | {
|
|
26
|
-
kind: "mobile_device";
|
|
27
|
-
source: "capacitor" | "continuity_probe";
|
|
28
|
-
} | {
|
|
29
|
-
kind: "charging";
|
|
30
|
-
} | {
|
|
31
|
-
kind: "screen_time_summary";
|
|
32
|
-
} | {
|
|
33
|
-
kind: "prior_baseline";
|
|
34
|
-
};
|
|
35
|
-
export type LifeOpsMessageReliabilityChannel = "imessage" | "eliza_chat" | "gmail" | "x_dm" | "discord" | "telegram" | "signal" | "whatsapp" | "sms";
|
|
36
|
-
export declare function resolveSourceReliability(key: LifeOpsReliabilityKey): number;
|
|
37
|
-
export declare function resolveActivitySignalReliability(source: LifeOpsActivitySignalSource, platform: string): number;
|
|
38
|
-
//# sourceMappingURL=source-reliability.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"source-reliability.d.ts","sourceRoot":"","sources":["../../src/sleep/source-reliability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAE1E,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,gCAAgC,CAAA;CAAE,GACvE;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GACzB;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,kBAAkB,CAAA;CAAE,GACnE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,qBAAqB,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE/B,MAAM,MAAM,gCAAgC,GACxC,UAAU,GACV,YAAY,GACZ,OAAO,GACP,MAAM,GACN,SAAS,GACT,UAAU,GACV,QAAQ,GACR,UAAU,GACV,KAAK,CAAC;AAiBV,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAiC3E;AAqBD,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,2BAA2B,EACnC,QAAQ,EAAE,MAAM,GACf,MAAM,CAKR"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
const MESSAGE_CHANNEL_WEIGHTS = {
|
|
2
|
-
imessage: 0.88,
|
|
3
|
-
eliza_chat: 0.88,
|
|
4
|
-
gmail: 0.8,
|
|
5
|
-
x_dm: 0.8,
|
|
6
|
-
discord: 0.8,
|
|
7
|
-
telegram: 0.8,
|
|
8
|
-
signal: 0.8,
|
|
9
|
-
whatsapp: 0.8,
|
|
10
|
-
sms: 0.8
|
|
11
|
-
};
|
|
12
|
-
function resolveSourceReliability(key) {
|
|
13
|
-
switch (key.kind) {
|
|
14
|
-
case "manual_override":
|
|
15
|
-
return 1;
|
|
16
|
-
case "mobile_health":
|
|
17
|
-
return key.permissionGranted ? 0.95 : 0;
|
|
18
|
-
case "desktop_power":
|
|
19
|
-
return key.transition === "system" ? 0.92 : key.transition === "screen" ? 0.92 : 0.85;
|
|
20
|
-
case "message_outbound":
|
|
21
|
-
return MESSAGE_CHANNEL_WEIGHTS[key.channel];
|
|
22
|
-
case "message_inbound":
|
|
23
|
-
return 0.15;
|
|
24
|
-
case "status_activity":
|
|
25
|
-
return 0.6;
|
|
26
|
-
case "desktop_idle":
|
|
27
|
-
return key.source === "iokit_hid" ? 0.8 : 0.75;
|
|
28
|
-
case "browser_focus":
|
|
29
|
-
return 0.7;
|
|
30
|
-
case "device_presence":
|
|
31
|
-
return key.transition ? 0.7 : 0.3;
|
|
32
|
-
case "mobile_device":
|
|
33
|
-
return key.source === "capacitor" ? 0.7 : 0.5;
|
|
34
|
-
case "charging":
|
|
35
|
-
return 0.4;
|
|
36
|
-
case "screen_time_summary":
|
|
37
|
-
return 0.55;
|
|
38
|
-
case "prior_baseline":
|
|
39
|
-
return 0.4;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const SOURCE_RELIABILITY_KEYS = {
|
|
43
|
-
app_lifecycle: { kind: "device_presence", transition: true },
|
|
44
|
-
page_visibility: { kind: "device_presence", transition: true },
|
|
45
|
-
desktop_power: { kind: "desktop_power", transition: "system" },
|
|
46
|
-
desktop_interaction: { kind: "desktop_idle", source: "iokit_hid" },
|
|
47
|
-
connector_activity: { kind: "message_outbound", channel: "gmail" },
|
|
48
|
-
imessage_outbound: { kind: "message_outbound", channel: "imessage" },
|
|
49
|
-
mobile_device: { kind: "mobile_device", source: "capacitor" },
|
|
50
|
-
mobile_health: { kind: "mobile_health", permissionGranted: true }
|
|
51
|
-
};
|
|
52
|
-
function resolveActivitySignalReliability(source, platform) {
|
|
53
|
-
if (source === "app_lifecycle" && platform === "manual_override") {
|
|
54
|
-
return resolveSourceReliability({ kind: "manual_override" });
|
|
55
|
-
}
|
|
56
|
-
return resolveSourceReliability(SOURCE_RELIABILITY_KEYS[source]);
|
|
57
|
-
}
|
|
58
|
-
export {
|
|
59
|
-
resolveActivitySignalReliability,
|
|
60
|
-
resolveSourceReliability
|
|
61
|
-
};
|
|
62
|
-
//# sourceMappingURL=source-reliability.js.map
|