본문으로 건너뛰기

dha-sleep-app-web 수면 오디오 분석 기능 설계

버전: 1.0.0 작성일: 2026-01-20 상태: Draft 대상 앱: apps/dha-sleep-app-web

목차

  1. 개요
  2. 시스템 아키텍처
  3. 데이터 수집 파이프라인
  4. ML 모델 개발
  5. 앱 구현 상세
  6. 서버 API
  7. 개발 로드맵
  8. 기술 스택
  9. 보안 및 프라이버시

1. 개요

1.1 기능 목적

dha-sleep-app-web에 스마트폰 마이크 기반 수면 오디오 분석 기능을 통합합니다.

1.2 핵심 전략: 단일 앱 통합 (Option B)

별도 앱을 만들지 않고, 기존 dha-sleep-app-web에 모든 기능을 통합합니다.

┌─────────────────────────────────────────────────────────────┐
│ dha-sleep-app-web (단일 앱) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 웨어러블 착용 사용자 │ │
│ │ • 웨어러블 수면 단계 사용 (정확) │ │
│ │ • 오디오 녹음 → 학습 데이터 수집 │ │
│ │ • 코골이/무호흡 감지 (마이크) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 웨어러블 미착용 사용자 │ │
│ │ • TFLite 모델로 수면 단계 추정 │ │
│ │ • 코골이/무호흡 감지 (마이크) │ │
│ │ • "추정치" 명시 │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

1.3 핵심 기능

기능데이터 소스정확도우선순위
코골이 감지마이크높음 (90%+)P0
무호흡 의심 감지마이크중간 (80%+)P0
수면 단계 (웨어러블)HealthKit/Health Connect높음 (85%)P0
수면 단계 (ML 추정)마이크 + TFLite중간 (70%)P1
수면 품질 리포트복합 데이터-P1

1.4 주요 제약사항

  • 배터리: 밤새 녹음 시 배터리 소모 30% 이하
  • 저장공간: 오디오 원본 저장 최소화 (스펙트로그램만 저장)
  • 프라이버시: GDPR 준수, 오디오 원본 서버 전송 금지
  • 오프라인: 기본 분석은 오프라인에서도 동작

2. 시스템 아키텍처

2.1 전체 시스템 구성도

┌─────────────────────────────────────────────────────────────────────────┐
│ 사용자 디바이스 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────────────────────────────────────┐ │
│ │ Apple Watch │ │ dha-sleep-app-web │ │
│ │ Galaxy Watch│ │ ┌─────────────────────────────────────┐ │ │
│ │ │────▶│ │ Capacitor Native Layer │ │ │
│ │ 심박/움직임 │ │ │ ┌───────────┐ ┌───────────────┐ │ │ │
│ │ 산소포화도 │ │ │ │ Audio │ │ TFLite │ │ │ │
│ └─────────────┘ │ │ │ Recorder │ │ Interpreter │ │ │ │
│ │ │ │ │ Plugin │ │ Plugin │ │ │ │
│ │ │ │ └───────────┘ └───────────────┘ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────────┴───────────────────┐ │ │
│ │ │ │ React Application │ │ │
│ │ │ │ ┌─────────┐ ┌─────────────────┐ │ │ │
│ │ │ │ │ Sleep │ │ Sleep Stage │ │ │ │
│ │ │ │ │ Analyzer│ │ Visualizer │ │ │ │
│ │ │ │ │ Service │ │ Component │ │ │ │
│ │ │ │ └─────────┘ └─────────────────┘ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │
│ ▼ └─────────────────────────────────────────────┘ │
│ ┌─────────────┐ │ │
│ │ HealthKit / │ │ │
│ │ Health │◀────────────────────────┘ │
│ │ Connect │ 수면 단계 조회 │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

│ 학습 데이터 (동의 시)

