feat(lotto): track_record + build_review_payload 집계
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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}
|
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]]:
|
def coverage_tickets(k: int, seed: Optional[int] = None) -> List[List[int]]:
|
||||||
"""greedy 커버리지 — 아직 덜 쓰인 번호를 우선 배치해 번호를 넓게 분산.
|
"""greedy 커버리지 — 아직 덜 쓰인 번호를 우선 배치해 번호를 넓게 분산.
|
||||||
(휠링/보장설계는 향후. 현재는 distinct + 번호 사용 균등화)"""
|
(휠링/보장설계는 향후. 현재는 distinct + 번호 사용 균등화)"""
|
||||||
|
|||||||
@@ -171,3 +171,21 @@ def test_get_calibrated_draw_nos(monkeypatch):
|
|||||||
nos = db.get_calibrated_draw_nos()
|
nos = db.get_calibrated_draw_nos()
|
||||||
assert isinstance(nos, set)
|
assert isinstance(nos, set)
|
||||||
assert {301, 302, 303}.issubset(nos)
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user