05/23 Phase 15
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user