┌─────────────────────────────────────────────────────────────────────────┐
│ GCP Backend │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Cloud Run │ │ Cloud │ │ Cloud Storage │ │
│ │ dta-wide-api│ │ Firestore │ │ ML Training Data │ │
│ │ │────▶│ 수면 분석 │ │ (익명화된 스펙트로그램) │ │
│ │ │ │ 결과 저장 │ └─────────────────────────┘ │
│ └─────────────┘ └─────────────┘ │ │
│ │ │
│ ┌─────────────────────────────────────────────────┐│ │
│ │ Vertex AI / Cloud Functions ││ │
│ │ ┌─────────────────────────────────────────┐ ││ │
│ │ │ ML Training Pipeline │◀──┘│ │
│ │ │ • 데이터 전처리 │ │ │
│ │ │ • 모델 학습 (TensorFlow) │ │ │
│ │ │ • TFLite 변환 │ │ │
│ │ │ • 모델 버전 관리 │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

2.2 앱 디렉토리 구조 (추가되는 파일들)

apps/dha-sleep-app-web/
├── lib/
│ ├── native/
│ │ ├── audio-recorder.ts # 오디오 녹음 추상화
│ │ ├── sleep-audio-analyzer.ts # 수면 분석 서비스 추상화
│ │ └── tflite-interpreter.ts # TFLite 모델 실행
│ │
│ ├── ml/
│ │ ├── feature-extractor.ts # 오디오 특징 추출
│ │ ├── spectrogram-generator.ts # 스펙트로그램 생성
│ │ ├── sleep-stage-classifier.ts # 수면 단계 분류
│ │ └── training-data-collector.ts # 학습 데이터 수집
│ │
│ └── sleep-analysis/
│ ├── analyzer-factory.ts # 분석기 팩토리
│ ├── wearable-analyzer.ts # 웨어러블 기반 분석
│ ├── ml-analyzer.ts # ML 기반 분석
│ └── snoring-detector.ts # 코골이 감지

├── hooks/
│ ├── use-sleep-recorder.ts # 수면 녹음 훅
│ ├── use-sleep-analysis.ts # 수면 분석 훅
│ └── use-wearable-sync.ts # 웨어러블 동기화 훅

├── components/
│ └── sleep-analysis/
│ ├── sleep-recorder-control.tsx # 녹음 시작/종료 UI
│ ├── realtime-monitor.tsx # 실시간 모니터링
│ ├── hypnogram-chart.tsx # 수면 단계 차트
│ ├── snoring-events.tsx # 코골이 이벤트 목록
│ ├── sleep-report.tsx # 종합 리포트
│ └── ml-data-consent-sheet.tsx # ML 데이터 동의 시트

├── ios/
│ └── App/
│ └── Plugins/
│ ├── SleepAudioRecorder/ # iOS 오디오 녹음 플러그인
│ │ ├── SleepAudioRecorderPlugin.swift
│ │ ├── AudioRecorder.swift
│ │ └── AudioAnalyzer.swift
│ └── TFLiteInterpreter/ # iOS TFLite 플러그인
│ └── TFLiteInterpreterPlugin.swift

├── android/
│ └── app/src/main/java/com/welt/dha/sleep/plugins/
│ ├── sleepaudio/
│ │ ├── SleepAudioRecorderPlugin.kt
│ │ ├── AudioRecorderService.kt # Foreground Service
│ │ └── AudioAnalyzer.kt
│ └── tflite/
│ └── TFLiteInterpreterPlugin.kt

└── assets/
└── models/
├── snoring_detector.tflite # 코골이 감지 모델
└── sleep_stage_classifier.tflite # 수면 단계 분류 모델

2.3 데이터 흐름

[취침 시작]


┌─────────────────────────────────────────┐
│ 오디오 녹음 시작 │
│ • Foreground Service (Android) │
│ • Background Mode (iOS) │
│ • 샘플링: 16kHz, Mono │
└─────────────────────────────────────────┘

