Sleep 도메인 모델
데이터 저장 규칙
- 수면 기록, 목표, 처방, 목표 달성 여부 등 사용자와 직접적으로 관련된 민감 데이터는
private스키마에 저장합니다. - 수면 문제, 긍정/부정 요인 등 일반 설정/조회 데이터는
sleep스키마에 저장합니다. - 모든 시간 관련 데이터는
TimeMachine도메인에서 제공하는 시간을 기준으로 저장하고 처리해야 합니다. 레거시 시스템에서는 Unix 타임스탬프(초 단위 정수)를 사용했을 수 있으므로, 도메인 경계에서 DateTime 객체와 타임스탬프 간 변환이 필요할 수 있습니다.
1. 엔티티 관계도 (ERD)
참고: 위 ERD의 User 엔티티는 Sleep 도메인 관점에서 필요한 최소 정보(ID)만 표시합니다. User 엔티티의 상세 정의 및 전체 스키마는 user/domain-model.md를 참조하세요.
2. 엔티티
2.1 SleepLog (수면 기록)
private.sleep_log 테이블에 해당합니다. 사용자의 일일 수면 데이터를 기록합니다.
interface SleepLog {
id: string;
userId: string;
userCycleId: string; // FK
date: Date; // 기록 대상 날짜 (YYYY-MM-DD), 내부 저장용
dayIndex: number; // 치료 주기 일차 (User 도메인 제공)
dns: boolean; // 수면 여부 (DNS)
// dns = false (잠을 잤을 경우)일 때만 의미 있는 필드
lot?: Date; // 취침 시도 시각 (lot, ISO 8601, TimeMachine 기준)
aet?: Date; // 최종 기상 시각 (aet, ISO 8601, TimeMachine 기준)
tst?: number; // 총 수면 시간 (tst, 분)
se?: number; // 수면 효율 (se, 0-1)
sol?: number; // 잠들기까지 걸린 시간 (sol, 분)
waso?: number; // 수면 중 깬 시간 (분)
sleepQuality?: SleepQuality; // 수면의 질 (1~5), dns = false일 때 필수
positiveFactorIds?: number[]; // 긍정적 영향 요인 ID 목록 (sleep.sleep_log_positive_factor 참조), dns = false일 때 필수
// 모든 경우에 필요한 필드
pill: boolean; // 수면제 복용 여부 (pill)
napMinutes: number; // 낮잠 시간 (nap_minutes, 분, 기본값 0)
negativeFactorIds: number[]; // 부정적 영향 요인 ID 목록 (sleep.sleep_log_negative_factor 참조)
positiveCustomFactors?: string[]; // 사용자가 직접 입력한 긍정 요인 (최대 10개 항목, 각 항목 최대 100자)
negativeCustomFactors?: string[]; // 사용자가 직접 입력한 부정 요인 (최대 10개 항목, 각 항목 최대 100자)
timezoneId: string; // 예: "Europe/Berlin"
timezoneOffset: number; // 분 단위 오프셋 (예: 60)
isTemporary?: boolean; // 임시 수면 기록 여부
createdAt: Date;
updatedAt: Date;
}
// 수면의 질 Enum (값은 legacy DB의 정수값과 매핑될 수 있음)
enum SleepQuality {
VERY_BAD = 1,
BAD = 2,
NORMAL = 3,
GOOD = 4,
VERY_GOOD = 5,
}
참고: tst, se, sol, waso는 계산된 지표이지만, 쿼리 편의성과 레거시 호환성을 위해 직접 저장합니다. 원본 시각/시간 데이터 변경 시 이 값들도 함께 업데이트되어야 데이터 일관성이 유지됩니다.
2.2 SleepGoal (수면 목표)
private.sleep_goal 테이블에 해당합니다. 사용자의 목표 취침/기상 시간 및 권장 수면 시간을 기록합니다.
interface SleepGoal {
id: string;
userId: string;
userCycleId: string; // FK
targetDate: Date; // 목표 적용 날짜
targetDayIndex: number; // 치료 주기 일차 (User 도메인 제공)
targetLOT: Date; // 목표 취침 시각 (lot, ISO 8601, TimeMachine 기준)
targetAET: Date; // 목표 기상 시각 (aet, ISO 8601, TimeMachine 기준)
rTIB: number; // 권장 TIB (분) - 필수 값
type: SleepGoalType; // 목표 설정 주체
timezoneId: string; // 예: "Europe/Berlin"
timezoneOffset: number; // 분 단위
createdAt: Date;
// updatedAt 필드는 legacy 스키마에 없음
}
// 목표 설정 주체 Enum (legacy DB의 type_id와 매핑)
enum SleepGoalType {
USER = 1, // 사용자가 직접 수정
RTIB_ALGORITHM = 2, // 알고리즘 계산 결과
SYSTEM = 3, // On-demand 생성 (이전 목표 기반)
// DOCTOR_SET? = ... // 필요시 추가
}
2.3 RtibRecommendation (rTIB 처방)
RTIB 알고리즘 계산 결과 및 근거 데이터를 저장합니다. private.rtib_recommendation 테이블과 연관될 수 있으며, 계산 근거를 별도로 저장합니다.
interface RtibRecommendation {
id: string;
userId: string;
userCycleId: string; // FK
calculationDate: Date; // rTIB 계산 수행 날짜
calculationDayIndex: number; // rTIB 계산 수행 일차(치료 주기 기준)
rTIB: number; // 처방된 TIB (분)
calculationBasis: {
// 계산 근거 데이터 (avgTst, avgSe, avgDse 등)
avgTst?: number; // 평균 총 수면 시간 (분)
avgSe?: number; // 평균 수면 효율 (%)
avgDse?: number; // 평균 잠자리 보낸 시간 (분) - TST + SOL + WASO (legacy 방식)
dataStartDate?: Date; // 계산에 사용된 데이터 시작일
dataEndDate?: Date; // 계산에 사용된 데이터 종료일
validLogCount?: number; // 계산에 사용된 유효 로그 수
isFirstRtib?: boolean;
isAdaptationPeriod?: boolean; // legacy generateRTIB 인자
previousRtib?: number; // legacy adjustRtibBasedOnPrevious 인자
};
calculatedOnSleepLogCreation?: boolean; // 수면 기록 작성 시점에 계산되었는지 여부
createdAt: Date;
}
2.4 SleepGoalAdherence (수면 목표 달성 여부)
private.sleep_goal_adherence 테이블에 해당합니다. 특정 날짜의 수면 목표 달성 여부를 기록합니다.
interface SleepGoalAdherence {
id: string;
userId: string;
userCycleId: string; // FK
targetDate: Date; // 평가 날짜
dayIndex: number; // 치료 주기 일차
sleepGoalId: string; // 관련 SleepGoal ID (FK)
lotSuccess: boolean; // 취침 목표 달성 여부
aetSuccess: boolean; // 기상 목표 달성 여부
createdAt: Date;
updatedAt: Date;
}
2.5 SleepLogPositiveFactor (긍정적 영향 요인)
sleep.sleep_log_positive_factor 테이블에 해당합니다. 선택 가능한 긍정적 영향 요인 목록입니다.
interface SleepLogPositiveFactor {
id: number; // PK
title: string;
isCustom: boolean;
isActive: boolean;
createdAt: Date;
}
2.6 SleepLogNegativeFactor (부정적 영향 요인)
sleep.sleep_log_negative_factor 테이블에 해당합니다. 선택 가능한 부정적 영향 요인 목록입니다.
interface SleepLogNegativeFactor {
id: number; // PK
title: string;
isCustom: boolean;
isActive: boolean;
createdAt: Date;
}
3. 값 객체
3.1 SleepLogInput (수면 기록 입력)
수면 기록 생성/수정에 사용되는 데이터 구조입니다.
interface SleepLogInput {
dayIndex: number; // 치료 주기 일차 (User 도메인에서 제공), 필수
dns: boolean; // 수면 여부, 필수
// dns = false (잠을 잤을 경우) 필수 입력 항목
lot?: string; // ISO 8601 또는 HH:mm (TimeMachine 기준 시간으로 변환 필요), dns = false일 때 필수
aet?: string; // 최종 기상 시각 (aet) (ISO 8601 또는 HH:mm), dns = false일 때 필수
sleepQuality?: SleepQuality; // 수면의 질, dns = false일 때 필수
waso?: number; // 침대에서 뒤척인 시간 (waso와 유사하게 사용될 수 있음), dns = false일 때 필수, 기본값: 0분
sol?: number; // 잠들기까지 걸린 시간, dns = false일 때 필수, 기본값: 0분
positiveFactorIds?: number[]; // 선택된 ID 목록, dns = false일 때 필수
// 모든 경우 필수 입력 항목
negativeFactorIds?: number[]; // 선택된 ID 목록, 항상 필수
pill?: boolean; // 항상 필수
napMinutes?: number; // 항상 필수, 기본값: 0
// 선택 입력 항목
positiveCustomFactors?: string[]; // 자유 입력 긍정 요인 (앱에서 길이/개수 제한 필요), 선택
negativeCustomFactors?: string[]; // 자유 입력 부정 요인 (앱에서 길이/개수 제한 필요), 선택
// 시간대 정보 (필수)
timezoneId: string; // 예: "Europe/Berlin", 필수
timezoneOffset: number; // 분 단위 오프셋 (예: 60), 필수
}
유효성 검사 규칙
dns = true(전혀 못 잤을 경우):negativeFactorIds,pill,napMinutes는 필수입니다.lot,aet,sleepQuality,sol,waso,positiveFactorIds는 무시됩니다.
dns = false(잠을 잤을 경우):lot,aet,sleepQuality,sol,waso,positiveFactorIds,negativeFactorIds,pill,napMinutes는 모두 필수입니다.sol,waso,napMinutes의 기본값은 0분입니다.
3.2 DailySleepMetrics (일일 수면 지표)
특정 SleepLog로부터 계산되는 주요 지표입니다.
interface DailySleepMetrics {
tst: number; // Total Sleep Time (분)
se: number; // Sleep Efficiency (%)
sol: number; // Sleep Onset Latency (분)
waso: number; // Wake After Sleep Onset (분)
tib: number; // Time In Bed (분) - aet - lot
// dse?: number; // Duration in Sleeping Episode (분) - Legacy RTIB 계산용 (TST + SOL + WASO)
}
3.3 WeeklySleepSummary (주간 수면 요약)
주간 리포트에 사용될 데이터 구조입니다.
interface WeeklySleepSummary {
userId: string;
userCycleId: string;
startDate: Date; // 주 시작일 (YYYY-MM-DD)
endDate: Date; // 주 종료일 (YYYY-MM-DD)
startDayIndex: number; // 시작 치료 주기 일차
// 일별 수면 시간 (TIB 또는 TST 기준?)
dailySleepDurations: { dayIndex: number; date: Date; durationMinutes: number; type: 'TIB' | 'TST' }[];
// 주간 평균 지표
avgTst?: number;
avgSe?: number;
avgSol?: number;
avgWaso?: number;
avgTib?: number;
// 목표 달성 횟수
goalAdherenceCounts: {
bedTimeMet: number;
wakeTimeMet: number;
totalDaysWithGoal: number;
};
// 일별 수면 효율
dailySleepEfficiency: { dayIndex: number; date: Date; efficiency: number }[];
}
3.4 WeeklySleepStatistics (주차별 수면 통계)
주차별 통계 API에 사용될 데이터 구조입니다.
interface WeeklySleepStatistics {
userId: string;
userCycleId: string;
startDayIndex: number; // 주차 시작 일차
endDayIndex: number; // 주차 종료 일차 (startDayIndex + 6 또는 가용 데이터까지)
// 수면 효율(SE) 통계
se: {
dailyData: { dayIndex: number; date: Date; value: number | null }[]; // 일차별 수치
average: number | null; // 평균 수면 효율
categories: {
// 범주별 빈도
poor: number; // SE <= 0.87 빈도
moderate: number; // 0.87 < SE < 0.95 빈도
good: number; // SE >= 0.95 빈도
};
};
// 수면의 질 통계
sleepQuality: {
dailyData: { dayIndex: number; date: Date; value: SleepQuality | null }[]; // 일차별 데이터
distribution: {
// 등급별 빈도 및 비율
[key in SleepQuality]?: { count: number; percentage: number };
};
};
// 총 수면 시간(TST) 통계
tst: {
dailyData: { dayIndex: number; date: Date; value: number | null }[]; // 일차별 데이터 (분 단위)
average: number | null; // 평균 총 수면 시간 (분)
min: number | null; // 최소 총 수면 시간 (분)
max: number | null; // 최대 총 수면 시간 (분)
};
// 수면제 복용 통계
sleepMedication: {
dailyData: { dayIndex: number; date: Date; value: boolean | null }[]; // 일차별 데이터
totalDays: number; // 복용 일수
percentage: number; // 복용 비율
};
// SOL(잠들기까지 걸린 시간) 통계
sol: {
dailyData: { dayIndex: number; date: Date; value: number | null }[]; // 일차별 데이터 (분 단위)
average: number | null; // 평균 SOL (분)
min: number | null; // 최소 SOL (분)
max: number | null; // 최대 SOL (분)
};
// 낮잠 시간 통계
napTime: {
dailyData: { dayIndex: number; date: Date; value: number | null }[]; // 일차별 데이터 (분 단위)
average: number | null; // 평균 낮잠 시간 (분)
count: number | null; // 총 낮잠 시간 (분)
};
// 수면 영향 요인 통계
sleepFactors: {
positive: {
// 긍정적 요인
topFactors: { id: number; title: string; count: number; percentage: number }[]; // 상위 빈도 요인
dailyData: { dayIndex: number; date: Date; factors: { id: number; title: string }[] }[]; // 일차별 데이터
};
negative: {
// 부정적 요인
topFactors: { id: number; title: string; count: number; percentage: number }[]; // 상위 빈도 요인
dailyData: { dayIndex: number; date: Date; factors: { id: number; title: string }[] }[]; // 일차별 데이터
};
};
// 목표 달성 통계
goalAdherence: {
dailyData: {
dayIndex: number;
date: Date;
targetLOT: Date | null;
actualBedTime: Date | null;
bedTimeGoalMet: boolean | null;
targetAET: Date | null;
actualWakeTime: Date | null;
wakeTimeGoalMet: boolean | null;
}[]; // 일차별 데이터
bedTimeMetCount: number; // 취침 목표 달성 횟수
wakeTimeMetCount: number; // 기상 목표 달성 횟수
totalDaysWithGoal: number; // 목표가 있는 총 일수
};
}
3.5 targetAETUpdate (목표 기상 시각 업데이트)
사용자가 rTIB 처방 후 목표 기상 시각을 설정/변경할 때 사용됩니다.
interface targetAETUpdate {
userId: string;
userCycleId: string;
targetAET: string; // HH:mm 형식 (5분 단위)
rtibRecommendationId: string; // 어떤 처방에 대한 목표 시간 설정인지 식별
targetDate: Date; // 목표가 적용될 시작 날짜
}
3.6 TemporarySleepLogInput (임시 수면 기록 입력)
임시 수면 기록 생성에 사용되는 데이터 구조입니다. 일부 필수 필드가 누락되어도 저장 가능합니다.
interface TemporarySleepLogInput {
dayIndex: number; // 치료 주기 일차 (User 도메인에서 제공), 필수
dns?: boolean; // 수면 여부, 선택 (기본값: false)
// 최소한 다음 정보 중 하나 이상 필요
lot?: string; // ISO 8601 또는 HH:mm (TimeMachine 기준 시간으로 변환 필요), 선택
aet?: string; // 최종 기상 시각 (aet) (ISO 8601 또는 HH:mm), 선택
sleepQuality?: SleepQuality; // 수면의 질, 선택
pill?: boolean; // 수면제 복용 여부, 선택
// 선택적 추가 정보
sol?: number; // 잠들기까지 걸린 시간, 선택
waso?: number; // 침대에서 뒤척인 시간, 선택
napMinutes?: number; // 낮잠 시간, 선택
positiveFactorIds?: number[]; // 선택된 ID 목록, 선택
negativeFactorIds?: number[]; // 선택된 ID 목록, 선택
positiveCustomFactors?: string[]; // 자유 입력 긍정 요인, 선택
negativeCustomFactors?: string[]; // 자유 입력 부정 요인, 선택
// 시간대 정보 (필수)
timezoneId: string; // 예: "Europe/Berlin", 필수
timezoneOffset: number; // 분 단위 오프셋 (예: 60), 필수
}
3.7 UpdateSleepRecordInput (수면 기록 업데이트 입력)
수면 기록 부분 업데이트에 사용되는 데이터 구조입니다. 변경할 필드만 포함합니다.
interface UpdateSleepRecordInput {
dns?: boolean; // 수면 여부
// dns = false (잠을 잤을 경우) 관련 필드
lot?: string; // ISO 8601 또는 HH:mm
aet?: string; // ISO 8601 또는 HH:mm
sleepQuality?: SleepQuality; // 수면의 질
sol?: number; // 잠들기까지 걸린 시간
waso?: number; // 침대에서 뒤척인 시간
positiveFactorIds?: number[]; // 선택된 ID 목록
// 모든 경우 가능한 필드
negativeFactorIds?: number[]; // 선택된 ID 목록
pill?: boolean; // 수면제 복용 여부
napMinutes?: number; // 낮잠 시간
positiveCustomFactors?: string[]; // 자유 입력 긍정 요인
negativeCustomFactors?: string[]; // 자유 입력 부정 요인
// 시간대 정보 (변경 시)
timezoneId?: string; // 예: "Europe/Berlin"
timezoneOffset?: number; // 분 단위 오프셋 (예: 60)
}
3.8 CompleteSleepRecordInput (임시 수면 기록 완성 입력)
임시 수면 기록을 완전한 수면 기록으로 변환하는데 사용되는 데이터 구조입니다.
interface CompleteSleepRecordInput {
dns: boolean; // 수면 여부, 필수
// dns = false (잠을 잤을 경우) 필수 입력 항목
lot?: string; // ISO 8601 또는 HH:mm, dns = false일 때 필수 (기존 값이 없는 경우)
aet?: string; // ISO 8601 또는 HH:mm, dns = false일 때 필수 (기존 값이 없는 경우)
sleepQuality?: SleepQuality; // 수면의 질, dns = false일 때 필수 (기존 값이 없는 경우)
sol?: number; // 잠들기까지 걸린 시간, dns = false일 때 필수, 기본값: 0분
waso?: number; // 침대에서 뒤척인 시간, dns = false일 때 필수, 기본값: 0분
positiveFactorIds?: number[]; // 선택된 ID 목록, dns = false일 때 필수
// 모든 경우 필수 입력 항목
negativeFactorIds?: number[]; // 선택된 ID 목록, 항상 필수 (기존 값이 없는 경우)
pill?: boolean; // 항상 필수 (기존 값이 없는 경우)
napMinutes?: number; // 항상 필수, 기본값: 0 (기존 값이 없는 경우)
// 선택 입력 항목
positiveCustomFactors?: string[]; // 자유 입력 긍정 요인
negativeCustomFactors?: string[]; // 자유 입력 부정 요인
}
4. 집계 (Aggregates)
- SleepLog Aggregate (Root:
SleepLog) - SleepGoal Aggregate (Root:
SleepGoal) - RtibRecommendation Aggregate (Root:
RtibRecommendation) - SleepGoalAdherence Aggregate (Root:
SleepGoalAdherence) - Lookup Aggregates:
SleepLogPositiveFactor,SleepLogNegativeFactor(각각 Root) - 주로 관리용
5. 도메인 서비스
5.1 SleepLogService (수면 기록 서비스)
interface SleepLogService {
/**
* 사용자의 특정 주기, 특정 일차의 수면 기록을 생성하거나 업데이트합니다.
* 내부적으로 DailySleepMetrics(tst, se, sol, waso 등)를 계산하여 저장할 수 있습니다.
* 사용자의 치료 활동 일시 정지 상태를 확인하고, 정지 상태인 경우 예외를 발생시킵니다.
* dns 값에 따라 필수 입력값을 검증하고, 부적절한 입력의 경우 예외를 발생시킵니다.
* - dns = true: negativeFactorIds, pill, napMinutes 필수
* - dns = false: lot, aet, sleepQuality 등 모든 필드 필수
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param input 수면 기록 입력 데이터
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 생성/업데이트된 수면 기록
* @throws 사용자가 치료 활동 일시 정지 상태이거나 필수 입력값이 누락된 경우 예외 발생
*/
recordSleep(userId: string, userCycleId: string, input: SleepRecordInput, currentTime: Date): Promise<SleepLog>;
/**
* 사용자의 특정 주기, 특정 일차의 임시 수면 기록을 생성합니다.
* 임시 수면 기록은 일부 필수 필드가 누락되어도 저장이 가능합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param input 임시 수면 기록 입력 데이터 (최소한 lot, aet, sleepQuality, pill 중 하나 필요)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 생성된 임시 수면 기록
* @throws 사용자가 치료 활동 일시 정지 상태이거나 최소 입력값도 누락된 경우 예외 발생
*/
recordTemporarySleep(userId: string, userCycleId: string, input: TemporarySleepRecordInput, currentTime: Date): Promise<SleepLog>;
/**
* 수면 기록 dayIndex와 현재 일차를 검증합니다.
* 수면 기록은 현재 진행 중인 일차에 한해서만 생성/수정이 가능합니다.
* @param dayIndex 수면 기록 일차
* @param currentDayIndex 현재 일차 (User 도메인에서 제공)
* @returns 유효한 일차인지 여부 (true: 유효함, false: 유효하지 않음)
* @throws 일차가 현재 일차와 일치하지 않는 경우 DayIndexValidationFailedException 예외 발생
*/
validateSleepLogDayIndex(dayIndex: number, currentDayIndex: number): boolean;
/**
* 사용자의 특정 주기, 특정 일차의 수면 기록을 부분 업데이트합니다.
* 수정은 당일 자정까지만 가능하며, 변경된 필드만 검증하고 업데이트합니다.
* dns 값이 변경될 경우 해당 상태에 맞는 필수 입력항목 검증을 다시 적용합니다.
* dns를 false에서 true로 변경하는 경우, DNS에 필요한 데이터(부정적 영향 요인, 수면제 복용 여부, 낮잠 시간) 이외의
* 모든 항목(수면의 질, 잠자리에 든 시각, 일어난 시각, 잠들기까지 걸린 시간, 수면 중 깬 시간 총합, 긍정적 영향 요인, 계산된 수면 지표 등)은
* 자동으로 초기화(null 또는 기본값으로 설정)됩니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param dayIndex 수정할 일차
* @param input 수정할 필드 데이터
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 수정된 수면 기록
* @throws 사용자가 치료 활동 일시 정지 상태이거나, 당일이 아닌 기록을 수정하려는 경우, 필수 입력값이 누락된 경우 예외 발생
*/
updateSleep(userId: string, userCycleId: string, date: Date, input: UpdateSleepRecordInput, currentTime: Date): Promise<SleepLog>;
/**
* 임시 수면 기록을 완전한 수면 기록으로 변환합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param date 변환할 날짜
* @param input 완전한 수면 기록에 필요한 추가 데이터
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 변환된 완전한 수면 기록
* @throws 사용자가 치료 활동 일시 정지 상태이거나, 임시 기록이 존재하지 않는 경우, 필수 입력값이 누락된 경우 예외 발생
*/
completeTemporarySleep(userId: string, userCycleId: string, date: Date, input: CompleteSleepRecordInput, currentTime: Date): Promise<SleepLog>;
/**
* 사용자의 치료 활동 일시 정지 상태를 확인합니다.
* @param userId 사용자 ID
* @returns 정지 상태 여부 (true: 정지 상태, false: 활성 상태)
*/
checkUserSuspensionStatus(userId: string): Promise<boolean>;
/**
* 특정 날짜의 사용자 수면 기록을 조회합니다.
* @param userCycleId 사용자 주기 ID
* @param date 조회할 날짜 (YYYY-MM-DD)
* @returns 해당 날짜의 수면 기록 (없으면 null)
*/
getSleepLog(userCycleId: string, date: Date): Promise<SleepLog | null>;
/**
* 특정 치료 주기 일차의 사용자 수면 기록을 조회합니다.
* @param userCycleId 사용자 주기 ID
* @param dayIndex 치료 주기 일차
* @returns 해당 일차의 수면 기록 (없으면 null)
*/
getSleepLogByDayIndex(userCycleId: string, dayIndex: number): Promise<SleepLog | null>;
/**
* 특정 기간 동안의 사용자 수면 기록 목록을 조회합니다.
* @param userCycleId 사용자 주기 ID
* @param startDate 시작일
* @param endDate 종료일
* @returns 수면 기록 목록
*/
getSleepLogsBetween(userCycleId: string, startDate: Date, endDate: Date): Promise<SleepLog[]>;
/**
* 특정 치료 주기 일차 범위 내의 사용자 수면 기록 목록을 조회합니다.
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 시작 치료 주기 일차
* @param endDayIndex 종료 치료 주기 일차
* @returns 수면 기록 목록
*/
getSleepLogsByDayIndexRange(userCycleId: string, startDayIndex: number, endDayIndex: number): Promise<SleepLog[]>;
/**
* 오늘의 수면 기록 요약을 제공합니다 (간략 정보).
* @param userCycleId 사용자 주기 ID
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns { lot, aet, timeInBed } 형식의 요약 정보 또는 null
*/
getTodaySleepSummary(userCycleId: string, currentTime: Date): Promise<{ lot?: Date; aet?: Date; timeInBedMinutes?: number } | null>;
/**
* 수면 기록을 기반으로 일일 수면 지표를 계산합니다.
* @param log 수면 기록
* @returns 계산된 일일 수면 지표
*/
calculateDailySleepMetrics(log: SleepLog): DailySleepMetrics;
/**
* TimeMachine 시간 변경 시 특정 시점 이후의 특정 주기 수면 기록을 삭제합니다.
* @param userCycleId 사용자 주기 ID
* @param cutoffTime 삭제 기준 시각
*/
deleteSleepDataAfter(userCycleId: string, cutoffTime: Date): Promise<void>;
}
5.2 RtibService (rTIB 서비스)
interface RtibService {
/**
* 특정 주기의 사용자에 대해 특정 날짜를 기준으로 rTIB를 계산하고 처방 정보(RtibRecommendation)를 저장합니다.
* Legacy 로직(첫 계산 여부, 데이터 필터링, 평균 계산, SE 기반 분기, 올림, 최소/최대값, 이전 값 기반 조정)을 포함합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param calculationDate 계산 기준 날짜 (YYYY-MM-DD)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 생성된 rTIB 처방 정보 또는 null (데이터 부족 등)
*/
calculateAndRecommendRtib(userId: string, userCycleId: string, calculationDate: Date, currentTime: Date): Promise<RtibRecommendation | null>;
/**
* 배치 작업을 통해 모든 대상 사용자의 rTIB를 계산합니다.
* 이미 당일에 수면 기록 작성 시점에 rTIB가 계산된 사용자는 제외합니다.
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 계산된 rTIB 처방 정보 목록 (사용자별)
*/
batchCalculateRtib(currentTime: Date): Promise<RtibRecommendation[]>;
/**
* 사용자가 수면 기록을 작성하는 시점에 rTIB 계산 조건을 확인하고, 조건 충족 시 즉시 새로운 rTIB를 계산합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param sleepLog 작성된 수면 기록
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 생성된 rTIB 처방 정보 또는 null (조건 미충족 등)
*/
calculateRtibOnSleepLogCreation(userId: string, userCycleId: string, sleepLog: SleepLog, currentTime: Date): Promise<RtibRecommendation | null>;
/**
* 사용자가 rTIB 계산 조건을 충족하는지 확인합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 조건 충족 여부 및 이유 (예: { eligible: true, reason: 'FIRST_RTIB_ELIGIBLE' })
*/
checkRtibCalculationEligibility(userId: string, userCycleId: string, currentTime: Date): Promise<{ eligible: boolean; reason: string }>;
/**
* 특정 날짜를 기준으로 유효한 (가장 최근 계산된) rTIB 처방 정보를 조회합니다.
* @param userCycleId 사용자 주기 ID
* @param date 조회 기준 날짜 (YYYY-MM-DD)
* @returns 유효한 rTIB 처방 정보 또는 null
*/
getActiveRtibRecommendation(userCycleId: string, date: Date): Promise<RtibRecommendation | null>;
/**
* 특정 날짜를 기준으로 유효한 사용자 수면 목표(SleepGoal)를 조회합니다.
* 만약 해당 날짜의 목표가 저장되어 있지 않다면 (예: 야간 배치 전 조회 시),
* rTIB 재계산 조건 충족 여부에 따라 실시간으로 목표를 계산하여 반환합니다.
* - 재계산 조건 충족 시: 새로운 rTIB 기반 목표 생성 (goalType: RTIB_ALGORITHM)
* - 재계산 조건 미충족 시: 전날 목표 시간 기반 목표 생성 (goalType: SYSTEM)
* @param userCycleId 사용자 주기 ID
* @param date 조회 기준 날짜 (YYYY-MM-DD)
* @param currentTime 현재 시간 (TimeMachine 제공, 실시간 계산 시 필요)
* @returns 유효한 수면 목표 정보 (저장된 값 또는 실시간 계산 결과) 또는 null (데이터 부족 등)
*/
getActiveSleepGoal(userCycleId: string, date: Date, currentTime: Date): Promise<SleepGoal | null>;
/**
* rTIB 처방 후 사용자가 목표 기상 시각을 설정/변경할 때 호출됩니다. (처방 당일만 가능)
* 입력된 `targetAET`과 해당 처방의 `rTIB`를 사용하여
* 새로운 `targetLOT`을 자동으로 계산하고 `SleepGoal`을 생성/업데이트합니다.
* 이때 `goalType`은 `USER`로 설정됩니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param update 목표 기상 시각 업데이트 정보 (rtibRecommendationId 포함)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 사용자가 설정한 수면 목표 정보
*/
setSleepGoalBasedOnRtib(userId: string, userCycleId: string, update: targetAETUpdate, currentTime: Date): Promise<SleepGoal>;
/**
* TimeMachine 시간 변경 시 특정 시점 이후의 특정 주기 rTIB 처방 및 목표 데이터를 삭제합니다.
* @param userCycleId 사용자 주기 ID
* @param cutoffTime 삭제 기준 시각
*/
deleteRtibDataAfter(userCycleId: string, cutoffTime: Date): Promise<void>;
/**
* 특정 치료 주기 일차의 수면 목표를 조회합니다.
* @param userCycleId 사용자 주기 ID
* @param dayIndex 치료 주기 일차
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 해당 일차의 수면 목표 또는 null
*/
getSleepGoalByDayIndex(userCycleId: string, dayIndex: number, currentTime: Date): Promise<SleepGoal | null>;
/**
* 특정 치료 주기 일차 범위의 수면 목표 목록을 조회합니다.
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 시작 치료 주기 일차
* @param endDayIndex 종료 치료 주기 일차
* @returns 수면 목표 목록
*/
getSleepGoalsByDayIndexRange(userCycleId: string, startDayIndex: number, endDayIndex: number): Promise<SleepGoal[]>;
}
5.3 SleepReportService (수면 리포트 서비스)
주간 요약 등 리포트 생성을 담당합니다.
interface SleepReportService {
/**
* 주간 수면 데이터를 계산하여 요약 정보를 제공합니다.
* 내부적으로 SleepLogService, SleepGoalAdherenceService 등을 사용하여 데이터를 집계합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param weekStartDate 해당 주의 시작일 (일요일 또는 월요일 기준 - 정책 필요)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 주간 수면 요약 데이터
*/
generateWeeklySummary(userId: string, userCycleId: string, weekStartDate: Date, currentTime: Date): Promise<WeeklySleepSummary>;
/**
* 치료 주기 일차 기준으로 주차별 수면 통계를 계산하여 제공합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 주차 시작 일차(dayIndex)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 주차별 수면 통계 데이터
*/
getWeeklySleepStatistics(userId: string, userCycleId: string, startDayIndex: number, currentTime: Date): Promise<WeeklySleepStatistics>;
/**
* 치료 주기 일차 기준으로 수면 효율(SE) 통계를 계산하여 제공합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 주차 시작 일차(dayIndex)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 수면 효율 통계 데이터
*/
getWeeklySleepEfficiency(userId: string, userCycleId: string, startDayIndex: number, currentTime: Date): Promise<WeeklySleepStatistics['sleepEfficiency']>;
/**
* 치료 주기 일차 기준으로 수면의 질 통계를 계산하여 제공합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 주차 시작 일차(dayIndex)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 수면의 질 통계 데이터
*/
getWeeklySleepQuality(userId: string, userCycleId: string, startDayIndex: number, currentTime: Date): Promise<WeeklySleepStatistics['sleepQuality']>;
/**
* 치료 주기 일차 기준으로 총 수면 시간(TST) 통계를 계산하여 제공합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 주차 시작 일차(dayIndex)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 총 수면 시간 통계 데이터
*/
getWeeklyTotalSleepTime(userId: string, userCycleId: string, startDayIndex: number, currentTime: Date): Promise<WeeklySleepStatistics['totalSleepTime']>;
/**
* 치료 주기 일차 기준으로 수면제 복용 통계를 계산하여 제공합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 주차 시작 일차(dayIndex)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 수면제 복용 통계 데이터
*/
getWeeklySleepMedication(userId: string, userCycleId: string, startDayIndex: number, currentTime: Date): Promise<WeeklySleepStatistics['sleepMedication']>;
/**
* 치료 주기 일차 기준으로 SOL(잠들기까지 걸린 시간) 통계를 계산하여 제공합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 주차 시작 일차(dayIndex)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns SOL 통계 데이터
*/
getWeeklySleepOnsetLatency(userId: string, userCycleId: string, startDayIndex: number, currentTime: Date): Promise<WeeklySleepStatistics['sleepOnsetLatency']>;
/**
* 치료 주기 일차 기준으로 낮잠 시간 통계를 계산하여 제공합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 주차 시작 일차(dayIndex)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 낮잠 시간 통계 데이터
*/
getWeeklyNapTime(userId: string, userCycleId: string, startDayIndex: number, currentTime: Date): Promise<WeeklySleepStatistics['napTime']>;
/**
* 치료 주기 일차 기준으로 수면 영향 요인 통계를 계산하여 제공합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 주차 시작 일차(dayIndex)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 수면 영향 요인 통계 데이터
*/
getWeeklySleepFactors(userId: string, userCycleId: string, startDayIndex: number, currentTime: Date): Promise<WeeklySleepStatistics['sleepFactors']>;
/**
* 치료 주기 일차 기준으로 목표 달성 통계를 계산하여 제공합니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param startDayIndex 주차 시작 일차(dayIndex)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 목표 달성 통계 데이터
*/
getWeeklyGoalAdherence(userId: string, userCycleId: string, startDayIndex: number, currentTime: Date): Promise<WeeklySleepStatistics['goalAdherence']>;
}
5.4 SleepGoalAdherenceService (수면 목표 달성 서비스)
interface SleepGoalAdherenceService {
/**
* 특정 날짜의 SleepLog와 SleepGoal을 비교하여 목표 달성 여부(SleepGoalAdherence)를 계산하고 저장/업데이트합니다.
* @param userCycleId 사용자 주기 ID
* @param date 평가할 날짜 (YYYY-MM-DD)
* @returns 계산된 목표 달성 여부 정보 또는 null (로그나 목표가 없는 경우)
*/
calculateAndRecordGoalAdherence(userCycleId: string, date: Date): Promise<SleepGoalAdherence | null>;
/**
* 특정 기간 동안의 목표 달성 기록 목록을 조회합니다.
* @param userCycleId 사용자 주기 ID
* @param startDate 시작일
* @param endDate 종료일
* @returns 목표 달성 기록 목록
*/
getGoalAdherenceBetween(userCycleId: string, startDate: Date, endDate: Date): Promise<SleepGoalAdherence[]>;
/**
* TimeMachine 시간 변경 시 특정 시점 이후의 특정 주기 목표 달성 기록을 삭제합니다.
* @param userCycleId 사용자 주기 ID
* @param cutoffTime 삭제 기준 시각
*/
deleteGoalAdherenceDataAfter(userCycleId: string, cutoffTime: Date): Promise<void>;
}
5.5 SleepNotificationService (수면 알림 서비스) - 의존성: 알림 도메인
interface SleepNotificationService {
/**
* rTIB 처방 시 사용자에게 알림을 보냅니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param recommendation 처방 정보
*/
notifyRtibRecommendation(userId: string, userCycleId: string, recommendation: RtibRecommendation): Promise<void>;
/**
* 목표 취침 시간 1시간 전에 알림을 보냅니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param goal 수면 목표 정보
*/
notifyUpcomingBedtime(userId: string, userCycleId: string, goal: SleepGoal): Promise<void>;
/**
* 목표 기상 시간에 알림을 보냅니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param goal 수면 목표 정보
*/
notifyWakeUpTime(userId: string, userCycleId: string, goal: SleepGoal): Promise<void>;
/**
* 데이터 부족으로 rTIB 처방이 지연될 경우 알림을 보냅니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
*/
notifyRtibDelayed(userId: string, userCycleId: string): Promise<void>;
/**
* rTIB 계산 결과 피드백 메시지를 알림으로 보냅니다. (예: 안정적 패턴)
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param message 피드백 메시지
*/
notifyRtibFeedback(userId: string, userCycleId: string, message: string): Promise<void>;
/**
* 수면 기록 저장 후 새로운 rTIB가 계산되었을 경우 알림을 보냅니다.
* @param userId 사용자 ID
* @param userCycleId 사용자 주기 ID
* @param recommendation 계산된 새로운 rTIB 처방 정보
*/
notifyRtibCalculatedOnSleepLogCreation(userId: string, userCycleId: string, recommendation: RtibRecommendation): Promise<void>;
}
5.6 DataManagementService (데이터 관리 서비스)
GDPR 준수 및 데이터 보관 정책 관련 로직을 담당합니다.
interface DataManagementService {
/**
* 비활성 사용자 주기를 식별합니다. (마지막 활동 후 6개월 경과 기준)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 비활성 사용자 주기 ID 목록
*/
identifyInactiveUserCycles(currentTime: Date): Promise<string[]>;
/**
* 특정 사용자 주기를 비활성(INACTIVE) 상태로 표시합니다.
* @param userCycleId 사용자 주기 ID
* @param currentTime 현재 시간 (TimeMachine 제공)
*/
markUserCycleAsInactive(userCycleId: string, currentTime: Date): Promise<void>;
/**
* 아카이빙 대상 사용자 주기를 식별합니다. (비활성 상태 1년 경과 기준)
* @param currentTime 현재 시간 (TimeMachine 제공)
* @returns 아카이빙 대상 사용자 주기 ID 목록
*/
identifyUserCyclesForArchiving(currentTime: Date): Promise<string[]>;
/**
* 비활성 사용자 주기의 수면 관련 데이터를 콜드 스토리지로 아카이빙하고,
* 해당 주기를 아카이브됨(ARCHIVED) 상태로 표시합니다.
* @param userCycleId 사용자 주기 ID
* @param currentTime 현재 시간 (TimeMachine 제공)
*/
archiveInactiveUserCycleData(userCycleId: string, currentTime: Date): Promise<void>;
/**
* 아카이브된 사용자 주기의 데이터를 콜드 스토리지에서 복원하고,
* 해당 주기를 활성(ACTIVE) 상태로 변경합니다.
* @param userCycleId 사용자 주기 ID
* @param currentTime 현재 시간 (TimeMachine 제공)
*/
restoreArchivedUserCycleData(userCycleId: string, currentTime: Date): Promise<void>;
/**
* 사용자 데이터 삭제 요청을 처리합니다.
* 주 데이터베이스 및 콜드 스토리지에서 해당 사용자의 모든 데이터를 영구 삭제합니다.
* @param userId 사용자 ID
* @param currentTime 현재 시간 (TimeMachine 제공)
*/
deleteUserData(userId: string, currentTime: Date): Promise<void>;
/**
* 사용자 데이터를 익명화합니다. (개인 식별 정보 제거)
* @param userId 사용자 ID
*/
anonymizeUserData(userId: string): Promise<void>;
/**
* 데이터 보관 기간(5년)이 만료된 사용자 데이터를 식별하고 삭제합니다.
* @param currentTime 현재 시간 (TimeMachine 제공)
*/
enforceDataRetentionPolicy(currentTime: Date): Promise<void>;
/**
* 사용자의 데이터 추출 요청을 처리합니다.
* @param userId 사용자 ID
* @param format 추출 형식 (JSON, CSV 등)
* @returns 추출된 데이터 (압축/암호화된 형태)
*/
extractUserData(userId: string, format: 'JSON' | 'CSV'): Promise<Buffer>;
/**
* 관리자 또는 서비스 계정의 데이터 접근을 감사 로그에 기록합니다.
* @param accessorId 접근 주체 ID (Admin ID, Service Account ID 등)
* @param targetUserId 접근 대상 사용자 ID
* @param action 수행한 작업 (e.g., 'READ_SLEEP_LOGS', 'EXPORT_DATA')
* @param timestamp 작업 시각 (TimeMachine 제공)
*/
auditDataAccess(accessorId: string, targetUserId: string, action: string, timestamp: Date): Promise<void>;
}
6. 도메인 이벤트
6.1 수면 기록 관련 이벤트
interface SleepLogRecordedEvent {
logId: string;
userId: string;
userCycleId: string;
date: Date;
timestamp: Date; // 이벤트 발생 시각 (TimeMachine 기준)
metrics?: DailySleepMetrics; // 계산된 지표 포함 가능
}
/**
* 수면 기록의 날짜 검증에 실패했을 때 발생하는 이벤트
*/
interface DateValidationFailedEvent {
userId: string;
userCycleId: string;
deletedLogIds: string[];
cutoffTime: Date;
timestamp: Date;
}
// 주차별 데이터 계산 관련 이벤트 추가
interface WeeklySleepMetricsCalculatedEvent {
userId: string;
userCycleId: string;
startDayIndex: number;
endDayIndex: number;
timestamp: Date;
}
interface WeeklySleepEfficiencyCalculatedEvent {
userId: string;
userCycleId: string;
startDayIndex: number;
endDayIndex: number;
timestamp: Date;
}
interface WeeklySleepQualityCalculatedEvent {
userId: string;
userCycleId: string;
startDayIndex: number;
endDayIndex: number;
timestamp: Date;
}
interface WeeklyTotalSleepTimeCalculatedEvent {
userId: string;
userCycleId: string;
startDayIndex: number;
endDayIndex: number;
timestamp: Date;
}
interface WeeklySleepMedicationCalculatedEvent {
userId: string;
userCycleId: string;
startDayIndex: number;
endDayIndex: number;
timestamp: Date;
}
interface WeeklySleepOnsetLatencyCalculatedEvent {
userId: string;
userCycleId: string;
startDayIndex: number;
endDayIndex: number;
timestamp: Date;
}
interface WeeklyNapTimeCalculatedEvent {
userId: string;
userCycleId: string;
startDayIndex: number;
endDayIndex: number;
timestamp: Date;
}
interface WeeklySleepFactorsCalculatedEvent {
userId: string;
userCycleId: string;
startDayIndex: number;
endDayIndex: number;
timestamp: Date;
}
interface WeeklyGoalAdherenceCalculatedEvent {
userId: string;
userCycleId: string;
startDayIndex: number;
endDayIndex: number;
timestamp: Date;
}
6.2 rTIB 관련 이벤트
interface RtibCalculatedEvent {
recommendationId: string;
userId: string;
userCycleId: string;
rTIB: number;
calculationDate: Date;
timestamp: Date;
calculatedOnSleepLogCreation?: boolean; // 수면 기록 작성 시점에 계산되었는지 여부
}
interface RtibCalculationFailedEvent {
userId: string;
userCycleId: string;
calculationDate: Date;
reason: string; // 예: "INSUFFICIENT_DATA"
timestamp: Date;
}
interface RtibDataDeletedEvent {
// TimeMachine 변경 등으로 삭제될 때
userId: string;
userCycleId: string;
deletedRecommendationIds: string[];
cutoffTime: Date;
timestamp: Date;
}
6.3 수면 목표 관련 이벤트
interface SleepGoalSetEvent {
goalId: string;
userId: string;
userCycleId: string;
targetDate: Date;
targetLOT: Date;
targetAET: Date;
goalType: SleepGoalType;
timestamp: Date;
}
// SleepGoalUpdatedEvent 는 현재 요구사항에 명시적으로 없음 (rTIB 재처방 시 새로 생성)
interface SleepGoalDeletedEvent {
// TimeMachine 변경 등으로 삭제될 때
userId: string;
userCycleId: string;
deletedGoalIds: string[];
cutoffTime: Date;
timestamp: Date;
}
6.4 목표 달성 관련 이벤트
interface SleepGoalAdherenceCalculatedEvent {
adherenceId: string;
userId: string;
userCycleId: string;
targetDate: Date;
sleepGoalId: string;
lotSuccess: boolean;
aetSuccess: boolean;
timestamp: Date;
}
interface SleepGoalAdherenceDeletedEvent {
// TimeMachine 변경 등으로 삭제될 때
userId: string;
userCycleId: string;
deletedAdherenceIds: string[];
cutoffTime: Date;
timestamp: Date;
}
6.5 데이터 관리 및 개인정보 보호 관련 이벤트
interface UserCycleMarkedInactiveEvent {
userCycleId: string;
userId: string;
markedAt: Date; // 비활성 처리된 시각
}
interface UserCycleDataArchivedEvent {
userCycleId: string;
userId: string;
archivedAt: Date; // 아카이빙 완료 시각
storageLocation?: string; // 콜드 스토리지 위치 (선택적)
}
interface UserCycleDataRestoredEvent {
userCycleId: string;
userId: string;
restoredAt: Date; // 복원 완료 시각
}
interface UserDataDeletionRequestedEvent {
userId: string;
requestedAt: Date;
reason?: string;
}
interface UserDataDeletedEvent {
userId: string;
deletedAt: Date;
deletedBy: string; // 삭제 주체 (e.g., 'USER_REQUEST', 'RETENTION_POLICY')
}
interface UserDataAnonymizedEvent {
userId: string;
anonymizedAt: Date;
}
interface UserDataExtractionRequestedEvent {
userId: string;
format: 'JSON' | 'CSV';
requestedAt: Date;
}
interface UserDataExtractedEvent {
userId: string;
format: 'JSON' | 'CSV';
extractedAt: Date;
// 추출된 데이터 자체는 이벤트에 포함하지 않음 (크기 문제)
}
interface DataAccessAuditedEvent {
accessorId: string;
targetUserId: string;
action: string;
timestamp: Date;
}
interface DataPolicyUpdatedEvent {
updatedBy: string; // 정책 업데이트 주체 ID
updatedAt: Date;
previousPolicy?: any; // 이전 정책 (선택적)
newPolicy: any; // 새 정책
}
7. 도메인 규칙
Sleep 도메인의 상세한 비즈니스 규칙은 수면 도메인 비즈니스 규칙 문서를 참조하세요. 아래는 주요 규칙 카테고리입니다:
- 수면 기록 규칙: 입력 유효성, 계산 규칙(TST, SE, SOL, WASO 등), 수정/삭제 제약, 임시 기록 관리
- 수면 기록 날짜 규칙: 수면 기록은 오늘(현재 날짜)에 한해서만 생성/수정 가능, TimeMachine 시간과 요청 날짜 일치 검증
- rTIB 계산 규칙: 주기, 데이터 요구사항, 유효 기록 정의, 평균 계산, SE 기반 분기
- 수면 목표 설정 규칙: 기상 시각 설정 우선, 취침 시각 파생, 유효 기간
- 목표 달성 규칙: 달성 기준 (±30분)
- 시간 처리 규칙:
TimeMachine사용 - 치료 일시 정지 규칙: 일시 정지 상태 확인, dayIndex 기반 데이터 처리
- 주차별 데이터 계산 규칙: 주차 단위 계산, dayIndex 기반 연속성
8. 데이터베이스 스키마 (Prisma)
generator client {
provider = "prisma-client-js"
previewFeatures = ["multiSchema"] // 필요한 경우 유지
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["sleep", "private"] // 스키마 분리
}
// --- Enums ---
// 수면의 질 열거형 (private 스키마 내 모델에서 사용)
enum SleepQuality {
VERY_BAD @map("1")
BAD @map("2")
NORMAL @map("3")
GOOD @map("4")
VERY_GOOD @map("5")
}
// 수면 목표 설정 주체 (private 스키마 내 모델에서 사용)
enum SleepGoalType {
USER @map("1")
RTIB_ALGORITHM @map("2")
SYSTEM @map("3")
// DOCTOR_SET @map("...") // 필요시 추가
}
// 사용자 주기 상태 (private 스키마 내 모델에서 사용)
enum UserCycleStatus {
ACTIVE
INACTIVE
ARCHIVED
}
// --- private 스키마 ---
// ... UserCycle 모델이 정의되어 있다고 가정하고 status 필드 추가 ...
// 예시 (실제 UserCycle 모델은 user 도메인에서 정의될 수 있음)
model UserCycle {
id String @id @default(uuid())
userId String @map("user_id")
startDate DateTime @map("start_date") @db.Timestamptz
endDate DateTime? @map("end_date") @db.Timestamptz
status UserCycleStatus @default(ACTIVE)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
sleepLogs SleepLog[]
sleepGoals SleepGoal[]
rtibRecommendations RtibRecommendation[]
sleepGoalAdherence SleepGoalAdherence[]
// User 모델과의 관계
// user User @relation(fields: [userId], references: [id])
@@index([userId])
@@map("user_cycle") // 테이블 이름 예시
@@schema("private")
}
model SleepLog {
id String @id @default(uuid())
userId String @map("user_id") // User 도메인 FK (실제 관계는 주석 처리)
userCycleId String @map("user_cycle_id") // UserCycle 도메인 FK (실제 관계는 주석 처리)
date DateTime @db.Date // 기록 대상 날짜
dayIndex Int @map("day_index") // 치료 주기 일차 (User 도메인에서 제공)
dns Boolean @default(false) @map("dns")
lot DateTime? @map("lot") @db.Timestamptz // Unix timestamp (int)를 Timestamptz로 매핑
sleepStartTime DateTime? @map("ast") @db.Timestamptz // Unix timestamp (int)를 Timestamptz로 매핑
sleepEndTime DateTime? @map("aet") @db.Timestamptz // Unix timestamp (int)를 Timestamptz로 매핑
tst Int? @map("tst") @default(0)
sleepEfficiency Float? @map("se") @default(0) @db.Real
sol Int? @map("sol") // 잠들기까지 걸린 시간 (분) - 추가
waso Int? @map("waso")
pill Boolean? @map("pill") @default(false)
napMinutes Int? @map("nap") @default(0)
sleepQuality Int? @map("sleep_quality") // SleepQuality enum과 매핑되는 정수값 저장
positiveFactorIds Int[] @map("positive_factor_ids") // sleep.SleepLogPositiveFactor FK 배열
negativeFactorIds Int[] @map("negative_factor_ids") // sleep.SleepLogNegativeFactor FK 배열
positiveCustomFactors String[] @default([]) @map("positive_custom_factors") // 자유 입력 긍정 요인 (최대 10개, 각 항목 최대 100자 - 앱 레벨 검증)
negativeCustomFactors String[] @default([]) @map("negative_custom_factors") // 자유 입력 부정 요인 (최대 10개, 각 항목 최대 100자 - 앱 레벨 검증)
isTemporary Boolean @default(false) @map("is_temporary") // 임시 수면 기록 여부
timezoneId String @default("Europe/Berlin") @map("timezone_id")
timezoneOffset Int @default(60) @map("timezone_offset") // 분 단위 (Europe/Berlin의 경우 표준시는 60(UTC+1), 서머타임(DST) 적용 시 120(UTC+2) 등 날짜에 따라 동적으로 달라질 수 있음. luxon 등 라이브러리 사용 시 자동 계산 가능)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// 관계 (User, UserCycle 모델은 다른 도메인에서 정의 가정)
// user User @relation(fields: [userId], references: [id])
// userCycle UserCycle @relation(fields: [userCycleId], references: [id])
@@unique([userCycleId, date]) // 사용자 주기별 하루 하나의 기록만 허용
@@unique([userCycleId, dayIndex]) // 사용자 주기별 특정 일차에 하나의 기록만 허용
@@index([userId]) // 사용자 ID 인덱스 (선택적)
@@index([userCycleId])
@@index([date])
@@index([dayIndex])
@@map("sleep_log")
@@schema("private")
}
model SleepGoal {
id String @id @default(uuid())
userId String @map("user_id")
userCycleId String @map("user_cycle_id")
targetDate DateTime @map("target_date") @db.Date // 목표 적용 날짜
targetDayIndex Int @map("target_day_index") // 치료 주기 일차 (User 도메인에서 제공)
targetLOT DateTime @map("target_lot") @db.Timestamptz // 목표 취침 시각 (lot, ISO 8601, TimeMachine 기준)
targetAET DateTime @map("target_aet") @db.Timestamptz // 목표 기상 시각 (aet, ISO 8601, TimeMachine 기준)
rTIB Int @map("duration_in_minutes") // 권장 TIB (분) - 필수 값
type Int @map("type") // SleepGoalType enum과 매핑되는 정수값 (1:USER, 2:RTIB, 3:SYSTEM)
timezoneId String @default("Europe/Berlin") @map("timezone_id")
timezoneOffset Int @default(60) @map("timezone_offset") // 분 단위 (Europe/Berlin의 경우 표준시는 60(UTC+1), 서머타임(DST) 적용 시 120(UTC+2) 등 날짜에 따라 동적으로 달라질 수 있음. luxon 등 라이브러리 사용 시 자동 계산 가능)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
// updatedAt 필드는 legacy 스키마에 없음
// 관계 (User, UserCycle 모델은 다른 도메인에서 정의 가정)
// user User @relation(fields: [userId], references: [id])
// userCycle UserCycle @relation(fields: [userCycleId], references: [id])
adherence SleepGoalAdherence? // 1:1 관계
@@index([userCycleId, targetDate]) // 특정 주기의 특정 날짜 목표 조회용
@@unique([userCycleId, dayIndex]) // 사용자 주기별 특정 일차에 하나의 목표만 허용
@@index([userId]) // 사용자 ID 인덱스 (선택적)
@@index([userCycleId])
@@index([dayIndex]) // 치료 주기 일차별 조회를 위한 인덱스
@@map("sleep_goal")
@@schema("private")
}
// rTIB 처방 모델 (별도 테이블 또는 JSON 필드로 관리 가능, 여기서는 별도 모델로 가정)
model RtibRecommendation {
id String @id @default(uuid())
userId String @map("user_id")
userCycleId String @map("user_cycle_id")
calculationDate DateTime @map("calculation_date") @db.Date // rTIB 계산 수행 날짜
calculationDayIndex Int @map("calculation_day_index") // rTIB 계산 수행 일차(치료 주기 기준)
rTIB Int @map("recommended_tib_minutes")
calculationBasis Json @map("calculation_basis") // 계산 근거 데이터 (avgTst, avgSe, avgDse 등)
calculatedOnSleepLogCreation Boolean @default(false) @map("calculated_on_sleep_log_creation") // 수면 기록 작성 시점에 계산되었는지 여부
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
// 관계 (User, UserCycle 모델은 다른 도메인에서 정의 가정)
// user User @relation(fields: [userId], references: [id])
// userCycle UserCycle @relation(fields: [userCycleId], references: [id])
@@unique([userCycleId, calculationDate]) // 주기별 하루 하나의 처방만 허용 가정
@@index([userId])
@@index([userCycleId])
@@index([calculationDate])
@@index([calculatedOnSleepLogCreation])
@@map("rtib_recommendation")
@@schema("private")
}
model SleepGoalAdherence {
id String @id @default(uuid())
userId String @map("user_id")
userCycleId String @map("user_cycle_id")
targetDate DateTime @map("target_date") @db.Date // 평가 날짜
dayIndex Int @map("day_index") // 치료 주기 일차
sleepGoalId String @map("sleep_goal_id") // SleepGoal ID (FK)
lotSuccess Boolean @default(false) @map("lot_success")
aetSuccess Boolean @default(false) @map("aet_success")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// 관계
// user User @relation(fields: [userId], references: [id])
// userCycle UserCycle @relation(fields: [userCycleId], references: [id])
goal SleepGoal @relation(fields: [sleepGoalId], references: [id])
@@unique([userCycleId, targetDate]) // 주기별 하루 하나의 달성 기록만 허용
@@unique([userCycleId, dayIndex]) // 주기별 일차별 하나의 달성 기록만 허용
@@index([userId])
@@index([userCycleId])
@@index([dayIndex])
@@map("sleep_goal_adherence")
@@schema("private")
}
// --- sleep 스키마 ---
// 긍정적 영향 요인 모델 (sleep_log_positive_factor)
model SleepLogPositiveFactor {
id Int @id @default(autoincrement())
title String?
isCustom Boolean? @default(false) @map("is_custom")
isActive Boolean? @default(false) @map("is_active")
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz
@@map("sleep_log_positive_factor")
@@schema("sleep")
}
// 부정적 영향 요인 모델 (sleep_log_negative_factor)
model SleepLogNegativeFactor {
id Int @id @default(autoincrement())
title String?
isCustom Boolean? @default(false) @map("is_custom")
isActive Boolean? @default(false) @map("is_active")
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz
@@map("sleep_log_negative_factor")
@@schema("sleep")
}
9. 변경 이력
| 버전 | 날짜 | 작성자 | 변경 내용 |
|---|---|---|---|
| 0.1.0 | 2025-04-14 | bok@weltcorp.com | 최초 작성 (requirements.md 기반) |
| 0.2.0 | 2025-04-29 | bok@weltcorp.com | 수면 기록 작성 시점의 rTIB 계산 및 치료 일시 정지 관련 내용 추가 |
| 0.3.0 | 2025-05-03 | bok@weltcorp.com | 주차별 데이터 조회 관련 도메인 모델 추가 (WeeklySleepStatistics 등) |
| 0.4.0 | 2025-05-03 | bok@weltcorp.com | 수면 기록 수정 규칙, 임시 수면 기록 규칙, 치료 일시 정지와 주차 단위 조회 규칙 추가 |
| 0.5.0 | 2025-05-04 | bok@weltcorp.com | DNS 상태 변경 시 불필요 데이터 초기화 규칙 추가 |
| 0.6.0 | 2025-05-05 | bok@weltcorp.com | 수면 기록 생성/수정 시 날짜 검증 기능 및 관련 이벤트 추가 |
| 0.7.0 | 2025-05-13 | bok@weltcorp.com | GDPR 준수 및 데이터 보관 정책 관련 모델 변경 (UserCycle 상태 추가, DataManagementService, 관련 이벤트 추가) |