사용자 패턴 분석 설계 결정 사항
2025. 12. 8. 16:48ㆍAI 개발
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 (통합 테이블) + 수동 입력 테이블 분리
최종 테이블 구조:
- TB_HEALTH_LOGS (자동 동기화용)
- 대상: apple_health, google_fit
- 특징: 날짜별로 여러 레코드 저장 가능, 항상 새로운 레코드로 추가 (History 유지)
- 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 (패턴 기반)
- 패턴 분석 결과 (최우선): TB_USER_PATTERN_SETTINGS 조회 (평일/주말 구분)
- 에러 처리: 설정 없음 (온보딩 필요 메시지 반환)
- 이유: 패턴 평균값을 사용하여 데이터가 없는 시점(아침 기상 전)에도 일관된 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. 단계별 검증 로드맵
- Step 1 (현재): 백엔드 & API 통신 검증
- 에뮬레이터에서 manual 모드로 가짜 데이터를 전송.
- 서버가 데이터를 받아 Phase를 올바르게 계산해서 응답하는지 확인.
- 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 기반 분기 처리
- 자동 동기화 (apple_health, google_fit)
- TB_HEALTH_LOGS 사용
- 항상 INSERT (기존 데이터 확인 안 함, 히스토리 보존)
- 매주 월요일 7일치 데이터 적재
- 수동 입력 (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. 최종 아키텍처
데이터 흐름
- Flutter 앱 실행 ↓
- HealthKit/Health Connect 데이터 확인 (사용자 동의 시)
- 평일(화~일): 오늘 데이터만
- 월요일: 지난 7일(월~일) 데이터 ↓
- POST /sync → 서버로 전송 ↓
- DB 저장 (Source Type 분기)
- apple_health/google_fit → TB_HEALTH_LOGS에 추가 저장 (Append)
- manual → TB_MANUAL_HEALTH_LOGS에 업데이트 (Single Record) ↓
- 주간 패턴 분석 자동 실행 (월요일인 경우)
- TB_HEALTH_LOGS의 히스토리 데이터만 사용하여 분석 ↓
- TB_USER_PATTERN_SETTINGS 업데이트 (평일/주말 평균 기상 시간 등) ↓
- GET /current (Phase 조회) ↓
- 패턴 분석 결과 기준으로 Phase 계산 ↓
- 응답 반환 (current_phase, hours_since_wake 등)
9. 주요 트레이드오프
- 실시간 데이터 vs 패턴 평균: 패턴 평균 우선 (일관성 및 알림 기능 보장)
- 확장성 vs 단순함: 확장성 고려 (RAW_DATA 저장, 테이블 분리)
- 공평성 vs 기능: 공평성 우선 (Android 미지원 데이터 제외)
10. 향후 개선 사항
- [ ] Flutter 앱 HealthKit/Health Connect 연동
- [ ] 월요일 자동 7일 데이터 동기화 로직 구현
- [ ] 실시간 데이터 활용 여부 재검토 (테스트 후)
11. 핵심 설계 원칙
- 실시간 데이터 우선: (정책 변경 → 패턴 기반 우선, 데이터는 백그라운드 적재)
- Fallback 명확: 단계별 Fallback으로 안정성 확보
- 확장 가능: RAW_DATA 및 분리된 테이블 구조로 유연성 확보
- 사용자 중심: 갱년기 여성의 라이프스타일 패턴 반영
'AI 개발' 카테고리의 다른 글
| 사용자 패턴 분석 엔진 고찰 (0) | 2025.12.08 |
|---|---|
| GPT-4o-mini가 복잡한 JSON 시나리오 생성에 실패했던 이유와 해결 방법 (실전 트러블슈팅) (0) | 2025.12.03 |
| [Dev Log] LLM으로 '무한 시나리오 게임' 만들기: 기획부터 JSON 생성까지 자동화 파이프라인 (0) | 2025.12.02 |
| 마음봄 프로젝트 - 감정 분석 RAG 모델 (1) | 2025.12.02 |
| [Tech Log] 단순 챗봇을 넘어 'AI 에이전트'로: 멘탈케어 서비스 '마음봄' 아키텍처 설계기 (0) | 2025.12.02 |