▼ 30초마다
┌─────────────────────────────────────────┐
│ 실시간 분석 (온디바이스) │
│ 1. 오디오 → 스펙트로그램 변환 │
│ 2. 코골이 감지 모델 실행 (TFLite) │
│ 3. 이벤트 발생 시 앱에 알림 │
└─────────────────────────────────────────┘


[기상]

├─────────────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│웨어러블 있음 │ │웨어러블 없음 │
│ │ │ │
│HealthKit에서│ │저장된 스펙트 │
│수면단계 조회 │ │로그램+TFLite│
│ │ │로 수면 추정 │
└──────┬──────┘ └──────┬──────┘
│ │
└────────┬─────────┘

┌─────────────────────────────────────────┐
│ 수면 분석 결과 생성 │
│ • 수면 단계 타임라인 (Hypnogram) │
│ • 코골이/무호흡 이벤트 목록 │
│ • 수면 품질 점수 │
└─────────────────────────────────────────┘

3. 데이터 수집 파이프라인

3.1 학습 데이터 스키마

// types/ml-training-data.ts

interface TrainingDataSample {
sampleId: string; // UUID
anonymousUserId: string; // SHA-256 해시된 사용자 ID
sessionDate: string; // ISO date (시간 제외, 프라이버시)
epochIndex: number; // 30초 단위 인덱스

// 특징 데이터 (오디오 원본 대신)
spectrogram: number[][]; // Mel spectrogram (64 x 128)
features: {
mfcc: number[]; // MFCC 13개
spectralCentroid: number;
spectralBandwidth: number;
zeroCrossingRate: number;
rmsEnergy: number;
};

// 라벨 (웨어러블에서 획득)
label: {
sleepStage: 'awake' | 'light' | 'deep' | 'rem';
confidence: number; // 0-1
source: 'apple_watch' | 'galaxy_watch' | 'fitbit';
};

// 품질 지표
quality: {
audioQuality: number; // 0-1
labelConfidence: number; // 0-1
isTransitionPeriod: boolean; // 수면 단계 전환 구간 여부
};
}

interface CollectionSession {
sessionId: string;
anonymousUserId: string;
date: string;
totalEpochs: number;
validEpochs: number;

deviceInfo: {
platform: 'ios' | 'android';
osVersion: string;
appVersion: string;
wearableModel: string;
};

sleepSummary: {
totalDuration: number;
sleepEfficiency: number;
stageDistribution: {
awake: number;
light: number;
deep: number;
rem: number;
};
};
}

3.2 데이터 수집 흐름

[앱 - 수면 중]
1. 오디오 녹음 (30초 버퍼)
2. 스펙트로그램 변환
3. 특징 추출 (MFCC 등)
4. 임시 저장 (기기 내 SQLite)


[앱 - 기상 후]
5. HealthKit/Health Connect에서 수면 단계 조회
6. 오디오 epoch와 수면 단계 시간 동기화
7. 라벨 매핑 (각 epoch에 수면 단계 할당)
8. 품질 필터링 (신뢰도 낮은 데이터 제외)


[동의 확인]
9. 사용자 동의 확인 (ML 학습 데이터 제공)
10. 동의 시 → 익명화 처리
• userId → SHA-256 해시
• 정확한 시간 제거 (날짜만 유지)
• 기기 정보 일반화


[서버]
11. Cloud Run API로 데이터 수신
12. 2차 품질 검증
13. Cloud Storage에 저장
14. 주기적 데이터셋 빌드 (TFRecord 형식)

3.3 데이터 품질 필터링 규칙

// lib/ml/quality-filter.ts

