05/23 Phase 16

This commit is contained in:
2026-05-23 16:18:08 -04:00
parent 57c777dc82
commit 7506e94d61
5 changed files with 265 additions and 39 deletions
+135
View File
@@ -0,0 +1,135 @@
"""
tests/test_quick_pick.py
------------------------
Tests for the quick_pick strategy and the exclude parameter
added to all predictor strategies in Phase 17.
"""
import pytest
from db.models import get_game_by_name, add_game, insert_draw
from core.predictor import (
quick_pick, hot_numbers, due_numbers,
weighted_random, monte_carlo, positional_pick,
)
@pytest.fixture
def pb(tmp_db):
game = get_game_by_name("Powerball")
insert_draw(game["id"], "2024-01-01", [1, 13, 36, 61, 69], bonus=7)
insert_draw(game["id"], "2024-01-03", [1, 7, 13, 45, 61], bonus=15)
insert_draw(game["id"], "2024-01-05", [2, 13, 22, 36, 55], bonus=3)
return game
# ── quick_pick — basic validity ───────────────────────────────────────────────
def test_quick_pick_count(pb):
assert len(quick_pick(pb["id"])["numbers"]) == pb["main_count"]
def test_quick_pick_range(pb):
result = quick_pick(pb["id"])
assert all(1 <= n <= pb["main_max"] for n in result["numbers"])
def test_quick_pick_no_duplicates(pb):
nums = quick_pick(pb["id"])["numbers"]
assert len(nums) == len(set(nums))
def test_quick_pick_sorted(pb):
nums = quick_pick(pb["id"])["numbers"]
assert nums == sorted(nums)
def test_quick_pick_bonus_in_range(pb):
bonus = quick_pick(pb["id"])["bonus"]
assert bonus is not None
assert 1 <= bonus <= pb["bonus_max"]
def test_quick_pick_empty_db(tmp_db):
game = get_game_by_name("Powerball")
result = quick_pick(game["id"])
assert len(result["numbers"]) == 5
assert all(1 <= n <= 69 for n in result["numbers"])
def test_quick_pick_no_bonus_game(tmp_db):
gid = add_game("NoBonusLotto", 6, 49, bonus_count=0, bonus_max=0)
result = quick_pick(gid)
assert result["bonus"] is None
assert len(result["numbers"]) == 6
assert all(1 <= n <= 49 for n in result["numbers"])
# ── quick_pick — exclude parameter ───────────────────────────────────────────
def test_quick_pick_exclude_numbers(pb):
excl = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result = quick_pick(pb["id"], exclude=excl)
assert not any(n in excl for n in result["numbers"])
def test_quick_pick_exclude_tight(pb):
# Exclude all but exactly main_count numbers — should still pick valid ticket
excl = set(range(1, 65)) # leaves 6569 (exactly 5 for Powerball)
result = quick_pick(pb["id"], exclude=excl)
assert len(result["numbers"]) == 5
assert all(n >= 65 for n in result["numbers"])
def test_quick_pick_exclude_too_many_falls_back(pb):
# Exclude so many that not enough remain — silently falls back
excl = set(range(1, 69)) # only [69] left, need 5
result = quick_pick(pb["id"], exclude=excl)
assert len(result["numbers"]) == 5 # must always return a full ticket
def test_quick_pick_exclude_empty_set(pb):
result = quick_pick(pb["id"], exclude=set())
assert len(result["numbers"]) == 5
def test_quick_pick_exclude_none(pb):
result = quick_pick(pb["id"], exclude=None)
assert len(result["numbers"]) == 5
# ── exclude parameter — all existing strategies ───────────────────────────────
def test_hot_numbers_exclude(pb):
excl = {1, 13, 61} # the most frequent numbers in our fixture
result = hot_numbers(pb["id"], exclude=excl)
assert not any(n in excl for n in result["numbers"])
assert len(result["numbers"]) == 5
def test_due_numbers_exclude(pb):
excl = {36, 69}
result = due_numbers(pb["id"], exclude=excl)
assert not any(n in excl for n in result["numbers"])
assert len(result["numbers"]) == 5
def test_weighted_random_exclude(pb):
excl = {1, 7, 13, 22, 36, 45, 55, 61, 69}
result = weighted_random(pb["id"], exclude=excl)
assert not any(n in excl for n in result["numbers"])
assert len(result["numbers"]) == 5
def test_monte_carlo_exclude(pb):
excl = {1, 13}
result = monte_carlo(pb["id"], simulations=200, exclude=excl)
assert not any(n in excl for n in result["numbers"])
assert len(result["numbers"]) == 5
def test_positional_pick_exclude(pb):
excl = {1, 2, 3, 4, 5}
result = positional_pick(pb["id"], exclude=excl)
assert not any(n in excl for n in result["numbers"])
assert len(result["numbers"]) == 5
def test_exclude_none_unchanged(pb):
# All strategies accept exclude=None without breaking
for fn in (hot_numbers, due_numbers, weighted_random, positional_pick):
result = fn(pb["id"], exclude=None)
assert len(result["numbers"]) == 5
def test_exclude_empty_set_unchanged(pb):
for fn in (hot_numbers, due_numbers, weighted_random, positional_pick):
result = fn(pb["id"], exclude=set())
assert len(result["numbers"]) == 5
def test_monte_carlo_exclude_none(pb):
result = monte_carlo(pb["id"], simulations=100, exclude=None)
assert len(result["numbers"]) == 5