Files
2026-05-23 15:05:58 -04:00

143 lines
5.4 KiB
Python

"""
tests/test_odds.py
------------------
Tests for core/odds.py — combinatorics-based prize-odds calculator.
Reference values:
Powerball 5/69 + 1/26 → total 292,201,338
Mega Millions 5/70 + 1/25 → total 302,575,350
UK-style 6/49 no bonus → total 13,983,816
"""
import pytest
from core.odds import total_combinations, prize_odds, game_odds
from db.models import get_game_by_name, add_game
# ── total_combinations ────────────────────────────────────────────────────────
def test_total_powerball(tmp_db):
assert total_combinations(5, 69, 1, 26) == 292_201_338
def test_total_mega_millions(tmp_db):
assert total_combinations(5, 70, 1, 25) == 302_575_350
def test_total_no_bonus(tmp_db):
# C(49, 6) = 13,983,816
assert total_combinations(6, 49, 0, 0) == 13_983_816
def test_total_single_ball_no_bonus(tmp_db):
# C(10, 1) = 10
assert total_combinations(1, 10, 0, 0) == 10
# ── prize_odds — Powerball (5/69 + 1/26) ─────────────────────────────────────
def test_powerball_jackpot_odds(tmp_db):
rows = prize_odds(5, 69, 1, 26)
jackpot = next(r for r in rows if r["tier"] == "Jackpot")
assert jackpot["ways"] == 1
assert jackpot["odds"] == pytest.approx(292_201_338, rel=1e-6)
def test_powerball_match5_odds(tmp_db):
rows = prize_odds(5, 69, 1, 26)
m5 = next(r for r in rows if r["tier"] == "Match 5")
assert m5["ways"] == 25
assert m5["odds"] == pytest.approx(11_688_053.52, rel=1e-4)
def test_powerball_match4_bonus_odds(tmp_db):
rows = prize_odds(5, 69, 1, 26)
m4b = next(r for r in rows if r["tier"] == "Match 4 + Bonus")
assert m4b["ways"] == 320
assert m4b["odds"] == pytest.approx(913_129.18, rel=1e-4)
def test_powerball_bonus_only_odds(tmp_db):
rows = prize_odds(5, 69, 1, 26)
bo = next(r for r in rows if r["tier"] == "Bonus Only")
# C(5,0)*C(64,5)*1 = 7,624,512 ways
assert bo["ways"] == 7_624_512
assert bo["odds"] == pytest.approx(38.32, rel=1e-3)
def test_powerball_tier_count(tmp_db):
rows = prize_odds(5, 69, 1, 26)
# Jackpot, Match5, M4+B, M4, M3+B, M3, M2+B, M2, M1+B, M1, BonusOnly = 11
assert len(rows) == 11
def test_powerball_tiers_sorted_hardest_first(tmp_db):
rows = prize_odds(5, 69, 1, 26)
odds_list = [r["odds"] for r in rows]
assert odds_list == sorted(odds_list, reverse=True)
def test_powerball_probabilities_sum_to_less_than_one(tmp_db):
rows = prize_odds(5, 69, 1, 26)
total_prob = sum(r["probability"] for r in rows)
assert total_prob < 1.0
def test_powerball_ways_sum_lte_total(tmp_db):
rows = prize_odds(5, 69, 1, 26)
total = rows[0]["total"]
assert sum(r["ways"] for r in rows) <= total
# ── prize_odds — no-bonus game (6/49) ────────────────────────────────────────
def test_no_bonus_jackpot_odds(tmp_db):
rows = prize_odds(6, 49, 0, 0)
jackpot = next(r for r in rows if r["tier"] == "Jackpot")
assert jackpot["ways"] == 1
assert jackpot["odds"] == pytest.approx(13_983_816, rel=1e-6)
def test_no_bonus_tier_count(tmp_db):
# Jackpot + Match5 + Match4 + Match3 + Match2 + Match1 = 6
rows = prize_odds(6, 49, 0, 0)
assert len(rows) == 6
def test_no_bonus_no_bonus_tiers(tmp_db):
rows = prize_odds(6, 49, 0, 0)
assert all("Bonus" not in r["tier"] for r in rows)
def test_no_bonus_tier_labels(tmp_db):
rows = prize_odds(6, 49, 0, 0)
labels = [r["tier"] for r in rows]
assert labels == ["Jackpot", "Match 5", "Match 4", "Match 3", "Match 2", "Match 1"]
# ── prize_odds — Mega Millions (5/70 + 1/25) ─────────────────────────────────
def test_mega_millions_jackpot_odds(tmp_db):
rows = prize_odds(5, 70, 1, 25)
jackpot = next(r for r in rows if r["tier"] == "Jackpot")
assert jackpot["odds"] == pytest.approx(302_575_350, rel=1e-6)
# ── game_odds (DB-backed) ─────────────────────────────────────────────────────
def test_game_odds_powerball(tmp_db):
game = get_game_by_name("Powerball")
rows = game_odds(game["id"])
assert len(rows) == 11
jackpot = next(r for r in rows if r["tier"] == "Jackpot")
assert jackpot["odds"] == pytest.approx(292_201_338, rel=1e-6)
def test_game_odds_invalid_id(tmp_db):
assert game_odds(99999) == []
def test_game_odds_custom_no_bonus(tmp_db):
gid = add_game("UK Style", 6, 49, bonus_count=0, bonus_max=0)
rows = game_odds(gid)
assert len(rows) == 6
jackpot = next(r for r in rows if r["tier"] == "Jackpot")
assert jackpot["odds"] == pytest.approx(13_983_816, rel=1e-6)
# ── odds_str formatting ───────────────────────────────────────────────────────
def test_jackpot_odds_str_integer(tmp_db):
rows = prize_odds(5, 69, 1, 26)
jackpot = next(r for r in rows if r["tier"] == "Jackpot")
assert jackpot["odds_str"] == "1 in 292,201,338"
def test_match5_odds_str_decimal(tmp_db):
rows = prize_odds(5, 69, 1, 26)
m5 = next(r for r in rows if r["tier"] == "Match 5")
assert m5["odds_str"].startswith("1 in 11,688,053")