feat : initial commit
HealthSync Intelligence CI / build-and-push (push) Has been cancelled

This commit is contained in:
hyerimmy
2025-06-20 05:28:30 +00:00
commit 910bd902b1
72 changed files with 6758 additions and 0 deletions
+16
View File
@@ -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"
]
+107
View File
@@ -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)}")
+198
View File
@@ -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)}")
+38
View File
@@ -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)}")
+20
View File
@@ -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"
]
+45
View File
@@ -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}"
+45
View File
@@ -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
"""
+71
View File
@@ -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)}")