feat(lotto): track_record + build_review_payload 집계

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 17:19:05 +09:00
parent 850638ae58
commit a425bb8809
2 changed files with 57 additions and 0 deletions

View File

@@ -202,6 +202,45 @@ def run_forward_purchase(draw_no: int, k: int = 5000, pool_n: int = 20000,
return {"ok": True, "draw_no": draw_no}
def track_record() -> Dict[str, Any]:
"""전략별 누적 등수 집계 (engine_w는 라벨 합산)."""
db = _db()
rows = db.get_backtest_runs()
agg: Dict[str, Dict[str, int]] = {}
for r in rows:
a = agg.setdefault(r["strategy"], {
"n_tickets": 0, "1st": 0, "2nd": 0, "3rd": 0, "4th": 0, "5th": 0, "draws": 0})
p = prize_counts(r)
a["n_tickets"] += r["n_tickets"]
for tier in ("1st", "2nd", "3rd", "4th", "5th"):
a[tier] += p[tier]
a["draws"] += 1
return {"by_strategy": agg}
def build_review_payload(draw_no: int) -> Dict[str, Any]:
"""일요 회고 브리핑용 조립."""
db = _db()
cal = db.get_winner_calibration(draw_no)
runs = db.get_backtest_runs(draw_no=draw_no)
hist = db.get_calibration_history(limit=12)
forward = []
for r in runs:
forward.append({"strategy": r["strategy"], "label": r["weight_label"],
"prizes": prize_counts(r), "best_match": r["best_match"],
"avg_meta_score": r["avg_meta_score"]})
return {
"draw_no": draw_no,
"winner_analysis": cal, # score_* + percentile
"forward": forward,
"track_record": track_record()["by_strategy"],
"calibration_trend": [
{"draw_no": h["draw_no"], "score_total": h["score_total"],
"percentile": h["percentile"]} for h in hist
],
}
def coverage_tickets(k: int, seed: Optional[int] = None) -> List[List[int]]:
"""greedy 커버리지 — 아직 덜 쓰인 번호를 우선 배치해 번호를 넓게 분산.
(휠링/보장설계는 향후. 현재는 distinct + 번호 사용 균등화)"""

View File

@@ -171,3 +171,21 @@ def test_get_calibrated_draw_nos(monkeypatch):
nos = db.get_calibrated_draw_nos()
assert isinstance(nos, set)
assert {301, 302, 303}.issubset(nos)
def test_track_record_and_review_payload(monkeypatch):
db = _fresh_db(monkeypatch)
_seed_draws(db, 40)
from app import backtest as bt
bt.run_forward_purchase(draw_no=40, k=20, pool_n=500, sample_seed=5)
bt.calibrate_winner(40, sample_m=200)
tr = bt.track_record()
assert "random_null" in tr["by_strategy"]
assert tr["by_strategy"]["random_null"]["n_tickets"] >= 20
payload = bt.build_review_payload(40)
assert payload["draw_no"] == 40
assert "winner_analysis" in payload # 당첨조합 5분석치
assert "forward" in payload # 이번 회차 전략별 성적
assert "calibration_trend" in payload