14 KiB
14 KiB
Lotto 구매 연동 + 전략 진화 시스템 설계
작성일: 2026-04-05 상태: 승인 대기
1. 목표
로또 번호 추천 기능을 고도화하여:
- 동행복권 실 구매 연동 — 추천 번호를 클립보드 복사 + 동행복권 바로가기로 실제 구매 지원
- 가상 구매 모드 — 돈을 쓰지 않고 "이 번호로 구매한다"를 등록, 결과 발표 후 자동 가상 수익률 계산
- 전략 진화 시스템 — 구매 이력 기반으로 각 추천 전략(combined, simulation, heatmap, manual, custom)의 성과를 추적하고, EMA + Softmax로 가중치를 자동 조정하는 메타 전략
- 통합 구매 이력 — 실제/가상 구매를 하나의 리스트에서 관리하되, 실 구매는 시각적으로 강조
2. 접근 방식
방식 1 (단일 확장) 채택: 기존 lotto-backend(backend/) 서비스 내부에 모듈 추가.
- NAS Celeron J4025 환경에서 새 컨테이너 추가는 리소스 부담
- 기존 checker/recommender/DB와 자연스러운 연동 가능
- 파일 수준 모듈 분리로 유지보수성 확보
3. 데이터 모델
3.1 기존 purchase_history 테이블 마이그레이션
현재 스키마:
CREATE TABLE purchase_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
draw_no INTEGER NOT NULL,
amount INTEGER NOT NULL,
sets INTEGER NOT NULL DEFAULT 1,
prize INTEGER NOT NULL DEFAULT 0,
note TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
마이그레이션 전략: ALTER TABLE로 컬럼 추가 (기존 데이터 보존)
ALTER TABLE purchase_history ADD COLUMN numbers TEXT NOT NULL DEFAULT '[]';
ALTER TABLE purchase_history ADD COLUMN is_real INTEGER NOT NULL DEFAULT 1;
ALTER TABLE purchase_history ADD COLUMN source_strategy TEXT NOT NULL DEFAULT 'manual';
ALTER TABLE purchase_history ADD COLUMN source_detail TEXT NOT NULL DEFAULT '{}';
ALTER TABLE purchase_history ADD COLUMN checked INTEGER NOT NULL DEFAULT 0;
ALTER TABLE purchase_history ADD COLUMN results TEXT NOT NULL DEFAULT '[]';
ALTER TABLE purchase_history ADD COLUMN total_prize INTEGER NOT NULL DEFAULT 0;
- 기존 레코드:
is_real=1,source_strategy='manual',checked=0(기본값) - 기존
prize컬럼은 하위호환용으로 유지. 신규 로직은total_prize+results사용 - 기존
sets컬럼은 하위호환용으로 유지. 신규 로직은numbersJSON 배열 길이로 세트 수 산출
3.2 신규 strategy_performance 테이블
CREATE TABLE IF NOT EXISTS strategy_performance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
strategy TEXT NOT NULL,
draw_no INTEGER NOT NULL,
sets_count INTEGER NOT NULL DEFAULT 0,
total_correct INTEGER NOT NULL DEFAULT 0,
max_correct INTEGER NOT NULL DEFAULT 0,
prize_total INTEGER NOT NULL DEFAULT 0,
avg_score REAL NOT NULL DEFAULT 0.0,
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
UNIQUE(strategy, draw_no)
);
3.3 신규 strategy_weights 테이블
CREATE TABLE IF NOT EXISTS strategy_weights (
id INTEGER PRIMARY KEY AUTOINCREMENT,
strategy TEXT NOT NULL UNIQUE,
weight REAL NOT NULL DEFAULT 0.2,
ema_score REAL NOT NULL DEFAULT 0.15,
total_sets INTEGER NOT NULL DEFAULT 0,
total_hits_3plus INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
초기 가중치 (첫 실행 시 seed):
| strategy | weight | ema_score |
|---|---|---|
| combined | 0.30 | 0.15 |
| simulation | 0.25 | 0.15 |
| heatmap | 0.20 | 0.15 |
| manual | 0.15 | 0.15 |
| custom | 0.10 | 0.15 |
4. API 설계
4.1 구매 API (기존 경로 확장)
| 메서드 | 경로 | 변경 사항 |
|---|---|---|
POST |
/api/lotto/purchase |
요청 바디 확장 (numbers, is_real, source_strategy, source_detail 추가) |
GET |
/api/lotto/purchase |
필터 추가: is_real, strategy, checked |
GET |
/api/lotto/purchase/stats |
응답 확장: total/real/virtual + by_strategy 섹션 |
PUT |
/api/lotto/purchase/{id} |
기존 그대로 (allowed 필드 확장) |
DELETE |
/api/lotto/purchase/{id} |
기존 그대로 |
POST 요청 바디:
{
"draw_no": 1125,
"numbers": [[3, 12, 23, 34, 38, 45], [7, 14, 21, 29, 36, 42]],
"is_real": true,
"amount": 2000,
"source_strategy": "combined",
"source_detail": {"recommendation_ids": [451, 452]},
"note": ""
}
하위호환: numbers가 빈 배열이면 기존 방식(sets + amount만)으로 동작. is_real 미지정 시 기본값 true.
GET /purchase/stats 응답:
{
"total": {"sets": 48, "invested": 48000, "prize": 15000, "roi": -68.75, "win_rate": 12.5},
"real": {"sets": 20, "invested": 20000, "prize": 10000, "roi": -50.0, "win_rate": 15.0},
"virtual": {"sets": 28, "invested": 28000, "prize": 5000, "roi": -82.14, "win_rate": 10.7},
"by_strategy": {
"combined": {"sets": 15, "avg_correct": 1.8, "hits_3plus": 3, "roi": -45.0},
"simulation": {"sets": 12, "avg_correct": 2.1, "hits_3plus": 4, "roi": -30.0}
}
}
4.2 전략 진화 API (신규)
| 메서드 | 경로 | 설명 |
|---|---|---|
GET |
/api/lotto/strategy/weights |
현재 전략별 가중치 + 성과 요약 + trend |
GET |
/api/lotto/strategy/performance |
전략별 회차 성과 이력 (차트용, days 파라미터) |
POST |
/api/lotto/strategy/evolve |
수동 가중치 재계산 트리거 |
GET /strategy/weights 응답:
{
"weights": [
{"strategy": "combined", "weight": 0.32, "ema_score": 0.285, "total_sets": 15, "hits_3plus": 3, "trend": "up"},
{"strategy": "simulation", "weight": 0.28, "ema_score": 0.312, "total_sets": 12, "hits_3plus": 4, "trend": "up"},
{"strategy": "heatmap", "weight": 0.18, "ema_score": 0.195, "total_sets": 10, "hits_3plus": 1, "trend": "down"},
{"strategy": "manual", "weight": 0.14, "ema_score": 0.160, "total_sets": 8, "hits_3plus": 1, "trend": "stable"},
{"strategy": "custom", "weight": 0.08, "ema_score": 0.105, "total_sets": 3, "hits_3plus": 0, "trend": "stable"}
],
"last_evolved": "2026-04-05T09:10:00",
"min_data_draws": 10,
"current_data_draws": 32,
"status": "active"
}
4.3 스마트 추천 API (신규)
| 메서드 | 경로 | 설명 |
|---|---|---|
GET |
/api/lotto/recommend/smart |
전략 가중치 기반 메타 전략 추천. sets 파라미터 (기본 5) |
응답:
{
"sets": [
{
"numbers": [3, 12, 23, 34, 38, 45],
"meta_score": 0.847,
"source_strategy": "simulation",
"contribution": {"simulation": 0.42, "combined": 0.31, "heatmap": 0.27},
"individual_scores": {"frequency": 0.82, "fingerprint": 0.91, "gap": 0.78, "cooccur": 0.85, "diversity": 0.73}
}
],
"strategy_weights_used": {"combined": 0.32, "simulation": 0.28, "heatmap": 0.18, "manual": 0.14, "custom": 0.08},
"learning_status": {"draws_learned": 32, "status": "active", "message": ""}
}
5. 전략 진화 알고리즘
5.1 성과 점수 산출 (회차별, 세트별)
set_score = correct_count / 6.0
# 당첨 등수별 보너스
RANK_BONUS = {5: 0.1, 4: 0.3, 3: 0.6, 2: 0.8, 1: 1.0}
set_score += RANK_BONUS.get(rank, 0)
# 한 구매 건의 draw_score = avg(set_scores)
5.2 EMA 갱신
ALPHA = 0.3 # 최근 3~4회차가 EMA의 ~65% 차지
new_ema = ALPHA * draw_score + (1 - ALPHA) * old_ema
5.3 가중치 변환 (Softmax)
TEMPERATURE = 2.0
MIN_WEIGHT = 0.05
raw = {s: exp(ema / TEMPERATURE) for s, ema in ema_scores.items()}
total = sum(raw.values())
weights = {s: max(v / total, MIN_WEIGHT) for s, v in raw.items()}
# 재정규화하여 합 = 1.0
remainder = 1.0 - sum(weights.values())
# ... 비례 배분으로 조정
5.4 재계산 타이밍
- 자동:
check_results_for_draw()→ purchases 체크 → strategy_performance 갱신 → weights 재계산 - 수동:
POST /api/lotto/strategy/evolve
5.5 스마트 추천 흐름
strategy_weights로드- 각 전략에서 후보 10세트 생성:
combined:generate_combined_recommendation()x 10simulation:get_best_picks()상위 10개heatmap:recommend_with_heatmap()x 10manual:recommend_numbers()x 10custom: 데이터 없으면 skip
meta_score = original_score x strategy_weight- 전체 풀에서 중복 제거 후 상위 N세트 선출
- 각 세트에 출처 전략 + 기여도 breakdown 첨부
5.6 콜드 스타트
- 구매 이력 0건: 초기 가중치 그대로 사용
- 특정 전략 구매 0건: 해당 전략 EMA 초기값(0.15) 유지
- 10회차 미만: 스마트 추천 응답에
status: "learning"+ 기존 combined 추천 병행
5.7 Trend 판정
recent_delta = current_ema - ema_5_draws_ago
if recent_delta > 0.02: trend = "up"
elif recent_delta < -0.02: trend = "down"
else: trend = "stable"
6. 체커 연동 (자동 파이프라인)
기존 흐름에 purchase 체크를 연결:
Scheduler (09:10 / 21:10)
→ sync_latest()
→ 새 회차 감지 시:
→ check_results_for_draw() # 기존: recommendations 체크
→ check_purchases_for_draw() # 신규: purchases 체크
→ 각 세트별 rank/correct/bonus 계산 (checker._calc_rank 재사용)
→ purchases.results, total_prize, checked=1 갱신
→ strategy_performance upsert
→ strategy_evolver.recalculate_weights()
7. 백엔드 모듈 구조
7.1 신규 파일
| 파일 | 역할 |
|---|---|
purchase_manager.py |
구매 이력 관리 + 결과 체크 |
strategy_evolver.py |
EMA 계산 + 가중치 진화 + 스마트 추천 |
7.2 수정 파일
| 파일 | 변경 내용 |
|---|---|
db.py |
purchase_history ALTER + 신규 테이블 2개 + CRUD 함수 추가 |
main.py |
신규 엔드포인트 9개 + Pydantic 모델 + import |
checker.py |
check_results_for_draw() 끝에 purchase 체크 호출 추가 |
7.3 기존 유지 파일 (변경 없음)
recommender.py, generator.py, analyzer.py, collector.py, utils.py
8. 프론트엔드 변경
8.1 신규 컴포넌트
| 컴포넌트 | 역할 |
|---|---|
SmartRecommendPanel.jsx |
전략 진화 기반 메타 추천 + 구매 버튼 |
PurchaseHub.jsx |
통합 구매 이력 (기존 PurchasePanel 대체) |
StrategyDashboard.jsx |
전략 가중치 시각화 + 성과 추이 차트 |
PurchaseButton.jsx |
공통 구매 버튼 (실구매/가상구매) |
8.2 수정 컴포넌트
| 컴포넌트 | 변경 내용 |
|---|---|
CombinedRecommendPanel.jsx |
구매 버튼(PurchaseButton) 추가 |
Functions.jsx |
신규 패널 3개 추가 + import |
8.3 신규 훅
| 훅 | 역할 |
|---|---|
useStrategyWeights.js |
전략 가중치/성과 데이터 fetch |
8.4 수정 훅
| 훅 | 변경 내용 |
|---|---|
usePurchases.js |
새 API 스키마 연동 (numbers, is_real, source_strategy 등) |
8.5 API 헬퍼 추가 (api.js)
// 전략
getStrategyWeights() // GET /api/lotto/strategy/weights
getStrategyPerformance(days) // GET /api/lotto/strategy/performance
triggerStrategyEvolve() // POST /api/lotto/strategy/evolve
// 스마트 추천
getSmartRecommend(sets) // GET /api/lotto/recommend/smart
8.6 동행복권 바로가기
별도 API 없음. 프론트엔드 PurchaseButton에서:
- 번호를 클립보드에 복사
window.open('https://dhlottery.co.kr/gameResult.do?method=byWin')— 새 탭- 확인 다이얼로그 "구매 완료했나요?" → 예 →
POST /api/lotto/purchase (is_real=1)
8.7 UI 시각 구분
- 실 구매: 금색/강조 배경 + 지갑 아이콘
- 가상 구매: 기본 배경 + 게임패드 아이콘
- 미확인: 시계 아이콘
- 당첨: 초록 하이라이트 + 체크 아이콘
9. 전체 데이터 흐름
추천(기존) ──[구매 버튼]──→ POST /purchase
│
스마트 추천(신규) ──[구매 버튼]──┘
↓
purchase_history 테이블
│
매주 토요일 추첨 결과 ──→ sync_latest()
↓
check_results_for_draw()
├── recommendations 체크 (기존)
└── check_purchases_for_draw() (신규)
↓
strategy_performance 갱신
↓
recalculate_weights()
↓
strategy_weights 갱신
↓
다음 스마트 추천에 반영 ──→ 순환
10. 비기능 요구사항
- 하위호환: 기존 purchase API 사용자(프론트 PurchasePanel)는 마이그레이션 중에도 동작해야 함
- 성능: 스마트 추천은 각 전략 10세트 생성 → 총 50세트 중 상위 N개 선출. 1-2초 내 응답 목표
- 데이터 안전: ALTER TABLE은 SQLite 트랜잭션으로 안전하게 실행. 기존 데이터 유실 없음
- 콜드 스타트: 구매 데이터 없어도 스마트 추천 동작 (초기 가중치 사용)
11. 범위 외 (추후 고려)
- 동행복권 자동 로그인/자동 구매 (CAPTCHA + 보안 정책으로 불가)
- 번호 자동 입력 브라우저 확장 프로그램
- 푸시 알림 (당첨 결과 통보)
- 다중 사용자 지원