사용자 패턴 분석 설계 결정 사항

2025. 12. 8. 16:48AI 개발

 

1. 서비스 위치 및 구조 결정

고민 사항

  • Engine vs Service: backend/engine/에 둘지, backend/app/에 둘지?
  • 서비스 이름: “user_pattern” vs “user_phase”

결정

  • 위치: backend/app/user_phase/ (Service로 구현)
    • 이유: 비즈니스 로직 서비스이므로 app/ 폴더가 적합 (Engine은 AI/ML 모델용, Service는 사용자 데이터 관리용)
  • 이름: “user_phase” (현재 상태 중심)
    • 이유: “패턴 분석”보다는 “현재 Phase 판별”이 핵심 기능

구조

backend/app/user_phase/
├── __init__.py
├── models.py      # Pydantic 모델
├── service.py     # 비즈니스 로직
└── routes.py      # API 엔드포인트

2. 데이터베이스 설계 결정

2.1. 테이블 구조

고민: 단일 테이블 vs OS별 테이블 분리 vs 데이터 성격별 분리

  • 옵션 1: OS별 테이블 분리
    • TB_APPLE_HEALTH_LOGS, TB_ANDROID_HEALTH_LOGS
  • 옵션 2: 통합 테이블
    • TB_HEALTH_LOGS (SOURCE_TYPE으로 구분)

결정: 옵션 2 (통합 테이블) + 수동 입력 테이블 분리

최종 테이블 구조:

  1. TB_HEALTH_LOGS (자동 동기화용)
    • 대상: apple_health, google_fit
    • 특징: 날짜별로 여러 레코드 저장 가능, 항상 새로운 레코드로 추가 (History 유지)
  2. TB_MANUAL_HEALTH_LOGS (수동 입력용)
    • 대상: manual
    • 특징: 사용자당 하나의 레코드만 유지, 업데이트 방식 (Latest Snapshot)

이유:

  • 데이터 성격 차이: 자동 동기화는 패턴 분석을 위한 시계열(History) 데이터가 필요하고, 수동 입력은 데이터가 없을 때를 대비한 최신 상태값(Snapshot)이 필요함.
  • 로직 명확화: 테이블을 분리함으로써 History Accumulation 로직과 Single Record Update 로직을 명확히 구분.

2.2. 데이터 저장 방식

  • 결정: 핵심 + 원본 (하이브리드)
    • SLEEP_END_TIME (빠른 조회용)
    • RAW_DATA (JSON, 원본 데이터 전체)
  • 이유: Phase 계산은 인덱싱된 컬럼으로 빠르게 처리하고, 추후 기능 확장(수면 질 분석 등)을 위해 원본 데이터 보존.

2.3. 갱년기 특화 데이터

  • 결정: 공통 데이터만 사용
    • 포함: 수면(시간, 시작/종료), 심박수(평균, 안정시, HRV), 활동량(걸음, 칼로리 등)
    • 제외: BODY_TEMPERATURE (Android Health Connect 미지원 이슈)
  • 이유: iOS/Android 간 기능 차별 방지 및 공평성 유지.

2.4. NULL 처리

  • 결정: 대부분 Optional (nullable=True)
  • 필수: USER_ID, LOG_DATE, SOURCE_TYPE, CREATED_AT
  • 이유: 사용자가 특정 건강 데이터를 사용하지 않을 수 있으며, 일부 데이터만으로도 Phase 계산이 가능해야 함.

3. Phase 계산 로직 결정

3.1. Fallback 전략

  • 결정: 2단계 Fallback (패턴 기반)
    1. 패턴 분석 결과 (최우선): TB_USER_PATTERN_SETTINGS 조회 (평일/주말 구분)
    2. 에러 처리: 설정 없음 (온보딩 필요 메시지 반환)
  • 이유: 패턴 평균값을 사용하여 데이터가 없는 시점(아침 기상 전)에도 일관된 Phase를 제공하고 알림 발송이 가능하도록 함.

3.2. 패턴 분석 방식

  • 결정: 주 1회 자동 실행 (월요일) + 수동 트리거
    • 월요일에 지난 7일(월~일) 데이터 동기화 후 자동 분석.
    • POST /analyze 엔드포인트로 수동 실행 가능.
  • 데이터 소스: TB_HEALTH_LOGS (자동 동기화된 히스토리 데이터)만 사용.

