From 0f8c71c55266872ffbae7426aac3c02f28524b28 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 22 May 2026 03:35:20 +0900 Subject: [PATCH] =?UTF-8?q?fix(lotto-evolver):=20previous=5Fbase=20diff=20?= =?UTF-8?q?+=20=EC=9D=BC=EC=9A=94=EC=9D=BC=20cron=20skip=20+=20idempotent?= =?UTF-8?q?=20evaluate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - weight_evolver.evaluate_weekly: save_base_history 직전에 current_base를 previous_base로 캡처해 return dict에 포함 → formatter가 진짜 diff 표시 가능 - evaluate_weekly: same effective_from row 이미 존재 시 save skip + idempotent return (토 22:00 lotto cron과 agent-office 22:15 재호출 중복 row 방지) - main._run_weight_evolver_daily: 일요일(weekday=6) 도 skip — 토요일 trial을 INSERT OR REPLACE로 덮어쓰는 문제 방지 - telegram_lotto._format_evolution_report: eval_result.previous_base 우선 사용 (없으면 current_base 폴백) → diff 자기 자신 비교 버그 수정 - test_lotto_evolution_format: previous_base 키 추가 + 새 diff 검증 테스트 Co-Authored-By: Claude Sonnet 4.6 --- agent-office/app/notifiers/telegram_lotto.py | 3 ++- .../tests/test_lotto_evolution_format.py | 27 +++++++++++++++++++ lotto/app/main.py | 4 +-- lotto/app/weight_evolver.py | 19 ++++++++++++- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/agent-office/app/notifiers/telegram_lotto.py b/agent-office/app/notifiers/telegram_lotto.py index f86035d..7835629 100644 --- a/agent-office/app/notifiers/telegram_lotto.py +++ b/agent-office/app/notifiers/telegram_lotto.py @@ -199,7 +199,8 @@ def _format_evolution_report(eval_result: Dict[str, Any], current_base: List[flo "", f"📊 다음주 base 변경 ({reason}):", ] - base_now = current_base or [0.2] * 5 + # 우선순위: eval_result.previous_base > current_base (eval 직후 stale) > 균등 fallback + base_now = eval_result.get("previous_base") or current_base or [0.2] * 5 for i, (cur, new) in enumerate(zip(base_now, new_base)): diff = new - cur if abs(diff) < 0.005: diff --git a/agent-office/tests/test_lotto_evolution_format.py b/agent-office/tests/test_lotto_evolution_format.py index 2085ff6..16c9924 100644 --- a/agent-office/tests/test_lotto_evolution_format.py +++ b/agent-office/tests/test_lotto_evolution_format.py @@ -17,6 +17,7 @@ def test_evolution_report_winner_4plus(): "n_picks": 5, }, "new_base": [0.18, 0.32, 0.20, 0.22, 0.08], + "previous_base": [0.20, 0.20, 0.20, 0.20, 0.20], "update_reason": "winner_4plus", "per_day": [ {"day_of_week": 0, "avg_score": 0.20, "max_correct": 2}, @@ -58,3 +59,29 @@ def test_evolution_report_empty_returns_empty(): """evaluate가 ok=False면 빈 문자열 (발송 skip).""" text = _format_evolution_report({"ok": False, "reason": "no_trials"}, [0.2]*5) assert text == "" + + +def test_evolution_report_uses_previous_base_for_diff(): + """previous_base와 new_base 차이가 메시지 diff에 정확히 반영됨.""" + eval_result = { + "ok": True, + "draw_no": 1227, + "winner": { + "day_of_week": 0, + "weight": [0.30, 0.20, 0.20, 0.20, 0.10], + "avg_score": 0.50, + "max_correct": 4, + "n_picks": 5, + }, + "new_base": [0.30, 0.20, 0.20, 0.20, 0.10], + "previous_base": [0.20, 0.20, 0.20, 0.20, 0.20], + "update_reason": "winner_4plus", + } + # current_base는 stale (post-update 값) — previous_base가 우선 적용되어야 함 + text = _format_evolution_report(eval_result, [0.30, 0.20, 0.20, 0.20, 0.10]) + # freq: 0.20 → 0.30 (+0.10 = "++") + # divers: 0.20 → 0.10 (-0.10 = "--") + assert "0.20 → 0.30" in text # freq 증가 + assert "0.20 → 0.10" in text # divers 감소 + assert "(++)" in text or "(+)" in text # freq marker + assert "(--)" in text or "(-)" in text # divers marker diff --git a/lotto/app/main.py b/lotto/app/main.py index 8b24896..9e9949c 100644 --- a/lotto/app/main.py +++ b/lotto/app/main.py @@ -133,11 +133,11 @@ async def _run_weight_evolver_weekly(): async def _run_weight_evolver_daily(): - """매일 09:00 (월요일 제외 — 월은 weekly cron이 inline으로 처리).""" + """매일 09:00 (월/일 제외 — 월=weekly inline, 일=토 trial 보호).""" try: from datetime import datetime, timezone, timedelta KST = timezone(timedelta(hours=9)) - if datetime.now(KST).weekday() == 0: + if datetime.now(KST).weekday() in (0, 6): return apply_today_and_pick(n=5) except Exception as e: diff --git a/lotto/app/weight_evolver.py b/lotto/app/weight_evolver.py index 8c5fd35..6969748 100644 --- a/lotto/app/weight_evolver.py +++ b/lotto/app/weight_evolver.py @@ -277,8 +277,24 @@ def evaluate_weekly() -> Dict[str, Any]: ) next_monday = today + timedelta(days=(7 - today.weekday()) % 7 or 7) + next_monday_iso = next_monday.isoformat() + + # Idempotent guard: 같은 effective_from으로 이미 저장된 row가 있으면 skip + existing = db.get_base_history(limit=1) + if existing and existing[0]["effective_from"] == next_monday_iso: + return { + "ok": True, + "draw_no": latest["drw_no"], + "week_start": week_start, + "previous_base": existing[0].get("weight"), + "winner": winner, + "new_base": existing[0]["weight"], # 이미 저장된 값 + "update_reason": existing[0].get("update_reason", "idempotent_skip"), + "per_day": per_day, + } + db.save_base_history( - effective_from=next_monday.isoformat(), + effective_from=next_monday_iso, weight=new_base, source_trial_id=winner["trial_id"], update_reason=reason, @@ -290,6 +306,7 @@ def evaluate_weekly() -> Dict[str, Any]: "ok": True, "draw_no": latest["drw_no"], "week_start": week_start, + "previous_base": current_base, # save 이전에 캡처한 값 — diff 계산용 "winner": winner, "new_base": new_base, "update_reason": reason,