20 KiB
20 KiB
LottoSight — Project Documentation
App Overview
Name: LottoSight
Type: Desktop Application
Framework: Python + Tkinter
Database: SQLite (local, zero-config)
Purpose: Analyze historical lottery drawing data and predict next winning numbers using multiple statistical strategies.
Tech Stack
| Component | Technology |
|---|---|
| UI | Tkinter + ttk |
| Charts | Matplotlib (embedded in Tkinter) |
| Database | SQLite via sqlite3 |
| Data Fetch | requests + BeautifulSoup |
| Analysis | collections, statistics, numpy |
| Scheduler | APScheduler |
| Export | openpyxl |
| Packaging | PyInstaller |
Supported Games
| Game | Main Balls | Pool | Bonus Ball | Pool |
|---|---|---|---|---|
| Powerball | 5 | 1–69 | 1 | 1–26 |
| Mega Millions | 5 | 1–70 | 1 | 1–25 |
| Custom | config | config | optional | config |
Data Sources
| Source | Game | Coverage | Method |
|---|---|---|---|
| NY Open Data API | Powerball | 2010–present | REST API |
| NY Open Data API | Mega Millions | 2002–present | REST API |
| Texas Lottery CSV | Mega Millions | 2003–present | CSV download |
API Endpoints
- Powerball (NY):
https://data.ny.gov/resource/d6yy-54nr.json - Mega Millions (NY):
https://data.ny.gov/resource/5xaw-6ayf.json - Mega Millions (TX):
https://www.texaslottery.com/export/sites/lottery/Games/Mega_Millions/Winning_Numbers/download.html
File Structure
lottosight/
├── main.py # Entry point, app window, toolbar
├── db/
│ ├── database.py # SQLite setup, schema, migrations
│ └── models.py # CRUD operations, duplicate check
├── core/
│ ├── analyzer.py # All analysis logic
│ ├── predictor.py # Prediction strategies (5 total)
│ └── fetcher.py # Fetch logic — auto + manual, all 3 sources
├── ui/
│ ├── dashboard.py # Home/summary screen
│ ├── history.py # Draw history browser
│ ├── analysis.py # Charts and stats screen
│ ├── predictor_ui.py # Prediction generator screen
│ ├── settings.py # Settings screen + manual fetch button
│ └── statusbar.py # Bottom status bar component
├── assets/
│ └── icon.png
├── exports/ # Excel/CSV exports output folder
├── requirements.txt
└── CLAUDE.md # This file
Database Schema
Table: games
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key |
| name | TEXT | Game name (e.g. Powerball) |
| main_count | INTEGER | Number of main balls |
| main_max | INTEGER | Max value for main balls |
| bonus_count | INTEGER | Number of bonus balls (0 if none) |
| bonus_max | INTEGER | Max value for bonus ball |
| active | INTEGER | 1 = active, 0 = disabled |
Table: draws
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key |
| game_id | INTEGER | Foreign key → games.id |
| draw_date | TEXT | ISO date string (YYYY-MM-DD) |
| numbers | TEXT | Comma-separated main numbers |
| bonus | TEXT | Bonus ball number(s) |
| multiplier | TEXT | Power Play / Megaplier (nullable) |
| source | TEXT | Data source identifier |
| created_at | TEXT | Record insert timestamp |
Table: predictions
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key |
| game_id | INTEGER | Foreign key → games.id |
| strategy | TEXT | Strategy name used |
| numbers | TEXT | Predicted main numbers |
| bonus | TEXT | Predicted bonus number |
| created_at | TEXT | Prediction timestamp |
Table: fetch_log
| Column | Type | Description |
|---|---|---|
| id | INTEGER | Primary key |
| source | TEXT | Source name |
| fetched_at | TEXT | Timestamp of fetch |
| added | INTEGER | New records inserted |
| skipped | INTEGER | Duplicate records skipped |
| status | TEXT | success / error |
| message | TEXT | Error message or notes |
Fetch System
Auto-Fetch
- Triggers on app launch (background thread)
- Repeats every 24 hours via APScheduler
- Runs in background — does not block UI
Manual Fetch
- Button in Toolbar (always visible, all screens)
- Button in Settings screen
- Shows "Fetching…" state while running
Duplicate Handling
- Compare incoming records by
draw_date+game_id - Skip if already exists in DB
- Report result in status bar:
X added, Y skipped
Status Bar Format
Last fetch: Powerball — 3 added, 2 skipped | Mega Millions — 5 added, 0 skipped | 2026-05-23 08:42 AM
Analysis Features
| Feature | Description |
|---|---|
| Frequency analysis | Hot/cold numbers by total draw count |
| Gap/skip analysis | How many draws since each number last appeared |
| Positional frequency | Which numbers appear most in each draw position |
| Pair/triplet patterns | Number combinations that appear together often |
| Odd/even ratio | Ratio of odd vs even numbers per draw |
| Sum range analysis | Distribution of total sums across all draws |
| Delta patterns | Differences between consecutive numbers in a draw |
Prediction Strategies
| # | Strategy | Description |
|---|---|---|
| 1 | Hot Numbers | Top N most frequent numbers in last X draws |
| 2 | Due Numbers | Numbers overdue based on expected frequency gap |
| 3 | Weighted Random | numpy random choice weighted by historical frequency |
| 4 | Monte Carlo | 10,000 simulations, pick most common result |
| 5 | Positional | Most frequent number per draw position |
UI Screens
| Screen | Description |
|---|---|
| Dashboard | Summary stats, last draw result, quick actions |
| History | Searchable/sortable draw history table |
| Analysis | Charts — frequency bar, heatmap, gap chart |
| Predictor | Pick strategy → generate ticket numbers |
| Settings | Game config, data sources, fetch interval, manual fetch |
Logging
All actions are logged to console and optionally to a log file:
[FETCH]— auto/manual fetch events[DB]— insert, skip, error events[PREDICT]— prediction generated[EXPORT]— export actions[ERROR]— any exception with traceback
Build Phases & To-Do
✅ Phase 0 — Planning
- Define app scope and features
- Choose tech stack
- Design database schema
- Design fetch system
- Define data sources and API endpoints
- Create CLAUDE.md
✅ Phase 1 — Database Setup
- Create
lottosight/project folder structure - Write
db/database.py— SQLite init, create all tables - Write
db/models.py— CRUD: insert draw, get draws, check duplicate, insert prediction, insert fetch log - Seed default game configs (Powerball, Mega Millions)
- Write
requirements.txt - Test DB creation and seed on fresh run
- Write
tests/conftest.py— shared tmp_db fixture - Write
tests/test_database.py— 8 tests for database.py (47/47 pass) - Write
tests/test_models.py— 39 tests for models.py (47/47 pass) - Write
.gitea/workflows/ci.yml— CI on every push (syntax check + pytest)
✅ Phase 2 — Fetch System
- Write
core/fetcher.pyfetch_powerball_ny()— NY Open Data APIfetch_megamillions_ny()— NY Open Data APIfetch_megamillions_tx()— Texas Lottery CSVfetch_all()— calls all 3, aggregates results- Duplicate detection logic
- Return added/skipped counts per source
- Write
ui/statusbar.py— bottom status bar widget - Wire auto-fetch on app launch (background thread)
- Wire 24hr scheduled fetch (APScheduler)
- Write
main.py— app window, toolbar with manual fetch button - Wire manual fetch button →
fetch_all()→ update status bar - Test: fresh DB → fetch → verify records inserted (67/67 pass)
- Test: second fetch → verify duplicates skipped, counts correct
- Test: cross-source dedup (same date, different source → skipped)
✅ Phase 3 — History Browser
- Write
ui/history.py- Treeview table with columns: Game, Date, Numbers, Bonus, Mult., Source
- Filter by game (dropdown)
- Search by date range (From / To entries)
- Sort by column headers (▲/▼ indicators, numeric sort for bonus/multiplier)
- Row count display
- Connect history screen to DB reads via
get_draws_with_game() - Auto-refresh after fetch completes
- 13 tests (80/80 total passing)
✅ Phase 4 — Analysis Engine + Charts
- Write
core/analyzer.pyfrequency_analysis(game_id, last_n)gap_analysis(game_id)positional_frequency(game_id)pair_analysis(game_id)odd_even_ratio(game_id)sum_range_analysis(game_id)delta_analysis(game_id)
- Write
ui/analysis.py- Frequency bar chart (Matplotlib + FigureCanvasTkAgg)
- Positional frequency heatmap (imshow, YlOrRd colormap)
- Gap chart (color-coded: blue=recent, red=due)
- Game dropdown + frequency window selector
- NavigationToolbar on each tab for zoom/pan
- 26 tests for all 7 analysis functions (106/106 total passing)
✅ Phase 5 — Prediction Engine
- Write
core/predictor.pyhot_numbers(game_id, last_n=100)— top-frequency + most-frequent bonusdue_numbers(game_id)— highest gap numbers from poolweighted_random(game_id)— numpy weighted choice (min weight 1 for unseen)monte_carlo(game_id, simulations=10000)— tally-based selectionpositional_pick(game_id)— per-position best with dedup- All strategies: random fallback on empty DB
- Write
ui/predictor_ui.py- Game + strategy + ticket count dropdowns
- Generate button (disables during generation)
- Treeview results: #, zero-padded numbers, bonus
- Save to DB (insert_prediction per ticket) + Clear
- Strategy description label
- 18 tests — all 5 strategies × validity + empty DB + edge cases (124/124 total)
✅ Phase 6 — Settings Screen
- Write
ui/settings.py- Game enable/disable checkboxes (set_game_active on toggle)
- Fetch interval display (24 hours, read-only)
- Manual fetch button (shared on_fetch callback from main.py)
- Last fetch timestamp + added/skipped per source (colour-coded)
- DB stats (draws per game + prediction count)
- Scrollable canvas layout for future growth
- Wire settings to DB reads/writes via main.py callback injection
- 14 tests (138/138 total passing)
✅ Phase 7 — Export + Polish
- Write
core/exporter.pyexport_draws_excel(filepath, game_id, date_from, date_to)— openpyxl, styled headerexport_draws_csv(filepath, game_id, date_from, date_to)— stdlib csvexport_predictions_excel(filepath, game_id)— openpyxlexport_predictions_csv(filepath, game_id)— stdlib csvexport_frequency_excel(filepath, game_id, last_n)— number + freq + gapcreate_icon_png(path, size)— valid PNG, stdlib only (struct + zlib)
- Add Export Excel + Export CSV buttons to History screen (filter-aware)
- Add Export Excel + Export CSV buttons to Predictor screen (DB predictions)
- Add Export Frequency button to Analysis screen (respects frequency window)
- Auto-create
assets/icon.pngon startup if missing - Window min-size set (900×600) + resizable layout (all screens)
- Error handling — try/except + messagebox.showerror on all export paths
- 27 tests for exporter (165/165 total passing)
✅ Phase 13 — Ticket Checker
- Write
core/checker.pyprize_tier(main_matches, bonus_match)— maps match counts to Jackpot/Match 5/Match 4+Bonus/… tier labelscheck_ticket(game_id, numbers, bonus)— compares ticket against all draws, returns matches sorted by quality descparse_numbers(raw)— parses space- or comma-separated user input, raises ValueError on bad input
- Add "Check Ticket" tab to
ui/predictor_ui.py(3rd tab in Notebook)- Game dropdown, Numbers entry, Bonus entry, Check / Clear buttons
- Hint label with accepted input format
- Results treeview: Draw Date, Draw Numbers, Bonus, Main Hits, Bonus Hit, Prize Tier
- Row colours: purple=Jackpot, green=≥3 matches or bonus hit, grey=low match
- Summary bar: "N draws matched • Best: "
- Input validation: number count, range check, bonus range
- 27 tests in
tests/test_checker.py(254/254 total passing)
✅ Phase 15 — Odds Calculator
- Write
core/odds.pytotal_combinations(main_count, main_max, bonus_count, bonus_max) -> intprize_odds(main_count, main_max, bonus_count, bonus_max) -> list[dict]— generates all prize tiers dynamically, sorted hardest-first by oddsgame_odds(game_id) -> list[dict]— DB-backed convenience wrapper- Handles 0-bonus and 1-bonus games; bonus_count ≥ 2 returns jackpot only
- Verified against known Powerball (1 in 292,201,338) and Mega Millions (1 in 302,575,350) jackpot odds
- Add "Odds" tab to
ui/analysis.py(8th tab, ttk.Treeview — no matplotlib)- Columns: Prize Tier | Odds (1 in X) | Probability
- Refreshes when game dropdown changes
- Write
tests/test_odds.py— 22 tests (296/296 total passing)
✅ Phase 14 — Custom Game Management
- Add
_BUILTIN_GAMES = {"Powerball", "Mega Millions"}constant todb/models.py - Add
add_game(name, main_count, main_max, bonus_count, bonus_max) -> int- Rejects blank names, duplicate names, and builtin game names (ValueError)
- Inserts with
active=1, returns new row id
- Add
delete_game(game_id) -> bool- Refuses builtin games → returns False
- Refuses games with existing draw records → returns False
- Cascades: deletes predictions for game first, then removes game → returns True
- Update
ui/settings.py- Delete button on each non-builtin game row; warns if draws exist, asks confirm
- "+ Add Custom Game" button at bottom of Games section
_AddGameDialogmodal (tk.Toplevel): name + main_count/max + bonus_count/max spinboxes- Error label in dialog for inline validation feedback
- Refresh games list after add or delete
- Write
tests/test_custom_games.py— 20 tests (274/274 total passing)
✅ Phase 12 — Number Search + Next Draw Schedule
- Number search in History screen
- Add
numberparam toget_draws_with_game()— SQL:',' || numbers || ',' LIKE ?for exact boundary matching - Add "Number:" entry field to History filter bar; bound to
<Return>for quick search - Wire through
_apply_filter()and_clear_filter() - 7 new tests — exact match, boundary false-positive prevention, combined with game/date filter
- Add
- Next draw schedule on Dashboard
_next_draw_date(game_name, from_date)— computes next Powerball (Mon/Wed/Sat) or Mega Millions (Tue/Fri) draw date- Shown on each Last Draw card as "Next draw: Sat, May 24" in green
- 6 new tests — day-of-week transitions, unknown game, always-in-future guarantee
- 227/227 tests passing
✅ Phase 11 — Saved Predictions Viewer
- Add
delete_prediction(pred_id)anddelete_all_predictions(game_id=None)todb/models.py - Refactor
ui/predictor_ui.pyto ttk.Notebook with two tabs- Generate tab — existing UI unchanged
- Saved tab — treeview of all DB predictions (ID, Game, Strategy, Numbers, Bonus, Matches, Saved)
- Match column — shows "X/5" comparing prediction vs last real draw (green if > 0, grey if zero)
- Game filter dropdown — show all games or filter to one
- Delete Selected — removes checked rows from DB + refreshes
- Clear All — confirm dialog, then wipes all (or game-filtered) predictions
- Auto-refreshes Saved tab after Save to DB
_count_matches()helper — set intersection between prediction and last draw numberstests/test_saved_predictions.py— 17 tests (214/214 total passing)
✅ Phase 10 — Complete Analysis Screen (7 Charts)
- Extend
ui/analysis.pywith 4 new chart tabs (was 3, now 7)- Pairs — horizontal bar chart, top-20 most common number pairs
- Odd/Even — bar chart of draw-split distribution (e.g. 3O/2E = 38%)
- Sum Range — histogram of total draw-sum distribution
- Deltas — bar chart of gaps between consecutive numbers within draws
- Add
_draw_pairs,_draw_odd_even,_draw_sum_range,_draw_deltaspure functions tests/test_analysis_charts.py— 23 tests using Agg backend (no display needed)- 200/200 total tests passing
✅ Phase 9 — Dashboard Screen
- Write
ui/dashboard.py- Last Draw Results — card per active game (date, numbers, bonus, multiplier)
- Database Summary — draw counts per game + prediction total
- Hot Numbers — top-5 most-frequent per game (last 100 draws) with counts
- Scrollable canvas layout (same pattern as Settings)
on_fetchcallback injection for future toolbar wiring
- Wire Dashboard into
main.py— replaces "coming soon" placeholder; opens on launch - 12 tests (177/177 total passing)
✅ Phase 8 — Packaging
- Write
core/paths.py—user_data_dir()+bundled_asset()helpers (frozen-aware) - Update
db/database.py+core/exporter.pyto useuser_data_dir()so DB + exports land next to the .exe when frozen - Update
main.pyto usebundled_asset()for icon lookup - Write
lottosight.spec— directory build, no console, bundlesassets/, matplotlib data, openpyxl templates, APScheduler hidden imports - Build verified:
pyinstaller lottosight.spec --cleansucceeds →dist/LottoSight/(~115 MB) assets/icon.pngconfirmed present indist/LottoSight/_internal/assets/- Fix
.gitignore— un-ignore*.spec, adddata/*.db+exports/ - 165/165 tests still passing after paths refactor
- Test packaged app on a clean machine (no Python installed)
Notes
- SQLite DB file stored at:
lottosight/data/lottosight.db - Exports land in:
lottosight/exports/ - All fetch operations run in background threads to keep UI responsive
- NY Open Data API supports
$limitand$offsetparams for pagination - Texas Lottery CSV requires parsing — columns vary, needs header detection
- Powerball matrix changed Oct 7, 2015 (5/69+1/26) — pre-2015 data has different pool sizes, flag in DB or filter