feat(lotto): calibrate_winner + backfill (멱등·청크)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,49 @@ def calibrate_winner_compute(draws, target_draw_no, winning6,
|
|||||||
return {"scores": scores, "percentile": percentile, "cache_draws": len(pit)}
|
return {"scores": scores, "percentile": percentile, "cache_draws": len(pit)}
|
||||||
|
|
||||||
|
|
||||||
|
MIN_HISTORY = 30 # point-in-time 캐시 최소 회차 (이 미만은 캘리브레이션 skip)
|
||||||
|
|
||||||
|
|
||||||
|
def _db():
|
||||||
|
from . import db as _db_mod
|
||||||
|
return _db_mod
|
||||||
|
|
||||||
|
|
||||||
|
def calibrate_winner(draw_no: int, sample_m: int = 2000) -> Dict[str, Any]:
|
||||||
|
"""DB 진입점: 회차 1개 캘리브레이션 후 저장 (멱등)."""
|
||||||
|
db = _db()
|
||||||
|
draws = db.get_all_draw_numbers()
|
||||||
|
row = db.get_draw(draw_no)
|
||||||
|
if row is None:
|
||||||
|
return {"ok": False, "reason": "no_draw"}
|
||||||
|
pit = point_in_time_draws(draws, draw_no)
|
||||||
|
if len(pit) < MIN_HISTORY:
|
||||||
|
return {"ok": False, "reason": "insufficient_history"}
|
||||||
|
winning6 = [row["n1"], row["n2"], row["n3"], row["n4"], row["n5"], row["n6"]]
|
||||||
|
res = calibrate_winner_compute(draws, draw_no, winning6, sample_m=sample_m)
|
||||||
|
db.save_winner_calibration(
|
||||||
|
draw_no=draw_no, winning=winning6, scores=res["scores"],
|
||||||
|
percentile=res["percentile"], my_pick_avg=None,
|
||||||
|
cache_draws=res["cache_draws"],
|
||||||
|
)
|
||||||
|
return {"ok": True, "draw_no": draw_no, **res}
|
||||||
|
|
||||||
|
|
||||||
|
def backfill_calibration(batch: int = 50, sample_m: int = 2000) -> Dict[str, Any]:
|
||||||
|
"""미처리 회차만 batch개 캘리브레이션 (멱등·재개 가능)."""
|
||||||
|
db = _db()
|
||||||
|
draws = db.get_all_draw_numbers()
|
||||||
|
done = db.get_calibrated_draw_nos()
|
||||||
|
todo = [d for d, _ in draws if d not in done and d > MIN_HISTORY]
|
||||||
|
todo.sort()
|
||||||
|
n = 0
|
||||||
|
for draw_no in todo[:batch]:
|
||||||
|
r = calibrate_winner(draw_no, sample_m=sample_m)
|
||||||
|
if r.get("ok"):
|
||||||
|
n += 1
|
||||||
|
return {"calibrated": n, "remaining": max(0, len(todo) - batch)}
|
||||||
|
|
||||||
|
|
||||||
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 + 번호 사용 균등화)"""
|
||||||
|
|||||||
@@ -65,6 +65,31 @@ def test_winner_calibration_upsert(monkeypatch):
|
|||||||
assert row["score_total"] == 2.00
|
assert row["score_total"] == 2.00
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_draws(db, n=40):
|
||||||
|
rows = []
|
||||||
|
import random as _r; _r.seed(2)
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
s = sorted(_r.sample(range(1, 46), 6))
|
||||||
|
rows.append({"drw_no": i, "drw_date": f"2020-01-{(i%28)+1:02d}",
|
||||||
|
"n1": s[0], "n2": s[1], "n3": s[2], "n4": s[3],
|
||||||
|
"n5": s[4], "n6": s[5], "bonus": ((s[5] % 45) + 1)})
|
||||||
|
db.upsert_many_draws(rows)
|
||||||
|
|
||||||
|
def test_backfill_calibration_idempotent(monkeypatch):
|
||||||
|
db = _fresh_db(monkeypatch)
|
||||||
|
_seed_draws(db, 40)
|
||||||
|
from app import backtest as bt
|
||||||
|
r1 = bt.backfill_calibration(batch=15, sample_m=200)
|
||||||
|
# 첫 회차는 point-in-time 데이터가 빈약 → min_history 이후만 처리
|
||||||
|
done1 = len(db.get_calibrated_draw_nos())
|
||||||
|
assert done1 > 0
|
||||||
|
r2 = bt.backfill_calibration(batch=100, sample_m=200) # 나머지
|
||||||
|
done2 = len(db.get_calibrated_draw_nos())
|
||||||
|
assert done2 >= done1
|
||||||
|
r3 = bt.backfill_calibration(batch=100, sample_m=200) # 재실행 → 추가 0
|
||||||
|
assert r3["calibrated"] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_get_calibrated_draw_nos(monkeypatch):
|
def test_get_calibrated_draw_nos(monkeypatch):
|
||||||
"""저장된 draw_no 집합이 get_calibrated_draw_nos에 포함되어야 한다."""
|
"""저장된 draw_no 집합이 get_calibrated_draw_nos에 포함되어야 한다."""
|
||||||
db = _fresh_db(monkeypatch)
|
db = _fresh_db(monkeypatch)
|
||||||
|
|||||||
Reference in New Issue
Block a user