Skip to content

Commit 84256d1

Browse files
committed
Auto-retrain classifier model when stale or missing
- Retrain in background if model >24 hours old or never trained - Only retrain if user has sufficient sleep data (10+ stages) - Non-blocking: session starts immediately with current/default model - Logs training results (accuracy, REM sensitivity, nights analyzed)
1 parent fde557c commit 84256d1

1 file changed

Lines changed: 53 additions & 1 deletion

File tree

services/sleep.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
stopNativeAudioCapture,
1818
isNativeAudioAvailable,
1919
} from './nativeAudio';
20+
import { loadRemOptimizedModel, trainRemOptimizedModel } from './remOptimizedClassifier';
21+
import * as healthConnect from './healthConnect';
2022

2123
export type SleepTrackingSource = 'audio' | 'wearable' | 'manual';
2224

@@ -127,16 +129,66 @@ function generateId(): string {
127129
return `sleep_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
128130
}
129131

132+
const MODEL_STALE_HOURS = 24;
133+
134+
async function shouldRetrainModel(): Promise<boolean> {
135+
const model = await loadRemOptimizedModel();
136+
if (!model) return true;
137+
138+
const lastUpdated = new Date(model.lastUpdated);
139+
const hoursSinceUpdate = (Date.now() - lastUpdated.getTime()) / (1000 * 60 * 60);
140+
return hoursSinceUpdate >= MODEL_STALE_HOURS;
141+
}
142+
143+
async function hasEnoughSleepData(): Promise<boolean> {
144+
if (Platform.OS !== 'android' && Platform.OS !== 'ios') return false;
145+
146+
try {
147+
const sleepStages = await healthConnect.getRecentSleepSessions(48);
148+
return sleepStages.length >= 10;
149+
} catch {
150+
return false;
151+
}
152+
}
153+
154+
async function maybeRetrainInBackground(): Promise<void> {
155+
const needsRetrain = await shouldRetrainModel();
156+
if (!needsRetrain) {
157+
console.log('[Sleep] Model is fresh, skipping retrain');
158+
return;
159+
}
160+
161+
const hasData = await hasEnoughSleepData();
162+
if (!hasData) {
163+
console.log('[Sleep] Insufficient sleep data for training, using defaults');
164+
return;
165+
}
166+
167+
console.log('[Sleep] Starting background model training...');
168+
try {
169+
const { model, report } = await trainRemOptimizedModel(87600);
170+
console.log('[Sleep] Model trained:', {
171+
accuracy: report.validationResults?.overallAccuracy,
172+
remSens: report.validationResults?.remSensitivity,
173+
nights: model.nightsAnalyzed,
174+
});
175+
} catch (err) {
176+
console.error('[Sleep] Background training failed:', err);
177+
}
178+
}
179+
130180
export async function startSleepSession(
131181
source: SleepTrackingSource = 'manual'
132182
): Promise<SleepSession> {
133183
if (currentSession?.isActive) {
134184
return currentSession;
135185
}
136186

137-
learnFromRecentNights(87600).catch(console.error); // ~10 years - get all available data
187+
learnFromRecentNights(87600).catch(console.error);
138188
await startHybridSession();
139189

190+
maybeRetrainInBackground().catch(console.error);
191+
140192
const session: SleepSession = {
141193
id: generateId(),
142194
startTime: new Date().toISOString(),

0 commit comments

Comments
 (0)