3.3. 평일/주말 구분

  • 결정: 평일/주말 구분
    • WEEKDAY_WAKE_TIME (월~금 평균)
    • WEEKEND_WAKE_TIME (토~일 평균)
  • 이유: 갱년기 여성의 생활 패턴(주말 늦잠 등)을 반영하여 정확도 향상.

4. 알림 기능 관련 결정

  • 결정: 패턴 기반 Phase 계산
  • 이유: 알림은 사용자가 앱을 켜기 전에 발송되어야 하므로, 실시간 데이터보다는 '예측된 패턴(평균 기상 시간)'을 기준으로 하는 것이 안정적임.

5. 데이터 동기화 전략 결정

5.1. 동기화 시점

  • 결정: 앱 실행 시 (Foreground)
  • 이유: 구현이 간단하고 배터리 효율적이며, Flutter 환경에서 제어가 용이함.

5.2. 동기화 범위

  • 결정: 오늘만 (기본) + 주간 동기화 (월요일)
    • 평일(화~일): 오늘 데이터만
    • 월요일: 지난 7일(월~일) 데이터 한 번에 동기화

5.3. 수동 입력 vs 자동 동기화

  • 자동 동기화 (사용자 동의 시):
    • 대상: 삼성 헬스, 애플 헬스킷
    • 동작: 매주 월요일 지난 7일 데이터를 가져와 TB_HEALTH_LOGS에 항상 추가(Append).
    • 목적: 히스토리 데이터를 축적하여 패턴 분석에 활용.
  • 수동 입력 (방어 전략):
    • 대상: 사용자 직접 입력
    • 동작: TB_MANUAL_HEALTH_LOGS에 사용자당 하나의 레코드만 유지(Update).
    • 목적: 자동 동기화 미동의자를 위한 최소한의 데이터 확보 (패턴 분석에는 미사용).

6. 테스트 전략 결정

  • 결정: 단계별 접근
    • 현재: 백엔드 API 로직 검증 (Swagger, 수동 데이터)
    • 추후: Flutter 앱 개발 후 실제 HealthKit/Health Connect 연동 테스트.

6.1. 직면한 문제 (Pain Point)

  • 에뮬레이터 환경의 한계: 현재 개발 단계에서 사용하는 Android Studio 에뮬레이터(또는 iOS 시뮬레이터)는 물리적인 센서가 없어 실제 건강 데이터(걸음 수, 수면 등)가 발생하지 않음.
  • 권한 획득 불가: 에뮬레이터는 보안 정책상 삼성 헬스(Health Connect)나 애플 헬스킷(HealthKit)의 파트너 권한 승인 및 데이터 접근이 원천적으로 차단됨.
  • 개발 병목: 실제 폰에 올리기 전까지 백엔드 로직(Phase 계산)을 검증할 수 없는 'Blocking' 이슈 발생.

6.2. 해결 전략: 가상 데이터 파이프라인 구축

이 문제를 해결하기 위해 **'Production 코드에 영향을 주지 않는 테스트 트랙'**을 설계에 반영함.

A. API 레벨: manual 소스 타입 지원

  • 앱에서 source_type: "manual"로 데이터를 보낼 수 있도록 API 스펙 확장.
  • 실제 헬스킷 연동 코드 없이, UI 상의 '개발자용 버튼' 등을 통해 임의의 걸음 수와 수면 시간을 서버로 전송 가능하게 함.

B. DB 레벨: 데이터 격리 (Table Separation)

  • 테스트용 데이터가 실제 서비스용 히스토리(TB_HEALTH_LOGS)를 오염시키는 것을 방지.
  • TB_MANUAL_HEALTH_LOGS 테이블을 별도로 두어, 개발자가 입력한 가짜 데이터는 여기서만 처리됨.
  • Phase 계산 로직은 manual 데이터가 있으면 그것을 우선하여 계산 결과를 반환하되, 실제 통계에는 집계되지 않도록 함.

