relvistcb commited on
Commit
e2516eb
Β·
verified Β·
1 Parent(s): dcfdf56

Upload app.txt

Browse files
Files changed (1) hide show
  1. app.txt +376 -0
app.txt ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ from pathlib import Path
4
+ import re
5
+ import json
6
+ from collections import Counter
7
+
8
+ # Import V3.0 backend
9
+ from lotto_predictor import (
10
+ predict_for_game_v3,
11
+ GAME_CONFIGS,
12
+ NumpyEncoder,
13
+ clean_powerball_df,
14
+ load_csv_for_game
15
+ )
16
+
17
+ # Data paths (adjust if your files live in a data/ subfolder)
18
+ DATA_PATHS = {
19
+ "G5 (Gimme 5)": "gimme5_results.csv",
20
+ "LA (Lotto America)": "la_results.csv",
21
+ "L4L (Lucky for Life)": "Lucky For Life.csv", # βœ… NEW
22
+ "MB (Megabucks)": "mb_results.csv",
23
+ "MM (Mega Millions)": "mm_results.csv",
24
+ "PB (Powerball)": "pb_results.csv",
25
+ "wheel_template": "wheel.txt",
26
+ }
27
+
28
+ st.set_page_config(page_title="Multi Lotto AI Engine V5.0", layout="centered")
29
+ st.title("🎯 Lotto AI Engine (V5.0)")
30
+
31
+ # -------------------------
32
+ # Helper functions for V3.0
33
+ # -------------------------
34
+
35
+ def get_hot_and_cold_numbers(df: pd.DataFrame, cfg, top_n: int = 10):
36
+ """Calculate hot and cold numbers from the dataframe"""
37
+ # Count frequency of each number across all main columns
38
+ all_numbers = []
39
+ for col in cfg.main_cols:
40
+ all_numbers.extend(df[col].astype(int).tolist())
41
+
42
+ freq_counter = Counter(all_numbers)
43
+
44
+ # Get all possible numbers for this game
45
+ all_possible = list(range(cfg.main_min, cfg.main_max + 1))
46
+
47
+ # Create frequency list with zeros for missing numbers
48
+ freq_list = [(num, freq_counter.get(num, 0)) for num in all_possible]
49
+
50
+ # Sort by frequency
51
+ sorted_by_freq = sorted(freq_list, key=lambda x: x[1], reverse=True)
52
+
53
+ # Hot numbers (most frequent)
54
+ hot = sorted_by_freq[:top_n]
55
+
56
+ # Cold numbers (least frequent)
57
+ cold = sorted_by_freq[-top_n:]
58
+ cold.reverse() # Show coldest first
59
+
60
+ return hot, cold
61
+
62
+ @st.cache_data
63
+ def load_wheel_raw_text(path: str) -> str:
64
+ """
65
+ Read wheel template as raw text using latin-1 fallback (robust to special bytes).
66
+ Returns empty string if missing or unreadable.
67
+ """
68
+ p = Path(path)
69
+ if not p.exists():
70
+ return ""
71
+ try:
72
+ # latin-1 will never fail for single-byte encodings; errors='replace' for safety
73
+ text = p.read_text(encoding="latin-1", errors="replace")
74
+ return text
75
+ except Exception:
76
+ return ""
77
+
78
+ def select_20_wheel_numbers(hot: list, cold: list):
79
+ """Select 20 numbers for wheeling using hot/cold analysis"""
80
+ wheel_map = {}
81
+ wheel_labels = list("ABCDEFGHIJKLMNOPQRST")
82
+
83
+ # Take top 10 hot numbers
84
+ hot_numbers = [num for num, freq in hot[:10]]
85
+
86
+ # Take bottom 10 cold numbers
87
+ cold_numbers = [num for num, freq in cold[:10]]
88
+
89
+ # Combine them
90
+ selected_numbers = hot_numbers + cold_numbers
91
+
92
+ # Map to letters A-T
93
+ for i, letter in enumerate(wheel_labels):
94
+ if i < len(selected_numbers):
95
+ wheel_map[letter] = selected_numbers[i]
96
+
97
+ return wheel_map
98
+
99
+ def convert_numeric_wheel_to_letter_template(raw_text: str, wheel_size: int = 20) -> str:
100
+ """
101
+ Convert numeric wheel lines like:
102
+ 1-01-02-06-18-19-46
103
+ into letter-template lines:
104
+ A B C D E
105
+ """
106
+ if not raw_text:
107
+ return ""
108
+
109
+ lines = raw_text.splitlines()
110
+ out_lines = []
111
+ for line in lines:
112
+ # Detect lines that start with an index + dash (e.g. " 1-01-02-06-18-19-46")
113
+ if re.match(r'^\s*\d+\s*-', line):
114
+ # extract all integer tokens (1 or 2 digits)
115
+ nums = re.findall(r'\d{1,2}', line)
116
+ if not nums:
117
+ continue
118
+ # Many files start the line with the ticket index; drop it if present and equals first num
119
+ first_num_match = re.match(r'^\s*(\d+)', line)
120
+ if first_num_match and nums and nums[0] == first_num_match.group(1):
121
+ nums = nums[1:]
122
+ if len(nums) < 5:
123
+ # skip if fewer than 5 picks found
124
+ continue
125
+ picks = nums[:5] # take the first five numbers
126
+ letters = []
127
+ for n_str in picks:
128
+ n = int(n_str)
129
+ # Map 1->A, 2->B, ... wrap/clamp if needed
130
+ idx = (n - 1) % 26
131
+ letters.append(chr(ord('A') + idx))
132
+ if len(letters) >= 5:
133
+ out_lines.append(" ".join(letters))
134
+ return "\n".join(out_lines)
135
+
136
+ def expand_wheel_with_template(wheel_map: dict, template: str):
137
+ """Expand wheel template into actual number combinations"""
138
+ combos = []
139
+ lines = template.strip().split('\n')
140
+
141
+ for line in lines:
142
+ letters = line.strip().split()
143
+ if len(letters) >= 5:
144
+ combo = []
145
+ for letter in letters[:5]: # Take first 5 letters
146
+ if letter in wheel_map:
147
+ combo.append(wheel_map[letter])
148
+ if len(combo) == 5:
149
+ combos.append(sorted(combo))
150
+
151
+ return combos
152
+
153
+ # -------------------------
154
+ # Display helpers
155
+ # -------------------------
156
+ def display_hot_cold_tables(hot_df: pd.DataFrame, cold_df: pd.DataFrame):
157
+ hot_df.index = range(1, len(hot_df) + 1)
158
+ hot_df.index.name = "No"
159
+ cold_df.index = range(1, len(cold_df) + 1)
160
+ cold_df.index.name = "No"
161
+ with st.expander("πŸ”₯ Hot Numbers (Top 10)"):
162
+ st.table(hot_df)
163
+ with st.expander("❄️ Cold Numbers (Bottom 10)"):
164
+ st.table(cold_df)
165
+
166
+ def display_wheel_table_from_hotcold(hot_df: pd.DataFrame, cold_df: pd.DataFrame):
167
+ """
168
+ Build the 20-number wheel mapping and show the table in the UI.
169
+ Returns wheel_map (dict letter->number).
170
+ """
171
+ hot = [(int(n), f) for n, f in hot_df.values]
172
+ cold = [(int(n), f) for n, f in cold_df.values]
173
+ wheel_map = select_20_wheel_numbers(hot, cold)
174
+ wheel_labels = list("ABCDEFGHIJKLMNOPQRST")
175
+ ordered_numbers = [wheel_map.get(l, None) for l in wheel_labels]
176
+ wheel_df = pd.DataFrame([ordered_numbers], columns=wheel_labels)
177
+ with st.expander("🎑 Your 20 Numbers to Wheel"):
178
+ st.table(wheel_df)
179
+ return wheel_map
180
+
181
+ def display_wheel_combinations_from_raw(wheel_map: dict, raw_template_text: str):
182
+ """
183
+ Convert numeric wheel template to letter-template, then expand and display combos.
184
+ """
185
+ if not raw_template_text:
186
+ st.warning("Wheel template file is empty or not found.")
187
+ return
188
+
189
+ letter_template = convert_numeric_wheel_to_letter_template(raw_template_text)
190
+ if not letter_template:
191
+ st.warning("Wheel template parsing found no valid ticket lines.")
192
+ return
193
+
194
+ combos = expand_wheel_with_template(wheel_map, letter_template)
195
+ if not combos:
196
+ st.warning("No combinations produced after expansion.")
197
+ return
198
+
199
+ df = pd.DataFrame(combos, columns=["Num1", "Num2", "Num3", "Num4", "Num5"])
200
+ df.index = [f"Ticket{i+1}" for i in range(len(df))]
201
+ df.index.name = "No"
202
+ with st.expander(f"🎟️ Wheel Combinations ({len(df)} tickets)"):
203
+ st.dataframe(df)
204
+
205
+ # -------------------------
206
+ # UI & main logic
207
+ # -------------------------
208
+ lotto_options = [
209
+ "G5 (Gimme 5)",
210
+ "LA (Lotto America)",
211
+ "L4L (Lucky for Life)", # βœ… NEW
212
+ "MB (Megabucks)",
213
+ "MM (Mega Millions)",
214
+ "PB (Powerball)",
215
+ ]
216
+ lotto_type = st.selectbox("Select Lotto Type:", options=lotto_options, index=0)
217
+
218
+ # Map display names -> keys used in GAME_CONFIGS and predict_for_game_v3
219
+ GAME_KEY_MAP = {
220
+ "G5 (Gimme 5)": "gimme5",
221
+ "LA (Lotto America)": "la",
222
+ "L4L (Lucky for Life)": "l4l", # βœ… NEW
223
+ "MB (Megabucks)": "mb",
224
+ "MM (Mega Millions)": "mm",
225
+ "PB (Powerball)": "pb",
226
+ }
227
+
228
+ try:
229
+ game_key = GAME_KEY_MAP[lotto_type]
230
+ data_path = DATA_PATHS[lotto_type]
231
+ cfg = GAME_CONFIGS[game_key]
232
+
233
+ # Load dataset using V3.0 loader
234
+ df, _ = load_csv_for_game(Path(data_path), game_key)
235
+
236
+ # Hot/cold numbers computed using our helper
237
+ hot, cold = get_hot_and_cold_numbers(df, cfg)
238
+ hot_df = pd.DataFrame(hot, columns=["Number", "Frequency"])
239
+ cold_df = pd.DataFrame(cold, columns=["Number", "Frequency"])
240
+
241
+ # UI options - simplified for V3.0
242
+ run_backtest = st.checkbox("πŸ§ͺ Run Backtest (slower but shows model performance)", value=False)
243
+ use_wheel = st.checkbox("🎑 Generate Wheel Combinations (if wheel.txt available)", value=False)
244
+
245
+ wheel_raw_text = load_wheel_raw_text(DATA_PATHS["wheel_template"]) if use_wheel else ""
246
+
247
+ # Styling
248
+ st.markdown(
249
+ """
250
+ <style>
251
+ table { margin-left:auto; margin-right:auto; }
252
+ th, td { text-align:center !important; vertical-align: middle !important; }
253
+ </style>
254
+ """,
255
+ unsafe_allow_html=True,
256
+ )
257
+
258
+ if st.button("🎰 Generate Prediction" if not run_backtest else "πŸ§ͺ Run Backtest"):
259
+ with st.spinner("Building ensemble models and generating results..."):
260
+ # Run V3.0 predictor
261
+ result = predict_for_game_v3(
262
+ csv_path=Path(data_path),
263
+ game_key=game_key,
264
+ run_backtest=run_backtest
265
+ )
266
+
267
+ if run_backtest:
268
+ # Display backtest results
269
+ if 'error' in result:
270
+ st.error(f"❌ Backtest Error: {result['error']}")
271
+ else:
272
+ st.success("βœ… Backtest Complete!")
273
+
274
+ # Show summary metrics
275
+ st.subheader("πŸ“Š Backtest Summary")
276
+ col1, col2, col3 = st.columns(3)
277
+
278
+ with col1:
279
+ st.metric("Model 3+ Matches", f"{result.get('model_3plus_rate', 0)}%")
280
+ with col2:
281
+ st.metric("Random 3+ Matches", f"{result.get('random_3plus_rate', 0)}%")
282
+ with col3:
283
+ st.metric("Even Count Accuracy", f"{result.get('even_count_accuracy', 0)}%")
284
+
285
+ # Detailed hit rates
286
+ st.subheader("🎯 Hit Rate Comparison")
287
+ hit_data = []
288
+ for i in range(6):
289
+ model_rate = result.get(f'model_hit_{i}_rate', 0)
290
+ random_rate = result.get(f'random_hit_{i}_rate', 0)
291
+ hit_data.append({
292
+ 'Matches': i,
293
+ 'Model Rate (%)': model_rate,
294
+ 'Random Rate (%)': random_rate,
295
+ 'Improvement': f"+{model_rate - random_rate:.1f}%" if model_rate > random_rate else f"{model_rate - random_rate:.1f}%"
296
+ })
297
+
298
+ hit_df = pd.DataFrame(hit_data)
299
+ st.table(hit_df)
300
+
301
+ # Raw results
302
+ with st.expander("πŸ“‹ Full Backtest Results"):
303
+ st.json(result)
304
+
305
+ else:
306
+ # -------------------------------
307
+ # Display prediction results
308
+ # -------------------------------
309
+ primary_numbers = result.get("numbers", [])
310
+ primary_star = result.get("star", None)
311
+
312
+ # Primary pick (same look as before, just a bit safer)
313
+ if primary_numbers:
314
+ st.success(f"🧠 Predicted Numbers: {primary_numbers}")
315
+ else:
316
+ st.warning("No primary numbers returned from engine.")
317
+
318
+ if primary_star is not None:
319
+ star_col_name = cfg.star_col or 'Star'
320
+ st.success(f"🌟 Predicted {star_col_name}: {primary_star}")
321
+ else:
322
+ st.info("ℹ️ No bonus number for this game")
323
+
324
+ # πŸ‘‰ NEW: Show up to 5 GOD MODE sets (if present)
325
+ god_sets = result.get("godmode_sets", [])
326
+ if god_sets:
327
+ st.subheader("🎲 GOD MODE Sets (up to 5)")
328
+ for i, s in enumerate(god_sets[:5], start=1):
329
+ style = s.get("style", "unknown").replace("_", " ").title()
330
+ nums = s.get("numbers", [])
331
+ star = s.get("star", None)
332
+ label = f"{i}) {style}"
333
+ if nums:
334
+ nums_str = "-".join(str(n) for n in nums)
335
+ else:
336
+ nums_str = "(none)"
337
+
338
+ if star is not None:
339
+ st.write(f"**{label}:** {nums_str} | ⭐ {star}")
340
+ else:
341
+ st.write(f"**{label}:** {nums_str}")
342
+ else:
343
+ st.info("No GOD MODE sets available in this result.")
344
+
345
+ # Show additional info (same as before)
346
+ if primary_numbers:
347
+ st.info(f"πŸ”’ Total Sum: {sum(primary_numbers)} (Expected Range: {cfg.sum_min}–{cfg.sum_max})")
348
+ else:
349
+ st.info(f"πŸ”’ Expected Sum Range: {cfg.sum_min}–{cfg.sum_max}")
350
+
351
+ model_info = result.get('model_info', {})
352
+ st.info(f"πŸ€– Models built for {model_info.get('numbers_modeled', 0)}/{model_info.get('total_possible', 0)} numbers")
353
+
354
+ # Show hot/cold tables
355
+ display_hot_cold_tables(hot_df, cold_df)
356
+
357
+ # Wheel (if requested)
358
+ if use_wheel and wheel_raw_text:
359
+ # Build and show wheel table (A..T mapping)
360
+ wheel_map = display_wheel_table_from_hotcold(hot_df, cold_df)
361
+
362
+ # Expand numeric wheel.txt to letter-template and display combos
363
+ display_wheel_combinations_from_raw(wheel_map, wheel_raw_text)
364
+ elif use_wheel:
365
+ st.warning("⚠️ Wheel template file (wheel.txt) not found or empty")
366
+
367
+ # Raw prediction results
368
+ with st.expander("πŸ“‹ Full Prediction Details"):
369
+ st.json(result)
370
+
371
+ except FileNotFoundError:
372
+ st.error(f"❌ File not found: `{data_path}`")
373
+ except Exception as e:
374
+ st.error(f"⚠️ Error: {str(e)}")
375
+ import traceback
376
+ st.error(f"Details: {traceback.format_exc()}")