Skip to content

Commit afc4537

Browse files
9keyyyyGukhee Jo
andauthored
feat: 로또 번호 업데이트 스케줄러 추가 (#20)
* fix: 불필요 필드 제거 * fix: apscheduler package 추가 * feat: 로또 번호 업데이트 스케줄러 추가 * feat: 로또 회차별 조회 시 유저의 참여 여부 표기 추가 * fix: 스케줄러 요일 추가 --------- Co-authored-by: Gukhee Jo <[email protected]>
1 parent 3bb1aa2 commit afc4537

File tree

12 files changed

+281
-56
lines changed

12 files changed

+281
-56
lines changed

poetry.lock

Lines changed: 60 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ dependencies = [
2020
"gunicorn (>=23.0.0,<24.0.0)",
2121
"httpx (>=0.28.1,<0.29.0)",
2222
"pyyaml (>=6.0.2,<7.0.0)",
23-
"greenlet (>=3.2.3,<4.0.0)"
23+
"greenlet (>=3.2.3,<4.0.0)",
24+
"apscheduler (>=3.11.0,<4.0.0)"
2425
]
2526

2627

requirements.txt

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/common/scheduler/scheduler.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
2+
3+
from src.common.scheduler.tasks import update_next_lotto_draw
4+
5+
scheduler = AsyncIOScheduler(timezone="Asia/Seoul")
6+
7+
scheduler.add_job(update_next_lotto_draw, "cron", day_of_week="sat", hour="21", minute="0")

src/common/scheduler/tasks.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from src.common.logger import logger
2+
from src.config.config import db_config
3+
from src.config.database import Mysql
4+
from src.lotto.repository import LottoRepository
5+
from src.lotto.service import LottoService
6+
7+
8+
async def update_next_lotto_draw():
9+
"""다음 회차 로또 데이터를 동행복권 API에서 가져와서 데이터베이스에 저장합니다."""
10+
db = Mysql(db_config)
11+
12+
try:
13+
async with db.session() as session:
14+
repository = LottoRepository(session)
15+
lotto_service = LottoService(repository)
16+
success = await lotto_service.update_next_lotto_draw()
17+
await session.commit()
18+
if success:
19+
logger.info("로또 데이터 업데이트가 성공적으로 완료되었습니다.")
20+
else:
21+
logger.warning("로또 데이터 업데이트가 실패했습니다.")
22+
23+
except Exception as e:
24+
logger.error(f"로또 데이터 업데이트 중 오류 발생: {e}")
25+
finally:
26+
await db.close()

src/config/lifespan.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
from fastapi import FastAPI
44

55
from src.common.logger import start_logging
6+
from src.common.scheduler.scheduler import scheduler
67
from src.config.config import db_config
78
from src.config.database import Mysql
89

910

1011
@asynccontextmanager
1112
async def lifespan(app: FastAPI):
1213
try:
14+
scheduler.start()
1315
app.state.log_processor_task = await start_logging()
1416
app.state.mysql = Mysql(db_config)
1517
yield

src/four_pillars/common/calculator.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
import asyncio
2-
import traceback
1+
from collections import Counter
32
from datetime import datetime, date
43
from pathlib import Path
5-
from typing import List, Tuple, Dict
6-
from collections import Counter
4+
from typing import List, Tuple
75

8-
from src.four_pillars.entities.schemas import FourPillar, FourPillarDetail, PillarInfo
96
from src.four_pillars.entities.enums import FiveElements, TenGods
7+
from src.four_pillars.entities.schemas import FourPillar, FourPillarDetail, PillarInfo
108
from src.hcx_client.client import HCXClient
119
from src.hcx_client.common.utils import HCXUtils
12-
from src.hcx_client.common.parser import Parser
1310

1411

1512
class FourPillarsCalculator:
@@ -413,16 +410,11 @@ def calculate_four_pillars(self, birth_date: datetime) -> FourPillar:
413410
minute = None
414411

415412
pillars = self._calculate_kanshi(year, month, day, hour, minute)
416-
417-
# 오행 분석
418-
strong_elements, weak_elements = self._analyze_five_elements(pillars)
419413
result: FourPillar = {
420414
"year_pillar": pillars[0], # 년주
421415
"month_pillar": pillars[1], # 월주
422416
"day_pillar": pillars[2], # 일주
423417
"time_pillar": pillars[3], # 시주
424-
"strong_elements": strong_elements,
425-
"weak_elements": weak_elements,
426418
}
427419

428420
return result

src/four_pillars/entities/schemas.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ class FourPillar(TypedDict, total=False):
1919
month_pillar: str # 월주
2020
day_pillar: str # 일주
2121
time_pillar: Optional[str] # 시주
22-
strong_elements: Optional[List[str]] # 가장 많은 오행
23-
weak_elements: Optional[List[str]] # 가장 적은 오행
2422

2523

2624
class FourPillarDetail(CommonBase):

src/lotto/entities/schemas.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import date, datetime
1+
from datetime import date
22
from typing import List, Optional
33

44
from pydantic import BaseModel, Field, field_validator
@@ -19,6 +19,7 @@ class LottoDraw(CommonBase):
1919
bonus_num: int
2020
first_prize_amount: int
2121
total_winners: int
22+
has_recommendation: bool = False
2223

2324

2425
class LottoDrawList(CommonBase):

src/lotto/repository.py

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,42 @@ def __init__(self, session: AsyncSession = Depends(get_db_session)):
1515
self.session = session
1616

1717
async def get_lotto_draws(
18-
self, cursor: Optional[int] = None, limit: int = 10
18+
self,
19+
user_id: Optional[str] = None,
20+
cursor: Optional[int] = None,
21+
limit: int = 10,
1922
) -> Tuple[List[LottoDraws], Optional[int]]:
20-
query = select(LottoDraws).order_by(desc(LottoDraws.round))
23+
if user_id:
24+
query = (
25+
select(
26+
LottoDraws,
27+
(LottoRecommendations.id.is_not(None)).label("has_recommendation"),
28+
)
29+
.outerjoin(
30+
LottoRecommendations,
31+
(LottoDraws.round == LottoRecommendations.round)
32+
& (LottoRecommendations.user_id == user_id),
33+
)
34+
.order_by(desc(LottoDraws.round))
35+
)
36+
else:
37+
query = select(LottoDraws).order_by(desc(LottoDraws.round))
38+
2139
if cursor:
2240
query = query.where(LottoDraws.round < cursor)
2341

2442
query = query.limit(limit)
2543

2644
result = await self.session.execute(query)
27-
draws = result.scalars().all()
45+
46+
if user_id:
47+
rows = result.fetchall()
48+
draws = []
49+
for draw, has_recommendation in rows:
50+
draw.has_recommendation = bool(has_recommendation)
51+
draws.append(draw)
52+
else:
53+
draws = result.scalars().all()
2854

2955
next_cursor = draws[-1].round if len(draws) == limit else None
3056
return draws, next_cursor
@@ -57,6 +83,66 @@ async def get_latest_round(self) -> Optional[int]:
5783
latest = result.scalar_one_or_none()
5884
return latest
5985

86+
async def create_lotto_draw(self, lotto_data: dict) -> LottoDraws:
87+
"""로또 추첨 데이터를 생성합니다."""
88+
lotto_draw = LottoDraws(**lotto_data)
89+
self.session.add(lotto_draw)
90+
await self.session.flush()
91+
await self.session.refresh(lotto_draw)
92+
return lotto_draw
93+
94+
async def get_lotto_draw_by_round(self, round: int) -> Optional[LottoDraws]:
95+
"""특정 회차의 로또 추첨 데이터를 조회합니다."""
96+
query = select(LottoDraws).where(LottoDraws.round == round)
97+
result = await self.session.execute(query)
98+
return result.scalar_one_or_none()
99+
100+
async def update_lotto_statistics(self, lotto_data: dict) -> None:
101+
"""로또 통계 데이터를 업데이트합니다."""
102+
# 메인 번호들 (1-6번)과 보너스 번호
103+
main_numbers = [
104+
lotto_data["num1"],
105+
lotto_data["num2"],
106+
lotto_data["num3"],
107+
lotto_data["num4"],
108+
lotto_data["num5"],
109+
lotto_data["num6"],
110+
]
111+
bonus_number = lotto_data["bonus_num"]
112+
113+
# 당첨된 번호들만 수집 (중복 제거)
114+
winning_numbers = set(main_numbers + [bonus_number])
115+
116+
# 당첨된 번호들의 통계만 조회
117+
stats_query = select(LottoStatistics).where(
118+
LottoStatistics.num.in_(winning_numbers)
119+
)
120+
result = await self.session.execute(stats_query)
121+
existing_stats = {stat.num: stat for stat in result.scalars().all()}
122+
123+
# 각 당첨 번호에 대해 통계 업데이트
124+
for num in winning_numbers:
125+
if num in existing_stats:
126+
stat = existing_stats[num]
127+
else:
128+
# 새로운 통계 생성
129+
stat = LottoStatistics(
130+
num=num, main_count=0, bonus_count=0, total_count=0
131+
)
132+
self.session.add(stat)
133+
134+
# 카운트 업데이트
135+
if num in main_numbers:
136+
stat.main_count += 1
137+
stat.total_count += 1
138+
elif num == bonus_number:
139+
stat.bonus_count += 1
140+
stat.total_count += 1
141+
142+
# 마지막 출현 정보 업데이트
143+
stat.last_round = lotto_data["round"]
144+
stat.last_date = lotto_data["draw_date"]
145+
60146
async def create_lotto_recommendation(
61147
self, user_id: str, round: int, content: dict
62148
) -> LottoRecommendations:

0 commit comments

Comments
 (0)