const qualityFilters = {
// 라벨 신뢰도
minLabelConfidence: 0.7, // 70% 이상

// 오디오 품질
minAudioQuality: 0.5, // 50% 이상
maxSilenceRatio: 0.95, // 95% 이상 무음이면 제외
minSnr: 10, // 최소 SNR 10dB

// 수면 단계 전환 구간 제외
excludeTransitionPeriods: true,
transitionBufferSeconds: 30, // 전환 전후 30초 제외

// 수면 단계 최소 지속 시간
minStageDuration: 60, // 1분 미만 수면 단계 제외

// 세션 품질
minSessionDuration: 180, // 최소 3시간 수면
minValidEpochRatio: 0.5, // 50% 이상 유효 데이터
};

3.4 예상 데이터 규모

Phase사용자기간예상 Epochs저장 용량
MVP50명30일630,000~630MB
Beta500명90일18.9M~19GB
Production5,000명180일378M~378GB

4. ML 모델 개발

4.1 코골이 감지 모델 (P0)

┌─────────────────────────────────────────────────────────────┐
│ 코골이 감지 모델 아키텍처 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Input: Mel Spectrogram (64 x 64 x 1) - 2초 오디오 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DepthwiseSeparableConv2D(32) + BatchNorm + ReLU │ │
│ │ MaxPool(2,2) → 32 x 32 x 32 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DepthwiseSeparableConv2D(64) + BatchNorm + ReLU │ │
│ │ MaxPool(2,2) → 16 x 16 x 64 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DepthwiseSeparableConv2D(128) + BatchNorm + ReLU │ │
│ │ GlobalAveragePooling → 128 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Dense(64, ReLU) → Dropout(0.5) → Dense(1, Sigmoid) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Output: Snoring Probability (0-1) │
│ │
│ • 모델 크기: ~200KB (INT8 양자화 후) │
│ • 추론 시간: ~5ms (모바일) │
│ • 목표 정확도: 90%+ │
│ │
└─────────────────────────────────────────────────────────────┘

4.2 수면 단계 분류 모델 (P1)

┌─────────────────────────────────────────────────────────────┐
│ 수면 단계 분류 모델 아키텍처 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Input: Mel Spectrogram (64 x 128 x 1) - 30초 오디오 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CNN Feature Extractor │ │
│ │ Conv2D(32) → Conv2D(64) → Conv2D(128) │ │
│ │ 각 레이어: BatchNorm + ReLU + MaxPool │ │
│ │ Output: 8 x 32 x 128 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Reshape: (8, 32, 128) → (32, 1024) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Bidirectional LSTM(64) → Bidirectional LSTM(32) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Dense(64, ReLU) → Dropout(0.5) → Dense(4, Softmax) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Output: [P(awake), P(light), P(deep), P(rem)] │
│ │
│ • 모델 크기: ~1.5MB (INT8 양자화 후) │
│ • 추론 시간: ~50ms (모바일) │
│ • 목표 정확도: 70%+ │
│ │
└─────────────────────────────────────────────────────────────┘

4.3 모델 평가 기준

모델지표목표
코골이 감지Accuracy≥ 90%
Precision≥ 85%
Recall≥ 90%
F1 Score≥ 87%
수면 단계Overall Accuracy≥ 70%
Cohen's Kappa≥ 0.55
Sleep/Wake 이진≥ 85%

4.4 모델 배포 전략

Phase 1: 앱 번들 포함 (권장 - 초기)

  • 모델을 앱 바이너리에 포함
  • 오프라인 동작 보장
  • 앱 업데이트로 모델 갱신

Phase 2: 동적 다운로드 (향후)

  • Firebase ML Model Hosting 사용
  • 앱 업데이트 없이 모델 갱신

5. 앱 구현 상세

5.1 Capacitor 플러그인 인터페이스

// types/capacitor-sleep-audio.d.ts

