05/23 Phase 14
This commit is contained in:
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user