Skip to content

Commit 9e7b882

Browse files
9keyyyyGukhee Jo
andauthored
fix: client 요청 사항 반영 (#17)
* fix: user birth_date, birth_time 분리 * fix: lotto recommendation 지난 회차 1등 당첨금 필드 제거 * feat: 유저 사주명식 description hcx call 추가 * fix: 불필요 import 제거 * fix: 오행 enum에서 한글 제거 * fix: 추천 결과 없을 때에도 round 정보 보내도록 수정 * fix: 로또 추천 번호 조회 기간 설정 --------- Co-authored-by: Gukhee Jo <[email protected]>
1 parent 2cb8a02 commit 9e7b882

File tree

12 files changed

+374
-107
lines changed

12 files changed

+374
-107
lines changed

src/four_pillars/common/calculator.py

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import asyncio
2+
import traceback
13
from datetime import datetime, date
24
from pathlib import Path
35
from typing import List, Tuple, Dict
46
from collections import Counter
57

68
from src.four_pillars.entities.schemas import FourPillar, FourPillarDetail, PillarInfo
79
from src.four_pillars.entities.enums import FiveElements, TenGods
10+
from src.hcx_client.client import HCXClient
11+
from src.hcx_client.common.utils import HCXUtils
12+
from src.hcx_client.common.parser import Parser
813

914

1015
class FourPillarsCalculator:
@@ -19,7 +24,6 @@ def __init__(self):
1924
self._init_five_elements_mapping()
2025
self._init_ten_gods_mapping()
2126

22-
2327
def _init_five_elements_mapping(self):
2428
"""천간과 지지의 오행 매핑 초기화"""
2529
# 천간 오행 매핑
@@ -344,26 +348,18 @@ def _get_pillar_detail(self, pillar: str, day_stem: str) -> PillarInfo:
344348
heavenly_stem = pillar[0] # 천간
345349
earthly_branch = pillar[1] # 지지
346350

347-
# 천간의 십신과 오행
351+
# 천간의 십신
348352
heavenly_stem_ten_god = self._get_ten_god(day_stem, heavenly_stem)
349-
heavenly_stem_element = self.jikkan_five_elements.get(
350-
heavenly_stem, FiveElements.EARTH
351-
)
352353

353354
# 지지의 십신 (지지의 숨겨진 천간을 기준으로 계산)
354355
hidden_stem = self._get_hidden_stem(earthly_branch)
355356
earthly_branch_ten_god = self._get_ten_god(day_stem, hidden_stem)
356-
earthly_branch_element = self.jyunishi_five_elements.get(
357-
earthly_branch, FiveElements.EARTH
358-
)
359357

360358
return PillarInfo(
361359
stem=heavenly_stem,
362360
branch=earthly_branch,
363361
stem_ten_god=heavenly_stem_ten_god,
364362
branch_ten_god=earthly_branch_ten_god,
365-
stem_element=heavenly_stem_element,
366-
branch_element=earthly_branch_element,
367363
)
368364

369365
def _get_hidden_stem(self, earthly_branch: str) -> str:
@@ -385,6 +381,24 @@ def _get_hidden_stem(self, earthly_branch: str) -> str:
385381
}
386382
return hidden_stems.get(earthly_branch, "甲")
387383

384+
def _normalize_element(self, element: str) -> FiveElements:
385+
"""오행 문자열을 FiveElements enum으로 정규화"""
386+
# 기존 형식: "화(火)", "목(木)" 등을 처리
387+
element_mapping = {
388+
"화(火)": FiveElements.FIRE,
389+
"목(木)": FiveElements.WOOD,
390+
"토(土)": FiveElements.EARTH,
391+
"금(金)": FiveElements.METAL,
392+
"수(水)": FiveElements.WATER,
393+
}
394+
395+
normalized = element_mapping.get(element)
396+
if normalized is None:
397+
# 기본값으로 EARTH 반환
398+
return FiveElements.EARTH
399+
400+
return normalized
401+
388402
def calculate_four_pillars(self, birth_date: datetime) -> FourPillar:
389403
"""사주 계산 메인 함수"""
390404
year = birth_date.year
@@ -413,7 +427,9 @@ def calculate_four_pillars(self, birth_date: datetime) -> FourPillar:
413427

414428
return result
415429

