로또 프리미엄 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:
@@ -355,6 +355,76 @@ def get_statistical_report(draws: List[Tuple[int, List[int]]]) -> Dict[str, Any]
|
||||
}
|
||||
|
||||
|
||||
def analyze_personal_patterns(
|
||||
all_numbers: List[List[int]],
|
||||
draws: List[Tuple[int, List[int]]],
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
사용자 추천 이력 기반 개인 패턴 분석.
|
||||
all_numbers: 저장된 모든 추천 번호 리스트 (각 원소는 6개짜리 리스트)
|
||||
draws: 역대 당첨번호 (홀짝/합계 평균 비교용)
|
||||
"""
|
||||
if not all_numbers:
|
||||
return {"total_analyzed": 0, "message": "추천 이력이 없습니다"}
|
||||
|
||||
total = len(all_numbers)
|
||||
flat = [n for nums in all_numbers for n in nums]
|
||||
freq = Counter(flat)
|
||||
|
||||
# 번호별 선택 빈도
|
||||
number_frequency = {n: freq.get(n, 0) for n in range(1, 46)}
|
||||
top_picks = sorted(range(1, 46), key=lambda n: -freq.get(n, 0))[:10]
|
||||
least_picks = [n for n in sorted(range(1, 46), key=lambda n: freq.get(n, 0)) if freq.get(n, 0) == 0][:10]
|
||||
|
||||
# 패턴 지표
|
||||
odd_counts = [sum(1 for n in nums if n % 2 == 1) for nums in all_numbers]
|
||||
sums = [sum(nums) for nums in all_numbers]
|
||||
ranges = [max(nums) - min(nums) for nums in all_numbers]
|
||||
consecutive_count = sum(
|
||||
1 for nums in all_numbers
|
||||
if any(sorted(nums)[i + 1] - sorted(nums)[i] == 1 for i in range(5))
|
||||
)
|
||||
|
||||
zone_totals = {k: 0 for k in ["1-9", "10-19", "20-29", "30-39", "40-45"]}
|
||||
zone_ranges = [("1-9", 1, 9), ("10-19", 10, 19), ("20-29", 20, 29), ("30-39", 30, 39), ("40-45", 40, 45)]
|
||||
for nums in all_numbers:
|
||||
for label, lo, hi in zone_ranges:
|
||||
zone_totals[label] += sum(1 for n in nums if lo <= n <= hi)
|
||||
zone_avg = {k: round(v / total, 2) for k, v in zone_totals.items()}
|
||||
|
||||
avg_odd = sum(odd_counts) / total
|
||||
avg_sum = sum(sums) / total
|
||||
avg_range = sum(ranges) / total
|
||||
|
||||
# 역대 당첨번호 평균과 비교
|
||||
if draws:
|
||||
draw_odd_avg = sum(sum(1 for n in nums if n % 2 == 1) for _, nums in draws) / len(draws)
|
||||
draw_sum_avg = sum(sum(nums) for _, nums in draws) / len(draws)
|
||||
else:
|
||||
draw_odd_avg = 3.0
|
||||
draw_sum_avg = 138.0
|
||||
|
||||
return {
|
||||
"total_analyzed": total,
|
||||
"number_frequency": number_frequency,
|
||||
"top_picks": top_picks,
|
||||
"least_picks": least_picks,
|
||||
"pattern": {
|
||||
"avg_odd_count": round(avg_odd, 2),
|
||||
"avg_sum": round(avg_sum, 1),
|
||||
"avg_range": round(avg_range, 1),
|
||||
"consecutive_rate": round(consecutive_count / total, 3),
|
||||
"zone_avg": zone_avg,
|
||||
},
|
||||
"vs_draw_avg": {
|
||||
"odd_diff": round(avg_odd - draw_odd_avg, 2),
|
||||
"sum_diff": round(avg_sum - draw_sum_avg, 1),
|
||||
"odd_tendency": "홀수 선호" if avg_odd > draw_odd_avg + 0.2 else ("짝수 선호" if avg_odd < draw_odd_avg - 0.2 else "균형"),
|
||||
"sum_tendency": "고합계 선호" if avg_sum > draw_sum_avg + 5 else ("저합계 선호" if avg_sum < draw_sum_avg - 5 else "균형"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def generate_weekly_report(draws: List[Tuple[int, List[int]]], target_drw_no: int) -> Dict[str, Any]:
|
||||
"""
|
||||
특정 회차 공략 리포트 생성.
|
||||
|
||||
Reference in New Issue
Block a user