This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
HealthSync AI 기본 컨트롤러 클래스
|
||||
"""
|
||||
from fastapi import HTTPException, status
|
||||
from typing import Any
|
||||
|
||||
from fastapi.params import Depends
|
||||
|
||||
from app.core.dependencies import get_settings
|
||||
from app.models.base import BaseResponse, ErrorResponse
|
||||
from app.config.settings import settings, Settings
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
class BaseController:
|
||||
"""기본 컨트롤러 클래스"""
|
||||
|
||||
def __init__(self):
|
||||
self.settings = settings
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
def create_success_response(self, data: Any = None, message: str = "성공적으로 처리되었습니다.") -> BaseResponse:
|
||||
"""성공 응답 생성"""
|
||||
return BaseResponse(success=True, message=message, data=data, timestamp=datetime.now())
|
||||
|
||||
def handle_service_error(self, error: Exception, operation: str = "unknown"):
|
||||
"""서비스 에러 처리"""
|
||||
self.logger.error(f"Service error in {operation}: {str(error)}", exc_info=True)
|
||||
|
||||
if isinstance(error, ValueError):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error))
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="서버 내부 오류가 발생했습니다.")
|
||||
|
||||
def log_request(self, endpoint: str, user_id: int = None, **kwargs):
|
||||
"""요청 로그 기록"""
|
||||
log_data = {"endpoint": endpoint, "controller": self.__class__.__name__, "timestamp": datetime.now().isoformat()}
|
||||
if user_id:
|
||||
log_data["user_id"] = user_id
|
||||
log_data.update(kwargs)
|
||||
self.logger.info(f"Request to {endpoint}", extra=log_data)
|
||||
|
||||
def handle_service_error(self, error: Exception, operation: str = "unknown"):
|
||||
"""서비스 에러 처리"""
|
||||
self.logger.error(f"Service error in {operation}: {str(error)}", exc_info=True)
|
||||
|
||||
if isinstance(error, ValueError):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error))
|
||||
elif "찾을 수 없습니다" in str(error) or "없음" in str(error):
|
||||
# 데이터 조회 실패인 경우 404로 처리
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error))
|
||||
elif "Claude API" in str(error) or "AI" in str(error):
|
||||
# AI 서비스 오류인 경우 구체적 메시지 전달
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=f"AI 서비스 오류: {str(error)}")
|
||||
elif "데이터베이스" in str(error) or "쿼리" in str(error):
|
||||
# 데이터베이스 오류인 경우
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"데이터베이스 오류: {str(error)}")
|
||||
else:
|
||||
# 기타 오류는 구체적 메시지와 함께 500으로 처리
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(error))
|
||||
@@ -0,0 +1,124 @@
|
||||
# app/controllers/chat_controller.py
|
||||
"""
|
||||
HealthSync AI 챗봇 상담 컨트롤러
|
||||
"""
|
||||
from fastapi import APIRouter, status, HTTPException, Query
|
||||
from app.controllers.base_controller import BaseController
|
||||
from app.services.chat_service import ChatService
|
||||
from app.dto.request.chat_request import ChatRequest
|
||||
from app.dto.response.chat_response import ChatResponse
|
||||
from app.dto.response.chat_history_response import ChatHistoryResponse
|
||||
|
||||
|
||||
class ChatController(BaseController):
|
||||
"""챗봇 상담 관련 컨트롤러"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.chat_service = ChatService()
|
||||
self.router = APIRouter()
|
||||
self._setup_routes()
|
||||
|
||||
def _setup_routes(self):
|
||||
"""라우트 설정"""
|
||||
|
||||
@self.router.post("/consultation",
|
||||
response_model=ChatResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="💬 AI 건강 상담 챗봇",
|
||||
description="""
|
||||
사용자의 건강검진 데이터를 기반으로 AI가 개인화된 건강 상담을 제공합니다.
|
||||
|
||||
**처리 과정:**
|
||||
1. 사용자 질문 데이터베이스에 저장
|
||||
2. 사용자 기본 정보 조회 (직업, 나이 등)
|
||||
3. 최신 건강검진 데이터 조회
|
||||
4. 사용자 질문과 건강 데이터를 Claude AI로 분석
|
||||
5. 맞춤형 건강 상담 답변 생성
|
||||
6. 질문에 대한 응답 내용을 데이터베이스에 저장
|
||||
|
||||
**상담 특징:**
|
||||
- 개인 건강 데이터 기반 맞춤형 답변
|
||||
- 직업 특성을 고려한 건강 조언
|
||||
- 의학적 근거에 기반한 정확한 정보 제공
|
||||
- 실질적이고 실행 가능한 건강 관리 방법 제시
|
||||
- 모든 상담 내용 자동 저장 및 이력 관리
|
||||
|
||||
**주의사항:**
|
||||
- 이 서비스는 의학적 진단이나 치료를 대체하지 않습니다
|
||||
- 심각한 증상이 있을 경우 반드시 의료진과 상담하세요
|
||||
""")
|
||||
async def health_consultation(request: ChatRequest) -> ChatResponse:
|
||||
"""AI 기반 건강 상담 챗봇 (DB 저장 포함)"""
|
||||
try:
|
||||
self.log_request("health_consultation", user_id=request.user_id,
|
||||
message_preview=request.message[:50] + "..." if len(request.message) > 50 else request.message)
|
||||
|
||||
# 챗봇 상담 서비스 호출 (DB 저장 포함)
|
||||
response = await self.chat_service.get_health_consultation(
|
||||
user_id=request.user_id,
|
||||
message=request.message
|
||||
)
|
||||
|
||||
self.logger.info(f"건강 상담 성공 - user_id: {request.user_id}, "
|
||||
f"질문 길이: {len(request.message)}, 답변 길이: {len(response.response)}")
|
||||
|
||||
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, "health_consultation")
|
||||
|
||||
@self.router.get("/history",
|
||||
response_model=ChatHistoryResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="📋 채팅 히스토리 조회",
|
||||
description="""
|
||||
사용자의 모든 채팅 기록을 조회합니다.
|
||||
|
||||
**처리 과정:**
|
||||
1. 사용자 ID로 데이터베이스에서 채팅 기록 조회
|
||||
2. 시간 역순으로 정렬하여 반환
|
||||
3. 질문과 응답을 모두 포함한 전체 이력 제공
|
||||
|
||||
**응답 특징:**
|
||||
- 최신 채팅이 먼저 표시됨 (시간 역순)
|
||||
- 페이지네이션 없이 전체 기록 반환
|
||||
- 메시지 타입별 구분 (상담, 축하, 독려 등)
|
||||
- 각 채팅의 고유 ID와 생성 시간 포함
|
||||
|
||||
**활용 방안:**
|
||||
- 이전 상담 내용 참고
|
||||
- 건강 관리 진행 상황 확인
|
||||
- AI 응답 품질 개선을 위한 피드백 수집
|
||||
""")
|
||||
async def get_chat_history(user_id: int = Query(..., gt=0, description="사용자 ID")) -> ChatHistoryResponse:
|
||||
"""사용자 채팅 히스토리 조회"""
|
||||
try:
|
||||
self.log_request("get_chat_history", user_id=user_id)
|
||||
|
||||
# 채팅 이력 서비스 호출
|
||||
response = await self.chat_service.get_chat_history(user_id)
|
||||
|
||||
self.logger.info(f"채팅 이력 조회 성공 - user_id: {user_id}, "
|
||||
f"총 채팅 수: {response.total_count}")
|
||||
|
||||
return response
|
||||
|
||||
except ValueError as e:
|
||||
self.logger.warning(f"잘못된 요청 - user_id: {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, "get_chat_history")
|
||||
|
||||
|
||||
# 컨트롤러 인스턴스 생성
|
||||
chat_controller = ChatController()
|
||||
@@ -0,0 +1,70 @@
|
||||
# app/controllers/health_controller.py
|
||||
"""
|
||||
HealthSync AI 건강 분석 컨트롤러
|
||||
"""
|
||||
from fastapi import APIRouter, status, HTTPException, Query
|
||||
from app.controllers.base_controller import BaseController
|
||||
from app.services.health_service import HealthService
|
||||
from app.dto.response.health_response import HealthDiagnosisResponse
|
||||
|
||||
|
||||
class HealthController(BaseController):
|
||||
"""건강 분석 관련 컨트롤러"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.health_service = HealthService()
|
||||
self.router = APIRouter()
|
||||
self._setup_routes()
|
||||
|
||||
def _setup_routes(self):
|
||||
"""라우트 설정"""
|
||||
|
||||
@self.router.get("/diagnosis",
|
||||
response_model=HealthDiagnosisResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="🔬 AI 건강검진 3줄 요약 진단",
|
||||
description="""
|
||||
사용자의 건강검진 데이터를 기반으로 AI가 3줄로 요약 진단을 제공합니다.
|
||||
|
||||
**처리 과정:**
|
||||
1. 사용자 기본 정보 조회 (직업, 나이 등)
|
||||
2. 최신 건강검진 데이터 조회
|
||||
3. Claude AI를 통한 의학적 분석
|
||||
4. 3문장으로 구성된 요약 진단 반환
|
||||
|
||||
**진단 특징:**
|
||||
- 객관적이고 정확한 의학적 판단
|
||||
- 가장 중요한 건강 지표 우선 분석
|
||||
- 직업 특성을 고려한 맞춤형 조언
|
||||
- 개선점과 긍정적 요소의 균형 있는 제시
|
||||
""")
|
||||
async def get_health_diagnosis(user_id: int = Query(..., gt=0, description="사용자 ID")) -> HealthDiagnosisResponse:
|
||||
"""AI 기반 건강검진 3줄 요약 진단"""
|
||||
try:
|
||||
self.log_request("get_health_diagnosis", user_id=user_id)
|
||||
|
||||
# 건강 진단 서비스 호출
|
||||
diagnosis = await self.health_service.get_three_sentence_diagnosis(user_id)
|
||||
|
||||
# 응답 생성
|
||||
response = HealthDiagnosisResponse(threeSentenceSummary=diagnosis)
|
||||
|
||||
self.logger.info(f"건강 진단 성공 - user_id: {user_id}, "
|
||||
f"진단 길이: {len(diagnosis)} 문자")
|
||||
|
||||
return response
|
||||
|
||||
except ValueError as e:
|
||||
# 사용자나 데이터를 찾을 수 없는 경우
|
||||
self.logger.warning(f"데이터 없음 - user_id: {user_id}, error: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(e)
|
||||
)
|
||||
except Exception as e:
|
||||
self.handle_service_error(e, "get_health_diagnosis")
|
||||
|
||||
|
||||
# 컨트롤러 인스턴스 생성
|
||||
health_controller = HealthController()
|
||||
@@ -0,0 +1,295 @@
|
||||
# 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()
|
||||
@@ -0,0 +1,98 @@
|
||||
# app/controllers/status_controller.py
|
||||
"""
|
||||
HealthSync AI 헬스체크 컨트롤러 (DB 연결 기능 추가)
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, status, Query, HTTPException
|
||||
from app.controllers.base_controller import BaseController
|
||||
from app.models.base import BaseResponse
|
||||
from app.core.dependencies import get_settings
|
||||
from app.config.settings import Settings
|
||||
from app.utils.database_utils import simple_db
|
||||
import time, psutil
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List
|
||||
|
||||
class StatusController(BaseController):
|
||||
"""상태체크 관련 컨트롤러 (DB 기능 포함)"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.router = APIRouter()
|
||||
self._setup_routes()
|
||||
|
||||
def _setup_routes(self):
|
||||
"""라우트 설정"""
|
||||
|
||||
@self.router.get("/check", response_model=BaseResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="🔧 시스템 상태 확인")
|
||||
async def get_system_status(app_settings: Settings = Depends(get_settings)):
|
||||
"""시스템 상태 확인"""
|
||||
try:
|
||||
self.log_request("system_status")
|
||||
try:
|
||||
memory_mb = round(psutil.Process().memory_info().rss / 1024 / 1024, 2)
|
||||
except:
|
||||
memory_mb = 0.0
|
||||
|
||||
status_data = {
|
||||
"status": "running",
|
||||
"service": app_settings.app_name,
|
||||
"version": app_settings.app_version,
|
||||
"memory_mb": memory_mb,
|
||||
"environment": "development" if app_settings.debug else "production"
|
||||
}
|
||||
|
||||
return self.create_success_response(
|
||||
data=status_data,
|
||||
message="서비스가 정상 동작 중입니다! 🚀"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.handle_service_error(e, "system_status")
|
||||
|
||||
@self.router.get("/database",
|
||||
response_model=BaseResponse[Dict[str, Any]],
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="🗄️ 데이터베이스 연결 테스트")
|
||||
async def test_database_connection():
|
||||
"""PostgreSQL 데이터베이스 연결 상태 확인"""
|
||||
try:
|
||||
self.log_request("database_connection_test")
|
||||
|
||||
connection_info = await simple_db.test_connection()
|
||||
|
||||
if connection_info.get("status") == "connected":
|
||||
return self.create_success_response(
|
||||
data=connection_info,
|
||||
message="데이터베이스 연결이 정상입니다! 🎉"
|
||||
)
|
||||
else:
|
||||
return self.create_success_response(
|
||||
data=connection_info,
|
||||
message="데이터베이스 연결에 문제가 있습니다. ⚠️"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.handle_service_error(e, "database_connection_test")
|
||||
|
||||
@self.router.get("/tables",
|
||||
response_model=BaseResponse[List[Dict[str, Any]]],
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="📋 테이블 목록 조회")
|
||||
async def list_database_tables():
|
||||
"""데이터베이스 테이블 목록 조회"""
|
||||
try:
|
||||
self.log_request("database_tables_list")
|
||||
|
||||
tables = await simple_db.list_tables()
|
||||
|
||||
return self.create_success_response(
|
||||
data=tables,
|
||||
message=f"총 {len(tables)}개의 테이블을 조회했습니다. 📊"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.handle_service_error(e, "database_tables_list")
|
||||
|
||||
status_controller = StatusController()
|
||||
Reference in New Issue
Block a user