HealthSync_Intelligence/app/controllers/mission_controller.py
hyerimmy 910bd902b1
Some checks failed
HealthSync Intelligence CI / build-and-push (push) Has been cancelled
feat : initial commit
2025-06-20 05:28:30 +00:00

296 lines
15 KiB
Python

# app/controllers/mission_controller.py
"""
HealthSync AI 미션 관련 컨트롤러 (다층 특성 기반 AI 이모지 자동 매핑)
"""
from fastapi import APIRouter, status, HTTPException, Query
from app.controllers.base_controller import BaseController
from app.services.mission_service import MissionService
from app.dto.request.mission_request import MissionRecommendRequest
from app.dto.response.mission_response import MissionRecommendationResponse
from app.dto.request.celebration_request import CelebrationRequest
from app.dto.response.celebration_response import CelebrationResponse
from app.dto.response.similar_mission_news_response import SimilarMissionNewsResponse
from app.exceptions import (
UserNotFoundException,
HealthDataNotFoundException,
DatabaseException,
ClaudeAPIException
)
class MissionController(BaseController):
"""미션 관련 컨트롤러 (다층 특성 기반 AI 이모지 자동 매핑)"""
def __init__(self):
super().__init__()
self.mission_service = MissionService()
self.router = APIRouter()
self._setup_routes()
def _setup_routes(self):
"""라우트 설정"""
@self.router.post("/recommend",
response_model=MissionRecommendationResponse,
status_code=status.HTTP_200_OK,
summary="🎯 AI 건강 미션 추천",
description="""
사용자의 건강검진 데이터를 기반으로 AI가 맞춤형 건강 미션을 추천합니다.
**처리 과정:**
1. 사용자 기본 정보 조회 (직업, 나이 등)
2. 최신 건강검진 데이터 조회
3. Claude AI를 통한 개인화된 미션 생성
4. 미션별 추천 이유와 함께 반환
**추천 미션 특징:**
- 일일 목표 횟수 1-5회 범위
- 사용자 건강 상태에 맞춤화
- 일상에서 실행 가능한 건강 행동
- 각 미션별 상세한 추천 이유 제공
""")
async def recommend_missions(request: MissionRecommendRequest) -> MissionRecommendationResponse:
"""AI 기반 건강 미션 추천"""
try:
self.log_request("recommend_missions", user_id=request.user_id)
# 미션 추천 서비스 호출
response = await self.mission_service.recommend_missions(request.user_id)
self.logger.info(f"미션 추천 성공 - user_id: {request.user_id}, "
f"추천 미션 수: {len(response.missions)}")
return response
except ValueError as e:
self.logger.warning(f"잘못된 요청 - user_id: {request.user_id}, error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"잘못된 요청입니다: {str(e)}"
)
except Exception as e:
self.handle_service_error(e, "recommend_missions")
@self.router.post("/celebrate",
response_model=CelebrationResponse,
status_code=status.HTTP_200_OK,
summary="🎉 미션 달성 축하 메시지",
description="""
사용자가 달성한 미션에 대해 AI가 개인화된 축하 메시지를 생성합니다.
**처리 과정:**
1. 미션 ID(숫자)로 데이터베이스에서 미션 정보 조회 (미션명, 설명, 목표 등)
2. 조회된 미션 정보를 기반으로 Claude AI 호출
3. 미션 내용을 반영한 맞춤형 축하 메시지 생성
4. 생성된 축하 메시지를 Chat DB에 "celebration" 타입으로 저장
5. 이모지와 함께 1줄 축하 메시지 반환
**축하 메시지 특징:**
- DB에서 조회한 실제 미션 정보 반영
- 간결하고 따뜻한 1줄 메시지 (50자 내외)
- 다양한 이모지로 시각적 효과
- 미션별 맞춤형 축하 내용
- 지속적인 동기부여 유도
""")
async def celebrate_mission(request: CelebrationRequest) -> CelebrationResponse:
"""미션 달성 축하 메시지 생성 및 Chat DB 저장"""
try:
self.log_request("celebrate_mission",
user_id=request.userId, mission_id=request.missionId)
# 축하 메시지 서비스 호출 (Chat DB 저장 포함)
response = await self.mission_service.generate_celebration_message(
user_id=request.userId,
mission_id=request.missionId
)
self.logger.info(f"미션 축하 성공 (Chat DB 저장 완료) - user_id: {request.userId}, "
f"mission_id: {request.missionId}, "
f"메시지 길이: {len(response.congratsMessage)}")
return response
except HealthDataNotFoundException as e:
self.logger.warning(f"미션을 찾을 수 없음 - mission_id: {request.missionId}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"미션 ID {request.missionId}를 찾을 수 없습니다."
)
except DatabaseException as e:
self.logger.error(f"데이터베이스 오류 - error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="데이터베이스 연결에 문제가 있습니다. 잠시 후 다시 시도해 주세요."
)
except ClaudeAPIException as e:
self.logger.error(f"Claude API 오류 - error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail="AI 서비스에 일시적인 문제가 있습니다. 잠시 후 다시 시도해 주세요."
)
except Exception as e:
self.handle_service_error(e, "celebrate_mission")
@self.router.get("/similar-news",
response_model=SimilarMissionNewsResponse,
status_code=status.HTTP_200_OK,
summary="🔔 유사 사용자 미션 완료 소식 (5가지 기준별 선별)",
description="""
**5가지 유사도 기준별로 각각 1명씩 총 5개의 미션 완료 소식을 조회합니다.**
### 🎯 5가지 유사도 기준:
1. **직업 유사도**: 동일 직업 또는 유사 직업군 (테크/케어/서비스)
2. **나이 유사도**: 연령대가 비슷한 사용자 (10세 차이 내)
3. **BMI/체형 유사도**: 체형이 비슷한 사용자 (마른/표준/통통)
4. **혈압 유사도**: 혈압 수치가 비슷한 사용자 (정상/주의/높음)
5. **혈당 유사도**: 혈당 수치가 비슷한 사용자 (정상/주의/높음)
### 📋 다양한 메시지 형태 (각 기준별 1개씩):
1. **직업 기준**: "IT직군 김OO님이 물마시기 미션을 완료했어요! 💧"
2. **나이 기준**: "23세 홍OO님이 산책을 완료했어요! 🚶‍♀️"
3. **체형 기준**: "통통한 박OO님이 스트레칭을 완료했어요! 🧘‍♂️"
4. **혈압 기준**: "혈압높은 이OO님이 명상을 완료했어요! 🧘‍♀️"
5. **혈당 기준**: "혈당주의 최OO님이 계단오르기를 완료했어요! 🏃‍♂️"
### ⚡ 성능 최적화:
- **빠른 이모지 매핑**: AI 대신 키워드 기반 매핑으로 빠른 응답
- **중복 제거**: 같은 사용자가 여러 기준에서 선정되는 것 방지
- **다양성 보장**: 5가지 다른 기준으로 다양한 유사성 표현
- **최소 유사도 임계값**: 0.3 이상의 유사도를 가진 사용자만 선별
### 🔄 선별 프로세스:
1. **후보 수집**: 벡터 유사도 기준 상위 30명 조회
2. **기준별 계산**: 각 후보에 대해 5가지 기준별 유사도 점수 계산
3. **최적 선별**: 각 기준별로 가장 높은 점수의 사용자 1명씩 선택
4. **중복 제거**: 동일 사용자가 여러 기준에서 선정될 경우 가장 높은 점수 기준만 적용
5. **결과 반환**: 최대 5개의 다양한 유사 사용자 소식 반환
### 💡 유사 직업군 분류:
- **테크 그룹**: 사무직(OFF001), IT직군(ENG001)
- **케어 그룹**: 의료진(MED001), 교육직(EDU001)
- **서비스 그룹**: 서비스직(SRV001)
### 🏥 건강 특성 분류:
- **BMI**: 마른(<18.5), 표준(18.5-25), 통통(≥25)
- **혈압**: 정상(<130), 주의(130-139), 높음(≥140)
- **혈당**: 정상(<100), 주의(100-125), 높음(≥126)
### 🔒 개인정보 보호:
- 이름 마스킹 처리 (김OO 형식)
- 구체적인 수치 노출 없이 특성만 표시
- 다양한 기준으로 분산하여 개인 식별 위험 최소화
**이제 5가지 다른 기준으로 선별된 다양한 유사 사용자들의 미션 완료 소식을 빠르게 확인할 수 있습니다!**
""")
async def get_similar_mission_news(
user_id: int = Query(..., gt=0, description="사용자 ID")) -> SimilarMissionNewsResponse:
"""유사 사용자 미션 완료 소식 조회 (다층 특성 기반 AI 이모지)"""
try:
self.log_request("get_similar_mission_news", user_id=user_id)
response = await self.mission_service.get_similar_mission_news(user_id)
return response
except Exception as e:
self.handle_service_error(e, "get_similar_mission_news")
@self.router.post("/upsert-vector",
status_code=status.HTTP_200_OK,
summary="📊 모든 사용자 벡터 일괄 저장/업데이트",
description="""
**모든 사용자의 건강 중심 벡터를 Pinecone DB에 일괄 저장/업데이트합니다.**
### 🔄 처리 과정:
1. **전체 사용자 조회**: PostgreSQL에서 모든 사용자 목록 조회
2. **기존 벡터 확인**: Pinecone에서 이미 저장된 벡터 ID 목록 조회
3. **신규 사용자 필터링**: 벡터가 없는 사용자만 필터링
4. **건강 데이터 조회**: 각 사용자별 최신 건강검진 데이터 조회
5. **건강 중심 벡터 생성**: 1024차원 건강 특성 벡터 생성
6. **벡터 저장**: Pinecone에 저장
7. **진행상황 로깅**: 실시간 처리 현황 로그 출력
### 🎯 건강 중심 벡터 구성 (1024차원):
- **나이 특성** (50차원): 연령대별 유사도 강화
- **건강 위험도** (300차원): BMI, 혈압, 혈당, 콜레스테롤 등 주요 지표
- **상세 건강 특성** (500차원): 세부 건강 데이터 정교한 벡터화
- **직업 특성** (100차원): 직업별 건강 위험 프로필
- **생활습관** (74차원): 흡연, 음주 상태
### ⚡ 성능 최적화:
- **스킵 로직**: 이미 벡터가 있는 사용자는 건너뛰기
- **배치 처리**: 여러 사용자를 한 번에 처리
- **에러 핸들링**: 개별 사용자 실패 시에도 전체 프로세스 계속 진행
- **진행률 표시**: 전체 진행 상황 실시간 확인
### 📊 응답 정보:
- **총 사용자 수**: 전체 사용자 수
- **기존 벡터 수**: 이미 저장된 벡터 수
- **신규 처리 수**: 새로 저장된 벡터 수
- **성공/실패 수**: 처리 결과 통계
- **소요 시간**: 전체 처리 시간
### 🔧 사용 시점:
- 초기 시스템 구축 시 모든 사용자 벡터 생성
- 정기적인 벡터 데이터 동기화
- 새로운 사용자 대량 등록 후 벡터 일괄 생성
- 벡터 알고리즘 업데이트 후 재생성
### ⚠️ 주의사항:
- 대량 데이터 처리로 시간이 오래 걸릴 수 있음
- Pinecone API 요청 한도 고려 필요
- 처리 중 중단되어도 부분적으로 완료된 데이터는 유지됨
""")
async def upsert_all_user_vectors():
"""모든 사용자 건강 중심 벡터 일괄 저장/업데이트 (기존 벡터 스킵)"""
try:
self.log_request("upsert_all_user_vectors")
# 모든 사용자 벡터 일괄 처리 서비스 호출
result = await self.mission_service.upsert_all_user_vectors()
if result["success"]:
self.logger.info(f"✅ 모든 사용자 건강 중심 벡터 일괄 처리 완료 - "
f"총 사용자: {result['total_users']}, "
f"기존 벡터: {result['existing_vectors']}, "
f"신규 저장: {result['new_vectors']}, "
f"실패: {result['failed']}")
return self.create_success_response(
data=result,
message=f"건강 중심 벡터 일괄 처리 완료! 신규 {result['new_vectors']}개 저장, "
f"기존 {result['existing_vectors']}개 스킵"
)
else:
self.logger.warning(f"⚠️ 벡터 일괄 처리 부분 실패 - "
f"성공: {result['new_vectors']}, 실패: {result['failed']}")
return self.create_success_response(
data=result,
message=f"벡터 일괄 처리 부분 완료. 일부 사용자 처리 실패: {result['failed']}"
)
except DatabaseException as e:
self.logger.error(f"❌ 데이터베이스 오류 - error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="데이터베이스 연결에 문제가 있습니다. 잠시 후 다시 시도해 주세요."
)
except Exception as e:
self.logger.error(f"❌ 벡터 일괄 처리 중 예상치 못한 오류: {str(e)}", exc_info=True)
# 일괄 처리 실패해도 서비스는 계속 동작 가능하도록 처리
return self.create_success_response(
data={
"success": False,
"total_users": 0,
"existing_vectors": 0,
"new_vectors": 0,
"failed": 0,
"error_message": "벡터 일괄 처리에 실패했지만 개별 기능은 정상 이용 가능합니다.",
"error_type": type(e).__name__
},
message="벡터 일괄 처리에 실패했지만 서비스는 계속 이용 가능합니다."
)
# 컨트롤러 인스턴스 생성
mission_controller = MissionController()