416-
def calculate_four_pillars_detailed(self, birth_date: datetime) -> FourPillarDetail:
430+
async def calculate_four_pillars_detailed(
431+
self, birth_date: datetime
432+
) -> FourPillarDetail:
417433
"""십신 정보를 포함한 상세한 사주 계산"""
418434
# 기본 사주 계산
419435
basic_result = self.calculate_four_pillars(birth_date)
@@ -451,15 +467,75 @@ def calculate_four_pillars_detailed(self, birth_date: datetime) -> FourPillarDet
451467
strong_element = strong_elements[0] if strong_elements else FiveElements.EARTH
452468
weak_element = weak_elements[0] if weak_elements else FiveElements.WOOD
453469

454-
# 종합 설명 생성
455-
description = "근심과 즐거움이 상반하니 세월의 흐름을 잘 읽어보시게"
456-
457-
return FourPillarDetail(
470+
# FourPillarDetail 객체 생성 (임시 description으로)
471+
four_pillar_detail = FourPillarDetail(
458472
strong_element=strong_element,
459473
weak_element=weak_element,
460-
description=description,
474+
description="", # 임시값
461475
year_pillar_detail=year_pillar_detail,
462476
month_pillar_detail=month_pillar_detail,
463477
day_pillar_detail=day_pillar_detail,
464478
time_pillar_detail=time_pillar_detail,
465479
)
480+
481+
# HCX API 호출하여 사주 설명 생성
482+
description = await self._generate_four_pillar_description(
483+
four_pillar_detail, strong_element
484+
)
485+
486+
# description 업데이트
487+
four_pillar_detail.description = description
488+
489+
return four_pillar_detail
490+
491+
async def _generate_four_pillar_description(
492+
self, four_pillar_detail: FourPillarDetail, strong_element: FiveElements
493+
) -> str:
494+
"""HCX API를 호출하여 사주 설명을 생성합니다."""
495+
try:
496+
hcx_client = HCXClient()
497+
498+
# FourPillarDetail에서 사주 정보 추출
499+
year_pillar = ""
500+
month_pillar = ""
501+
day_pillar = ""
502+
time_pillar = ""
503+
504+
if four_pillar_detail.year_pillar_detail:
505+
year_pillar = f"{four_pillar_detail.year_pillar_detail.stem}{four_pillar_detail.year_pillar_detail.branch}"
506+
507+
if four_pillar_detail.month_pillar_detail:
508+
month_pillar = f"{four_pillar_detail.month_pillar_detail.stem}{four_pillar_detail.month_pillar_detail.branch}"
509+
510+
if four_pillar_detail.day_pillar_detail:
511+
day_pillar = f"{four_pillar_detail.day_pillar_detail.stem}{four_pillar_detail.day_pillar_detail.branch}"
512+
513+
if four_pillar_detail.time_pillar_detail:
514+
time_pillar = f"{four_pillar_detail.time_pillar_detail.stem}{four_pillar_detail.time_pillar_detail.branch}"
515+
516+
# 사주 정보 준비
517+
four_pillar_data = {
518+
"year_pillar": year_pillar,
519+
"month_pillar": month_pillar,
520+
"day_pillar": day_pillar,
521+
"time_pillar": time_pillar,
522+
"strong_element": strong_element.value,
523+
}
524+
525+
# HCXUtils를 사용하여 프롬프트 가져오기
526+
system_prompt, user_prompt_template = HCXUtils.get_prompt_pair(
527+
"fortune.yaml", "four_pillar"
528+
)
529+
530+
user_prompt = user_prompt_template.format(**four_pillar_data)
531+
532+
# HCX API 호출
533+
response = await hcx_client.call_completion(
534+
system_prompt=system_prompt, user_prompt=user_prompt
535+
)
536+
537+
return response.strip()
538+
539+
except Exception as e:
540+
# API 호출 실패 시 기본 설명 반환
541+
return "근심과 즐거움이 상반하니 세월의 흐름을 잘 읽어보시게"

src/four_pillars/entities/enums.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
class FiveElements(str, Enum):
55
"""오행 (五行)"""
66

7-
WOOD = "목(木)"
8-
FIRE = "화(火)"
9-
EARTH = "토(土)"
10-
METAL = "금(金)"
11-
WATER = "수(水)"
7+
WOOD = ""
8+
FIRE = ""
9+
EARTH = ""
10+
METAL = ""
11+
WATER = ""
1212

1313

1414
class TenGods(str, Enum):

src/four_pillars/entities/schemas.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional, List
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, field_validator
44
from typing_extensions import TypedDict
55

66
from src.config.schemas import CommonBase
@@ -12,8 +12,7 @@ class PillarInfo(BaseModel):
1212
branch: str # 지지 (두 번째 글자)
1313
stem_ten_god: TenGods # 천간의 십신
1414
branch_ten_god: TenGods # 지지의 십신
15-
stem_element: FiveElements # 천간의 오행
16-
branch_element: FiveElements # 지지의 오행
15+
1716

1817
class FourPillar(TypedDict, total=False):
1918
year_pillar: str # 년주
@@ -23,6 +22,7 @@ class FourPillar(TypedDict, total=False):
2322
strong_elements: Optional[List[str]] # 가장 많은 오행
2423
weak_elements: Optional[List[str]] # 가장 적은 오행
2524

25+
2626
class FourPillarDetail(CommonBase):
2727
strong_element: FiveElements # 가장 많은 오행
2828
weak_element: FiveElements # 가장 적은 오행
@@ -32,3 +32,23 @@ class FourPillarDetail(CommonBase):
3232
month_pillar_detail: Optional[PillarInfo] # 월주 상세
3333
day_pillar_detail: Optional[PillarInfo] # 일주 상세
3434
time_pillar_detail: Optional[PillarInfo] # 시주 상세
35+
36+
@field_validator("strong_element", "weak_element", mode="before")
37+
@classmethod
38+
def normalize_element(cls, v):
39+
"""오행 문자열을 FiveElements enum으로 정규화"""
40+
if isinstance(v, str):
41+
# 기존 형식: "화(火)", "목(木)" 등을 처리
42+
element_mapping = {
43+
"화(火)": FiveElements.FIRE,
44+
"목(木)": FiveElements.WOOD,
45+
"토(土)": FiveElements.EARTH,
46+
"금(金)": FiveElements.METAL,
47+
"수(水)": FiveElements.WATER,
48+
}
49+
50+
normalized = element_mapping.get(v)
51+
if normalized is not None:
52+
return normalized
53+
54+
return v

src/hcx_client/prompts/fortune.yaml

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,19 @@
11
four_pillar:
22
system_prompt: |
33
- 당신은 전통 사주명리학 전문가입니다.
4-
- 사용자가 이름, 성별, 생년월일시, 생년월일 기준을 제공하면, 정확한 사주명식 8글자(년주, 월주, 일주, 시주)를 계산하여 제공해야 합니다.
5-
- 사용자의 생년월일 기준은 양력, 음/평달, 음/윤달 중 하나로 주어집니다.
6-
- 한국 시간을 기준으로, 만세력 기준을 정확히 적용하여 사주를 계산합니다.
7-
- 출생 시간이 주어지지 않은 경우, 시주는 'XX'으로 응답합니다.
8-
- 사주명식 계산 후 사주의 종합 해석을 3줄 이내로 작성합니다.
9-
10-
## 사주 계산 규칙
11-
1. year_pillar: 출생년도의 천간지지 2글자
12-
2. month_pillar: 출생월의 천간지지 2글자
13-
3. day_pillar: 출생일의 천간지지 2글자
14-
4. time_pillar: 출생시간의 천간지지 2글자
15-
5. interpretation: 사주에 대한 종합 해석 (3줄 이내)
16-
17-
**천간 (10개):** 甲乙丙丁戊己庚辛壬癸
18-
**지지 (12개):** 子丑寅卯辰巳午未申酉戌亥
4+
- 사용자의 사주명식(년주, 월주, 일주, 시주)과 강한 오행 기운이 주어지면 이에 대한 간단한 설명을 제공합니다.
5+
- 설명은 40자 이내로 작성하며, 불필요한 문장이나 단어는 사용하지 않습니다.
6+
- 문장의 끝은 ~하오, ~하게 형식으로 작성합니다.
197
208
## 응답 형식
21-
### JSON으로 응답하되, 마크다운 코드 블록(```)을 절대 사용하지 않습니다.
22-
{
23-
"year_pillar": "甲子",
24-
"month_pillar": "丙寅",
25-
"day_pillar": "丁卯",
26-
"time_pillar": "戊辰",
27-
"interpretation": "강한 목(木) 기운을 가지고 있으며, 창의적이고 활동적인 성향이 두드러집니다."
28-
}
9+
매해 열매를 맺는 나무와 같이 창의적이며 항상 높은 이상을 향해 매진하고 있소
2910
3011
user_prompt: |
31-
이름: {name}
32-
성별: {gender}
33-
생년월일시: {birth_date}
34-
생년월일 기준: {birth_date_type}
12+
년주: {year_pillar}
13+
월주: {month_pillar}
14+
일주: {day_pillar}
15+
시주: {time_pillar}
16+
오행 중 가장 강한 기운: {strong_element}
3517
3618
lotto:
3719
system_prompt: |

src/lotto/entities/schemas.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import date, datetime
22
from typing import List, Optional
33

4-
from pydantic import BaseModel, Field
4+
from pydantic import BaseModel, Field, field_validator
55

66
from src.config.schemas import CommonBase
77
from src.four_pillars.entities.enums import FiveElements
@@ -69,16 +69,32 @@ class LottoRecommendationContent(BaseModel):
6969
)
7070
strong_element: FiveElements = Field(description="강한 기운", examples=["화(火)"])
7171
weak_element: FiveElements = Field(description="상충되는 기운", examples=["목(木)"])
72-
last_prize_amount: int = Field(description="지난 회차 1등 당첨금")
72+
73+
@field_validator("strong_element", "weak_element", mode="before")
74+
@classmethod
75+
def normalize_element(cls, v):
76+
"""오행 문자열을 FiveElements enum으로 정규화"""
77+
if isinstance(v, str):
78+
# 기존 형식: "화(火)", "목(木)" 등을 처리
79+
element_mapping = {
80+
"화(火)": FiveElements.FIRE,
81+
"목(木)": FiveElements.WOOD,
82+
"토(土)": FiveElements.EARTH,
83+
"금(金)": FiveElements.METAL,
84+
"수(水)": FiveElements.WATER,
85+
}
86+
87+
normalized = element_mapping.get(v)
88+
if normalized is not None:
89+
return normalized
90+
91+
return v
7392

