로또 프리미엄 Phase 2 — 구매 이력 + 개인 패턴 분석 + 주간 리포트 캐싱
- purchase_history 테이블 추가 (draw_no, amount, sets, prize, note) - weekly_reports 캐시 테이블 추가 (drw_no UNIQUE, report JSON) - GET /api/lotto/purchase 구매 이력 조회 (draw_no, days 필터) - POST /api/lotto/purchase 구매 이력 추가 - PUT /api/lotto/purchase/:id 구매 이력 수정 (당첨금 업데이트) - DELETE /api/lotto/purchase/:id 구매 이력 삭제 - GET /api/lotto/purchase/stats 투자 수익률 통계 - GET /api/lotto/analysis/personal 개인 패턴 분석 (top/least picks, 홀짝/구간/연속번호) - GET /api/lotto/report/history 저장된 주간 리포트 목록 - GET /api/lotto/report/:drw_no 캐시 우선 조회 + cached 플래그 - 스케줄러: 토요일 09:00 주간 리포트 자동 생성 및 DB 캐싱 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,13 +22,19 @@ from .db import (
|
||||
get_subscription_profile, upsert_subscription_profile,
|
||||
# 성과 통계
|
||||
get_recommendation_performance,
|
||||
# Phase 2: 구매 이력
|
||||
add_purchase, get_purchases, update_purchase, delete_purchase, get_purchase_stats,
|
||||
# Phase 2: 주간 리포트 캐시
|
||||
save_weekly_report, get_weekly_report_list, get_weekly_report,
|
||||
# Phase 2: 개인 패턴 분석
|
||||
get_all_recommendation_numbers,
|
||||
)
|
||||
from .recommender import recommend_numbers, recommend_with_heatmap
|
||||
from .collector import sync_latest, sync_ensure_all
|
||||
from .generator import run_simulation, generate_smart_recommendations
|
||||
from .checker import check_results_for_draw
|
||||
from .utils import calc_metrics, calc_recent_overlap
|
||||
from .analyzer import get_statistical_report, generate_weekly_report
|
||||
from .analyzer import get_statistical_report, generate_weekly_report, analyze_personal_patterns
|
||||
|
||||
app = FastAPI()
|
||||
scheduler = BackgroundScheduler(timezone=os.getenv("TZ", "Asia/Seoul"))
|
||||
@@ -57,6 +63,20 @@ def on_startup():
|
||||
|
||||
scheduler.add_job(_run_simulation_job, "cron", hour="0,4,8,12,16,20", minute=5)
|
||||
|
||||
# 3. 토요일 오전 9시 — 다음 회차 공략 리포트 자동 캐싱
|
||||
def _save_weekly_report_job():
|
||||
import json as _json
|
||||
draws = get_all_draw_numbers()
|
||||
latest = get_latest_draw()
|
||||
if not draws or not latest:
|
||||
return
|
||||
target = latest["drw_no"] + 1
|
||||
report = generate_weekly_report(draws, target)
|
||||
save_weekly_report(target, _json.dumps(report, ensure_ascii=False))
|
||||
print(f"[WeeklyReport] {target}회차 리포트 저장 완료")
|
||||
|
||||
scheduler.add_job(_save_weekly_report_job, "cron", day_of_week="sat", hour=9, minute=0)
|
||||
|
||||
scheduler.start()
|
||||
|
||||
|
||||
@@ -179,20 +199,95 @@ def api_report_latest():
|
||||
return generate_weekly_report(draws, target)
|
||||
|
||||
|
||||
@app.get("/api/lotto/report/history")
|
||||
def api_report_history(limit: int = 10):
|
||||
"""저장된 주간 리포트 목록 (자동 저장된 것만)"""
|
||||
return {"reports": get_weekly_report_list(limit=min(limit, 52))}
|
||||
|
||||
|
||||
@app.get("/api/lotto/report/{drw_no}")
|
||||
def api_report_by_draw(drw_no: int):
|
||||
"""
|
||||
특정 회차 공략 리포트 (해당 회차 이전 데이터 기준).
|
||||
drw_no: 공략 대상 회차 번호
|
||||
특정 회차 공략 리포트 (캐시 우선, 없으면 실시간 생성).
|
||||
"""
|
||||
cached = get_weekly_report(drw_no)
|
||||
if cached:
|
||||
return {**cached, "cached": True}
|
||||
|
||||
draws = get_all_draw_numbers()
|
||||
if not draws:
|
||||
raise HTTPException(status_code=404, detail="No data yet")
|
||||
# drw_no 이전 데이터만 사용
|
||||
base_draws = [(no, nums) for no, nums in draws if no < drw_no]
|
||||
if not base_draws:
|
||||
raise HTTPException(status_code=400, detail=f"{drw_no}회차 이전 데이터가 없습니다")
|
||||
return generate_weekly_report(base_draws, drw_no)
|
||||
return {**generate_weekly_report(base_draws, drw_no), "cached": False}
|
||||
|
||||
|
||||
# ── 개인 패턴 분석 (Phase 2) ─────────────────────────────────────────────────
|
||||
@app.get("/api/lotto/analysis/personal")
|
||||
def api_personal_analysis():
|
||||
"""
|
||||
저장된 추천 이력 기반 개인 패턴 분석.
|
||||
- 자주 선택한 번호 TOP 10 / 한 번도 선택 안 한 번호
|
||||
- 홀짝 비율, 합계, 범위, 연속번호 포함률
|
||||
- 구간별 분포, 역대 당첨번호 평균과 비교
|
||||
"""
|
||||
all_numbers = get_all_recommendation_numbers()
|
||||
draws = get_all_draw_numbers()
|
||||
return analyze_personal_patterns(all_numbers, draws)
|
||||
|
||||
|
||||
# ── 구매 이력 API (Phase 2) ───────────────────────────────────────────────────
|
||||
|
||||
class PurchaseCreate(BaseModel):
|
||||
draw_no: int
|
||||
amount: int
|
||||
sets: int = 1
|
||||
prize: int = 0
|
||||
note: str = ""
|
||||
|
||||
|
||||
class PurchaseUpdate(BaseModel):
|
||||
draw_no: Optional[int] = None
|
||||
amount: Optional[int] = None
|
||||
sets: Optional[int] = None
|
||||
prize: Optional[int] = None
|
||||
note: Optional[str] = None
|
||||
|
||||
|
||||
@app.get("/api/lotto/purchase/stats")
|
||||
def api_purchase_stats():
|
||||
"""투자 수익률 통계 (총 투자금, 총 당첨금, 수익률 등)"""
|
||||
return get_purchase_stats()
|
||||
|
||||
|
||||
@app.get("/api/lotto/purchase")
|
||||
def api_purchase_list(draw_no: Optional[int] = None, days: Optional[int] = None):
|
||||
"""구매 이력 조회 (draw_no, days 필터 선택)"""
|
||||
return {"records": get_purchases(draw_no=draw_no, days=days)}
|
||||
|
||||
|
||||
@app.post("/api/lotto/purchase", status_code=201)
|
||||
def api_purchase_create(body: PurchaseCreate):
|
||||
"""구매 이력 추가"""
|
||||
return add_purchase(body.draw_no, body.amount, body.sets, body.prize, body.note)
|
||||
|
||||
|
||||
@app.put("/api/lotto/purchase/{purchase_id}")
|
||||
def api_purchase_update(purchase_id: int, body: PurchaseUpdate):
|
||||
"""구매 이력 수정 (당첨금 업데이트 등)"""
|
||||
updated = update_purchase(purchase_id, body.model_dump(exclude_none=True))
|
||||
if updated is None:
|
||||
raise HTTPException(status_code=404, detail="Purchase not found")
|
||||
return updated
|
||||
|
||||
|
||||
@app.delete("/api/lotto/purchase/{purchase_id}")
|
||||
def api_purchase_delete(purchase_id: int):
|
||||
"""구매 이력 삭제"""
|
||||
if not delete_purchase(purchase_id):
|
||||
raise HTTPException(status_code=404, detail="Purchase not found")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ── 통계 분석 리포트 ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user