""" core/exporter.py ---------------- Export draw history, predictions, and frequency analysis to Excel (.xlsx via openpyxl) and CSV. All functions accept a filepath and write to it. Callers (UI) handle file-dialog; tests pass tmp paths directly. """ import csv import os import struct import zlib from datetime import datetime import openpyxl from openpyxl.styles import Alignment, Font, PatternFill from db.models import get_draws_with_game, get_game_by_id, get_predictions from core.paths import user_data_dir EXPORTS_DIR = os.path.join(user_data_dir(), "exports") def ensure_exports_dir(): os.makedirs(EXPORTS_DIR, exist_ok=True) def default_path(stem: str, ext: str) -> str: ensure_exports_dir() ts = datetime.now().strftime("%Y%m%d_%H%M%S") return os.path.join(EXPORTS_DIR, f"{stem}_{ts}.{ext}") # ── Shared helpers ──────────────────────────────────────────────────────────── def _header_row(ws, labels: list[str], fill_hex: str): bold_white = Font(bold=True, color="FFFFFF") fill = PatternFill(fill_type="solid", fgColor=fill_hex) center = Alignment(horizontal="center") for col, label in enumerate(labels, start=1): cell = ws.cell(row=1, column=col, value=label) cell.font = bold_white cell.fill = fill cell.alignment = center def _autofit(ws): for col in ws.columns: width = max((len(str(cell.value or "")) for cell in col), default=0) ws.column_dimensions[col[0].column_letter].width = min(width + 4, 60) # ── Draw History ────────────────────────────────────────────────────────────── _DRAW_HEADERS = ["Game", "Date", "Numbers", "Bonus", "Multiplier", "Source"] def export_draws_excel(filepath: str, game_id=None, date_from=None, date_to=None): """Write draw history to filepath (.xlsx). Returns filepath.""" rows = get_draws_with_game(game_id=game_id, date_from=date_from, date_to=date_to, order="DESC") wb = openpyxl.Workbook() ws = wb.active ws.title = "Draw History" _header_row(ws, _DRAW_HEADERS, "2C3E50") for i, row in enumerate(rows, start=2): ws.cell(i, 1, row["game_name"]) ws.cell(i, 2, row["draw_date"]) ws.cell(i, 3, row["numbers"]) ws.cell(i, 4, row["bonus"] or "") ws.cell(i, 5, row["multiplier"] or "") ws.cell(i, 6, row["source"] or "") _autofit(ws) wb.save(filepath) return filepath def export_draws_csv(filepath: str, game_id=None, date_from=None, date_to=None): """Write draw history to filepath (.csv). Returns filepath.""" rows = get_draws_with_game(game_id=game_id, date_from=date_from, date_to=date_to, order="DESC") with open(filepath, "w", newline="", encoding="utf-8") as f: w = csv.writer(f) w.writerow(_DRAW_HEADERS) for row in rows: w.writerow([row["game_name"], row["draw_date"], row["numbers"], row["bonus"] or "", row["multiplier"] or "", row["source"] or ""]) return filepath # ── Predictions ─────────────────────────────────────────────────────────────── _PRED_HEADERS = ["ID", "Game", "Strategy", "Numbers", "Bonus", "Created"] def export_predictions_excel(filepath: str, game_id=None): """Write saved predictions to filepath (.xlsx). Returns filepath.""" preds = get_predictions(game_id=game_id, limit=100_000) wb = openpyxl.Workbook() ws = wb.active ws.title = "Predictions" _header_row(ws, _PRED_HEADERS, "1A5276") for i, p in enumerate(preds, start=2): ws.cell(i, 1, p["id"]) ws.cell(i, 2, p["game_name"]) ws.cell(i, 3, p["strategy"]) ws.cell(i, 4, p["numbers"]) ws.cell(i, 5, p["bonus"] or "") ws.cell(i, 6, p["created_at"][:16]) _autofit(ws) wb.save(filepath) return filepath def export_predictions_csv(filepath: str, game_id=None): """Write saved predictions to filepath (.csv). Returns filepath.""" preds = get_predictions(game_id=game_id, limit=100_000) with open(filepath, "w", newline="", encoding="utf-8") as f: w = csv.writer(f) w.writerow(_PRED_HEADERS) for p in preds: w.writerow([p["id"], p["game_name"], p["strategy"], p["numbers"], p["bonus"] or "", p["created_at"][:16]]) return filepath # ── Frequency Analysis ──────────────────────────────────────────────────────── _FREQ_HEADERS = ["Number", "Frequency", "Gap (draws since last seen)"] def export_frequency_excel(filepath: str, game_id: int, last_n=None): """Write frequency + gap analysis to filepath (.xlsx). Returns filepath.""" from core.analyzer import frequency_analysis, gap_analysis freq = frequency_analysis(game_id, last_n=last_n) gaps = gap_analysis(game_id) game = get_game_by_id(game_id) all_nums = range(1, game["main_max"] + 1) wb = openpyxl.Workbook() ws = wb.active ws.title = "Frequency Analysis" _header_row(ws, _FREQ_HEADERS, "1E8449") for i, num in enumerate(all_nums, start=2): ws.cell(i, 1, num) ws.cell(i, 2, freq.get(num, 0)) ws.cell(i, 3, gaps.get(num, "—")) _autofit(ws) wb.save(filepath) return filepath # ── App icon (stdlib only, no Pillow required) ──────────────────────────────── def create_icon_png(path: str, size: int = 48): """ Write a simple lottery-ball icon as a valid PNG to path. Uses only stdlib (struct + zlib) — no Pillow needed. """ os.makedirs(os.path.dirname(path) or ".", exist_ok=True) cx = cy = size // 2 r_outer = cx - 2 r_inner = cx - 10 rows = [] for y in range(size): row = b"\x00" # filter byte = None for x in range(size): d2 = (x - cx) ** 2 + (y - cy) ** 2 if d2 <= r_outer * r_outer: if d2 <= r_inner * r_inner: row += bytes([255, 255, 255]) # white centre else: row += bytes([44, 110, 203]) # blue ring else: row += bytes([235, 235, 235]) # light-grey bg rows.append(row) def _chunk(tag: bytes, data: bytes) -> bytes: payload = tag + data return (struct.pack(">I", len(data)) + payload + struct.pack(">I", zlib.crc32(payload) & 0xFFFFFFFF)) png = ( b"\x89PNG\r\n\x1a\n" + _chunk(b"IHDR", struct.pack(">II5B", size, size, 8, 2, 0, 0, 0)) + _chunk(b"IDAT", zlib.compress(b"".join(rows), 9)) + _chunk(b"IEND", b"") ) with open(path, "wb") as fh: fh.write(png)