로또 프리미엄 Phase 1 — 추천 성과 통계 + 회차 공략 리포트 API

- GET /api/lotto/stats/performance: 채점 이력 기반 성과 통계
  (평균 일치 수, 등수 분포, 무작위 대비 개선율)
- GET /api/lotto/report/latest: 다음 회차 공략 리포트 자동 생성
- GET /api/lotto/report/{drw_no}: 특정 회차 공략 리포트
  (과출현/냉각/오버듀 번호, 최근 패턴, 3가지 전략 추천, 신뢰도 점수)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 23:48:28 +09:00
parent 05e7ffdfd9
commit 2ce118baba
3 changed files with 201 additions and 1 deletions

View File

@@ -596,6 +596,54 @@ def delete_recommendation(rec_id: int) -> bool:
cur = conn.execute("DELETE FROM recommendations WHERE id = ?", (rec_id,))
return cur.rowcount > 0
def get_recommendation_performance() -> Dict[str, Any]:
"""채점된 추천 이력 기반 성과 통계"""
with _conn() as conn:
rows = conn.execute(
"SELECT correct_count, rank FROM recommendations WHERE checked = 1"
).fetchall()
if not rows:
return {
"total_checked": 0,
"avg_correct": 0.0,
"distribution": {str(i): 0 for i in range(7)},
"rate_3plus": 0.0,
"rate_4plus": 0.0,
"by_rank": {"rank_1": 0, "rank_2": 0, "rank_3": 0, "rank_4": 0, "rank_5": 0, "no_prize": 0},
"vs_random": {"our_avg": 0.0, "random_avg": 0.8, "improvement_pct": 0.0},
}
total = len(rows)
corrects = [r["correct_count"] or 0 for r in rows]
ranks = [r["rank"] or 0 for r in rows]
avg_correct = sum(corrects) / total
RANDOM_AVG = 0.8 # 이론 기댓값: 6 * (6/45)
improvement = (avg_correct - RANDOM_AVG) / RANDOM_AVG * 100
return {
"total_checked": total,
"avg_correct": round(avg_correct, 3),
"distribution": {str(i): corrects.count(i) for i in range(7)},
"rate_3plus": round(sum(1 for c in corrects if c >= 3) / total, 4),
"rate_4plus": round(sum(1 for c in corrects if c >= 4) / total, 4),
"by_rank": {
"rank_1": ranks.count(1),
"rank_2": ranks.count(2),
"rank_3": ranks.count(3),
"rank_4": ranks.count(4),
"rank_5": ranks.count(5),
"no_prize": ranks.count(0),
},
"vs_random": {
"our_avg": round(avg_correct, 3),
"random_avg": RANDOM_AVG,
"improvement_pct": round(improvement, 1),
},
}
def update_recommendation_result(rec_id: int, rank: int, correct_count: int, has_bonus: bool) -> bool:
with _conn() as conn:
cur = conn.execute(