05/23 Phase 14

This commit is contained in:
2026-05-23 12:58:57 -04:00
parent ae0c64f731
commit 8eea1e976f
9 changed files with 851 additions and 2 deletions
+174
View File
@@ -21,6 +21,7 @@ from db.models import (
from core.predictor import (
hot_numbers, due_numbers, weighted_random, monte_carlo, positional_pick,
)
from core.checker import check_ticket, parse_numbers
from core.exporter import export_predictions_excel, export_predictions_csv, ensure_exports_dir
logger = logging.getLogger(__name__)
@@ -73,11 +74,14 @@ class PredictorScreen(ttk.Frame):
gen_frame = ttk.Frame(nb)
saved_frame = ttk.Frame(nb)
check_frame = ttk.Frame(nb)
nb.add(gen_frame, text="Generate")
nb.add(saved_frame, text="Saved")
nb.add(check_frame, text="Check Ticket")
self._build_generate_tab(gen_frame)
self._build_saved_tab(saved_frame)
self._build_check_tab(check_frame)
# ── Generate tab ──────────────────────────────────────────────────────────
@@ -224,6 +228,7 @@ class PredictorScreen(ttk.Frame):
def refresh(self):
self._load_games()
self._load_check_games()
self._refresh_saved()
def _load_games(self):
@@ -409,3 +414,172 @@ class PredictorScreen(ttk.Frame):
return
delete_all_predictions(game_id=game_id)
self._refresh_saved()
# ── Check Ticket tab ──────────────────────────────────────────────────────
def _build_check_tab(self, parent):
# Input bar
bar = ttk.Frame(parent, padding=(6, 8, 6, 4))
bar.pack(fill="x")
ttk.Label(bar, text="Game:").pack(side="left")
self._chk_game_var = tk.StringVar()
self._chk_game_cb = ttk.Combobox(
bar, textvariable=self._chk_game_var, state="readonly", width=15
)
self._chk_game_cb.pack(side="left", padx=(4, 14))
ttk.Label(bar, text="Numbers:").pack(side="left")
self._chk_nums_var = tk.StringVar()
chk_nums_entry = ttk.Entry(bar, textvariable=self._chk_nums_var, width=22)
chk_nums_entry.pack(side="left", padx=(4, 4))
chk_nums_entry.bind("<Return>", lambda e: self._run_check())
ttk.Label(bar, text="Bonus:").pack(side="left")
self._chk_bonus_var = tk.StringVar()
chk_bonus_entry = ttk.Entry(bar, textvariable=self._chk_bonus_var, width=5)
chk_bonus_entry.pack(side="left", padx=(4, 14))
chk_bonus_entry.bind("<Return>", lambda e: self._run_check())
ttk.Button(bar, text="Check", command=self._run_check).pack(side="left", padx=(0, 4))
ttk.Button(bar, text="Clear", command=self._clear_check).pack(side="left")
self._chk_status_var = tk.StringVar()
ttk.Label(bar, textvariable=self._chk_status_var,
foreground="#c0392b").pack(side="left", padx=(12, 0))
# Hint text below bar
hint = ttk.Frame(parent, padding=(6, 0, 6, 4))
hint.pack(fill="x")
ttk.Label(hint,
text="Enter space- or comma-separated main numbers, and optionally a bonus number.",
foreground="#888888", font=("TkDefaultFont", 8)).pack(anchor="w")
# Results treeview
tree_frame = ttk.Frame(parent)
tree_frame.pack(fill="both", expand=True, padx=6, pady=(0, 4))
c_cols = ("date", "draw_numbers", "bonus", "main", "bonus_hit", "tier")
self._chk_tree = ttk.Treeview(
tree_frame, columns=c_cols, show="headings", selectmode="browse"
)
self._chk_tree.heading("date", text="Draw Date")
self._chk_tree.heading("draw_numbers", text="Draw Numbers")
self._chk_tree.heading("bonus", text="Bonus")
self._chk_tree.heading("main", text="Main Hits")
self._chk_tree.heading("bonus_hit", text="Bonus Hit")
self._chk_tree.heading("tier", text="Prize Tier")
self._chk_tree.column("date", width=100, anchor="center", stretch=False)
self._chk_tree.column("draw_numbers", width=190, anchor="w")
self._chk_tree.column("bonus", width=55, anchor="center", stretch=False)
self._chk_tree.column("main", width=70, anchor="center", stretch=False)
self._chk_tree.column("bonus_hit", width=70, anchor="center", stretch=False)
self._chk_tree.column("tier", width=160, anchor="w", stretch=False)
vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self._chk_tree.yview)
hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self._chk_tree.xview)
self._chk_tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self._chk_tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
tree_frame.rowconfigure(0, weight=1)
tree_frame.columnconfigure(0, weight=1)
# Row colour tags
self._chk_tree.tag_configure("jackpot", foreground="#8e44ad", font=("TkDefaultFont", 9, "bold"))
self._chk_tree.tag_configure("high", foreground="#1e8449")
self._chk_tree.tag_configure("low", foreground="#555555")
# Summary label
self._chk_summary_var = tk.StringVar()
ttk.Label(parent, textvariable=self._chk_summary_var,
anchor="e", padding=(6, 2)).pack(fill="x")
def _run_check(self):
self._chk_status_var.set("")
self._chk_tree.delete(*self._chk_tree.get_children())
self._chk_summary_var.set("")
# Resolve game
game = get_game_by_name(self._chk_game_var.get())
if game is None:
self._chk_status_var.set("Select a game first.")
return
# Parse main numbers
try:
numbers = parse_numbers(self._chk_nums_var.get())
except ValueError as e:
self._chk_status_var.set(str(e))
return
if len(numbers) != game["main_count"]:
self._chk_status_var.set(
f"{game['name']} requires {game['main_count']} main numbers "
f"(got {len(numbers)})."
)
return
# Validate range
invalid = [n for n in numbers if not (1 <= n <= game["main_max"])]
if invalid:
self._chk_status_var.set(
f"Numbers out of range 1{game['main_max']}: {invalid}"
)
return
# Parse optional bonus
bonus = None
raw_bonus = self._chk_bonus_var.get().strip()
if raw_bonus:
if not raw_bonus.isdigit():
self._chk_status_var.set("Bonus must be a number.")
return
bonus = int(raw_bonus)
if game["bonus_max"] and not (1 <= bonus <= game["bonus_max"]):
self._chk_status_var.set(
f"Bonus out of range 1{game['bonus_max']}."
)
return
results = check_ticket(game["id"], numbers, bonus=bonus)
for r in results:
nums_fmt = " ".join(f"{n:02d}" for n in r["draw_numbers"])
bonus_str = str(r["draw_bonus"]) if r["draw_bonus"] is not None else ""
bonus_hit = "" if r["bonus_match"] else ""
tier = r["prize_tier"]
if tier == "Jackpot":
tag = "jackpot"
elif r["main_matches"] >= 3 or r["bonus_match"]:
tag = "high"
else:
tag = "low"
self._chk_tree.insert("", "end", values=(
r["draw_date"], nums_fmt, bonus_str,
r["main_matches"], bonus_hit, tier,
), tags=(tag,))
total = len(results)
best = results[0]["prize_tier"] if results else ""
self._chk_summary_var.set(
f"{total} draw{'s' if total != 1 else ''} matched • Best: {best}"
)
def _clear_check(self):
self._chk_nums_var.set("")
self._chk_bonus_var.set("")
self._chk_status_var.set("")
self._chk_tree.delete(*self._chk_tree.get_children())
self._chk_summary_var.set("")
def _load_check_games(self):
games = get_all_games(active_only=True)
names = [g["name"] for g in games]
self._chk_game_cb["values"] = names
if not self._chk_game_var.get() or self._chk_game_var.get() not in names:
if names:
self._chk_game_var.set(names[0])