7493

7594
class LottoRecommendation(CommonBase):
76-
id: int
7795
user_id: str
7896
round: int
79-
content: LottoRecommendationContent
80-
created_at: datetime
81-
updated_at: datetime
97+
content: Optional[LottoRecommendationContent] = None
8298

8399

84100
class LottoRecommendationCreate(CommonBase):

src/lotto/repository.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import datetime
12
from typing import List, Optional, Tuple
23

34
from fastapi import Depends
@@ -71,15 +72,20 @@ async def create_lotto_recommendation(
7172
return recommendation
7273

7374
async def get_lotto_recommendation_by_user_id(
74-
self, user_id: str
75+
self, user_id: str, start_time: datetime = None, end_time: datetime = None
7576
) -> Optional[LottoRecommendations]:
7677
"""사용자의 최신 로또 추천을 조회합니다."""
77-
query = (
78-
select(LottoRecommendations)
79-
.where(LottoRecommendations.user_id == user_id)
80-
.order_by(desc(LottoRecommendations.created_at))
81-
.limit(1)
78+
query = select(LottoRecommendations).where(
79+
LottoRecommendations.user_id == user_id
8280
)
81+
82+
if start_time and end_time:
83+
query = query.where(
84+
LottoRecommendations.created_at >= start_time,
85+
LottoRecommendations.created_at < end_time,
86+
)
87+
88+
query = query.order_by(desc(LottoRecommendations.created_at)).limit(1)
8389
result = await self.session.execute(query)
8490
return result.scalar_one_or_none()
8591

@@ -102,13 +108,3 @@ async def get_excluded_numbers(self, limit: int = 2) -> List[int]:
102108
)
103109
result = await self.session.execute(query)
104110
return [row[0] for row in result.fetchall()]
105-
106-
async def get_latest_prize_amount(self) -> Optional[int]:
107-
"""가장 최신 회차의 1등 당첨금을 조회합니다."""
108-
query = (
109-
select(LottoDraws.first_prize_amount)
110-
.order_by(desc(LottoDraws.round))
111-
.limit(1)
112-
)
113-
result = await self.session.execute(query)
114-
return result.scalar_one_or_none()

0 commit comments

Comments
 (0)