This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
# app/repositories/__init__.py
|
||||
"""
|
||||
HealthSync AI 리포지토리 패키지
|
||||
데이터베이스 쿼리 로직을 관리합니다.
|
||||
"""
|
||||
from .health_repository import HealthRepository
|
||||
from .chat_repository import ChatRepository
|
||||
from .mission_repository import MissionRepository
|
||||
from .similar_mission_repository import SimilarMissionRepository
|
||||
|
||||
__all__ = [
|
||||
"HealthRepository",
|
||||
"ChatRepository",
|
||||
"MissionRepository",
|
||||
"SimilarMissionRepository"
|
||||
]
|
||||
@@ -0,0 +1,107 @@
|
||||
# app/repositories/chat_repository.py
|
||||
"""
|
||||
HealthSync AI 채팅 데이터 리포지토리
|
||||
"""
|
||||
from typing import Dict, Any, Optional, List
|
||||
from datetime import datetime
|
||||
from app.repositories.queries.chat_queries import ChatQueries
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ChatRepository:
|
||||
"""채팅 데이터 DB 조회/저장 리포지토리"""
|
||||
|
||||
@staticmethod
|
||||
def _get_db():
|
||||
"""simple_db를 lazy import로 가져오기 (순환 import 방지)"""
|
||||
from app.utils.database_utils import simple_db
|
||||
return simple_db
|
||||
|
||||
@staticmethod
|
||||
async def save_chat_message(user_id: int, message_type: str,
|
||||
message_content: Optional[str] = None,
|
||||
response_content: Optional[str] = None) -> int:
|
||||
"""채팅 메시지 저장 및 ID 반환"""
|
||||
try:
|
||||
simple_db = ChatRepository._get_db()
|
||||
|
||||
chat_data = {
|
||||
"member_serial_number": user_id,
|
||||
"message_type": message_type,
|
||||
"message_content": message_content,
|
||||
"response_content": response_content,
|
||||
"created_at": datetime.now()
|
||||
}
|
||||
|
||||
logger.info(f"채팅 메시지 저장 시도 - user_id: {user_id}, type: {message_type}")
|
||||
|
||||
# INSERT ... RETURNING 쿼리 실행
|
||||
result = await simple_db.execute_insert_with_return(
|
||||
ChatQueries.INSERT_CHAT_MESSAGE_WITH_RETURN,
|
||||
chat_data
|
||||
)
|
||||
|
||||
if result and "message_id" in result:
|
||||
message_id = result["message_id"]
|
||||
logger.info(f"채팅 메시지 저장 성공 - message_id: {message_id}, user_id: {user_id}")
|
||||
return message_id
|
||||
else:
|
||||
raise Exception("INSERT RETURNING에서 message_id 반환되지 않음")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"채팅 메시지 저장 실패 - user_id: {user_id}, error: {str(e)}")
|
||||
logger.error(f"저장 시도 데이터: {chat_data}")
|
||||
raise Exception(f"채팅 메시지 저장 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def update_chat_message_response(message_id: int, response_content: str) -> bool:
|
||||
"""채팅 메시지 응답 내용 업데이트"""
|
||||
try:
|
||||
simple_db = ChatRepository._get_db()
|
||||
|
||||
update_data = {
|
||||
"message_id": message_id,
|
||||
"response_content": response_content,
|
||||
"updated_at": datetime.now()
|
||||
}
|
||||
|
||||
logger.info(f"채팅 메시지 응답 업데이트 시도 - message_id: {message_id}")
|
||||
|
||||
# UPDATE 쿼리 실행
|
||||
affected_rows = await simple_db.execute_insert_update(
|
||||
ChatQueries.UPDATE_CHAT_MESSAGE_RESPONSE,
|
||||
update_data
|
||||
)
|
||||
|
||||
# None 체크 추가
|
||||
if affected_rows is not None and affected_rows > 0:
|
||||
logger.info(f"채팅 메시지 응답 업데이트 성공 - message_id: {message_id}, affected_rows: {affected_rows}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(
|
||||
f"채팅 메시지 응답 업데이트 실패 - 영향받은 행이 없음: message_id={message_id}, affected_rows={affected_rows}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"채팅 메시지 응답 업데이트 실패 - message_id: {message_id}, error: {str(e)}")
|
||||
# 업데이트 실패해도 예외를 발생시키지 않고 False만 반환
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
async def get_chat_history_by_user_id(user_id: int) -> List[Dict[str, Any]]:
|
||||
"""사용자 ID로 채팅 이력 조회"""
|
||||
try:
|
||||
simple_db = ChatRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
ChatQueries.GET_CHAT_HISTORY_BY_USER,
|
||||
{"user_id": user_id}
|
||||
)
|
||||
|
||||
logger.info(f"채팅 이력 조회 성공 - user_id: {user_id}, count: {len(result)}")
|
||||
return result if result else []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"채팅 이력 조회 실패 - user_id: {user_id}, error: {str(e)}")
|
||||
raise Exception(f"채팅 이력 조회 실패: {str(e)}")
|
||||
@@ -0,0 +1,198 @@
|
||||
# app/repositories/health_repository.py
|
||||
"""
|
||||
HealthSync AI 건강 데이터 리포지토리 (쿼리 분리)
|
||||
"""
|
||||
from typing import Dict, Any, Optional, List
|
||||
from app.repositories.queries import HealthQueries, UserQueries
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HealthRepository:
|
||||
"""건강 데이터 DB 조회 리포지토리"""
|
||||
|
||||
@staticmethod
|
||||
def _get_db():
|
||||
"""simple_db를 lazy import로 가져오기 (순환 import 방지)"""
|
||||
from app.utils.database_utils import simple_db
|
||||
return simple_db
|
||||
|
||||
@staticmethod
|
||||
async def get_latest_health_checkup_by_user_id(user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""사용자 ID로 최신 건강검진 데이터 조회"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
HealthQueries.GET_LATEST_HEALTH_CHECKUP,
|
||||
{"user_id": user_id}
|
||||
)
|
||||
|
||||
if result and len(result) > 0:
|
||||
return result[0]
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"건강검진 데이터 조회 실패 - user_id: {user_id}, error: {str(e)}")
|
||||
raise Exception(f"건강검진 데이터 조회 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def get_user_basic_info_by_id(user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""사용자 ID로 기본 정보 조회"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
UserQueries.GET_USER_BASIC_INFO,
|
||||
{"user_id": user_id}
|
||||
)
|
||||
|
||||
if result and len(result) > 0:
|
||||
return result[0]
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"사용자 기본정보 조회 실패 - user_id: {user_id}, error: {str(e)}")
|
||||
raise Exception(f"사용자 기본정보 조회 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def get_health_history_by_user_id(user_id: int, limit: int = 5) -> List[Dict[str, Any]]:
|
||||
"""사용자 건강검진 이력 조회"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
HealthQueries.GET_HEALTH_HISTORY,
|
||||
{"user_id": user_id, "limit": limit}
|
||||
)
|
||||
return result if result else []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"건강검진 이력 조회 실패 - user_id: {user_id}, error: {str(e)}")
|
||||
raise Exception(f"건강검진 이력 조회 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def get_normal_ranges_by_gender(gender_code: int = 0) -> List[Dict[str, Any]]:
|
||||
"""성별에 따른 정상치 기준 조회"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
HealthQueries.GET_NORMAL_RANGES,
|
||||
{"gender_code": gender_code}
|
||||
)
|
||||
return result if result else []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"정상치 기준 조회 실패 - gender_code: {gender_code}, error: {str(e)}")
|
||||
raise Exception(f"정상치 기준 조회 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def check_user_exists(user_id: int) -> bool:
|
||||
"""사용자 존재 여부 확인"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
UserQueries.CHECK_USER_EXISTS,
|
||||
{"user_id": user_id}
|
||||
)
|
||||
|
||||
if result and len(result) > 0:
|
||||
return result[0]["user_count"] > 0
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"사용자 존재 확인 실패 - user_id: {user_id}, error: {str(e)}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
async def get_user_by_google_id(google_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Google ID로 사용자 조회"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
UserQueries.GET_USER_BY_GOOGLE_ID,
|
||||
{"google_id": google_id}
|
||||
)
|
||||
|
||||
if result and len(result) > 0:
|
||||
return result[0]
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Google ID 사용자 조회 실패 - google_id: {google_id}, error: {str(e)}")
|
||||
raise Exception(f"Google ID 사용자 조회 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def insert_health_checkup(health_data: Dict[str, Any]) -> int:
|
||||
"""건강검진 데이터 삽입"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
result = await simple_db.execute_insert_update(
|
||||
HealthQueries.INSERT_HEALTH_CHECKUP,
|
||||
health_data
|
||||
)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"건강검진 데이터 삽입 실패 - error: {str(e)}")
|
||||
raise Exception(f"건강검진 데이터 삽입 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def update_health_checkup(checkup_id: int, update_data: Dict[str, Any]) -> int:
|
||||
"""건강검진 데이터 업데이트"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
update_data["checkup_id"] = checkup_id
|
||||
result = await simple_db.execute_insert_update(
|
||||
HealthQueries.UPDATE_HEALTH_CHECKUP,
|
||||
update_data
|
||||
)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"건강검진 데이터 업데이트 실패 - checkup_id: {checkup_id}, error: {str(e)}")
|
||||
raise Exception(f"건강검진 데이터 업데이트 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def insert_user(user_data: Dict[str, Any]) -> int:
|
||||
"""사용자 생성"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
result = await simple_db.execute_insert_update(
|
||||
UserQueries.INSERT_USER,
|
||||
user_data
|
||||
)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"사용자 생성 실패 - error: {str(e)}")
|
||||
raise Exception(f"사용자 생성 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def update_user_info(member_serial_number: int, user_data: Dict[str, Any]) -> int:
|
||||
"""사용자 정보 업데이트"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
user_data["member_serial_number"] = member_serial_number
|
||||
result = await simple_db.execute_insert_update(
|
||||
UserQueries.UPDATE_USER_INFO,
|
||||
user_data
|
||||
)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"사용자 정보 업데이트 실패 - member_serial_number: {member_serial_number}, error: {str(e)}")
|
||||
raise Exception(f"사용자 정보 업데이트 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def update_last_login(member_serial_number: int, last_login_at: str) -> int:
|
||||
"""최근 로그인 시간 업데이트"""
|
||||
try:
|
||||
simple_db = HealthRepository._get_db()
|
||||
result = await simple_db.execute_insert_update(
|
||||
UserQueries.UPDATE_LAST_LOGIN,
|
||||
{"member_serial_number": member_serial_number, "last_login_at": last_login_at}
|
||||
)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"로그인 시간 업데이트 실패 - member_serial_number: {member_serial_number}, error: {str(e)}")
|
||||
raise Exception(f"로그인 시간 업데이트 실패: {str(e)}")
|
||||
@@ -0,0 +1,38 @@
|
||||
# app/repositories/mission_repository.py
|
||||
"""
|
||||
HealthSync AI 미션 데이터 리포지토리
|
||||
"""
|
||||
from typing import Dict, Any, Optional, List
|
||||
from app.repositories.queries.mission_queries import MissionQueries
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MissionRepository:
|
||||
"""미션 데이터 DB 조회 리포지토리"""
|
||||
|
||||
@staticmethod
|
||||
def _get_db():
|
||||
"""simple_db를 lazy import로 가져오기 (순환 import 방지)"""
|
||||
from app.utils.database_utils import simple_db
|
||||
return simple_db
|
||||
|
||||
@staticmethod
|
||||
async def get_mission_by_id(mission_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""미션 ID로 미션 정보 조회"""
|
||||
try:
|
||||
simple_db = MissionRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
MissionQueries.GET_USER_MISSION_BY_ID,
|
||||
{"mission_id": mission_id}
|
||||
)
|
||||
|
||||
if result and len(result) > 0:
|
||||
logger.info(f"미션 정보 조회 성공 - mission_id: {mission_id}")
|
||||
return result[0]
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"미션 정보 조회 실패 - mission_id: {mission_id}, error: {str(e)}")
|
||||
raise Exception(f"미션 정보 조회 실패: {str(e)}")
|
||||
@@ -0,0 +1,20 @@
|
||||
# app/repositories/queries/__init__.py
|
||||
"""
|
||||
HealthSync AI 쿼리 패키지
|
||||
모든 SQL 쿼리를 한 곳에서 관리합니다.
|
||||
"""
|
||||
from .base_queries import BaseQueries
|
||||
from .health_queries import HealthQueries
|
||||
from .user_queries import UserQueries
|
||||
from .chat_queries import ChatQueries
|
||||
from .mission_queries import MissionQueries
|
||||
from .similar_mission_queries import SimilarMissionQueries
|
||||
|
||||
__all__ = [
|
||||
"BaseQueries",
|
||||
"HealthQueries",
|
||||
"UserQueries",
|
||||
"ChatQueries",
|
||||
"MissionQueries",
|
||||
"SimilarMissionQueries"
|
||||
]
|
||||
@@ -0,0 +1,45 @@
|
||||
# app/repositories/queries/base_queries.py
|
||||
"""
|
||||
HealthSync AI 기본 시스템 쿼리 모음
|
||||
"""
|
||||
|
||||
|
||||
class BaseQueries:
|
||||
"""기본 시스템 쿼리"""
|
||||
|
||||
# 데이터베이스 연결 테스트
|
||||
CONNECTION_TEST = "SELECT 1"
|
||||
|
||||
DATABASE_VERSION = "SELECT version()"
|
||||
|
||||
CURRENT_DATABASE = "SELECT current_database()"
|
||||
|
||||
CURRENT_USER = "SELECT current_user"
|
||||
|
||||
# 테이블 목록 조회
|
||||
LIST_TABLES = """
|
||||
SELECT table_name,
|
||||
table_schema,
|
||||
table_type
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema NOT IN ('information_schema', 'pg_catalog')
|
||||
ORDER BY table_schema, table_name LIMIT 20 \
|
||||
"""
|
||||
|
||||
# 테이블 컬럼 정보 조회
|
||||
GET_TABLE_COLUMNS = """
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = :table_name
|
||||
ORDER BY ordinal_position \
|
||||
"""
|
||||
|
||||
# 테이블 데이터 조회 (동적 쿼리 - 주의해서 사용)
|
||||
@staticmethod
|
||||
def get_table_data_query(table_name: str, limit: int = 5) -> str:
|
||||
"""테이블 데이터 조회 쿼리 생성 (SQL 인젝션 방지를 위한 검증 필요)"""
|
||||
# 테이블 이름 검증
|
||||
if not table_name.replace('_', '').replace('-', '').isalnum():
|
||||
raise ValueError("잘못된 테이블 이름입니다.")
|
||||
|
||||
return f"SELECT * FROM {table_name} LIMIT {limit}"
|
||||
@@ -0,0 +1,45 @@
|
||||
# app/repositories/queries/chat_queries.py
|
||||
"""
|
||||
HealthSync AI 채팅 관련 쿼리 모음
|
||||
"""
|
||||
|
||||
|
||||
class ChatQueries:
|
||||
"""채팅 메시지 관련 쿼리"""
|
||||
|
||||
# 채팅 메시지 저장 및 ID 반환 (RETURNING 사용)
|
||||
INSERT_CHAT_MESSAGE_WITH_RETURN = """
|
||||
INSERT INTO intelligence_service.chat_message
|
||||
(member_serial_number, message_type, message_content, response_content, created_at)
|
||||
VALUES (:member_serial_number, :message_type, :message_content, :response_content, :created_at)
|
||||
RETURNING message_id
|
||||
"""
|
||||
|
||||
# 일반 채팅 메시지 저장
|
||||
INSERT_CHAT_MESSAGE = """
|
||||
INSERT INTO intelligence_service.chat_message
|
||||
(member_serial_number, message_type, message_content, response_content, created_at)
|
||||
VALUES (:member_serial_number, :message_type, :message_content, :response_content, :created_at)
|
||||
"""
|
||||
|
||||
# 채팅 메시지 응답 내용 업데이트
|
||||
UPDATE_CHAT_MESSAGE_RESPONSE = """
|
||||
UPDATE intelligence_service.chat_message
|
||||
SET response_content = :response_content,
|
||||
created_at = :updated_at
|
||||
WHERE message_id = :message_id
|
||||
"""
|
||||
|
||||
# 사용자별 채팅 이력 조회 (전체, 시간 역순)
|
||||
GET_CHAT_HISTORY_BY_USER = """
|
||||
SELECT
|
||||
cm.message_id,
|
||||
cm.member_serial_number,
|
||||
cm.message_type,
|
||||
cm.message_content,
|
||||
cm.response_content,
|
||||
cm.created_at
|
||||
FROM intelligence_service.chat_message cm
|
||||
WHERE cm.member_serial_number = :user_id
|
||||
ORDER BY cm.created_at DESC
|
||||
"""
|
||||
@@ -0,0 +1,47 @@
|
||||
# app/repositories/queries/health_queries.py
|
||||
"""
|
||||
HealthSync AI 건강 관련 쿼리 모음
|
||||
"""
|
||||
|
||||
|
||||
class HealthQueries:
|
||||
"""건강 데이터 관련 쿼리"""
|
||||
|
||||
# 최신 건강검진 데이터 조회
|
||||
GET_LATEST_HEALTH_CHECKUP = """
|
||||
SELECT
|
||||
hc.checkup_id,
|
||||
hc.member_serial_number,
|
||||
hc.reference_year,
|
||||
hc.age,
|
||||
hc.height,
|
||||
hc.weight,
|
||||
hc.bmi,
|
||||
hc.waist_circumference,
|
||||
hc.visual_acuity_left,
|
||||
hc.visual_acuity_right,
|
||||
hc.hearing_left,
|
||||
hc.hearing_right,
|
||||
hc.systolic_bp,
|
||||
hc.diastolic_bp,
|
||||
hc.fasting_glucose,
|
||||
hc.total_cholesterol,
|
||||
hc.triglyceride,
|
||||
hc.hdl_cholesterol,
|
||||
hc.ldl_cholesterol,
|
||||
hc.hemoglobin,
|
||||
hc.urine_protein,
|
||||
hc.serum_creatinine,
|
||||
hc.ast,
|
||||
hc.alt,
|
||||
hc.gamma_gtp,
|
||||
hc.smoking_status,
|
||||
hc.drinking_status,
|
||||
hc.processed_at,
|
||||
hc.created_at
|
||||
FROM health_service.health_checkup hc
|
||||
INNER JOIN user_service.user u ON hc.member_serial_number = u.member_serial_number
|
||||
WHERE u.member_serial_number = :user_id
|
||||
ORDER BY hc.reference_year DESC, hc.created_at DESC
|
||||
LIMIT 1
|
||||
"""
|
||||
@@ -0,0 +1,37 @@
|
||||
# app/repositories/queries/mission_queries.py
|
||||
"""
|
||||
HealthSync AI 미션 관련 쿼리 모음
|
||||
"""
|
||||
|
||||
|
||||
class MissionQueries:
|
||||
"""미션 관련 쿼리"""
|
||||
|
||||
# 사용자 미션 정보 조회
|
||||
GET_USER_MISSION_BY_ID = """
|
||||
SELECT
|
||||
umg.mission_id,
|
||||
umg.member_serial_number,
|
||||
umg.mission_name,
|
||||
umg.mission_description,
|
||||
umg.daily_target_count,
|
||||
umg.is_active,
|
||||
umg.performance_date,
|
||||
umg.created_at
|
||||
FROM goal_service.user_mission_goal umg
|
||||
WHERE umg.mission_id = :mission_id
|
||||
"""
|
||||
|
||||
# 사용자별 활성 미션 목록 조회
|
||||
GET_ACTIVE_MISSIONS_BY_USER = """
|
||||
SELECT
|
||||
umg.mission_id,
|
||||
umg.mission_name,
|
||||
umg.mission_description,
|
||||
umg.daily_target_count,
|
||||
umg.performance_date
|
||||
FROM goal_service.user_mission_goal umg
|
||||
WHERE umg.member_serial_number = :user_id
|
||||
AND umg.is_active = true
|
||||
ORDER BY umg.created_at DESC
|
||||
"""
|
||||
@@ -0,0 +1,156 @@
|
||||
# app/repositories/queries/similar_mission_queries.py
|
||||
"""
|
||||
HealthSync AI 유사 사용자 미션 관련 쿼리 모음 (건강 데이터 포함)
|
||||
"""
|
||||
|
||||
|
||||
class SimilarMissionQueries:
|
||||
"""유사 사용자 미션 관련 쿼리 (건강 데이터 강화)"""
|
||||
|
||||
# 최근 24시간 내 미션 완료 이력 조회 (건강 데이터 포함)
|
||||
GET_RECENT_MISSION_COMPLETIONS = """
|
||||
SELECT
|
||||
mch.member_serial_number,
|
||||
u.name,
|
||||
u.occupation,
|
||||
u.birth_date,
|
||||
EXTRACT(YEAR FROM AGE(u.birth_date)) as age,
|
||||
umg.mission_name,
|
||||
umg.mission_description,
|
||||
mch.daily_completed_count,
|
||||
mch.completion_date,
|
||||
mch.created_at,
|
||||
-- 건강 데이터 추가
|
||||
hc.height,
|
||||
hc.weight,
|
||||
hc.bmi,
|
||||
hc.waist_circumference,
|
||||
hc.systolic_bp,
|
||||
hc.diastolic_bp,
|
||||
hc.fasting_glucose,
|
||||
hc.total_cholesterol,
|
||||
hc.hdl_cholesterol,
|
||||
hc.ldl_cholesterol,
|
||||
hc.triglyceride,
|
||||
hc.ast,
|
||||
hc.alt,
|
||||
hc.gamma_gtp,
|
||||
hc.serum_creatinine,
|
||||
hc.hemoglobin,
|
||||
hc.smoking_status,
|
||||
hc.drinking_status
|
||||
FROM goal_service.mission_completion_history mch
|
||||
INNER JOIN user_service.user u ON mch.member_serial_number = u.member_serial_number
|
||||
INNER JOIN goal_service.user_mission_goal umg ON mch.mission_id = umg.mission_id
|
||||
LEFT JOIN health_service.health_checkup hc ON u.member_serial_number = hc.member_serial_number
|
||||
WHERE mch.member_serial_number = ANY(:user_ids)
|
||||
AND mch.completion_date >= CURRENT_DATE - INTERVAL '1 day'
|
||||
AND mch.daily_completed_count >= mch.daily_target_count
|
||||
AND (hc.reference_year IS NULL OR hc.reference_year = (
|
||||
SELECT MAX(reference_year)
|
||||
FROM health_service.health_checkup
|
||||
WHERE member_serial_number = u.member_serial_number
|
||||
))
|
||||
ORDER BY mch.created_at DESC
|
||||
LIMIT 20
|
||||
"""
|
||||
|
||||
# 사용자 건강 정보 조회 (벡터 생성용)
|
||||
GET_USER_HEALTH_FOR_VECTOR = """
|
||||
SELECT
|
||||
u.member_serial_number,
|
||||
u.name,
|
||||
u.occupation,
|
||||
EXTRACT(YEAR FROM AGE(u.birth_date)) as age,
|
||||
u.updated_at,
|
||||
hc.height,
|
||||
hc.weight,
|
||||
hc.bmi,
|
||||
hc.waist_circumference,
|
||||
hc.systolic_bp,
|
||||
hc.diastolic_bp,
|
||||
hc.fasting_glucose,
|
||||
hc.total_cholesterol,
|
||||
hc.hdl_cholesterol,
|
||||
hc.ldl_cholesterol,
|
||||
hc.triglyceride,
|
||||
hc.ast,
|
||||
hc.alt,
|
||||
hc.gamma_gtp,
|
||||
hc.serum_creatinine,
|
||||
hc.hemoglobin,
|
||||
hc.smoking_status,
|
||||
hc.drinking_status
|
||||
FROM user_service.user u
|
||||
LEFT JOIN health_service.health_checkup hc ON u.member_serial_number = hc.member_serial_number
|
||||
WHERE u.member_serial_number = :user_id
|
||||
AND (hc.reference_year IS NULL OR hc.reference_year = (
|
||||
SELECT MAX(reference_year)
|
||||
FROM health_service.health_checkup
|
||||
WHERE member_serial_number = u.member_serial_number
|
||||
))
|
||||
"""
|
||||
|
||||
# 직업 코드별 이름 조회
|
||||
GET_OCCUPATION_NAME = """
|
||||
SELECT
|
||||
occupation_code,
|
||||
occupation_name,
|
||||
category
|
||||
FROM user_service.occupation_type
|
||||
WHERE occupation_code = :occupation_code
|
||||
"""
|
||||
|
||||
# 사용자 기본 정보 조회 (여러 사용자)
|
||||
GET_USERS_BASIC_INFO = """
|
||||
SELECT
|
||||
u.member_serial_number,
|
||||
u.name,
|
||||
u.occupation,
|
||||
ot.occupation_name,
|
||||
EXTRACT(YEAR FROM AGE(u.birth_date)) as age
|
||||
FROM user_service.user u
|
||||
LEFT JOIN user_service.occupation_type ot ON u.occupation = ot.occupation_code
|
||||
WHERE u.member_serial_number = ANY(:user_ids)
|
||||
"""
|
||||
|
||||
# 벡터 처리를 위한 모든 사용자 데이터 조회
|
||||
GET_ALL_USERS_FOR_VECTOR = """
|
||||
SELECT
|
||||
u.member_serial_number,
|
||||
u.name,
|
||||
u.occupation,
|
||||
EXTRACT(YEAR FROM AGE(u.birth_date)) as age,
|
||||
u.updated_at,
|
||||
hc.height,
|
||||
hc.weight,
|
||||
hc.bmi,
|
||||
hc.waist_circumference,
|
||||
hc.systolic_bp,
|
||||
hc.diastolic_bp,
|
||||
hc.fasting_glucose,
|
||||
hc.total_cholesterol,
|
||||
hc.hdl_cholesterol,
|
||||
hc.ldl_cholesterol,
|
||||
hc.triglyceride,
|
||||
hc.ast,
|
||||
hc.alt,
|
||||
hc.gamma_gtp,
|
||||
hc.serum_creatinine,
|
||||
hc.hemoglobin,
|
||||
hc.visual_acuity_left,
|
||||
hc.visual_acuity_right,
|
||||
hc.hearing_left,
|
||||
hc.hearing_right,
|
||||
hc.urine_protein,
|
||||
hc.smoking_status,
|
||||
hc.drinking_status
|
||||
FROM user_service.user u
|
||||
LEFT JOIN health_service.health_checkup hc ON u.member_serial_number = hc.member_serial_number
|
||||
WHERE hc.reference_year IS NULL OR hc.reference_year = (
|
||||
SELECT MAX(reference_year)
|
||||
FROM health_service.health_checkup
|
||||
WHERE member_serial_number = u.member_serial_number
|
||||
)
|
||||
ORDER BY u.member_serial_number
|
||||
"""
|
||||
@@ -0,0 +1,71 @@
|
||||
# app/repositories/queries/user_queries.py
|
||||
"""
|
||||
HealthSync AI 사용자 관련 쿼리 모음
|
||||
"""
|
||||
|
||||
|
||||
class UserQueries:
|
||||
"""사용자 관련 쿼리"""
|
||||
|
||||
# 사용자 기본 정보 조회
|
||||
GET_USER_BASIC_INFO = """
|
||||
SELECT
|
||||
u.member_serial_number,
|
||||
u.google_id,
|
||||
u.name,
|
||||
u.birth_date,
|
||||
u.occupation,
|
||||
EXTRACT(YEAR FROM AGE(u.birth_date)) as age,
|
||||
u.created_at,
|
||||
u.updated_at,
|
||||
u.last_login_at
|
||||
FROM user_service.user u
|
||||
WHERE u.member_serial_number = :user_id
|
||||
"""
|
||||
|
||||
# 사용자 존재 여부 확인
|
||||
CHECK_USER_EXISTS = """
|
||||
SELECT COUNT(*) as user_count
|
||||
FROM user_service.user
|
||||
WHERE member_serial_number = :user_id
|
||||
"""
|
||||
|
||||
# Google ID로 사용자 조회
|
||||
GET_USER_BY_GOOGLE_ID = """
|
||||
SELECT
|
||||
u.member_serial_number,
|
||||
u.google_id,
|
||||
u.name,
|
||||
u.birth_date,
|
||||
u.occupation,
|
||||
EXTRACT(YEAR FROM AGE(u.birth_date)) as age,
|
||||
u.created_at,
|
||||
u.updated_at,
|
||||
u.last_login_at
|
||||
FROM user_service.user u
|
||||
WHERE u.google_id = :google_id
|
||||
"""
|
||||
|
||||
# 사용자 생성
|
||||
INSERT_USER = """
|
||||
INSERT INTO user_service.user
|
||||
(google_id, name, birth_date, occupation, created_at, updated_at)
|
||||
VALUES (:google_id, :name, :birth_date, :occupation, :created_at, :updated_at)
|
||||
"""
|
||||
|
||||
# 사용자 정보 업데이트
|
||||
UPDATE_USER_INFO = """
|
||||
UPDATE user_service.user
|
||||
SET name = :name,
|
||||
birth_date = :birth_date,
|
||||
occupation = :occupation,
|
||||
updated_at = :updated_at
|
||||
WHERE member_serial_number = :member_serial_number
|
||||
"""
|
||||
|
||||
# 최근 로그인 시간 업데이트
|
||||
UPDATE_LAST_LOGIN = """
|
||||
UPDATE user_service.user
|
||||
SET last_login_at = :last_login_at
|
||||
WHERE member_serial_number = :member_serial_number
|
||||
"""
|
||||
@@ -0,0 +1,129 @@
|
||||
# app/repositories/similar_mission_repository.py
|
||||
"""
|
||||
HealthSync AI 유사 사용자 미션 데이터 리포지토리
|
||||
"""
|
||||
from typing import Dict, Any, Optional, List
|
||||
from app.repositories.queries.similar_mission_queries import SimilarMissionQueries
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SimilarMissionRepository:
|
||||
"""유사 사용자 미션 데이터 DB 조회 리포지토리"""
|
||||
|
||||
@staticmethod
|
||||
def _get_db():
|
||||
"""simple_db를 lazy import로 가져오기 (순환 import 방지)"""
|
||||
from app.utils.database_utils import simple_db
|
||||
return simple_db
|
||||
|
||||
@staticmethod
|
||||
async def get_recent_mission_completions(user_ids: List[int]) -> List[Dict[str, Any]]:
|
||||
"""유사 사용자들의 최근 24시간 미션 완료 이력 조회"""
|
||||
try:
|
||||
simple_db = SimilarMissionRepository._get_db()
|
||||
|
||||
if not user_ids:
|
||||
return []
|
||||
|
||||
result = await simple_db.execute_query(
|
||||
SimilarMissionQueries.GET_RECENT_MISSION_COMPLETIONS,
|
||||
{"user_ids": user_ids}
|
||||
)
|
||||
|
||||
logger.info(f"최근 미션 완료 이력 조회 성공 - user_count: {len(user_ids)}, "
|
||||
f"completion_count: {len(result)}")
|
||||
return result if result else []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"최근 미션 완료 이력 조회 실패 - user_ids: {user_ids}, error: {str(e)}")
|
||||
raise Exception(f"최근 미션 완료 이력 조회 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def get_user_health_for_vector(user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""벡터 생성을 위한 사용자 건강 정보 조회"""
|
||||
try:
|
||||
simple_db = SimilarMissionRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
SimilarMissionQueries.GET_USER_HEALTH_FOR_VECTOR,
|
||||
{"user_id": user_id}
|
||||
)
|
||||
|
||||
if result and len(result) > 0:
|
||||
logger.info(f"사용자 건강 정보 조회 성공 - user_id: {user_id}")
|
||||
return result[0]
|
||||
|
||||
logger.warning(f"사용자 건강 정보 없음 - user_id: {user_id}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"사용자 건강 정보 조회 실패 - user_id: {user_id}, error: {str(e)}")
|
||||
raise Exception(f"사용자 건강 정보 조회 실패: {str(e)}")
|
||||
|
||||
@staticmethod
|
||||
async def get_occupation_name(occupation_code: str) -> Optional[str]:
|
||||
"""직업 코드로 직업명 조회"""
|
||||
try:
|
||||
simple_db = SimilarMissionRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
SimilarMissionQueries.GET_OCCUPATION_NAME,
|
||||
{"occupation_code": occupation_code}
|
||||
)
|
||||
|
||||
if result and len(result) > 0:
|
||||
return result[0]["occupation_name"]
|
||||
|
||||
# 기본 직업명 매핑
|
||||
occupation_mapping = {
|
||||
"OFF001": "사무직",
|
||||
"MED001": "의료진",
|
||||
"EDU001": "교육직",
|
||||
"ENG001": "엔지니어",
|
||||
"SRV001": "서비스직"
|
||||
}
|
||||
return occupation_mapping.get(occupation_code, "기타")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"직업명 조회 실패 - occupation_code: {occupation_code}, error: {str(e)}")
|
||||
return "기타"
|
||||
|
||||
@staticmethod
|
||||
async def get_users_basic_info(user_ids: List[int]) -> List[Dict[str, Any]]:
|
||||
"""여러 사용자의 기본 정보 조회"""
|
||||
try:
|
||||
simple_db = SimilarMissionRepository._get_db()
|
||||
|
||||
if not user_ids:
|
||||
return []
|
||||
|
||||
result = await simple_db.execute_query(
|
||||
SimilarMissionQueries.GET_USERS_BASIC_INFO,
|
||||
{"user_ids": user_ids}
|
||||
)
|
||||
|
||||
logger.info(f"사용자 기본 정보 조회 성공 - user_count: {len(user_ids)}, "
|
||||
f"found_count: {len(result)}")
|
||||
return result if result else []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"사용자 기본 정보 조회 실패 - user_ids: {user_ids}, error: {str(e)}")
|
||||
raise Exception(f"사용자 기본 정보 조회 실패: {str(e)}")
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
async def get_all_users_for_vector() -> List[Dict[str, Any]]:
|
||||
"""벡터 처리를 위한 모든 사용자 데이터 조회"""
|
||||
try:
|
||||
simple_db = SimilarMissionRepository._get_db()
|
||||
result = await simple_db.execute_query(
|
||||
SimilarMissionQueries.GET_ALL_USERS_FOR_VECTOR
|
||||
)
|
||||
|
||||
logger.info(f"전체 사용자 벡터 데이터 조회 성공 - count: {len(result)}")
|
||||
return result if result else []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"전체 사용자 벡터 데이터 조회 실패 - error: {str(e)}")
|
||||
raise Exception(f"전체 사용자 벡터 데이터 조회 실패: {str(e)}")
|
||||
Reference in New Issue
Block a user