@@ -29,12 +29,12 @@ import {
2929 formatHealthConnectDebugReport ,
3030} from '@/services/healthConnect' ;
3131import {
32- trainAndValidate ,
33- loadEnhancedModel ,
34- clearEnhancedModel ,
35- type EnhancedModel ,
36- type ValidationResult ,
37- } from '@/services/enhancedSleepClassifier ' ;
32+ trainRemOptimizedModel ,
33+ loadRemOptimizedModel ,
34+ clearRemOptimizedModel ,
35+ formatTrainingReport ,
36+ type TrainingReport ,
37+ } from '@/services/remOptimizedClassifier ' ;
3838
3939export default function SettingsScreen ( ) {
4040 const [ showSleepDebug , setShowSleepDebug ] = useState ( false ) ;
@@ -47,9 +47,9 @@ export default function SettingsScreen() {
4747 const [ showHCDebug , setShowHCDebug ] = useState ( false ) ;
4848 const [ hcDebugReport , setHcDebugReport ] = useState < string | null > ( null ) ;
4949 const [ isLoadingHCDebug , setIsLoadingHCDebug ] = useState ( false ) ;
50- const [ showEnhancedTraining , setShowEnhancedTraining ] = useState ( false ) ;
51- const [ enhancedModelStatus , setEnhancedModelStatus ] = useState < string | null > ( null ) ;
52- const [ isTrainingEnhanced , setIsTrainingEnhanced ] = useState ( false ) ;
50+ const [ showRemOptimized , setShowRemOptimized ] = useState ( false ) ;
51+ const [ remOptimizedReport , setRemOptimizedReport ] = useState < string | null > ( null ) ;
52+ const [ isTrainingRemOptimized , setIsTrainingRemOptimized ] = useState ( false ) ;
5353
5454 const health = useHealth ( ) ;
5555
@@ -95,80 +95,47 @@ export default function SettingsScreen() {
9595 }
9696 } ;
9797
98- const formatEnhancedModelStatus = (
99- model : EnhancedModel | null ,
100- validation ?: ValidationResult
101- ) => {
102- if ( ! model ) return 'No enhanced model trained yet.' ;
103-
104- let status = `ENHANCED MODEL STATUS\n` ;
105- status += `━━━━━━━━━━━━━━━━━━━━━\n` ;
106- status += `Nights analyzed: ${ model . nightsAnalyzed } \n` ;
107- status += `Last updated: ${ new Date ( model . lastUpdated ) . toLocaleString ( ) } \n` ;
108- status += `Temporal smoothing: ${ model . temporalSmoothingStrength . toFixed ( 2 ) } \n\n` ;
109-
110- status += `FEATURE WEIGHTS\n` ;
111- status += `HR: ${ model . featureWeights . hr . toFixed ( 2 ) } , HRV: ${ model . featureWeights . hrv . toFixed ( 2 ) } \n` ;
112- status += `HRV Est: ${ model . featureWeights . hrvEst . toFixed ( 2 ) } , RR: ${ model . featureWeights . rr . toFixed ( 2 ) } \n\n` ;
113-
114- if ( model . validationAccuracy !== null ) {
115- status += `VALIDATION ACCURACY\n` ;
116- status += `━━━━━━━━━━━━━━━━━━━━━\n` ;
117- status += `Overall: ${ ( model . validationAccuracy * 100 ) . toFixed ( 1 ) } %\n\n` ;
118- status += `Per-Stage:\n` ;
119- status += ` Awake: ${ ( model . perStageAccuracy . awake * 100 ) . toFixed ( 1 ) } %\n` ;
120- status += ` Light: ${ ( model . perStageAccuracy . light * 100 ) . toFixed ( 1 ) } %\n` ;
121- status += ` Deep: ${ ( model . perStageAccuracy . deep * 100 ) . toFixed ( 1 ) } %\n` ;
122- status += ` REM: ${ ( model . perStageAccuracy . rem * 100 ) . toFixed ( 1 ) } %\n` ;
123- }
124-
125- if ( validation ) {
126- status += `\nCONFUSION MATRIX (rows=actual, cols=predicted)\n` ;
127- status += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n` ;
128- const stages : Array < 'awake' | 'light' | 'deep' | 'rem' > = [ 'awake' , 'light' , 'deep' , 'rem' ] ;
129- status += ` awake light deep rem\n` ;
130- for ( const actual of stages ) {
131- status += `${ actual . padEnd ( 6 ) } ` ;
132- for ( const predicted of stages ) {
133- status += `${ validation . confusionMatrix [ actual ] [ predicted ] . toString ( ) . padStart ( 6 ) } ` ;
134- }
135- status += '\n' ;
136- }
137- status += `\nTotal samples: ${ validation . totalSamples } \n` ;
98+ const handleTrainRemOptimized = async ( ) => {
99+ setIsTrainingRemOptimized ( true ) ;
100+ setRemOptimizedReport ( 'Starting REM-optimized (3-class) training...' ) ;
101+ try {
102+ const { model, report } = await trainRemOptimizedModel ( 48 ) ;
103+ setRemOptimizedReport ( formatTrainingReport ( report ) ) ;
104+ } catch ( error ) {
105+ setRemOptimizedReport ( `Training failed: ${ error } ` ) ;
106+ } finally {
107+ setIsTrainingRemOptimized ( false ) ;
138108 }
139-
140- return status ;
141109 } ;
142110
143- const handleLoadEnhancedModel = async ( ) => {
144- const model = await loadEnhancedModel ( ) ;
145- setEnhancedModelStatus ( formatEnhancedModelStatus ( model ) ) ;
146- } ;
111+ const handleLoadRemOptimized = async ( ) => {
112+ const model = await loadRemOptimizedModel ( ) ;
113+ if ( model ) {
114+ let status = '╔══════════════════════════════════════════╗\n' ;
115+ status += '║ REM-OPTIMIZED MODEL STATUS (3-CLASS) ║\n' ;
116+ status += '╚══════════════════════════════════════════╝\n\n' ;
117+ status += `Nights analyzed: ${ model . nightsAnalyzed } \n` ;
118+ status += `Last updated: ${ new Date ( model . lastUpdated ) . toLocaleString ( ) } \n\n` ;
119+
120+ if ( model . validationAccuracy !== null ) {
121+ status += `Overall Accuracy: ${ ( model . validationAccuracy * 100 ) . toFixed ( 1 ) } %\n` ;
122+ status += `REM Sensitivity: ${ ( ( model . remSensitivity ?? 0 ) * 100 ) . toFixed ( 1 ) } %\n` ;
123+ status += `REM Specificity: ${ ( ( model . remSpecificity ?? 0 ) * 100 ) . toFixed ( 1 ) } %\n\n` ;
124+ status += `Per-Stage Accuracy:\n` ;
125+ status += ` Awake: ${ ( model . perStageAccuracy . awake * 100 ) . toFixed ( 1 ) } %\n` ;
126+ status += ` NREM: ${ ( model . perStageAccuracy . nrem * 100 ) . toFixed ( 1 ) } %\n` ;
127+ status += ` REM: ${ ( model . perStageAccuracy . rem * 100 ) . toFixed ( 1 ) } %\n` ;
128+ }
147129
148- const handleTrainEnhanced = async ( ) => {
149- setIsTrainingEnhanced ( true ) ;
150- setEnhancedModelStatus ( 'Starting training...' ) ;
151- try {
152- const { model, validation, bestParams } = await trainAndValidate ( ( message ) => {
153- setEnhancedModelStatus ( message ) ;
154- } ) ;
155- let status = formatEnhancedModelStatus ( model , validation ) ;
156- status += `\nBEST PARAMETERS FOUND\n` ;
157- status += `━━━━━━━━━━━━━━━━━━━━━\n` ;
158- status += `Temporal smoothing: ${ bestParams . temporalSmoothing } \n` ;
159- status += `HR weight: ${ bestParams . hrWeight } \n` ;
160- status += `HRV weight: ${ bestParams . hrvWeight } \n` ;
161- setEnhancedModelStatus ( status ) ;
162- } catch ( error ) {
163- setEnhancedModelStatus ( `Training failed: ${ error } ` ) ;
164- } finally {
165- setIsTrainingEnhanced ( false ) ;
130+ setRemOptimizedReport ( status ) ;
131+ } else {
132+ setRemOptimizedReport ( 'No REM-optimized model trained yet.\nTap "Train 3-Class" to begin.' ) ;
166133 }
167134 } ;
168135
169- const handleClearEnhanced = async ( ) => {
170- await clearEnhancedModel ( ) ;
171- setEnhancedModelStatus ( 'Enhanced model cleared.') ;
136+ const handleClearRemOptimized = async ( ) => {
137+ await clearRemOptimizedModel ( ) ;
138+ setRemOptimizedReport ( 'REM-optimized model cleared.') ;
172139 } ;
173140
174141 useEffect ( ( ) => {
@@ -440,58 +407,58 @@ export default function SettingsScreen() {
440407 { ( Platform . OS === 'android' || Platform . OS === 'ios' ) && (
441408 < >
442409 < MenuRow
443- icon = "fitness -outline"
444- label = "Sleep Stage Classifier Training "
410+ icon = "moon -outline"
411+ label = "Sleep Stage Classifier"
445412 onPress = { ( ) => {
446- setShowEnhancedTraining ( ! showEnhancedTraining ) ;
447- if ( ! showEnhancedTraining && ! enhancedModelStatus ) {
448- handleLoadEnhancedModel ( ) ;
413+ setShowRemOptimized ( ! showRemOptimized ) ;
414+ if ( ! showRemOptimized && ! remOptimizedReport ) {
415+ handleLoadRemOptimized ( ) ;
449416 }
450417 } }
451418 />
452- { showEnhancedTraining && (
419+ { showRemOptimized && (
453420 < View style = { styles . expandedSection } >
454421 < Text variant = "caption" color = "muted" style = { { marginBottom : spacing . sm } } >
455- Learn your personal sleep patterns from your wearable data to improve REM
456- detection accuracy .
422+ Train a personalized REM detector using your wearable sleep data. Uses 90-minute
423+ ultradian cycles and HR variability patterns to predict REM windows .
457424 </ Text >
458425 < View style = { styles . debugButtonRow } >
459426 < Pressable
460427 style = { styles . debugButton }
461- onPress = { handleLoadEnhancedModel }
462- disabled = { isTrainingEnhanced }
428+ onPress = { handleLoadRemOptimized }
429+ disabled = { isTrainingRemOptimized }
463430 >
464431 < Ionicons name = "information-circle" size = { 16 } color = { colors . primary [ 500 ] } />
465432 < Text variant = "caption" color = "primary" >
466- Check Status
433+ Status
467434 </ Text >
468435 </ Pressable >
469436 < Pressable
470437 style = { [ styles . debugButton , styles . trainButton ] }
471- onPress = { handleTrainEnhanced }
472- disabled = { isTrainingEnhanced }
438+ onPress = { handleTrainRemOptimized }
439+ disabled = { isTrainingRemOptimized }
473440 >
474- { isTrainingEnhanced ? (
441+ { isTrainingRemOptimized ? (
475442 < ActivityIndicator size = "small" color = { colors . gray [ 950 ] } />
476443 ) : (
477444 < Ionicons name = "flash" size = { 16 } color = { colors . gray [ 950 ] } />
478445 ) }
479446 < Text variant = "caption" style = { { color : colors . gray [ 950 ] } } >
480- Train Model
447+ Train
481448 </ Text >
482449 </ Pressable >
483450 < Pressable
484451 style = { styles . debugButton }
485- onPress = { handleClearEnhanced }
486- disabled = { isTrainingEnhanced }
452+ onPress = { handleClearRemOptimized }
453+ disabled = { isTrainingRemOptimized }
487454 >
488455 < Ionicons name = "trash-outline" size = { 16 } color = { colors . error } />
489456 < Text variant = "caption" color = "primary" >
490457 Clear
491458 </ Text >
492459 </ Pressable >
493460 </ View >
494- { enhancedModelStatus && (
461+ { remOptimizedReport && (
495462 < ScrollView
496463 style = { styles . debugOutput }
497464 horizontal = { false }
@@ -503,7 +470,7 @@ export default function SettingsScreen() {
503470 style = { styles . debugOutputText }
504471 selectable = { true }
505472 >
506- { enhancedModelStatus }
473+ { remOptimizedReport }
507474 </ Text >
508475 </ ScrollView >
509476 ) }
0 commit comments