6.3. 단계별 검증 로드맵

  1. Step 1 (현재): 백엔드 & API 통신 검증
    • 에뮬레이터에서 manual 모드로 가짜 데이터를 전송.
    • 서버가 데이터를 받아 Phase를 올바르게 계산해서 응답하는지 확인.
  2. Step 2 (추후): 실물 기기 통합 테스트
    • 실제 안드로이드 폰에서 google_fit / apple_health 모드로 전환.
    • 실제 걸음 수가 서버로 넘어가는지 확인.

7. API 설계 결정

7.1. 엔드포인트 구조

  • POST /api/service/user-phase/sync: 동기화 + Phase 반환
  • GET /api/service/user-phase/current: Phase 조회
  • POST /api/service/user-phase/analyze: 패턴 분석 (수동)
  • (기타 설정 조회/수정 API 포함)

7.2. 데이터 저장 방식

로직 변경: 날짜 기반 Upsert → Source Type 기반 분기 처리

  1. 자동 동기화 (apple_health, google_fit)
    • TB_HEALTH_LOGS 사용
    • 항상 INSERT (기존 데이터 확인 안 함, 히스토리 보존)
    • 매주 월요일 7일치 데이터 적재
  2. 수동 입력 (manual)
    • TB_MANUAL_HEALTH_LOGS 사용
    • 사용자 ID 기준 UPSERT (항상 1개의 레코드 유지)
    • LOG_DATE: 사용자가 입력한 기준 날짜
    • UPDATED_AT: 업데이트 시점 기록

구현 예시 (Pseudo-code):

# 자동 동기화
if request.source_type in ["apple_health", "google_fit"]:
    # 항상 추가 저장 (History)
    new_log = HealthLog(...)
    db.add(new_log)

# 수동 입력
elif request.source_type == "manual":
    existing_log = db.query(ManualHealthLog).filter(
        ManualHealthLog.USER_ID == user_id
    ).first()
    
    if existing_log:
        # Update (Snapshot)
        existing_log.update(...)
    else:
        # Insert (첫 입력)
        db.add(ManualHealthLog(...))

8. 최종 아키텍처

데이터 흐름

  1. Flutter 앱 실행
  2. HealthKit/Health Connect 데이터 확인 (사용자 동의 시)
    • 평일(화~일): 오늘 데이터만
    • 월요일: 지난 7일(월~일) 데이터 ↓
  3. POST /sync → 서버로 전송 ↓
  4. DB 저장 (Source Type 분기)
    • apple_health/google_fit → TB_HEALTH_LOGS에 추가 저장 (Append)
    • manual → TB_MANUAL_HEALTH_LOGS에 업데이트 (Single Record)
  5. 주간 패턴 분석 자동 실행 (월요일인 경우)
    • TB_HEALTH_LOGS의 히스토리 데이터만 사용하여 분석 ↓
  6. TB_USER_PATTERN_SETTINGS 업데이트 (평일/주말 평균 기상 시간 등) ↓
  7. GET /current (Phase 조회) ↓
  8. 패턴 분석 결과 기준으로 Phase 계산
  9. 응답 반환 (current_phase, hours_since_wake 등)

9. 주요 트레이드오프

  1. 실시간 데이터 vs 패턴 평균: 패턴 평균 우선 (일관성 및 알림 기능 보장)
  2. 확장성 vs 단순함: 확장성 고려 (RAW_DATA 저장, 테이블 분리)
  3. 공평성 vs 기능: 공평성 우선 (Android 미지원 데이터 제외)

10. 향후 개선 사항

  • [ ] Flutter 앱 HealthKit/Health Connect 연동
  • [ ] 월요일 자동 7일 데이터 동기화 로직 구현
  • [ ] 실시간 데이터 활용 여부 재검토 (테스트 후)

11. 핵심 설계 원칙

  • 실시간 데이터 우선: (정책 변경 → 패턴 기반 우선, 데이터는 백그라운드 적재)
  • Fallback 명확: 단계별 Fallback으로 안정성 확보
  • 확장 가능: RAW_DATA 및 분리된 테이블 구조로 유연성 확보
  • 사용자 중심: 갱년기 여성의 라이프스타일 패턴 반영