declare module '@anthropic/capacitor-sleep-audio' {

export interface SleepAudioPlugin {
// 권한
checkPermissions(): Promise<PermissionStatus>;
requestPermissions(): Promise<PermissionStatus>;

// 녹음 제어
startRecording(options: RecordingOptions): Promise<void>;
stopRecording(): Promise<RecordingResult>;
isRecording(): Promise<{ recording: boolean }>;

// TFLite 모델
loadModel(options: ModelOptions): Promise<void>;
runInference(options: InferenceOptions): Promise<InferenceResult>;
unloadModel(): Promise<void>;

// 이벤트 리스너
addListener(
eventName: 'audioLevel',
callback: (data: AudioLevelEvent) => void
): Promise<PluginListenerHandle>;

addListener(
eventName: 'snoringDetected',
callback: (data: SnoringEvent) => void
): Promise<PluginListenerHandle>;

addListener(
eventName: 'analysisUpdate',
callback: (data: AnalysisUpdate) => void
): Promise<PluginListenerHandle>;

removeAllListeners(): Promise<void>;
}

export interface RecordingOptions {
sampleRate?: number; // default: 16000
channels?: number; // default: 1 (mono)
enableRealTimeAnalysis?: boolean;
analysisIntervalMs?: number; // default: 2000
lowPowerMode?: boolean;
}

export interface RecordingResult {
sessionId: string;
startTime: string;
endTime: string;
durationMs: number;
epochCount: number;
spectrograms: SpectrogramData[];
events: AudioEvent[];
}

export interface SnoringEvent {
timestamp: string;
durationMs: number;
intensity: number; // 0-1
confidence: number;
}
}

5.2 React Hook: 수면 녹음

// hooks/use-sleep-recorder.ts

import { useState, useCallback, useEffect, useRef } from 'react';
import { Capacitor } from '@capacitor/core';
import type { SleepAudioPlugin, RecordingOptions, RecordingResult, SnoringEvent } from '@anthropic/capacitor-sleep-audio';

export function useSleepRecorder() {
const [isRecording, setIsRecording] = useState(false);
const [audioLevel, setAudioLevel] = useState(-60);
const [snoringEvents, setSnoringEvents] = useState<SnoringEvent[]>([]);
const [recordingDuration, setRecordingDuration] = useState(0);
const [hasPermissions, setHasPermissions] = useState(false);

const pluginRef = useRef<SleepAudioPlugin | null>(null);

useEffect(() => {
if (Capacitor.isNativePlatform()) {
pluginRef.current = Capacitor.Plugins.SleepAudioRecorder as SleepAudioPlugin;
checkPermissions();
}
}, []);

const checkPermissions = async () => {
if (!pluginRef.current) return;
const status = await pluginRef.current.checkPermissions();
setHasPermissions(status.microphone === 'granted');
};

const requestPermissions = useCallback(async (): Promise<boolean> => {
if (!pluginRef.current) return false;
const status = await pluginRef.current.requestPermissions();
const granted = status.microphone === 'granted';
setHasPermissions(granted);
return granted;
}, []);

const startRecording = useCallback(async (options?: Partial<RecordingOptions>) => {
if (!pluginRef.current || isRecording) return;

// 리스너 설정
await pluginRef.current.addListener('audioLevel', (data) => {
setAudioLevel(data.level);
});

await pluginRef.current.addListener('snoringDetected', (event) => {
setSnoringEvents(prev => [...prev, event]);
});

// 녹음 시작
await pluginRef.current.startRecording({
sampleRate: 16000,
channels: 1,
enableRealTimeAnalysis: true,
analysisIntervalMs: 2000,
lowPowerMode: true,
...options
});

setIsRecording(true);
setSnoringEvents([]);
}, [isRecording]);

const stopRecording = useCallback(async (): Promise<RecordingResult | null> => {
if (!pluginRef.current || !isRecording) return null;

await pluginRef.current.removeAllListeners();
const result = await pluginRef.current.stopRecording();
setIsRecording(false);

return result;
}, [isRecording]);

return {
isRecording,
audioLevel,
snoringEvents,
recordingDuration,
startRecording,
stopRecording,
requestPermissions,
hasPermissions
};
}

