128 lines
3.8 KiB
Python
128 lines
3.8 KiB
Python
"""
|
|
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
|