05/23 Phase 4

This commit is contained in:
2026-05-23 11:25:56 -04:00
parent 5dcf93a85f
commit 0931cdcd8e
7 changed files with 639 additions and 16 deletions
+127
View File
@@ -0,0 +1,127 @@
"""
core/analyzer.py
----------------
All statistical analysis functions for LottoSight.
Each function takes game_id and returns structured Python data
(dicts / lists) — no UI concerns here.
"""
from collections import Counter
from itertools import combinations
from db.models import get_all_draws_numbers, get_game_by_id
def frequency_analysis(game_id, last_n=None):
"""
Count appearances of each main ball.
last_n: restrict to the most recent N draws (None = all).
Returns {number: count} sorted high → low.
"""
draws = get_all_draws_numbers(game_id) # ASC order
if last_n and last_n > 0:
draws = draws[-last_n:]
counter = Counter()
for draw in draws:
counter.update(draw["numbers"])
return dict(sorted(counter.items(), key=lambda kv: kv[1], reverse=True))
def gap_analysis(game_id):
"""
Draws elapsed since each number last appeared.
gap=0 → appeared in the most recent draw.
gap=N → last appeared N draws before the current one.
Numbers never drawn receive gap = total draw count.
Returns {number: gap} for every number in the pool.
"""
draws = get_all_draws_numbers(game_id) # ASC order
if not draws:
return {}
game = get_game_by_id(game_id)
total = len(draws)
last_seen = {}
for idx, draw in enumerate(draws):
for n in draw["numbers"]:
last_seen[n] = idx # keep updating → final value = most recent draw index
result = {}
for n in range(1, game["main_max"] + 1):
if n in last_seen:
result[n] = (total - 1) - last_seen[n]
else:
result[n] = total
return result
def positional_frequency(game_id):
"""
How often each number appears at each draw position (1-indexed).
Returns {pos: {number: count}} for positions 1 .. main_count.
"""
draws = get_all_draws_numbers(game_id)
game = get_game_by_id(game_id)
main_count = game["main_count"]
result = {pos: Counter() for pos in range(1, main_count + 1)}
for draw in draws:
for pos, num in enumerate(draw["numbers"][:main_count], start=1):
result[pos][num] += 1
return {pos: dict(c) for pos, c in result.items()}
def pair_analysis(game_id, top_n=50):
"""
Most frequent pairs of numbers appearing together in the same draw.
Returns {(n1, n2): count} for the top_n pairs (n1 < n2), high → low.
"""
draws = get_all_draws_numbers(game_id)
counter = Counter()
for draw in draws:
for pair in combinations(sorted(draw["numbers"]), 2):
counter[pair] += 1
return dict(counter.most_common(top_n))
def odd_even_ratio(game_id):
"""
Odd vs even ball count per draw.
Returns [{"date": str, "odd": int, "even": int}, ...] in chronological order.
"""
draws = get_all_draws_numbers(game_id)
result = []
for draw in draws:
odd = sum(1 for n in draw["numbers"] if n % 2 != 0)
even = len(draw["numbers"]) - odd
result.append({"date": draw["draw_date"], "odd": odd, "even": even})
return result
def sum_range_analysis(game_id):
"""
Sum of main balls per draw.
Returns [{"date": str, "sum": int}, ...] in chronological order.
"""
draws = get_all_draws_numbers(game_id)
return [{"date": d["draw_date"], "sum": sum(d["numbers"])} for d in draws]
def delta_analysis(game_id):
"""
Differences between consecutive sorted numbers within each draw.
Returns [{"date": str, "deltas": [int, ...]}, ...] in chronological order.
"""
draws = get_all_draws_numbers(game_id)
result = []
for draw in draws:
s = sorted(draw["numbers"])
deltas = [s[i + 1] - s[i] for i in range(len(s) - 1)]
result.append({"date": draw["draw_date"], "deltas": deltas})
return result