5.3 분석기 팩토리 패턴

// lib/sleep-analysis/analyzer-factory.ts

export interface SleepAnalyzer {
analyze(recordingResult: RecordingResult): Promise<SleepAnalysisResult>;
getSource(): 'wearable' | 'ml_model';
}

// 웨어러블 있는 경우: 정확한 분석 + 학습 데이터 수집
class WearableSleepAnalyzer implements SleepAnalyzer {
async analyze(recordingResult: RecordingResult): Promise<SleepAnalysisResult> {
// 1. 웨어러블에서 수면 단계 가져오기 (정확)
const stages = await getWearableSleepStages(
new Date(recordingResult.startTime),
new Date(recordingResult.endTime)
);

// 2. 학습 데이터 수집 (동의 시)
if (await hasMLDataConsent()) {
await collectTrainingData(recordingResult, stages);
}

return {
stages,
source: 'wearable',
confidence: 0.85
};
}

getSource() { return 'wearable' as const; }
}

// 웨어러블 없는 경우: ML 모델로 추정
class MLSleepAnalyzer implements SleepAnalyzer {
private model: TFLiteModel;

async analyze(recordingResult: RecordingResult): Promise<SleepAnalysisResult> {
// TFLite 모델로 수면 단계 추정
const stages = await this.estimateSleepStages(recordingResult.spectrograms);

return {
stages,
source: 'ml_model',
confidence: 0.65,
isEstimate: true // UI에서 "추정치" 표시용
};
}

getSource() { return 'ml_model' as const; }
}

// 팩토리 함수
export async function createSleepAnalyzer(): Promise<SleepAnalyzer> {
const hasWearable = await checkWearableConnection();
return hasWearable ? new WearableSleepAnalyzer() : new MLSleepAnalyzer();
}

5.4 UI 컴포넌트: 수면 리포트

// components/sleep-analysis/sleep-report.tsx

'use client';

import { SleepAnalysisResult } from '@/lib/sleep-analysis/types';
import { HypnogramChart } from './hypnogram-chart';
import { SnoringTimeline } from './snoring-timeline';
import { Badge } from '@dta-wi-design-system/components/badge';

interface SleepReportProps {
result: SleepAnalysisResult;
}

export function SleepReport({ result }: SleepReportProps) {
return (
<div className="space-y-6">
{/* 데이터 소스 표시 */}
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold">수면 분석 결과</h2>
{result.source === 'wearable' ? (
<Badge variant="success">Apple Watch 측정</Badge>
) : (
<Badge variant="secondary">AI 추정 • 참고용</Badge>
)}
</div>

{/* 수면 단계 차트 */}
<section>
<h3 className="font-semibold mb-2">수면 단계</h3>
<HypnogramChart stages={result.stages} />
</section>

{/* 수면 통계 */}
<section className="grid grid-cols-2 gap-4">
<StatCard
label="총 수면 시간"
value={formatDuration(result.statistics.totalSleepTime)}
/>
<StatCard
label="수면 효율"
value={`${result.statistics.sleepEfficiency}%`}
/>
<StatCard
label="깊은 수면"
value={formatDuration(result.statistics.stageDistribution.deep)}
/>
<StatCard
label="REM 수면"
value={formatDuration(result.statistics.stageDistribution.rem)}
/>
</section>

{/* 코골이 이벤트 */}
{result.snoringEvents.length > 0 && (
<section>
<h3 className="font-semibold mb-2">코골이 감지</h3>
<SnoringTimeline events={result.snoringEvents} />
</section>
)}

{/* ML 추정인 경우 안내 */}
{result.isEstimate && (
<p className="text-sm text-muted-foreground">
* 스마트워치 연동 시 더 정확한 분석이 가능합니다
</p>
)}
</div>
);
}

6. 서버 API

6.1 엔드포인트

