Files
lottosight/tests/test_quick_pick.py
2026-05-23 16:18:08 -04:00

136 lines
5.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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