""" 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