# 학습 데이터 수집
POST /api/ml/training-data
Description: 익명화된 학습 데이터 업로드
Auth: Bearer Token (User)
Request:
- segments: TrainingDataSample[]
- metadata: CollectionSession
Response:
- uploadId: string
- acceptedCount: number
- rejectedCount: number

# 수면 분석 결과 저장
POST /api/sleep-analysis/sessions
Description: 수면 분석 세션 결과 저장
Auth: Bearer Token (User)
Request:
- sessionId: string
- startTime: ISO DateTime
- endTime: ISO DateTime
- stages: SleepStageSegment[]
- events: AudioEvent[]
- statistics: SleepStatistics
- source: 'wearable' | 'ml_model'

# 수면 분석 결과 조회
GET /api/sleep-analysis/sessions/:sessionId
GET /api/sleep-analysis/sessions?from=&to=&limit=

# 모델 버전 확인 (동적 모델 배포 시)
GET /api/ml/models/:modelName/version
Response:
- version: string
- downloadUrl: string (signed URL)
- checksum: string

6.2 데이터베이스 스키마

// prisma/schema.prisma (추가)

model SleepAnalysisSession {
id String @id @default(uuid())
userId String

startTime DateTime
endTime DateTime
durationMin Int

source SleepAnalysisSource // WEARABLE, ML_MODEL
confidence Float

// 통계
totalSleepMin Int
sleepEfficiency Float
awakenings Int
awakeMin Int
lightMin Int
deepMin Int
remMin Int
snoringMin Int
snoringPercentage Float

// 관계
user User @relation(fields: [userId], references: [id])
stages SleepStage[]
events SleepEvent[]

createdAt DateTime @default(now())

@@index([userId, startTime])
}

enum SleepAnalysisSource {
WEARABLE
ML_MODEL
}

model SleepStage {
id String @id @default(uuid())
sessionId String
startTime DateTime
endTime DateTime
stage SleepStageType
confidence Float

session SleepAnalysisSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)

@@index([sessionId])
}

enum SleepStageType {
AWAKE
LIGHT
DEEP
REM
}

model SleepEvent {
id String @id @default(uuid())
sessionId String
type SleepEventType
timestamp DateTime
durationMs Int
intensity Float?
confidence Float

session SleepAnalysisSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)

@@index([sessionId])
}

enum SleepEventType {
SNORING
APNEA_SUSPECT
MOVEMENT
TALKING
COUGHING
}

// ML 학습 데이터 (익명화)
model MLTrainingData {
id String @id @default(uuid())
anonymousUserId String // SHA-256 해시

sessionDate DateTime @db.Date
epochIndex Int

spectrogram Json // 64x128 array
features Json // MFCC, spectral features

sleepStage SleepStageType
labelConfidence Float
labelSource String // apple_watch, galaxy_watch

audioQuality Float
isValid Boolean @default(true)

createdAt DateTime @default(now())

@@index([sessionDate])
@@index([sleepStage])
}

7. 개발 로드맵

7.1 Phase 개요

