Files
web-page-backend/docs/superpowers/specs/2026-04-05-lotto-purchase-strategy-evolution-design.md

14 KiB

Lotto 구매 연동 + 전략 진화 시스템 설계

작성일: 2026-04-05 상태: 승인 대기


1. 목표

로또 번호 추천 기능을 고도화하여:

  1. 동행복권 실 구매 연동 — 추천 번호를 클립보드 복사 + 동행복권 바로가기로 실제 구매 지원
  2. 가상 구매 모드 — 돈을 쓰지 않고 "이 번호로 구매한다"를 등록, 결과 발표 후 자동 가상 수익률 계산
  3. 전략 진화 시스템 — 구매 이력 기반으로 각 추천 전략(combined, simulation, heatmap, manual, custom)의 성과를 추적하고, EMA + Softmax로 가중치를 자동 조정하는 메타 전략
  4. 통합 구매 이력 — 실제/가상 구매를 하나의 리스트에서 관리하되, 실 구매는 시각적으로 강조

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 컬럼은 하위호환용으로 유지. 신규 로직은 numbers JSON 배열 길이로 세트 수 산출

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 스마트 추천 흐름

  1. strategy_weights 로드
  2. 각 전략에서 후보 10세트 생성:
    • combined: generate_combined_recommendation() x 10
    • simulation: get_best_picks() 상위 10개
    • heatmap: recommend_with_heatmap() x 10
    • manual: recommend_numbers() x 10
    • custom: 데이터 없으면 skip
  3. meta_score = original_score x strategy_weight
  4. 전체 풀에서 중복 제거 후 상위 N세트 선출
  5. 각 세트에 출처 전략 + 기여도 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에서:

  1. 번호를 클립보드에 복사
  2. window.open('https://dhlottery.co.kr/gameResult.do?method=byWin') — 새 탭
  3. 확인 다이얼로그 "구매 완료했나요?" → 예 → 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 + 보안 정책으로 불가)
  • 번호 자동 입력 브라우저 확장 프로그램
  • 푸시 알림 (당첨 결과 통보)
  • 다중 사용자 지원