┌─────────────────────────────────────────────────────────────┐
│ 개발 로드맵 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Phase 1: 기반 구축 (4주) │
│ ━━━━━━━━━━━━━━━━━━━━━━ │
│ • Capacitor 오디오 녹음 플러그인 개발 │
│ • 백그라운드 녹음 구현 (iOS/Android) │
│ • 기본 UI 컴포넌트 개발 │
│ • HealthKit/Health Connect 수면 단계 조회 구현 │
│ │
│ Phase 2: 코골이 감지 MVP (4주) │
│ ━━━━━━━━━━━━━━━━━━━━━━━━ │
│ • 공개 데이터셋으로 코골이 감지 모델 학습 │
│ • TFLite 변환 및 앱 통합 │
│ • 실시간 코골이 감지 기능 구현 │
│ • 내부 테스트 및 모델 개선 │
│ │
│ Phase 3: 학습 데이터 수집 (4주) │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ • 학습 데이터 수집 파이프라인 구축 │
│ • 사용자 동의 플로우 구현 │
│ • 서버 API 및 데이터베이스 구현 │
│ • 베타 테스터 모집 및 데이터 수집 시작 │
│ │
│ Phase 4: 수면 단계 모델 개발 (6주) │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ • 수집된 데이터로 수면 단계 모델 학습 │
│ • 모델 최적화 및 양자화 │
│ • 앱 통합 및 A/B 테스트 │
│ • 모델 성능 모니터링 체계 구축 │
│ │
│ Phase 5: 정식 출시 준비 (4주) │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ • 종합 수면 리포트 기능 완성 │
│ • 성능 최적화 (배터리, 메모리) │
│ • QA 및 버그 수정 │
│ • 문서화 및 출시 │
│ │
│ 총 예상 기간: 22주 (약 5.5개월) │
│ │
└─────────────────────────────────────────────────────────────┘

7.2 마일스톤

마일스톤시점완료 기준
M1Week 4iOS/Android 백그라운드 오디오 녹음 동작
M2Week 8코골이 감지 모델 90%+ 정확도
M3Week 12100+ 사용자 데이터 수집 시작
M4Week 18수면 단계 모델 70%+ 정확도
M5Week 22App Store / Play Store 출시

8. 기술 스택

8.1 앱 (dha-sleep-app-web)

영역기술버전
FrameworkNext.js14.x
UIReact18.x
StylingTailwind CSS3.x
NativeCapacitor8.x
상태관리Zustand4.x
ML RuntimeTensorFlow Lite-

8.2 네이티브 플러그인

플랫폼언어주요 라이브러리
iOSSwift 5.9AVFoundation, Accelerate, TensorFlowLiteSwift
AndroidKotlin 1.9AudioRecord, TensorFlow Lite, WorkManager

8.3 ML 파이프라인

단계기술
데이터 처리Python, Pandas, NumPy
오디오 처리Librosa
모델 학습TensorFlow 2.x
실험 추적Weights & Biases
모델 변환TFLite Converter (INT8 양자화)

9. 보안 및 프라이버시

9.1 프라이버시 보호 원칙

  1. 오디오 원본 최소화

    • 오디오 원본은 기기에서만 처리
    • 서버에는 스펙트로그램(특징)만 전송
    • 스펙트로그램에서 원본 오디오 복원 불가
  2. 익명화

    • 사용자 ID는 SHA-256 해시 처리
    • 정확한 시간 정보 제거 (날짜만 유지)
    • 기기 정보 일반화 (모델명 → 제조사만)
  3. 명시적 동의

    • 학습 데이터 제공은 별도 동의 필요
    • 동의 철회 시 수집 중단
    • 동의 없이는 로컬 분석만 수행
  4. 데이터 보존

    • 학습 데이터: 모델 학습 완료 후 2년 보존
    • 개인 분석 결과: 사용자 요청 시 즉시 삭제

9.2 GDPR 준수

요구사항구현 방법
동의 (Consent)명시적 opt-in, 언제든 철회 가능
접근권 (Access)앱 내에서 수집된 데이터 조회
삭제권 (Erasure)계정 삭제 시 모든 데이터 삭제
이동권 (Portability)데이터 내보내기 기능
최소화 (Minimization)필요한 데이터만 수집

9.3 보안 요구사항

const securityRequirements = {
transport: {
tlsVersion: '1.3',
certificatePinning: true,
},
storage: {
localEncryption: true, // iOS Keychain, Android Keystore
serverEncryption: 'AES-256-GCM',
},
dataProcessing: {
audioRetention: 'none', // 서버에 원본 저장 안 함
spectrogramRetention: '2years',
anonymization: 'required',
},
};

변경 이력

버전날짜변경 내용
1.0.02026-01-20초안 작성