Spaces:
Running
Running
Jean Marco Cruz Castellar commited on
Add files via upload
Browse files- proyecto_tinka_ml/proyecto_tinka_ml/README.md +36 -0
- proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/analizador.cpython-313.pyc +0 -0
- proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/config.cpython-313.pyc +0 -0
- proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/db_connector.cpython-313.pyc +0 -0
- proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/generador.cpython-313.pyc +0 -0
- proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/ml.cpython-313.pyc +0 -0
- proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/utils.cpython-313.pyc +0 -0
- proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/visualizador.cpython-313.pyc +0 -0
- proyecto_tinka_ml/proyecto_tinka_ml/analizador.py +156 -0
- proyecto_tinka_ml/proyecto_tinka_ml/config.py +31 -0
- proyecto_tinka_ml/proyecto_tinka_ml/data/cache_sorteos.json +242 -0
- proyecto_tinka_ml/proyecto_tinka_ml/data/combinaciones_generadas.json +19 -0
- proyecto_tinka_ml/proyecto_tinka_ml/data/probabilidades.json +503 -0
- proyecto_tinka_ml/proyecto_tinka_ml/data/reporte.html +76 -0
- proyecto_tinka_ml/proyecto_tinka_ml/db_connector.py +69 -0
- proyecto_tinka_ml/proyecto_tinka_ml/generador.py +165 -0
- proyecto_tinka_ml/proyecto_tinka_ml/main.py +250 -0
- proyecto_tinka_ml/proyecto_tinka_ml/ml.py +100 -0
- proyecto_tinka_ml/proyecto_tinka_ml/requirements.txt +6 -0
- proyecto_tinka_ml/proyecto_tinka_ml/utils.py +54 -0
- proyecto_tinka_ml/proyecto_tinka_ml/visualizador.py +63 -0
proyecto_tinka_ml/proyecto_tinka_ml/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Sistema de Análisis y Generación de Combinaciones — Tinka (con ML)
|
| 2 |
+
|
| 3 |
+
**Aviso:** La lotería es aleatoria. Este proyecto es **educativo**; no garantiza aciertos.
|
| 4 |
+
|
| 5 |
+
## 1) Requisitos
|
| 6 |
+
- Python 3.9+
|
| 7 |
+
- Instala dependencias:
|
| 8 |
+
```bash
|
| 9 |
+
pip install -r requirements.txt
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
## 2) Configurar BD
|
| 13 |
+
Edita `config.py` con tus credenciales (XAMPP por defecto). La tabla `resultados` debe tener:
|
| 14 |
+
`id_sorteo`, `fecha_sorteo`, `numeros`, `boliyapa`, `jackpot`, `created_at` (opcional).
|
| 15 |
+
|
| 16 |
+
## 3) Ejecutar
|
| 17 |
+
```bash
|
| 18 |
+
python main.py
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
## 4) Menú
|
| 22 |
+
1. Dashboard de estadísticas (exporta `data/reporte.html`)
|
| 23 |
+
2. Análisis por número
|
| 24 |
+
3. Generar combinaciones (elige estrategia y **cantidad**)
|
| 25 |
+
4. Evaluar tu combinación
|
| 26 |
+
5. Mejores históricas por score heurístico
|
| 27 |
+
6. Exportar análisis a HTML
|
| 28 |
+
7. Refrescar datos desde la BD (actualiza caché)
|
| 29 |
+
8. **Recomendación automática (ML)**: calcula probabilidades bayesianas (globales + recientes), genera un pool (estrategias clásicas + **Thompson Sampling**) y rankea con un score ML (log-prob + heurísticas). Guarda:
|
| 30 |
+
- `data/probabilidades.json`
|
| 31 |
+
- `data/combinaciones_generadas.json`
|
| 32 |
+
|
| 33 |
+
## 5) Notas
|
| 34 |
+
- Cambios robustos en caché (JSON atómico).
|
| 35 |
+
- SQL con SQLAlchemy para evitar warnings.
|
| 36 |
+
- Visualización simple con matplotlib (PNG embebido).
|
proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/analizador.cpython-313.pyc
ADDED
|
Binary file (11.8 kB). View file
|
|
|
proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/config.cpython-313.pyc
ADDED
|
Binary file (877 Bytes). View file
|
|
|
proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/db_connector.cpython-313.pyc
ADDED
|
Binary file (3.98 kB). View file
|
|
|
proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/generador.cpython-313.pyc
ADDED
|
Binary file (12.7 kB). View file
|
|
|
proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/ml.cpython-313.pyc
ADDED
|
Binary file (6.19 kB). View file
|
|
|
proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/utils.cpython-313.pyc
ADDED
|
Binary file (3.08 kB). View file
|
|
|
proyecto_tinka_ml/proyecto_tinka_ml/__pycache__/visualizador.cpython-313.pyc
ADDED
|
Binary file (3.64 kB). View file
|
|
|
proyecto_tinka_ml/proyecto_tinka_ml/analizador.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Lógica de análisis estadístico para Tinka/Boliyapa."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
from typing import Dict, List, Tuple
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from itertools import combinations
|
| 7 |
+
from collections import Counter
|
| 8 |
+
from config import TOTAL_NUMBERS, COMBINATION_SIZE, LAST_N_WINDOWS, SUM_RANGE
|
| 9 |
+
from utils import parse_numbers, is_prime
|
| 10 |
+
|
| 11 |
+
def _explode_numeros(df: pd.DataFrame) -> pd.DataFrame:
|
| 12 |
+
rows = []
|
| 13 |
+
for _, r in df.iterrows():
|
| 14 |
+
nums = parse_numbers(r['numeros'])
|
| 15 |
+
for n in nums:
|
| 16 |
+
rows.append({'id_sorteo': r['id_sorteo'], 'fecha_sorteo': r['fecha_sorteo'], 'numero': n})
|
| 17 |
+
return pd.DataFrame(rows, columns=['id_sorteo','fecha_sorteo','numero'])
|
| 18 |
+
|
| 19 |
+
def _coocurrencias(df: pd.DataFrame, k: int = 2):
|
| 20 |
+
cnt = Counter()
|
| 21 |
+
for _, r in df.iterrows():
|
| 22 |
+
arr = sorted(parse_numbers(r['numeros']))
|
| 23 |
+
for combo in combinations(arr, k):
|
| 24 |
+
cnt[combo] += 1
|
| 25 |
+
return cnt
|
| 26 |
+
|
| 27 |
+
def _ultimos(df: pd.DataFrame, n: int):
|
| 28 |
+
return df.tail(n) if len(df) >= n else df.copy()
|
| 29 |
+
|
| 30 |
+
def analisis_frecuencias(df: pd.DataFrame) -> Dict:
|
| 31 |
+
exploded = _explode_numeros(df)
|
| 32 |
+
freq_abs = Counter(exploded['numero'].tolist())
|
| 33 |
+
for k in range(1, TOTAL_NUMBERS+1):
|
| 34 |
+
freq_abs.setdefault(k, 0)
|
| 35 |
+
total_bolas = len(exploded)
|
| 36 |
+
freq_rel = {k: (v/total_bolas if total_bolas else 0.0) for k, v in freq_abs.items()}
|
| 37 |
+
top = sorted(freq_abs.items(), key=lambda x: (-x[1], x[0]))
|
| 38 |
+
hot = [k for k,_ in top[:15]]
|
| 39 |
+
cold = [k for k,_ in sorted(freq_abs.items(), key=lambda x: (x[1], x[0]))[:15]]
|
| 40 |
+
ult10 = set(_explode_numeros(_ultimos(df, 10))['numero'].tolist())
|
| 41 |
+
# dormidos: no aparecen hace >20 sorteos
|
| 42 |
+
last_seen_idx = {}
|
| 43 |
+
for idx, r in enumerate(df.itertuples(index=False), start=1):
|
| 44 |
+
for n in parse_numbers(r.numeros):
|
| 45 |
+
last_seen_idx[n] = idx
|
| 46 |
+
dormidos = []
|
| 47 |
+
threshold = max(1, len(df) - 20)
|
| 48 |
+
for n in range(1, TOTAL_NUMBERS+1):
|
| 49 |
+
if last_seen_idx.get(n, 0) < threshold:
|
| 50 |
+
dormidos.append(n)
|
| 51 |
+
return {'freq_abs': dict(sorted(freq_abs.items())),
|
| 52 |
+
'freq_rel': dict(sorted(freq_rel.items())),
|
| 53 |
+
'hot_15': hot,
|
| 54 |
+
'cold_15': cold,
|
| 55 |
+
'en_racha_ult10': sorted(ult10),
|
| 56 |
+
'dormidos_mas20': sorted(dormidos)}
|
| 57 |
+
|
| 58 |
+
def analisis_temporal(df: pd.DataFrame) -> Dict:
|
| 59 |
+
exploded = _explode_numeros(df)
|
| 60 |
+
tmp = exploded.copy()
|
| 61 |
+
tmp['fecha_sorteo'] = pd.to_datetime(tmp['fecha_sorteo'])
|
| 62 |
+
tmp['anio'] = tmp['fecha_sorteo'].dt.year
|
| 63 |
+
tmp['mes'] = tmp['fecha_sorteo'].dt.month
|
| 64 |
+
by_mes = tmp.groupby(['anio','mes','numero']).size().reset_index(name='freq')
|
| 65 |
+
gaps = {}
|
| 66 |
+
for n in range(1, TOTAL_NUMBERS+1):
|
| 67 |
+
apar = tmp.loc[tmp['numero']==n, 'fecha_sorteo'].sort_values().tolist()
|
| 68 |
+
if len(apar) >= 2:
|
| 69 |
+
difs = [(apar[i]-apar[i-1]).days for i in range(1, len(apar))]
|
| 70 |
+
gaps[n] = float(np.mean(difs))
|
| 71 |
+
else:
|
| 72 |
+
gaps[n] = None
|
| 73 |
+
ventanas = {}
|
| 74 |
+
for win in LAST_N_WINDOWS:
|
| 75 |
+
sub = _ultimos(df, win)
|
| 76 |
+
ventanas[str(win)] = analisis_frecuencias(sub)
|
| 77 |
+
return {'por_mes': by_mes.to_dict(orient='records'), 'gaps_prom_dias': gaps, 'ventanas': ventanas}
|
| 78 |
+
|
| 79 |
+
def analisis_patrones(df: pd.DataFrame) -> Dict:
|
| 80 |
+
even, odd = 0, 0
|
| 81 |
+
ranges = {'1-9':0,'10-18':0,'19-27':0,'28-36':0,'37-45':0}
|
| 82 |
+
suma_vals = []
|
| 83 |
+
consecutivos = 0
|
| 84 |
+
distancias = []
|
| 85 |
+
primos_cnt = 0
|
| 86 |
+
total_combinaciones = 0
|
| 87 |
+
for _, r in df.iterrows():
|
| 88 |
+
arr = sorted(parse_numbers(r['numeros']))
|
| 89 |
+
total_combinaciones += 1
|
| 90 |
+
suma_vals.append(sum(arr))
|
| 91 |
+
for x in arr:
|
| 92 |
+
if x % 2 == 0: even += 1
|
| 93 |
+
else: odd += 1
|
| 94 |
+
if 1 <= x <= 9: ranges['1-9'] += 1
|
| 95 |
+
elif 10 <= x <= 18: ranges['10-18'] += 1
|
| 96 |
+
elif 19 <= x <= 27: ranges['19-27'] += 1
|
| 97 |
+
elif 28 <= x <= 36: ranges['28-36'] += 1
|
| 98 |
+
elif 37 <= x <= 45: ranges['37-45'] += 1
|
| 99 |
+
if is_prime(x): primos_cnt += 1
|
| 100 |
+
consecutivos += sum(1 for a,b in zip(arr, arr[1:]) if b==a+1)
|
| 101 |
+
distancias += [abs(b-a) for a,b in zip(arr, arr[1:])]
|
| 102 |
+
return {'pares': even, 'impares': odd, 'rangos': ranges,
|
| 103 |
+
'suma_min': int(min(suma_vals)) if suma_vals else None,
|
| 104 |
+
'suma_max': int(max(suma_vals)) if suma_vals else None,
|
| 105 |
+
'suma_prom': float(np.mean(suma_vals)) if suma_vals else None,
|
| 106 |
+
'consecutivos_total': int(consecutivos),
|
| 107 |
+
'dist_prom': float(np.mean(distancias)) if distancias else None,
|
| 108 |
+
'primos_total': int(primos_cnt),
|
| 109 |
+
'total_combinaciones': total_combinaciones}
|
| 110 |
+
|
| 111 |
+
def analisis_coocurrencias(df: pd.DataFrame) -> Dict:
|
| 112 |
+
pairs = _coocurrencias(df, k=2)
|
| 113 |
+
trios = _coocurrencias(df, k=3)
|
| 114 |
+
top_pairs = pairs.most_common(20)
|
| 115 |
+
top_trios = trios.most_common(20)
|
| 116 |
+
return {'pairs_top20': [{'pair': list(k), 'freq': v} for k,v in top_pairs],
|
| 117 |
+
'trios_top20': [{'trio': list(k), 'freq': v} for k,v in top_trios]}
|
| 118 |
+
|
| 119 |
+
def analisis_boliyapa(df: pd.DataFrame) -> Dict:
|
| 120 |
+
bol = df['boliyapa'].dropna().astype(int).tolist()
|
| 121 |
+
cnt = Counter(bol)
|
| 122 |
+
total = len(bol)
|
| 123 |
+
freq_rel = {k: (v/total if total else 0.0) for k,v in cnt.items()}
|
| 124 |
+
return {'freq_abs': dict(sorted(cnt.items())),
|
| 125 |
+
'freq_rel': dict(sorted(freq_rel.items()))}
|
| 126 |
+
|
| 127 |
+
def analisis_chicuadrado(df: pd.DataFrame) -> Dict:
|
| 128 |
+
exploded = []
|
| 129 |
+
for _, r in df.iterrows():
|
| 130 |
+
exploded += parse_numbers(r['numeros'])
|
| 131 |
+
from collections import Counter
|
| 132 |
+
cnt = Counter(exploded)
|
| 133 |
+
import numpy as np
|
| 134 |
+
obs = np.array([cnt.get(i, 0) for i in range(1, TOTAL_NUMBERS+1)], dtype=float)
|
| 135 |
+
n = obs.sum()
|
| 136 |
+
if n == 0:
|
| 137 |
+
return {'chi2': None, 'p_value': None, 'expected': None}
|
| 138 |
+
expected = np.ones_like(obs) * (n / TOTAL_NUMBERS)
|
| 139 |
+
chi2 = ((obs - expected)**2 / expected).sum()
|
| 140 |
+
p_value = None
|
| 141 |
+
try:
|
| 142 |
+
from scipy.stats import chi2 as chi2_dist
|
| 143 |
+
dfree = TOTAL_NUMBERS - 1
|
| 144 |
+
p_value = float(1 - chi2_dist.cdf(chi2, dfree))
|
| 145 |
+
except Exception:
|
| 146 |
+
p_value = None
|
| 147 |
+
std_dev = float(np.std(obs))
|
| 148 |
+
return {'chi2': float(chi2), 'p_value': p_value, 'std_freq': std_dev, 'expected_each': float(n / TOTAL_NUMBERS)}
|
| 149 |
+
|
| 150 |
+
def analisis_completo(df: pd.DataFrame) -> Dict:
|
| 151 |
+
return {'frecuencias': analisis_frecuencias(df),
|
| 152 |
+
'temporal': analisis_temporal(df),
|
| 153 |
+
'patrones': analisis_patrones(df),
|
| 154 |
+
'coocurrencias': analisis_coocurrencias(df),
|
| 155 |
+
'boliyapa': analisis_boliyapa(df),
|
| 156 |
+
'chi_cuadrado': analisis_chicuadrado(df)}
|
proyecto_tinka_ml/proyecto_tinka_ml/config.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Configuración del sistema Tinka/Boliyapa."""
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
# === BD (XAMPP por defecto) ===
|
| 5 |
+
DB_CONFIG = {
|
| 6 |
+
"host": "localhost",
|
| 7 |
+
"port": 3306,
|
| 8 |
+
"database": "tinka_db",
|
| 9 |
+
"user": "root",
|
| 10 |
+
"password": ""
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
# Tabla con los resultados
|
| 14 |
+
TABLE_NAME = "resultados"
|
| 15 |
+
|
| 16 |
+
# === Carpetas ===
|
| 17 |
+
BASE_DIR = Path(__file__).resolve().parent
|
| 18 |
+
DATA_DIR = BASE_DIR / "data"
|
| 19 |
+
LOGS_DIR = BASE_DIR / "logs"
|
| 20 |
+
|
| 21 |
+
# Archivos de datos
|
| 22 |
+
CACHE_FILE = DATA_DIR / "cache_sorteos.json"
|
| 23 |
+
COMBOS_FILE = DATA_DIR / "combinaciones_generadas.json"
|
| 24 |
+
|
| 25 |
+
# Parámetros generales
|
| 26 |
+
TOTAL_NUMBERS = 45
|
| 27 |
+
COMBINATION_SIZE = 6
|
| 28 |
+
|
| 29 |
+
# Parámetros heurísticos
|
| 30 |
+
SUM_RANGE = (90, 180)
|
| 31 |
+
LAST_N_WINDOWS = [50, 100, 200]
|
proyecto_tinka_ml/proyecto_tinka_ml/data/cache_sorteos.json
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id_sorteo": 1206,
|
| 4 |
+
"fecha_sorteo": "2025-06-25",
|
| 5 |
+
"numeros": "38 30 08 27 48 14",
|
| 6 |
+
"boliyapa": 17,
|
| 7 |
+
"jackpot": 0.0,
|
| 8 |
+
"created_at": "2025-10-08 14:38:07"
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"id_sorteo": 1207,
|
| 12 |
+
"fecha_sorteo": "2025-06-29",
|
| 13 |
+
"numeros": "47 37 32 22 24 23",
|
| 14 |
+
"boliyapa": 4,
|
| 15 |
+
"jackpot": 0.0,
|
| 16 |
+
"created_at": "2025-10-08 14:38:08"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"id_sorteo": 1208,
|
| 20 |
+
"fecha_sorteo": "2025-07-02",
|
| 21 |
+
"numeros": "14 28 32 07 10 29",
|
| 22 |
+
"boliyapa": 36,
|
| 23 |
+
"jackpot": 0.0,
|
| 24 |
+
"created_at": "2025-10-08 14:55:10"
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"id_sorteo": 1209,
|
| 28 |
+
"fecha_sorteo": "2025-07-06",
|
| 29 |
+
"numeros": "29 20 14 11 03 15",
|
| 30 |
+
"boliyapa": 35,
|
| 31 |
+
"jackpot": 0.0,
|
| 32 |
+
"created_at": "2025-10-08 14:55:10"
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"id_sorteo": 1210,
|
| 36 |
+
"fecha_sorteo": "2025-07-09",
|
| 37 |
+
"numeros": "35 28 09 42 31 16",
|
| 38 |
+
"boliyapa": 45,
|
| 39 |
+
"jackpot": 0.0,
|
| 40 |
+
"created_at": "2025-10-08 14:55:11"
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"id_sorteo": 1211,
|
| 44 |
+
"fecha_sorteo": "2025-07-13",
|
| 45 |
+
"numeros": "09 32 43 49 38 24",
|
| 46 |
+
"boliyapa": 5,
|
| 47 |
+
"jackpot": 0.0,
|
| 48 |
+
"created_at": "2025-10-08 14:38:10"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"id_sorteo": 1212,
|
| 52 |
+
"fecha_sorteo": "2025-07-16",
|
| 53 |
+
"numeros": "36 22 37 11 07 01",
|
| 54 |
+
"boliyapa": 24,
|
| 55 |
+
"jackpot": 0.0,
|
| 56 |
+
"created_at": "2025-10-08 14:38:11"
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"id_sorteo": 1213,
|
| 60 |
+
"fecha_sorteo": "2025-07-20",
|
| 61 |
+
"numeros": "42 45 28 06 19 05",
|
| 62 |
+
"boliyapa": 10,
|
| 63 |
+
"jackpot": 0.0,
|
| 64 |
+
"created_at": "2025-10-08 14:38:12"
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"id_sorteo": 1214,
|
| 68 |
+
"fecha_sorteo": "2025-07-23",
|
| 69 |
+
"numeros": "22 13 24 43 02 01",
|
| 70 |
+
"boliyapa": 21,
|
| 71 |
+
"jackpot": 0.0,
|
| 72 |
+
"created_at": "2025-10-08 14:38:13"
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"id_sorteo": 1215,
|
| 76 |
+
"fecha_sorteo": "2025-07-27",
|
| 77 |
+
"numeros": "36 24 04 21 34 09",
|
| 78 |
+
"boliyapa": 17,
|
| 79 |
+
"jackpot": 0.0,
|
| 80 |
+
"created_at": "2025-10-08 14:38:15"
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"id_sorteo": 1216,
|
| 84 |
+
"fecha_sorteo": "2025-07-30",
|
| 85 |
+
"numeros": "43 28 03 09 01 25",
|
| 86 |
+
"boliyapa": 23,
|
| 87 |
+
"jackpot": 0.0,
|
| 88 |
+
"created_at": "2025-10-08 14:38:16"
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"id_sorteo": 1217,
|
| 92 |
+
"fecha_sorteo": "2025-08-03",
|
| 93 |
+
"numeros": "45 41 02 14 23 46",
|
| 94 |
+
"boliyapa": 22,
|
| 95 |
+
"jackpot": 0.0,
|
| 96 |
+
"created_at": "2025-10-08 14:55:12"
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"id_sorteo": 1218,
|
| 100 |
+
"fecha_sorteo": "2025-08-06",
|
| 101 |
+
"numeros": "38 23 06 21 33 36",
|
| 102 |
+
"boliyapa": 16,
|
| 103 |
+
"jackpot": 0.0,
|
| 104 |
+
"created_at": "2025-10-08 14:55:13"
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"id_sorteo": 1219,
|
| 108 |
+
"fecha_sorteo": "2025-08-10",
|
| 109 |
+
"numeros": "24 46 29 19 25 47",
|
| 110 |
+
"boliyapa": 26,
|
| 111 |
+
"jackpot": 0.0,
|
| 112 |
+
"created_at": "2025-10-08 14:38:17"
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"id_sorteo": 1220,
|
| 116 |
+
"fecha_sorteo": "2025-08-13",
|
| 117 |
+
"numeros": "36 07 43 32 20 33",
|
| 118 |
+
"boliyapa": 17,
|
| 119 |
+
"jackpot": 0.0,
|
| 120 |
+
"created_at": "2025-10-08 14:38:18"
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"id_sorteo": 1221,
|
| 124 |
+
"fecha_sorteo": "2025-08-17",
|
| 125 |
+
"numeros": "34 11 39 07 26 27",
|
| 126 |
+
"boliyapa": 37,
|
| 127 |
+
"jackpot": 0.0,
|
| 128 |
+
"created_at": "2025-10-08 14:38:19"
|
| 129 |
+
},
|
| 130 |
+
{
|
| 131 |
+
"id_sorteo": 1222,
|
| 132 |
+
"fecha_sorteo": "2025-08-20",
|
| 133 |
+
"numeros": "28 37 09 29 06 49",
|
| 134 |
+
"boliyapa": 48,
|
| 135 |
+
"jackpot": 0.0,
|
| 136 |
+
"created_at": "2025-10-08 14:38:21"
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"id_sorteo": 1223,
|
| 140 |
+
"fecha_sorteo": "2025-08-24",
|
| 141 |
+
"numeros": "12 37 16 08 22 17",
|
| 142 |
+
"boliyapa": 7,
|
| 143 |
+
"jackpot": 0.0,
|
| 144 |
+
"created_at": "2025-10-08 14:38:22"
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"id_sorteo": 1224,
|
| 148 |
+
"fecha_sorteo": "2025-08-27",
|
| 149 |
+
"numeros": "40 19 13 46 30 36",
|
| 150 |
+
"boliyapa": 3,
|
| 151 |
+
"jackpot": 0.0,
|
| 152 |
+
"created_at": "2025-10-08 14:38:23"
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"id_sorteo": 1225,
|
| 156 |
+
"fecha_sorteo": "2025-08-31",
|
| 157 |
+
"numeros": "30 35 32 12 38 04",
|
| 158 |
+
"boliyapa": 29,
|
| 159 |
+
"jackpot": 0.0,
|
| 160 |
+
"created_at": "2025-10-08 14:38:24"
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"id_sorteo": 1226,
|
| 164 |
+
"fecha_sorteo": "2025-09-03",
|
| 165 |
+
"numeros": "23 21 15 14 48 18",
|
| 166 |
+
"boliyapa": 10,
|
| 167 |
+
"jackpot": 0.0,
|
| 168 |
+
"created_at": "2025-10-08 14:55:14"
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"id_sorteo": 1227,
|
| 172 |
+
"fecha_sorteo": "2025-09-07",
|
| 173 |
+
"numeros": "29 15 07 33 17 23",
|
| 174 |
+
"boliyapa": 8,
|
| 175 |
+
"jackpot": 0.0,
|
| 176 |
+
"created_at": "2025-10-08 14:55:15"
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
"id_sorteo": 1228,
|
| 180 |
+
"fecha_sorteo": "2025-09-10",
|
| 181 |
+
"numeros": "01 16 06 45 33 38",
|
| 182 |
+
"boliyapa": 27,
|
| 183 |
+
"jackpot": 0.0,
|
| 184 |
+
"created_at": "2025-10-08 14:38:26"
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
"id_sorteo": 1229,
|
| 188 |
+
"fecha_sorteo": "2025-09-14",
|
| 189 |
+
"numeros": "35 06 12 33 38 02",
|
| 190 |
+
"boliyapa": 14,
|
| 191 |
+
"jackpot": 0.0,
|
| 192 |
+
"created_at": "2025-10-08 14:38:27"
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"id_sorteo": 1230,
|
| 196 |
+
"fecha_sorteo": "2025-09-17",
|
| 197 |
+
"numeros": "39 44 19 34 24 14",
|
| 198 |
+
"boliyapa": 47,
|
| 199 |
+
"jackpot": 0.0,
|
| 200 |
+
"created_at": "2025-10-08 14:38:28"
|
| 201 |
+
},
|
| 202 |
+
{
|
| 203 |
+
"id_sorteo": 1231,
|
| 204 |
+
"fecha_sorteo": "2025-09-21",
|
| 205 |
+
"numeros": "46 44 02 28 20 32",
|
| 206 |
+
"boliyapa": 39,
|
| 207 |
+
"jackpot": 0.0,
|
| 208 |
+
"created_at": "2025-10-08 14:38:29"
|
| 209 |
+
},
|
| 210 |
+
{
|
| 211 |
+
"id_sorteo": 1232,
|
| 212 |
+
"fecha_sorteo": "2025-09-24",
|
| 213 |
+
"numeros": "21 39 43 46 31 24",
|
| 214 |
+
"boliyapa": 41,
|
| 215 |
+
"jackpot": 0.0,
|
| 216 |
+
"created_at": "2025-10-08 14:38:31"
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"id_sorteo": 1233,
|
| 220 |
+
"fecha_sorteo": "2025-09-28",
|
| 221 |
+
"numeros": "24 17 31 09 35 44",
|
| 222 |
+
"boliyapa": 33,
|
| 223 |
+
"jackpot": 0.0,
|
| 224 |
+
"created_at": "2025-10-08 14:38:32"
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"id_sorteo": 1234,
|
| 228 |
+
"fecha_sorteo": "2025-10-01",
|
| 229 |
+
"numeros": "13 36 37 41 14 10",
|
| 230 |
+
"boliyapa": 1,
|
| 231 |
+
"jackpot": 0.0,
|
| 232 |
+
"created_at": "2025-10-08 14:55:16"
|
| 233 |
+
},
|
| 234 |
+
{
|
| 235 |
+
"id_sorteo": 1235,
|
| 236 |
+
"fecha_sorteo": "2025-10-05",
|
| 237 |
+
"numeros": "02 31 06 14 45 23",
|
| 238 |
+
"boliyapa": 5,
|
| 239 |
+
"jackpot": 0.0,
|
| 240 |
+
"created_at": "2025-10-08 14:55:17"
|
| 241 |
+
}
|
| 242 |
+
]
|
proyecto_tinka_ml/proyecto_tinka_ml/data/combinaciones_generadas.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"estrategia": "auto_ml",
|
| 4 |
+
"n": 1,
|
| 5 |
+
"ranked": [
|
| 6 |
+
{
|
| 7 |
+
"combo": [
|
| 8 |
+
2,
|
| 9 |
+
14,
|
| 10 |
+
23,
|
| 11 |
+
24,
|
| 12 |
+
33,
|
| 13 |
+
37
|
| 14 |
+
],
|
| 15 |
+
"score": -107.88746739789008
|
| 16 |
+
}
|
| 17 |
+
]
|
| 18 |
+
}
|
| 19 |
+
]
|
proyecto_tinka_ml/proyecto_tinka_ml/data/probabilidades.json
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"global": {
|
| 3 |
+
"1": {
|
| 4 |
+
"alpha": 8.0,
|
| 5 |
+
"beta": 52.0,
|
| 6 |
+
"p": 0.13333333333333333
|
| 7 |
+
},
|
| 8 |
+
"2": {
|
| 9 |
+
"alpha": 9.0,
|
| 10 |
+
"beta": 51.0,
|
| 11 |
+
"p": 0.15
|
| 12 |
+
},
|
| 13 |
+
"3": {
|
| 14 |
+
"alpha": 6.0,
|
| 15 |
+
"beta": 54.0,
|
| 16 |
+
"p": 0.1
|
| 17 |
+
},
|
| 18 |
+
"4": {
|
| 19 |
+
"alpha": 6.0,
|
| 20 |
+
"beta": 54.0,
|
| 21 |
+
"p": 0.1
|
| 22 |
+
},
|
| 23 |
+
"5": {
|
| 24 |
+
"alpha": 5.0,
|
| 25 |
+
"beta": 55.0,
|
| 26 |
+
"p": 0.08333333333333333
|
| 27 |
+
},
|
| 28 |
+
"6": {
|
| 29 |
+
"alpha": 10.0,
|
| 30 |
+
"beta": 50.0,
|
| 31 |
+
"p": 0.16666666666666666
|
| 32 |
+
},
|
| 33 |
+
"7": {
|
| 34 |
+
"alpha": 9.0,
|
| 35 |
+
"beta": 51.0,
|
| 36 |
+
"p": 0.15
|
| 37 |
+
},
|
| 38 |
+
"8": {
|
| 39 |
+
"alpha": 6.0,
|
| 40 |
+
"beta": 54.0,
|
| 41 |
+
"p": 0.1
|
| 42 |
+
},
|
| 43 |
+
"9": {
|
| 44 |
+
"alpha": 10.0,
|
| 45 |
+
"beta": 50.0,
|
| 46 |
+
"p": 0.16666666666666666
|
| 47 |
+
},
|
| 48 |
+
"10": {
|
| 49 |
+
"alpha": 6.0,
|
| 50 |
+
"beta": 54.0,
|
| 51 |
+
"p": 0.1
|
| 52 |
+
},
|
| 53 |
+
"11": {
|
| 54 |
+
"alpha": 7.0,
|
| 55 |
+
"beta": 53.0,
|
| 56 |
+
"p": 0.11666666666666667
|
| 57 |
+
},
|
| 58 |
+
"12": {
|
| 59 |
+
"alpha": 7.0,
|
| 60 |
+
"beta": 53.0,
|
| 61 |
+
"p": 0.11666666666666667
|
| 62 |
+
},
|
| 63 |
+
"13": {
|
| 64 |
+
"alpha": 7.0,
|
| 65 |
+
"beta": 53.0,
|
| 66 |
+
"p": 0.11666666666666667
|
| 67 |
+
},
|
| 68 |
+
"14": {
|
| 69 |
+
"alpha": 12.0,
|
| 70 |
+
"beta": 48.0,
|
| 71 |
+
"p": 0.2
|
| 72 |
+
},
|
| 73 |
+
"15": {
|
| 74 |
+
"alpha": 7.0,
|
| 75 |
+
"beta": 53.0,
|
| 76 |
+
"p": 0.11666666666666667
|
| 77 |
+
},
|
| 78 |
+
"16": {
|
| 79 |
+
"alpha": 7.0,
|
| 80 |
+
"beta": 53.0,
|
| 81 |
+
"p": 0.11666666666666667
|
| 82 |
+
},
|
| 83 |
+
"17": {
|
| 84 |
+
"alpha": 7.0,
|
| 85 |
+
"beta": 53.0,
|
| 86 |
+
"p": 0.11666666666666667
|
| 87 |
+
},
|
| 88 |
+
"18": {
|
| 89 |
+
"alpha": 5.0,
|
| 90 |
+
"beta": 55.0,
|
| 91 |
+
"p": 0.08333333333333333
|
| 92 |
+
},
|
| 93 |
+
"19": {
|
| 94 |
+
"alpha": 8.0,
|
| 95 |
+
"beta": 52.0,
|
| 96 |
+
"p": 0.13333333333333333
|
| 97 |
+
},
|
| 98 |
+
"20": {
|
| 99 |
+
"alpha": 7.0,
|
| 100 |
+
"beta": 53.0,
|
| 101 |
+
"p": 0.11666666666666667
|
| 102 |
+
},
|
| 103 |
+
"21": {
|
| 104 |
+
"alpha": 8.0,
|
| 105 |
+
"beta": 52.0,
|
| 106 |
+
"p": 0.13333333333333333
|
| 107 |
+
},
|
| 108 |
+
"22": {
|
| 109 |
+
"alpha": 8.0,
|
| 110 |
+
"beta": 52.0,
|
| 111 |
+
"p": 0.13333333333333333
|
| 112 |
+
},
|
| 113 |
+
"23": {
|
| 114 |
+
"alpha": 10.0,
|
| 115 |
+
"beta": 50.0,
|
| 116 |
+
"p": 0.16666666666666666
|
| 117 |
+
},
|
| 118 |
+
"24": {
|
| 119 |
+
"alpha": 12.0,
|
| 120 |
+
"beta": 48.0,
|
| 121 |
+
"p": 0.2
|
| 122 |
+
},
|
| 123 |
+
"25": {
|
| 124 |
+
"alpha": 6.0,
|
| 125 |
+
"beta": 54.0,
|
| 126 |
+
"p": 0.1
|
| 127 |
+
},
|
| 128 |
+
"26": {
|
| 129 |
+
"alpha": 5.0,
|
| 130 |
+
"beta": 55.0,
|
| 131 |
+
"p": 0.08333333333333333
|
| 132 |
+
},
|
| 133 |
+
"27": {
|
| 134 |
+
"alpha": 6.0,
|
| 135 |
+
"beta": 54.0,
|
| 136 |
+
"p": 0.1
|
| 137 |
+
},
|
| 138 |
+
"28": {
|
| 139 |
+
"alpha": 10.0,
|
| 140 |
+
"beta": 50.0,
|
| 141 |
+
"p": 0.16666666666666666
|
| 142 |
+
},
|
| 143 |
+
"29": {
|
| 144 |
+
"alpha": 9.0,
|
| 145 |
+
"beta": 51.0,
|
| 146 |
+
"p": 0.15
|
| 147 |
+
},
|
| 148 |
+
"30": {
|
| 149 |
+
"alpha": 7.0,
|
| 150 |
+
"beta": 53.0,
|
| 151 |
+
"p": 0.11666666666666667
|
| 152 |
+
},
|
| 153 |
+
"31": {
|
| 154 |
+
"alpha": 8.0,
|
| 155 |
+
"beta": 52.0,
|
| 156 |
+
"p": 0.13333333333333333
|
| 157 |
+
},
|
| 158 |
+
"32": {
|
| 159 |
+
"alpha": 10.0,
|
| 160 |
+
"beta": 50.0,
|
| 161 |
+
"p": 0.16666666666666666
|
| 162 |
+
},
|
| 163 |
+
"33": {
|
| 164 |
+
"alpha": 9.0,
|
| 165 |
+
"beta": 51.0,
|
| 166 |
+
"p": 0.15
|
| 167 |
+
},
|
| 168 |
+
"34": {
|
| 169 |
+
"alpha": 7.0,
|
| 170 |
+
"beta": 53.0,
|
| 171 |
+
"p": 0.11666666666666667
|
| 172 |
+
},
|
| 173 |
+
"35": {
|
| 174 |
+
"alpha": 8.0,
|
| 175 |
+
"beta": 52.0,
|
| 176 |
+
"p": 0.13333333333333333
|
| 177 |
+
},
|
| 178 |
+
"36": {
|
| 179 |
+
"alpha": 10.0,
|
| 180 |
+
"beta": 50.0,
|
| 181 |
+
"p": 0.16666666666666666
|
| 182 |
+
},
|
| 183 |
+
"37": {
|
| 184 |
+
"alpha": 9.0,
|
| 185 |
+
"beta": 51.0,
|
| 186 |
+
"p": 0.15
|
| 187 |
+
},
|
| 188 |
+
"38": {
|
| 189 |
+
"alpha": 10.0,
|
| 190 |
+
"beta": 50.0,
|
| 191 |
+
"p": 0.16666666666666666
|
| 192 |
+
},
|
| 193 |
+
"39": {
|
| 194 |
+
"alpha": 7.0,
|
| 195 |
+
"beta": 53.0,
|
| 196 |
+
"p": 0.11666666666666667
|
| 197 |
+
},
|
| 198 |
+
"40": {
|
| 199 |
+
"alpha": 5.0,
|
| 200 |
+
"beta": 55.0,
|
| 201 |
+
"p": 0.08333333333333333
|
| 202 |
+
},
|
| 203 |
+
"41": {
|
| 204 |
+
"alpha": 6.0,
|
| 205 |
+
"beta": 54.0,
|
| 206 |
+
"p": 0.1
|
| 207 |
+
},
|
| 208 |
+
"42": {
|
| 209 |
+
"alpha": 6.0,
|
| 210 |
+
"beta": 54.0,
|
| 211 |
+
"p": 0.1
|
| 212 |
+
},
|
| 213 |
+
"43": {
|
| 214 |
+
"alpha": 9.0,
|
| 215 |
+
"beta": 51.0,
|
| 216 |
+
"p": 0.15
|
| 217 |
+
},
|
| 218 |
+
"44": {
|
| 219 |
+
"alpha": 7.0,
|
| 220 |
+
"beta": 53.0,
|
| 221 |
+
"p": 0.11666666666666667
|
| 222 |
+
},
|
| 223 |
+
"45": {
|
| 224 |
+
"alpha": 8.0,
|
| 225 |
+
"beta": 52.0,
|
| 226 |
+
"p": 0.13333333333333333
|
| 227 |
+
}
|
| 228 |
+
},
|
| 229 |
+
"recent": {
|
| 230 |
+
"1": {
|
| 231 |
+
"alpha": 5.150367628938792,
|
| 232 |
+
"beta": 34.56371252205608,
|
| 233 |
+
"p": 0.1296861870993069
|
| 234 |
+
},
|
| 235 |
+
"2": {
|
| 236 |
+
"alpha": 6.392834501328441,
|
| 237 |
+
"beta": 33.32124564966642,
|
| 238 |
+
"p": 0.16097148610826623
|
| 239 |
+
},
|
| 240 |
+
"3": {
|
| 241 |
+
"alpha": 3.465809423819209,
|
| 242 |
+
"beta": 36.24827072717566,
|
| 243 |
+
"p": 0.08726903432339445
|
| 244 |
+
},
|
| 245 |
+
"4": {
|
| 246 |
+
"alpha": 3.6284088465513236,
|
| 247 |
+
"beta": 36.085671304443544,
|
| 248 |
+
"p": 0.09136328558425466
|
| 249 |
+
},
|
| 250 |
+
"5": {
|
| 251 |
+
"alpha": 2.737134608645551,
|
| 252 |
+
"beta": 36.97694554234931,
|
| 253 |
+
"p": 0.0689210123522648
|
| 254 |
+
},
|
| 255 |
+
"6": {
|
| 256 |
+
"alpha": 7.189970645879335,
|
| 257 |
+
"beta": 32.52410950511553,
|
| 258 |
+
"p": 0.18104336342533217
|
| 259 |
+
},
|
| 260 |
+
"7": {
|
| 261 |
+
"alpha": 5.94562565228181,
|
| 262 |
+
"beta": 33.768454498713055,
|
| 263 |
+
"p": 0.14971077334981073
|
| 264 |
+
},
|
| 265 |
+
"8": {
|
| 266 |
+
"alpha": 3.5157090897555836,
|
| 267 |
+
"beta": 36.198371061239285,
|
| 268 |
+
"p": 0.08852550723543606
|
| 269 |
+
},
|
| 270 |
+
"9": {
|
| 271 |
+
"alpha": 6.758123145934322,
|
| 272 |
+
"beta": 32.95595700506055,
|
| 273 |
+
"p": 0.17016944922907967
|
| 274 |
+
},
|
| 275 |
+
"10": {
|
| 276 |
+
"alpha": 3.6740036135632312,
|
| 277 |
+
"beta": 36.04007653743164,
|
| 278 |
+
"p": 0.09251136119971784
|
| 279 |
+
},
|
| 280 |
+
"11": {
|
| 281 |
+
"alpha": 4.247949109102931,
|
| 282 |
+
"beta": 35.466131041891934,
|
| 283 |
+
"p": 0.10696330099934385
|
| 284 |
+
},
|
| 285 |
+
"12": {
|
| 286 |
+
"alpha": 4.637483526283527,
|
| 287 |
+
"beta": 35.07659662471134,
|
| 288 |
+
"p": 0.11677177234501185
|
| 289 |
+
},
|
| 290 |
+
"13": {
|
| 291 |
+
"alpha": 4.592222765248582,
|
| 292 |
+
"beta": 35.12185738574628,
|
| 293 |
+
"p": 0.1156321069955222
|
| 294 |
+
},
|
| 295 |
+
"14": {
|
| 296 |
+
"alpha": 8.635239791619453,
|
| 297 |
+
"beta": 31.078840359375413,
|
| 298 |
+
"p": 0.21743522092889603
|
| 299 |
+
},
|
| 300 |
+
"15": {
|
| 301 |
+
"alpha": 4.475099900393831,
|
| 302 |
+
"beta": 35.23898025060104,
|
| 303 |
+
"p": 0.11268295484571927
|
| 304 |
+
},
|
| 305 |
+
"16": {
|
| 306 |
+
"alpha": 4.461371248866236,
|
| 307 |
+
"beta": 35.25270890212863,
|
| 308 |
+
"p": 0.11233726758630402
|
| 309 |
+
},
|
| 310 |
+
"17": {
|
| 311 |
+
"alpha": 4.7144253307027855,
|
| 312 |
+
"beta": 34.99965482029208,
|
| 313 |
+
"p": 0.11870916593757959
|
| 314 |
+
},
|
| 315 |
+
"18": {
|
| 316 |
+
"alpha": 2.882702996290655,
|
| 317 |
+
"beta": 36.831377154704214,
|
| 318 |
+
"p": 0.0725864223804373
|
| 319 |
+
},
|
| 320 |
+
"19": {
|
| 321 |
+
"alpha": 5.3298029142097345,
|
| 322 |
+
"beta": 34.38427723678513,
|
| 323 |
+
"p": 0.13420436515073658
|
| 324 |
+
},
|
| 325 |
+
"20": {
|
| 326 |
+
"alpha": 4.455681876257035,
|
| 327 |
+
"beta": 35.25839827473783,
|
| 328 |
+
"p": 0.1121940092611063
|
| 329 |
+
},
|
| 330 |
+
"21": {
|
| 331 |
+
"alpha": 5.389866710734496,
|
| 332 |
+
"beta": 34.32421344026037,
|
| 333 |
+
"p": 0.13571677073325028
|
| 334 |
+
},
|
| 335 |
+
"22": {
|
| 336 |
+
"alpha": 4.999458359063988,
|
| 337 |
+
"beta": 34.71462179193088,
|
| 338 |
+
"p": 0.12588629372896978
|
| 339 |
+
},
|
| 340 |
+
"23": {
|
| 341 |
+
"alpha": 7.025236122466341,
|
| 342 |
+
"beta": 32.68884402852853,
|
| 343 |
+
"p": 0.17689535035826212
|
| 344 |
+
},
|
| 345 |
+
"24": {
|
| 346 |
+
"alpha": 8.566584631168398,
|
| 347 |
+
"beta": 31.14749551982647,
|
| 348 |
+
"p": 0.21570648491914773
|
| 349 |
+
},
|
| 350 |
+
"25": {
|
| 351 |
+
"alpha": 3.5695074682336285,
|
| 352 |
+
"beta": 36.144572682761236,
|
| 353 |
+
"p": 0.08988014967644188
|
| 354 |
+
},
|
| 355 |
+
"26": {
|
| 356 |
+
"alpha": 2.8235910172675736,
|
| 357 |
+
"beta": 36.89048913372729,
|
| 358 |
+
"p": 0.07109798354971696
|
| 359 |
+
},
|
| 360 |
+
"27": {
|
| 361 |
+
"alpha": 3.49255479466063,
|
| 362 |
+
"beta": 36.22152535633424,
|
| 363 |
+
"p": 0.08794248239873029
|
| 364 |
+
},
|
| 365 |
+
"28": {
|
| 366 |
+
"alpha": 6.681595455699942,
|
| 367 |
+
"beta": 33.032484695294926,
|
| 368 |
+
"p": 0.16824248302607517
|
| 369 |
+
},
|
| 370 |
+
"29": {
|
| 371 |
+
"alpha": 5.9163256101910395,
|
| 372 |
+
"beta": 33.79775454080383,
|
| 373 |
+
"p": 0.14897299868703698
|
| 374 |
+
},
|
| 375 |
+
"30": {
|
| 376 |
+
"alpha": 4.398079777126934,
|
| 377 |
+
"beta": 35.316000373867936,
|
| 378 |
+
"p": 0.11074358918562938
|
| 379 |
+
},
|
| 380 |
+
"31": {
|
| 381 |
+
"alpha": 5.639025847924097,
|
| 382 |
+
"beta": 34.07505430307077,
|
| 383 |
+
"p": 0.14199059443109965
|
| 384 |
+
},
|
| 385 |
+
"32": {
|
| 386 |
+
"alpha": 6.711911303179578,
|
| 387 |
+
"beta": 33.00216884781529,
|
| 388 |
+
"p": 0.16900583565477442
|
| 389 |
+
},
|
| 390 |
+
"33": {
|
| 391 |
+
"alpha": 6.325025585089622,
|
| 392 |
+
"beta": 33.38905456590524,
|
| 393 |
+
"p": 0.1592640585163138
|
| 394 |
+
},
|
| 395 |
+
"34": {
|
| 396 |
+
"alpha": 4.51448229205958,
|
| 397 |
+
"beta": 35.19959785893529,
|
| 398 |
+
"p": 0.1136746029341558
|
| 399 |
+
},
|
| 400 |
+
"35": {
|
| 401 |
+
"alpha": 5.470499942519833,
|
| 402 |
+
"beta": 34.24358020847504,
|
| 403 |
+
"p": 0.1377471144168699
|
| 404 |
+
},
|
| 405 |
+
"36": {
|
| 406 |
+
"alpha": 6.931936391066081,
|
| 407 |
+
"beta": 32.782143759928786,
|
| 408 |
+
"p": 0.17454606438599413
|
| 409 |
+
},
|
| 410 |
+
"37": {
|
| 411 |
+
"alpha": 6.073354358668248,
|
| 412 |
+
"beta": 33.64072579232662,
|
| 413 |
+
"p": 0.152926980445652
|
| 414 |
+
},
|
| 415 |
+
"38": {
|
| 416 |
+
"alpha": 6.874240082502508,
|
| 417 |
+
"beta": 32.83984006849236,
|
| 418 |
+
"p": 0.17309327211825914
|
| 419 |
+
},
|
| 420 |
+
"39": {
|
| 421 |
+
"alpha": 4.715888128129645,
|
| 422 |
+
"beta": 34.998192022865226,
|
| 423 |
+
"p": 0.11874599915696418
|
| 424 |
+
},
|
| 425 |
+
"40": {
|
| 426 |
+
"alpha": 2.858565436437754,
|
| 427 |
+
"beta": 36.85551471455712,
|
| 428 |
+
"p": 0.07197863895045153
|
| 429 |
+
},
|
| 430 |
+
"41": {
|
| 431 |
+
"alpha": 3.765397284153859,
|
| 432 |
+
"beta": 35.94868286684101,
|
| 433 |
+
"p": 0.09481265258663012
|
| 434 |
+
},
|
| 435 |
+
"42": {
|
| 436 |
+
"alpha": 3.4442413898320985,
|
| 437 |
+
"beta": 36.269838761162774,
|
| 438 |
+
"p": 0.08672595151988727
|
| 439 |
+
},
|
| 440 |
+
"43": {
|
| 441 |
+
"alpha": 6.00435635465089,
|
| 442 |
+
"beta": 33.70972379634398,
|
| 443 |
+
"p": 0.15118961163955036
|
| 444 |
+
},
|
| 445 |
+
"44": {
|
| 446 |
+
"alpha": 4.8517455856746885,
|
| 447 |
+
"beta": 34.86233456532018,
|
| 448 |
+
"p": 0.12216688809681893
|
| 449 |
+
},
|
| 450 |
+
"45": {
|
| 451 |
+
"alpha": 5.423818343623212,
|
| 452 |
+
"beta": 34.29026180737166,
|
| 453 |
+
"p": 0.13657167238927834
|
| 454 |
+
}
|
| 455 |
+
},
|
| 456 |
+
"blend": {
|
| 457 |
+
"1": 0.1322391894631254,
|
| 458 |
+
"2": 0.15329144583247986,
|
| 459 |
+
"3": 0.09618071029701833,
|
| 460 |
+
"4": 0.0974089856752764,
|
| 461 |
+
"5": 0.07900963703901277,
|
| 462 |
+
"6": 0.1709796756942663,
|
| 463 |
+
"7": 0.14991323200494322,
|
| 464 |
+
"8": 0.09655765217063081,
|
| 465 |
+
"9": 0.16771750143539055,
|
| 466 |
+
"10": 0.09775340835991535,
|
| 467 |
+
"11": 0.11375565696646982,
|
| 468 |
+
"12": 0.11669819837017022,
|
| 469 |
+
"13": 0.11635629876532333,
|
| 470 |
+
"14": 0.20523056627866879,
|
| 471 |
+
"15": 0.11547155312038244,
|
| 472 |
+
"16": 0.11536784694255786,
|
| 473 |
+
"17": 0.11727941644794054,
|
| 474 |
+
"18": 0.08010926004746452,
|
| 475 |
+
"19": 0.1335946428785543,
|
| 476 |
+
"20": 0.11532486944499856,
|
| 477 |
+
"21": 0.1340483645533084,
|
| 478 |
+
"22": 0.13109922145202427,
|
| 479 |
+
"23": 0.1697352717741453,
|
| 480 |
+
"24": 0.20471194547574428,
|
| 481 |
+
"25": 0.09696404490293256,
|
| 482 |
+
"26": 0.07966272839824841,
|
| 483 |
+
"27": 0.09638274471961908,
|
| 484 |
+
"28": 0.1671394115744892,
|
| 485 |
+
"29": 0.1496918996061111,
|
| 486 |
+
"30": 0.11488974342235547,
|
| 487 |
+
"31": 0.13593051166266323,
|
| 488 |
+
"32": 0.167368417363099,
|
| 489 |
+
"33": 0.15277921755489415,
|
| 490 |
+
"34": 0.1157690475469134,
|
| 491 |
+
"35": 0.1346574676583943,
|
| 492 |
+
"36": 0.1690304859824649,
|
| 493 |
+
"37": 0.1508780941336956,
|
| 494 |
+
"38": 0.1685946483021444,
|
| 495 |
+
"39": 0.11729046641375593,
|
| 496 |
+
"40": 0.07992692501846879,
|
| 497 |
+
"41": 0.09844379577598902,
|
| 498 |
+
"42": 0.09601778545596618,
|
| 499 |
+
"43": 0.1503568834918651,
|
| 500 |
+
"44": 0.11831673309571233,
|
| 501 |
+
"45": 0.1343048350501168
|
| 502 |
+
}
|
| 503 |
+
}
|
proyecto_tinka_ml/proyecto_tinka_ml/data/reporte.html
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="es">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8"/>
|
| 5 |
+
<title>Reporte Tinka</title>
|
| 6 |
+
<style>
|
| 7 |
+
body { font-family: Arial, Helvetica, sans-serif; margin: 20px; }
|
| 8 |
+
h1, h2 { margin: 0.4em 0; }
|
| 9 |
+
pre { background: #f6f8fa; padding: 12px; overflow: auto; }
|
| 10 |
+
.small { color:#555; font-size: 0.9em; }
|
| 11 |
+
</style>
|
| 12 |
+
</head>
|
| 13 |
+
<body>
|
| 14 |
+
<h1>Reporte de Análisis — Tinka</h1>
|
| 15 |
+
<p class="small">Este reporte se genera a partir de los resultados históricos presentes en tu base de datos.</p>
|
| 16 |
+
|
| 17 |
+
<h2>Frecuencias</h2>
|
| 18 |
+
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABdwAAAJYCAYAAAB4syQkAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAXEgAAFxIBZ5/SUgAAV5BJREFUeJzt3QeYVOX5P+6HDgKKYEFFQREUMSoaK0GIiUqComAvscWusSS22GtirLFFjRpbbJHYO/aCImAsQUVFEDWoKFgogsL8r/d8/7O/BXapZ2fZ2fu+rnGGc86c885p4Gfeed4GhUKhEAAAAAAAwGJpuHhvBwAAAAAAEoE7AAAAAADkQOAOAAAAAAA5ELgDAAAAAEAOBO4AAAAAAJADgTsAAAAAAORA4A4AAAAAADkQuAMAAAAAQA4E7gAAAAAAkAOBOwAAAAAA5EDgDgAAAAAAORC4AwAAAABADgTuAAAAAACQA4E7AAAAAADkQOAOAAAAAAA5ELgDAAAAAEAOBO4AAAAAAJADgTsAAAAAAORA4A4AAAAAADkQuAMAAAAAQA4E7gAAAAAAkIPGeawEAACgvhsyZEg88cQT0bJlyzj22GOjcWP/uwUAUN/4FyAAAMBi+uSTT2KHHXaISZMmxf333y9sBwCop5SUAQCgwtixY6NBgwbZI72uK/r06ZO1+cwzz4y64Kabbsra26lTp6iv0rFK+yAdu7ruhx9+iF133TW+/PLLuPrqq6Nfv3613aR67frrr8/OrcMOOyzqm3HjxkWzZs1izTXXjBkzZtR2cwCgXhK4AwA1EqItyAPI33333Zddh+mZ0jjhhBPi5ZdfjtNPPz0OOuig2m5OvTZ58uQ47bTTstD5lFNOmWv+119/nf0CIR2r7bbbLlZaaaWKv5PSF2E16dBDD63Y1ry+bFvQv0c/+OCDud672mqrxf777x+jR4+Oq666qkY/DwBQNb9zBABqzIorrljbTWAhNWnSJNZaa62K19Q9KWi/+eabY999940dd9yxtptT9u65557461//GgcccECcddZZtd2ceu/CCy+Mzz77LI444ojo0KFDlddHCqRL7Zlnnom///3vC/WedA9u27ZttfOrK1t08sknxz/+8Y8499xzs8/apk2bhW4vALDoBO4AQI1JoQd1yyqrrBLvvvtubTcD6oyBAwdGoVCo7WYQEdOmTYvLL788ez2vcjLt27ePHj16xIYbbhgbbbRRdgxr0tSpU7NfPqSAfP3114/hw4cv0Pu22GKLePbZZxd6e6mX+69//eusJ38qr3PcccctQqsBgEWlpAwAAAB13l133ZWVjNlggw2ie/fuVS7zm9/8JsaPHx+PPPJI1gN8wIABNd6uVNomlXhJpYeqa1fe9t577+w59ar3hRAAlJbAHQBYogbpTKHEwQcfHKuvvnpWg3fOOrezZs2K2267Leu9l0rWNG3aNJZffvnYZptt4o477phvsPDOO+9kpQbWWWedaN26dbRq1SorobL77rvHv//972z9Raln4YLUmy8uU11PxEVtc/rsxbrCafC7VCoh9Y5s2bJlLLPMMrHVVlvFY489FvMzdOjQrKxAGkRvqaWWiqWXXjr7/KkExuOPP75Qg6a+8sorceKJJ0avXr2iY8eO0bx586xcwWabbRZ/+ctfsvrJi2rMmDHZOvr27Rtdu3bNPmc6PqmtxxxzTDYY4IJI++r888+P9dZbL1vHsssuG1tvvXU8+uij1b7nxx9/zIKpNIDncsstl5VyaNeuXXZu7LbbbnHDDTdU+9503HfZZZfs1wHpnE3v/8UvfhE33nhjzJw5c6H3w3777Zft//S8MIOuFs/XVE4mSc9z1nyufI6mX6BcccUVscMOO0S3bt2yc6pFixbZeXLggQfGyJEjY3GlfZ72fTpH0rFM5+8FF1yQDTK6INI5mI59CinT+9P5u/baa8fRRx+9wOfD/PbdiBEjsgFPUy3vdPzWWGON+P3vfx+TJk3K9fhU9/607Oabb57t/3Su/vKXv4znn39+tnMzHafUEztdu2m5dC957bXX5vk587jvpOs51Tr/yU9+kt0v57wvpPM7lS5J96J03qf9l66DdD3Mr2d2Csd/9atfZW1L11s6R7p06RL9+/fPao9///33sbCuu+667HnPPfesdplGjRpFKaV7Zup1n+5pp556asm2u/3222fH7P3331+kXvIAwGIoAADk6IwzzkgpTvZYEGPGjKlY/rbbbiu0atUqe73UUksVWrZsWejYsWPFsl999VVhyy23rFg+PZZZZpnZ/ty/f//C9OnTq9zW+eefX2jYsGHFss2bNy+0bdt2tmmTJk2qWP6ZZ55ZoM9SXCYtP6fFaXP67Gn+FVdcUdh0002z102aNKnYR+nRoEGDwg033FBlu3788cfCUUcdNdu20j5ddtlls/cV21Ld8Uivq/usxWOU1lV52jrrrFP4/PPPC4uid+/eFetp2rRpoV27drMdm9TWF154YZ7v/eMf/1jo1atX9rpx48aFNm3azNa+dH5WtZ+23nrruY5Rs2bNZptWlWOPPXa2Y5G216hRo4ppW221VeHbb7+d63033nhjNr/y+V207777ZvPSc3Wqev9LL71UWHHFFbPzunh+pz9XfqRl5txOcV+layE9F6elzz9o0KBCHveC9Ej7prj+dE2kY5Vep2NXlX/+85+zHYP0ukWLFhV/bt26deHxxx9f6HZV3nfpnpOuqeIxr3y+de/evfDdd9/ldnyqen/xddov6fNUPh4PPvhg4fvvvy9ss802FddEun4rX3/Dhw+vcvt53HcuuuiiQteuXSu2XbyWiveFr7/+utCnT5+K9aXzPi1TvLekx3HHHVdl+/bff//Z2pLuaenzVJ5W1f1nXlJ7isfv5ZdfXqj3FreZjlue0vHr1q1btk+effbZbFrxmFd1bsx57VR3bSyoX/ziF9l6TjjhhMVaDwCwcATuAMASE7in0CUFy8OGDauYP2rUqIpQtBiqbrDBBlkYNWXKlGze5MmTCzfffHNhhRVWyOYfc8wxc23nb3/722xB03/+85+KeWk9TzzxRGG33XYrfPPNN7kF7ovb5mLwlULtVVZZpXDfffcVZsyYkc179913C5tttlnFfkth05xSyFJs2wEHHFCxL5O0fFpf+szVHY+qAq/tt9++cNdddxXGjx9fMW3q1KmFe+65p7DWWmtl7xswYEBhURx99NGFq666qvDee+8VZs6cmU374YcfCkOHDi307ds3W/fKK6+cbW9Oxf1cDMqvueaawrRp07J548aNK+y8884Vn+v++++f7b233nprRUB9/fXXV4Sss2bNyr48SJ8tvX9O6YuQ4joPPvjgin2Sju2ll15aES7PuY9rKnBfmPcn55xzTuHCCy8svPXWW9l+TtJ+/+9//1vYa6+9Kr6g+fTTTwsLK+3j4r7ZZZddsmOQpGOXjnHlALeqUDFdjyk8TfswncfpXEzHIz3SuZ/Wmd679NJLFz766KOFaltx36WAN50rBx54YEX70vV55ZVXVoTwp512Wo0dn/T50xcI1157bcU5nT7bRhttlM3v1KlT4cgjj8y+CPnXv/6VXfvp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AAAAADkQuAMAAAAAQA4E7gAAAAAAkAOBOwAAAAAA5EDgDgAAAAAAOWicx0rqq/bt28eUKVNitdVWq+2mAAAAAAAwH+PGjYuWLVvGZ599FjVBD/fFkML2H374obabAQAAAADAAkh5bsp1a4oe7ouh2LN95MiRtd0UAAAAAADmo3v37lGT9HAHAAAAAIAcCNwBAAAAACAHAncAAAAAAMiBwB0AAAAAAHIgcAcAAAAAgBwI3AEAAAAAIAcCdwAAAAAAyIHAHQAAAAAAciBwBwAAAACAHAjcAQAAAAAgBwJ3AAAAAADIgcAdAAAAAAByUNaB+7Bhw2LXXXeNlVdeOZo0aRJt2rSJXr16xY033hiFQqG2mwcAAAAAQBlpHGXq3//+d+y2224xc+bM2HDDDbOgfcKECfHCCy/Eiy++GE8++WTcdttttd1MAAAAAADKRFn2cP/xxx/j8MMPz8L2FKqPGDEi7rrrrnj66afjzTffjLZt28btt98ezzzzTG03FQAAAACAMlGWgfu7774bX3zxRay11lqx5557zjavW7dusffee1eUnAEAAAAAgDyUZeDerFmzBVquXbt2Nd4WAAAAAADqh7IM3NdYY43o3LlzjBo1KisdU9k777wT//znP2PZZZeNAQMG1FobAQAAAAAoL2UZuDdq1ChuvvnmaNOmTey1116x0UYbxe677x5bbbVVrLfeetGhQ4d46qmnslruAAAAAACQh8ZRpnr27BnPPfdc1ov9tddeyx5J06ZNY+utt856wS+o7t27Vzl99OjRWU96AAAAAAAo28D9jjvuiP333z8222yz7HUKzf/3v//FRRddFBdffHE888wzMWTIkAWu9w4ANaHTSQ/nvs6x5/fLfZ1AzXM/AACAuq8sA/f3338/9t1331hhhRXioYceilatWmXTu3TpEtdee20WvKfp//jHP+Kwww6b7/pGjhy5UD3fAQAAAACof8qyhvudd94ZP/zwQ/Tt27cibK9s1113zZ6ff/75WmgdAAAAAADlqCwD908++SR7XmaZZaqcX5w+adKkkrYLAAAAAIDyVZaBe/v27bPn4cOHVzl/2LBh2XOnTp1K2i4AAAAAAMpXWQbuO+ywQ0XJmKuvvnq2ea+88kpceuml2eudd965VtoHAAAAAED5KcvAfcMNN4zjjjsue3344YfHuuuum9Vt/9nPfhY9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nXeyP/ft2zc6duwYV199dWy66aax0047xcYbbxzff/99/Pa3v12sbW255ZZx6623xujRo2Pq1KnZdtPgrI0bN47TTz89LrvssgVaT/fu3at8pPUCAAAAAECt9HDfZ599YplllolZs2ZV1HB/4IEHYtddd41hw4Zlj1T65cADD4yjjz56sbZ19tlnz/bnVE7m5JNPjp/+9Kex7bbbxplnnhkHH3xwtGjRYrG2Q83qdNLDua9z7Pn9cl8nNct5AADUB/7NU/+OkeOTL8eHUnLPXjT2G+Wu5IF7+/bt45BDDpltWqrdnnq8v/vuuzFp0qSs7EyxtntN2GabbbLQffjw4TF06NDo06fPPJcfOXJkldNTL3cAAAAAAKiVwH1e1l577ZJtq0uXLlngPn78+JJtEwAAAACA8lXyGu5LitSTPmnZsmVtNwUAAAAAgDJQ4z3cUx31Bg0axBFHHBFt27adq676vKT3nXbaabm3acKECfHCCy9krzfccMPc1w8AAAAAQP1T44F7Gpg0Bee77bZbFrgX/1woFGo0cB8yZEh88cUXsf3220ejRo0qpo8dOzb23nvvmDJlSvTv3z86dOiwSOsHAAAAAICSBu433nhj9rzSSivN9uea9t5778X++++fDdKaerG3adMmPvrooxgxYkR8//332YCn1113XUnaAgAAAABA+avxwH3fffed559ryqabbhqHHXZYDB06NIYNG5bVbE/12jfYYIPYZZddsnktWrQoSVsAAAAAACh/NR6415Zu3brF3/72t9puBgAAAAAA9UTDUm/w888/jwceeCDGjBlT7TJpXlom1WAHAAAAAIC6oOSB+yWXXBIDBgzI6qhXZ9q0adkyl112WUnbBgAAAAAAdSZwf/TRR7MBS1PJl+qss8462TIPP/xwSdsGAAAAAAB1JnD/6KOPomvXrvNdrkuXLjFu3LiStAkAAAAAAOpc4D5z5swFWq5BgwYxffr0Gm8PAAAAAADUycB9jTXWiJdffjl+/PHHapdJ89Iyq622WknbBgAAAAAAdSZw33777eOzzz6Lk046KQqFQpXL/PGPf8yW6d+/f6mbBwAAAAAAi6RxlNgf/vCHuOWWW+LSSy+NwYMHx29/+9vo3LlzNm/06NFxww03xH//+99o3759HH/88aVuHgAAAAAA1I3AvW3btvHEE0/EgAED4q233opjjz12tvmp13saVPXf//53LLfccqVuHgAAAAAA1I3APenWrVuMHDky7rnnnnjyySfj448/zqavuuqq8ctf/jIGDhwYjRo1qo2mAQAAAABA3QnckxSo77LLLtkDAAAAAADqupIPmgoAAAAAAOWo1nq4T506NYYPHx7jx4+P6dOnV7vcPvvsU9J2AQAAAABAnQncTz/99Lj00kuz0L06afDUBg0aCNwBAAAAAKgTSh64X3DBBXHuuedmNdz79esXXbt2jdatW5e6GQAAAAAAULcD9+uuuy5atGgRL7zwQmy44Yal3jwAAAAAAJTHoKkff/xx9O7dW9gOAAAAAEBZKXng3r59+2jZsmWpNwsAAAAAAOUVuO++++7x7LPPxpQpU0q9aQAAAAAAKJ/A/cwzz4xu3bpF//7944MPPij15gEAAAAAoDwGTf31r38ds2bNynq5p+C9Y8eO0aFDh2jYcO7sv0GDBvHUU0+VuokAAAAAALDkB+4paC+aOXNmfPjhh9mjKilwBwAAAACAuqDkgfuYMWNKvUkAAAAAACi/wD2VkAEAAAAAgHJT8kFTAQAAAACgHNVa4P7222/HscceGz179oy11lorTjjhhIp5Q4YMicsvvzwmTpxYW80DAAAAAIAlu6RMcskll8RJJ50UP/74Y8XgqF9++eVsy6QwvlmzZnHIIYfURhMBAAAAAGDJ7uH+8MMPx3HHHRerrrpq3HPPPfHFF19EoVCYbZktttgill9++bj//vtL3TwAAAAAAKgbPdxT7/aWLVvG4MGDY4011qh2uQ022CBGjRpV0rYBAAAAAECd6eE+YsSI2GyzzeYZtifLLbdcfPbZZyVrFwAAAAAA1KnAfcaMGdG6dev5LpdKzTRuXCsl5gEAAAAAYMkP3FdfffV444035hvKv/nmm9G1a9eStQsAAAAAAOpU4N6/f/8YO3ZsVsu9OhdccEFMmDAhBg4cWNK2AQAAAADAoip5zZYTTjghbrvttjj++ONj6NChMWDAgGz6559/Hvfee2/2SPNTT/gjjzyy1M0DAAAAAIC6Ebgvu+yy8eSTT8bOO+8cd999dwwaNCib/thjj2WPQqEQ66yzTtx3330LVOsdAAAAAACWBLUyKmmqzf7666/Hgw8+GE888URWYmbWrFnRoUOH2HrrrWOnnXaKRo0a1UbTAAAAAACg7gTuScOGDWOHHXbIHgAAAAAAUNeVfNDU2vLVV1/FCiusEA0aNIg111yztpsDAAAAAECZKXkP9+eff36hlt9yyy1z2e4f/vCH+PLLL3NZFwAAAAAA1Hrg3qdPn6yX+YKaOXPmYm/zqaeeiptvvjkOPvjg+Pvf/77Y6wMAAAAAgFoP3PfZZ58qA/c0aOrHH38cr732Wnz77bdZbfc2bdos9vamTZsWhxxySKyzzjpx3HHHCdwBAAAAACiPwP2mm26a5/xJkybFQQcdFP/973/j5ZdfXuztnXXWWfHhhx/Gc889F02aNFns9QEAAAAAQJ0YNHXZZZeNW265Jb755pv44x//uFjrevPNN+Piiy+O/fffP3r16pVbGwEAAAAAYIkP3JOllloqNtlkk3jggQcWeR2pRM2BBx6YlaW54IILcm0fAAAAAADUekmZBTV58uSsvMyiuuKKK2LYsGFx4403Rrt27RarLd27d69y+ujRo6Nz586LtW4AAAAAAMrDEhm4P/jgg/H8889nA50uinHjxsWpp54avXv3jv322y/39vF/Op30cO7rHHt+v9zXCQBLunL7O7VUn6fc9huUI9dp/TtGjg/UXe7ZUEcD9wMOOGCevdrfe++9eOutt6JQKMQf/vCHRdrGEUccETNmzIhrrrkm8jBy5MiF6vkOAAAAAED9U/LA/aabbprvMquttlqcccYZsc8++yzSNh566KGsdvuhhx462/Tvv/8+e/7000+jT58+2es777wz2rdvv0jbAQAAAACAWgvcn3nmmWrnNW3aNFZaaaXo1KnTYm/n66+/jueee67KeSl4L84rhvAAAAAAAFCnAvdUV72mpXI0VRk7dmysvvrq2UCnH3zwQY23AwAAAACA+qNhbTcAAAAAAADKQckD96effjoGDhwYL7zwQrXLPP/889ky6RkAAAAAAOqCkgfu1157bQwePDg22GCDapdJ85544om45pprSto2AAAAAACoMzXcX3311ejRo0e0bt262mWWXnrp2HDDDWPo0KG5bjsNxlpdfXcAAAAAAKhTPdw/++yzWHXVVee7XFpm/PjxJWkTAAAAAADUucC9ZcuW8fnnn893uS+++CKaN29ekjYBAAAAAECdC9xTOZmXXnopxo0bV+0yaV4aVHX99dcvadsAAAAAAKDOBO4HHHBATJ8+PbbbbrsYPnz4XPPTtO233z5++OGHbFkAAAAAAKgLSj5o6h577BH33ntvDBo0KDbddNOsF3vnzp2zeaNHj4433ngjG9h0wIAB8Zvf/KbUzQMAAAAAgLoRuCd33nln/OlPf4pLLrkkXn/99exR1KZNmzj22GPj5JNPro2mAQAAAABA3QncGzZsGKeeemqceOKJWQmZjz/+OJu+6qqrxkYbbRRNmzatjWYBAAAAAEDdCtyLmjRpEptvvnn2AAAAAACAuqxWA/eJEyfGiBEj4ssvv4yOHTvGFltsUZvNAQAAAACARdYwasGECRNizz33jPbt20ffvn1j7733juuvv75ifnrdtm3bePHFF2ujeQAAAAAAsOQH7qlXe+rJngZOXXfddePwww+PQqEw2zIDBw6M7777LgYNGlTq5gEAAAAAQN0I3M8777wYPXp0nH766fHaa6/FFVdcMdcyqXf7euutF88991ypmwcAAAAAAHUjcL/vvvuia9euceaZZ85zuc6dO8enn35asnYBAAAAAECdCtxTiL7++uvPd7kGDRrEt99+W5I2AQAAAABAnQvcl1566Rg/fvx8l0tlZ5ZffvmStAkAAAAAAOpc4L7xxhvHsGHDYsyYMdUu88Ybb8Trr78ePXv2LGnbAAAAAACgzgTuv/vd72L69OkxYMCAeOedd+aa/8EHH8RvfvObKBQKceSRR5a6eQAAAAAAUDcC9759+8YJJ5wQb775Zqy77rqx9tprZ/XaH3/88ay2e7du3eK///1vnHzyyfGzn/2s1M0DAAAAAIC6Ebgn559/ftx1113xk5/8JN57772sN3uq6/7WW29Fly5d4rbbbotzzjmnNpoGAAAAAACLpHGU2Lfffpv1aN9ll12yx4QJE2Ls2LExa9as6NChQ6yyyiqlbhIAAAAAANS9wL1Nmzax6aabxssvv5z9efnll88eAAAAAABQl5W8pMwyyywTa6yxRqk3CwAAAAAA5RW49+jRI0aPHl3qzQIAAAAAQHkF7ieeeGIMGzYsBg0aVOpNAwAAAABA+dRwb9GiRRx44IGx2267xXbbbRfbb799rLbaatG8efMql99yyy1L3UQAAAAAAFjyA/c+ffpEgwYNolAoxIMPPhgPPfTQPJefOXNmydoGAAAAAAB1JnDfZ599ssAdAAAAAADKSckD95tuuqnUmwQAAAAAgLo/aOpWW20VF1xwQZXzxo0bFxMnTqzpJgAAAAAAQN0P3J999tl49913q5y3+uqrx/HHH1/TTQAAAAAAgLofuM9LGjg1PQAAAAAAoK6r1cAdAAAAAADKhcAdAAAAAAByIHAHAAAAAIAcCNwBAAAAAKCuBO4333xzNGrUaK5HgwYNqp2XHo0bNy5F8wAAAAAAYLGVJNEuFAolfR8AAAAAAJRd4D5r1qya3gQAAAAAANS6sq7hfskll8TAgQOjS5cuscwyy0SzZs2iY8eOsc8++8Rbb71V280DAAAAAKCMlHXg/qc//SkeffTRaNu2bfziF7+Ifv36RfPmzePWW2+NjTbaKB566KHabiIAAAAAAGWirEclvf/++7NgPYXslf3tb3+LI444Ig488MD45JNPDM4KAAAAAMBiK+se7j179pwrbE8OP/zw6Ny5c3z++efx9ttv10rbAAAAAAAoL2UduM9LkyZNsuemTZvWdlMAAAAAACgD9TJwTzXcR40alQ2mmh4AAAAAALC46kXx8gsvvDBGjhwZU6ZMiXfeeSd7vfLKK8cdd9wRjRo1qu3mAQAAAABQBupF4P7444/HU089VfHnjh07xi233JINqLogunfvXuX00aNHZ7XgAQAAAACgXgTuTz75ZPb89ddfx1tvvRVnn3129O7dO84999w45ZRTart5AGWh00kP57q+sef3K8l2qttWqbZTKuW238rt+JSK/QYUuR8s+Ur1byuWbK5VEucB1C31InAvatOmTfTq1SseeeSR2HzzzeO0006LbbbZJjbeeON5vi+VoFmYnu8AAAAAANQ/9XLQ1CZNmsRuu+0WhUIhHnzwwdpuDgAAAAAAZaBeBu7Jcsstlz1PmDChtpsCAAAAAEAZqLeB+3PPPZc9G/QUAAAAAIA8lG3g/tJLL8Vjjz0Ws2bNmm36Dz/8EFdccUXceuut0aJFi6y0DAAAAAAALK6yHTT1/fffj/333z8rHbPRRhtFu3bt4ssvv4y33norxo8fH82bN4+bbropVl111dpuKgAAAAAAZaBsA/fevXvHySefnJWOefPNN7OwvWnTptGpU6fYeeed46ijjoo111yztpsJAAAAAECZKNvAffXVV4/zzjuvtpsBAAAAAEA9UbY13AEAAAAAoJQE7gAAAAAAkAOBOwAAAAAA5EDgDgAAAAAAORC4AwAAAABADgTuAAAAAACQA4E7AAAAAADkQOAOAAAA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gBwI3AEAAAAAIAcCdwAAAAAAyIHAHQAAAAAAciBwr2emTZsWp59+enTt2jWaN28eK6+8chxwwAHx6aef1nbTgBowYsSIOP/882PgwIHRoUOHaNCgQfaYn5tuuik22WSTaNWqVbRt2zZ+/etfx5AhQ0rSZqBmTJ06Ne6777747W9/G2uttVb274CWLVvG+uuvH2effXZMnjy52ve6J0D5ueSSS7J/H3Tp0iWWWWaZaNasWXTs2DH22WefeOutt6p9n/sBlL+vvvoqVlhhhez/G9Zcc815LuueAOWnT58+FdlBVY/HHnusyve5H/w/DQqFQqHSnylj33//ffz85z+PV155JVZaaaXo1atXjB07Nl599dVYfvnls+lrrLFGbTcTyNGOO+4Y999//1zT53XrP+aYY+Kyyy6LFi1axDbbbJPdO5566qnsPYMGDcrWCdQ9119/fRx00EHZ627dusW6664b3377bfaP4O+++y7WXnvteO6557L/wa7MPQHK03LLLRdTpkyJ9dZbL1ZZZZVs2siRI+O9996LJk2axD333BPbbbfdbO9xP4D6Yb/99otbbrklu7Y7d+4cH3zwQZXLuSdA+Qbu6f8Ldtpppyw8n9Mf/vCH+MlPfjLbNPeDOaTAnfrhlFNOSQlbYfPNNy989913FdMvvvjibHrv3r1rtX1A/s4///zCaaedVnjggQcK48ePLzRr1iy73qszePDgbH67du0K7733XsX0IUOGFJo2bVpo06ZNYdKkSSVqPZCnm266qXDwwQcX3n777dmm/+9//yv06NEju/b32GOP2ea5J0D5evHFFwvTpk2ba/pVV12VXfcrrrhi4YcffqiY7n4A9cOTTz6ZXevp3wzpuXPnzlUu554A5Svlg+n6HjNmzAIt734wN4F7PTF9+vTCMsssk10Ar7322lzz11tvvWze8OHDa6V9QGnML3D/1a9+lc2/9NJL55p31FFHZfMuuuiiGm4lUGrpH8Pp+k73iPRvhiL3BKifUsCWru833nijYpr7AZS/qVOnZtf/Ouusk4Vm8wrc3ROgfC1s4O5+MDc13OuJl156Kb755pvs52A9evSYa/7OO++cPT/44IO10DpgSRnj4emnn57tnlCZ+wSUr1THPZk+fXpWtzVxT4D6K5WUSZo2bZo9ux9A/XDWWWfFhx9+GNdcc03FfaAq7glAkftB1RpXM50y88Ybb2TPG264YZXzi9PffPPNkrYLWHKMGjUqC9vSmA5pgNU5uU9A+Ur/c52k/7lOAxwl7glQP916663Z9Z8GU02PxP0Ayl+6fi+++OLYf//9K8Z7q457AtQPN9xwQ9YZp2HDhtG1a9esDvtqq6022zLuB1UTuNcT48aNy56rOvkrT//oo49K2i6g7twnWrZsGW3atIlJkyZlAyy2bt26xC0Eakoa4Cjp27dvNGvWLHvtngD1w4UXXpgNlpoGUH3nnXey1yuvvHLccccd0ahRo2wZ9wMob7NmzYoDDzwwu44vuOCC+S7vngD1w7nnnjvbn4877rg47bTTskeR+0HVlJSpJyZPnpw9L7XUUtVeAEk6+YH6aX73icS9AsrPI488kvVeSb3bzznnnIrp7glQPzz++ONx8803x6BBg7KwvWPHjlnYvtFGG1Us434A5e2KK66IYcOGZV/AtWvXbr7LuydAedtyyy2zX7yNHj06pk6dmvViP++886Jx48Zx+umnV3TWSdwPqiZwBwCop959993Ye++900jK2f9kF2u5A/XHk08+md0DUs+z559/Pisj07t37+x/rIHyl3qnnnrqqdl1v99++9V2c4AlwNlnn539P8Iaa6wRLVq0yMrJnHzyyXHfffdl888888ysdjvVE7jXE61atcqe0zdTVUk/IU3qy087gIW/TyTuFVA+Pv3006yETArZfv/738fRRx8923z3BKhf0s+9U93m9KuX1Ls9/Vw89XhN3A+gfB1xxBExY8aMbKDUBeWeAPXTNttsEz/96U/j66+/jqFDh2bT3A+qpoZ7PVEc1OCTTz6pcn5xevoJKVA/ze8+kf6STH+xLrvssvXqL0ooRxMnTsz+wZzGbkmDo1100UVzLeOeAPVTKi+12267xYgRI+LBBx+MjTfe2P0AythDDz2UfeF26KGHzjb9+++/r/iCvk+fPtnrO++8M9q3b++eAPVY+iXc8OHDY/z48dmf3Q+qJnCvJ4o/EX/ttdeqnF+cvt5665W0XcCSY6211soGS5wwYUL2D+tVVllltvnuE1AeUp3FX/3qV/H222/HwIED47rrrosGDRrMtZx7AtRfyy23XPacrv/E/QDKWwrDnnvuuSrnpeC9OK8YwrsnQP2Vfh1buS67+0HVlJSpJ3r27BnLLLNMNuDB66+/Ptf8NEhSsv3229dC64AlQarNttVWW2Wv77777rnmu09A3Td9+vTYYYcd4tVXX41tt902GxixUaNGVS7rngD1VzFc69y5c/bsfgDlK43hUNVjzJgxFfeB4rROnTpl09wToH5KofoLL7yQvd5www2zZ/eDahSoN0455ZRCOuRbbLFFYfLkyRXTL7744mx67969a7V9QM1r1qxZdr1XZ/Dgwdn8du3aFd57772K6UOGDMne26ZNm8KkSZNK1FogTz/++GNhwIAB2TXeq1evwpQpU+b7HvcEKE8vvvhi4dFHHy3MnDlztukzZswoXH755YWGDRsWWrRoURg3blzFPPcDqF/GjBmTXfOdO3eucr57ApSnl156qXDvvfdm/+8w5z2hZ8+e2XXfv3//2ea5H8ytQfpPdWE85SX9/CvVXksDG6y00krZoEipdmv68/LLLx+vvPJKNgIxUD4efvjhOOeccyr+nHq1ptv+pptuWjEtDYrWr1+/ij8fc8wxcdlll8VSSy0VW2+9dTaI0uDBg7P3pW+nd9xxx5J/DmDxpes6Xd/JgAEDYumll65yuVTPvVhOInFPgPJz0003ZeM3pGs9DZDarl27+PLLL+Ott97KarI2b948br755th1111ne5/7AdQfY8eOjdVXXz3r4f7BBx9UuYx7ApTvvxHSeA2pF3sa4yFlh2lsl5Qrdu/ePZ5++ulYYYUVZnuf+8HsBO71zLRp0+LPf/5z3H777fHxxx9H27Zto2/fvlkg16FDh9puHlBDf1nOy4033hj77bffXO+78sor45133ommTZvGZpttlgXzW2yxRQ23GKgpZ555Zpx11lnzXS79hLz4k/Ei9wQoL+k6v/7667PSMR9++GEWtqdrO1376WfhRx11VKy55ppVvtf9AOqHBQncE/cEKC/pWr7iiiuyzrkpN0w121O99m7dusUuu+wShx12WFZGpiruB/+PwB0AAAAAAHJg0FQAAAAAAMiBwB0AAAAAAHIgcAcAAAAAgBwI3AEAAAAAIAcCdwAAAAAAyIHAHQAAAAAAciBwBwAAAACAHAjcAQAAAAAgBwJ3AAAAAADIgcAdAAAAAAByIHAHAIAycuWVV8bZZ58dX3/9dW03BQAA6h2BOwAAlIkbb7wxfve738WPP/4Ybdq0qe3mAABAvSNwBwCAWtKgQYPskcLx6nqkn3/++dkyZ5555jzXNXr06DjqqKNiwIABcdZZZ9VQiwEAgHkRuAMAQC375ptv4pJLLlnk98+cOTP23nvvWH311eOWW27JAnoAAKD0BO4AAFCLUjjevHnzuOyyy2LSpEmLtI733nsvtt1223jggQeiVatWubcRAABYMAJ3AACoRQ0bNoyDDz44vv3227jooosWaR3dunXLSs506tQp9/YBAAALTuAOAAC17KSTTooWLVrEFVdcEV999dUCvadPnz5Z7/ixY8fONS9NS/PSMpWlUD5Nv+mmm2LEiBHxq1/9Kqsf37Zt29h1113jk08+yZabMmVKnHDCCVmAn3rfr7vuujFo0KBq2/LOO+/EfvvtF6uuumo0a9YsVlxxxdh9991j5MiRcy2btl2sSZ965qfl0vLpi4f77ruvYrlHHnkktt5661h22WWzNqy11lrZfqqu1j0AACwJBO4AAFDLVlpppTj00EPju+++iwsvvLDGtzd06NDo2bNnTJgwIStF065du7j77rvjF7/4RVZP/uc//3ncfPPNsfHGG8fmm28eb7/9dhbIP/7443OtK4XkPXr0yJZfbrnlon///lkt+X/961+xySabxPPPP19lG0aNGpWt/9VXX822l8L1Jk2aZPP+/Oc/R79+/eLZZ5+NjTbaKHbccceYOnVq/OUvf4lNN900Pv/88xrfRwAAsCgE7gAAsAQ48cQTY6mlloorr7wyC8Jr0jXXXBN//etfY/jw4XHXXXdlgfovf/nLrMf5FltsEa1bt44PP/wwC+GfeeaZuO6666JQKMSf/vSnuXrSp8FaU1A+ePDg+M9//pO955VXXsl6qP/www/Z/BkzZszVhjvvvDP22WefeP/997PXKcxPIfuwYcPi1FNPzWrRv/jii/Hkk09m8z/44IPYZZddsjYeccQRNbp/AABgUQncAQBgCZDKqhx22GFZOZfUk7sm/exnP8t61BelwPx3v/td9vrdd9+Nq6++Olq2bFkxP5WLSb3XX3755SxEL0qhfWpv6pGeAvvK+vbtm32ejz/+OB5++OG52rD88stnn7NRo0azTU9fOMyaNStrT+rNXpRK1aR5qfTOvffem60XAACWNAJ3AABYgnq5p6A7Bd41WTZlm222mWvaGmuskT2nuu1du3adbV4KxTt27JiF7V9++WXF9CeeeCJ7HjhwYJXb6dWrV/acysbMKQX0qUf/nF544YXsea+99ppr3gorrJC1PQXyL7300nw/JwAAlJrAHQAAlhCp13cql5LqlZ9//vk1tp1VVlllrmmphEt18yrPnz59esW04oCt6T1pINQ5H6kETFI5pC9abbXVqtzO//73v4rgvyrF6Z9++ul8PiUAAJRe41rYJgAAUI3jjz8+/va3v2V11k844YRFWkfqAT4vDRs2XKR51W1n3333nedylUvDFDVv3jwWRQryAQBgSSVwBwCAJUiqlZ7ql6e66Omx8sorV7lc06ZNs+fJkyfPNa9U9c07dOgQo0ePjosvvjjatWuXyzrT5x0zZkx89NFHsc4668w1v3KvegAAWNIoKQMAAEuYP/zhD9G6dev4+9//Xm3plJVWWil7fu+99+aaN3jw4CiFrbfeOntOg5jmpVj3/Y477phr3oQJE+Lxxx/Pern37Nkzt20CAEBeBO4AALCESb3FjzrqqKxe+g033FDlMr17986eU+/yVPO96Omnn46//vWvJftioEWLFnHcccfFPffcM9f81P5BgwbFJ598ssDrTDXsU1mbyy+/PIYPH14xfcaMGVnP/2nTpmWDtK666qq5fQ4AAMiLwB0AAJZAKcxeeumls4C5KnvssUestdZaMWTIkOjWrVvsvPPOsdlmm2W9zg877LCStHHNNdfMeqL/8MMPsdNOO0WXLl2if//+Wdu23HLL7IuDNHBqVYOmVmeTTTaJc845J7799tvYfPPNs8+T1pe2ddddd2XbuOqqq2r0cwEAwKISuAMAwBJo2WWXjWOOOaba+aln+VNPPZWF0d9991088sgjMXPmzCyUTr3ES2WHHXaIN998Mw4//PCs1EsqZ/Pwww/HF198Edtvv33861//qrIW+7ycfPLJ8dBDD2W9+IcNG5b1nm/WrFk2iOzQoUNjxRVXrLHPAwAAi6NBoVAoLNYaAAAAAAAAPdwBAAAAACAPAncAAAAAAMiBwB0AAAAAAHIgcAcAAAAAgBwI3AEAAAAAIAcCdwAAAAAAyIHAHQAAAAAAciBwBwAAAACAHAjcAQAAAAAgBwJ3AAAAAADIgcAdAAAAAAByIHAHAAAAAIAcCNwBAAAAACAHAncAAAAAAMiBwB0AAAAAAHIgcAcAAAAAgBwI3AEAAAAAIAcCdwAAAAAAyIHAHQAAAAAAciBwBwAAAACAHAjcAQAAAAAgBwJ3AAAAAADIgcAdAAAAAAByIHAHAAAAAIAcCNwBAAAAACAHAncAAAAAAMiBwB0AAAAAAGLx/X8K4Q9hnzIlXgAAAABJRU5ErkJggg==" alt="Frecuencias"/>
|
| 19 |
+
<pre>01: ██████���██████████················· 4
|
| 20 |
+
02: █████████████████████············· 5
|
| 21 |
+
03: ████████·························· 2
|
| 22 |
+
04: ████████·························· 2
|
| 23 |
+
05: ████······························ 1
|
| 24 |
+
06: █████████████████████████········· 6
|
| 25 |
+
07: █████████████████████············· 5
|
| 26 |
+
08: ████████·························· 2
|
| 27 |
+
09: █████████████████████████········· 6
|
| 28 |
+
10: ████████·························· 2
|
| 29 |
+
11: ████████████······················ 3
|
| 30 |
+
12: ████████████······················ 3
|
| 31 |
+
13: ████████████······················ 3
|
| 32 |
+
14: ██████████████████████████████████ 8
|
| 33 |
+
15: ████████████······················ 3
|
| 34 |
+
16: ████████████······················ 3
|
| 35 |
+
17: ████████████······················ 3
|
| 36 |
+
18: ████······························ 1
|
| 37 |
+
19: █████████████████················· 4
|
| 38 |
+
20: ████████████······················ 3
|
| 39 |
+
21: █████████████████················· 4
|
| 40 |
+
22: █████████████████················· 4
|
| 41 |
+
23: █████████████████████████········· 6
|
| 42 |
+
24: ██████████████████████████████████ 8
|
| 43 |
+
25: ████████·························· 2
|
| 44 |
+
26: ████······························ 1
|
| 45 |
+
27: ████████·························· 2
|
| 46 |
+
28: █████████████████████████········· 6
|
| 47 |
+
29: █████████████████████············· 5
|
| 48 |
+
30: ████████████······················ 3
|
| 49 |
+
31: █████████████████················· 4
|
| 50 |
+
32: █████████████████████████········· 6
|
| 51 |
+
33: █████████████████████············· 5
|
| 52 |
+
34: ████████████······················ 3
|
| 53 |
+
35: █████████████████················· 4
|
| 54 |
+
36: █████████████████████████········· 6
|
| 55 |
+
37: █████████████████████············· 5
|
| 56 |
+
38: █████████████████████████········· 6
|
| 57 |
+
39: ████████████······················ 3
|
| 58 |
+
40: ████······························ 1
|
| 59 |
+
41: ████████·························· 2
|
| 60 |
+
42: ████████·························· 2
|
| 61 |
+
43: █████████████████████············· 5
|
| 62 |
+
44: ████████████······················ 3
|
| 63 |
+
45: █████████████████················· 4
|
| 64 |
+
46: █████████████████████············· 5
|
| 65 |
+
47: ████████·························· 2
|
| 66 |
+
48: ████████·························· 2
|
| 67 |
+
49: ████████·························· 2</pre>
|
| 68 |
+
|
| 69 |
+
<h2>Chi-cuadrado</h2>
|
| 70 |
+
<p>Chi2 = 37.89349112426035 p-value = 0.7296561046478667</p>
|
| 71 |
+
<p class="small">Si p-value es muy pequeño (< 0.05), indica desviación respecto a uniformidad; recuerda que los sorteos son procesos aleatorios.</p>
|
| 72 |
+
|
| 73 |
+
<h2>Disclaimer</h2>
|
| 74 |
+
<p class="small">Este sistema es informativo y educativo. La lotería es aleatoria. Juega con responsabilidad.</p>
|
| 75 |
+
</body>
|
| 76 |
+
</html>
|
proyecto_tinka_ml/proyecto_tinka_ml/db_connector.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Conexión y consultas a MySQL (XAMPP) con caché local robusto."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from urllib.parse import quote_plus
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from sqlalchemy import create_engine
|
| 7 |
+
|
| 8 |
+
from config import DB_CONFIG, TABLE_NAME, CACHE_FILE
|
| 9 |
+
from utils import load_json, save_json
|
| 10 |
+
|
| 11 |
+
SQL_BASE = f"""SELECT id_sorteo, fecha_sorteo, numeros, boliyapa, jackpot,
|
| 12 |
+
COALESCE(created_at, NOW()) AS created_at
|
| 13 |
+
FROM {TABLE_NAME}
|
| 14 |
+
WHERE numeros IS NOT NULL AND numeros <> ''
|
| 15 |
+
ORDER BY fecha_sorteo ASC, id_sorteo ASC
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
def get_engine():
|
| 19 |
+
user = DB_CONFIG.get("user", "")
|
| 20 |
+
pwd = quote_plus(DB_CONFIG.get("password", ""))
|
| 21 |
+
host = DB_CONFIG.get("host", "localhost")
|
| 22 |
+
port = DB_CONFIG.get("port", 3306)
|
| 23 |
+
db = DB_CONFIG.get("database", "")
|
| 24 |
+
url = f"mysql+mysqlconnector://{user}:{pwd}@{host}:{port}/{db}"
|
| 25 |
+
return create_engine(url, pool_pre_ping=True, pool_recycle=1800)
|
| 26 |
+
|
| 27 |
+
def fetch_from_db() -> pd.DataFrame:
|
| 28 |
+
eng = get_engine()
|
| 29 |
+
try:
|
| 30 |
+
df = pd.read_sql_query(SQL_BASE, eng)
|
| 31 |
+
return df
|
| 32 |
+
finally:
|
| 33 |
+
eng.dispose()
|
| 34 |
+
|
| 35 |
+
def load_cached():
|
| 36 |
+
data = load_json(CACHE_FILE, default=None)
|
| 37 |
+
if not data: return None
|
| 38 |
+
try:
|
| 39 |
+
df = pd.DataFrame(data)
|
| 40 |
+
if "fecha_sorteo" in df.columns:
|
| 41 |
+
df["fecha_sorteo"] = pd.to_datetime(df["fecha_sorteo"]).dt.date
|
| 42 |
+
return df
|
| 43 |
+
except Exception:
|
| 44 |
+
return None
|
| 45 |
+
|
| 46 |
+
def save_cache(df: pd.DataFrame) -> None:
|
| 47 |
+
df = df.copy()
|
| 48 |
+
for col in ("fecha_sorteo", "created_at"):
|
| 49 |
+
if col in df.columns:
|
| 50 |
+
df[col] = df[col].astype(str)
|
| 51 |
+
save_json(CACHE_FILE, df.to_dict(orient="records"))
|
| 52 |
+
|
| 53 |
+
def get_data(use_cache: bool = True):
|
| 54 |
+
if use_cache:
|
| 55 |
+
cached = load_cached()
|
| 56 |
+
if cached is not None and len(cached) > 0:
|
| 57 |
+
return cached.copy()
|
| 58 |
+
df = fetch_from_db()
|
| 59 |
+
if "fecha_sorteo" in df.columns:
|
| 60 |
+
df["fecha_sorteo"] = pd.to_datetime(df["fecha_sorteo"]).dt.date
|
| 61 |
+
save_cache(df)
|
| 62 |
+
return df
|
| 63 |
+
|
| 64 |
+
def refresh_cache():
|
| 65 |
+
df = fetch_from_db()
|
| 66 |
+
if "fecha_sorteo" in df.columns:
|
| 67 |
+
df["fecha_sorteo"] = pd.to_datetime(df["fecha_sorteo"]).dt.date
|
| 68 |
+
save_cache(df)
|
| 69 |
+
return df
|
proyecto_tinka_ml/proyecto_tinka_ml/generador.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generación y evaluación de combinaciones a partir de las estadísticas."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
from typing import List, Dict, Tuple
|
| 4 |
+
import random
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
from config import TOTAL_NUMBERS, COMBINATION_SIZE, SUM_RANGE
|
| 8 |
+
|
| 9 |
+
def _validate_combo(combo: List[int]) -> bool:
|
| 10 |
+
if len(combo) != COMBINATION_SIZE: return False
|
| 11 |
+
if len(set(combo)) != COMBINATION_SIZE: return False
|
| 12 |
+
if not all(1 <= x <= TOTAL_NUMBERS for x in combo): return False
|
| 13 |
+
return True
|
| 14 |
+
|
| 15 |
+
def _meets_heuristics(combo: List[int]) -> bool:
|
| 16 |
+
s = sum(combo)
|
| 17 |
+
if not (SUM_RANGE[0] <= s <= SUM_RANGE[1]):
|
| 18 |
+
return False
|
| 19 |
+
ev = sum(1 for x in combo if x % 2 == 0)
|
| 20 |
+
od = COMBINATION_SIZE - ev
|
| 21 |
+
if abs(ev - od) > 2:
|
| 22 |
+
return False
|
| 23 |
+
return True
|
| 24 |
+
|
| 25 |
+
def _weighted_choice(population: List[int], weights: List[float], k: int) -> List[int]:
|
| 26 |
+
arr = np.array(population)
|
| 27 |
+
w = np.array(weights, dtype=float)
|
| 28 |
+
if w.sum() <= 0:
|
| 29 |
+
w = np.ones_like(w) / len(w)
|
| 30 |
+
else:
|
| 31 |
+
w = w / w.sum()
|
| 32 |
+
picks = set()
|
| 33 |
+
for _ in range(2000):
|
| 34 |
+
x = np.random.choice(arr, p=w)
|
| 35 |
+
picks.add(int(x))
|
| 36 |
+
if len(picks) == k:
|
| 37 |
+
break
|
| 38 |
+
if len(picks) < k:
|
| 39 |
+
rest = [p for p in population if p not in picks]
|
| 40 |
+
random.shuffle(rest)
|
| 41 |
+
picks.update(rest[:(k-len(picks))])
|
| 42 |
+
return sorted(picks)
|
| 43 |
+
|
| 44 |
+
def _rango_bucket(n: int) -> int:
|
| 45 |
+
if 1 <= n <= 9: return 0
|
| 46 |
+
elif 10 <= n <= 18: return 1
|
| 47 |
+
elif 19 <= n <= 27: return 2
|
| 48 |
+
elif 28 <= n <= 36: return 3
|
| 49 |
+
elif 37 <= n <= 45: return 4
|
| 50 |
+
return 4
|
| 51 |
+
|
| 52 |
+
def _consecutivos(combo: List[int]) -> int:
|
| 53 |
+
arr = sorted(combo)
|
| 54 |
+
return sum(1 for a, b in zip(arr, arr[1:]) if b == a + 1)
|
| 55 |
+
|
| 56 |
+
# Estrategias clásicas
|
| 57 |
+
def estrategia_frecuencia_pura(freq_abs: Dict[int,int], n_combos: int = 10) -> List[List[int]]:
|
| 58 |
+
top = sorted(freq_abs.items(), key=lambda x: (-x[1], x[0]))
|
| 59 |
+
top_pool = [k for k,_ in top[:min(25, len(top))]]
|
| 60 |
+
combos, tries = [], 0
|
| 61 |
+
while len(combos) < n_combos and tries < 8000:
|
| 62 |
+
c = sorted(random.sample(top_pool, COMBINATION_SIZE))
|
| 63 |
+
if _validate_combo(c) and _meets_heuristics(c) and c not in combos:
|
| 64 |
+
combos.append(c)
|
| 65 |
+
tries += 1
|
| 66 |
+
return combos
|
| 67 |
+
|
| 68 |
+
def estrategia_equilibrio_hot_cold(freq_abs: Dict[int,int], hot_15: List[int], cold_15: List[int], n_combos: int = 10) -> List[List[int]]:
|
| 69 |
+
combos, tries = [], 0
|
| 70 |
+
pool_cold = cold_15[:]
|
| 71 |
+
pool_hot = hot_15[:]
|
| 72 |
+
if len(pool_hot) < 3 or len(pool_cold) < 3:
|
| 73 |
+
return estrategia_frecuencia_pura(freq_abs, n_combos)
|
| 74 |
+
while len(combos) < n_combos and tries < 8000:
|
| 75 |
+
c = sorted(random.sample(pool_hot, 3) + random.sample(pool_cold, 3))
|
| 76 |
+
if _validate_combo(c) and _meets_heuristics(c) and c not in combos:
|
| 77 |
+
combos.append(c)
|
| 78 |
+
tries += 1
|
| 79 |
+
return combos
|
| 80 |
+
|
| 81 |
+
def estrategia_temporal_inteligente(freq_last: Dict[int,int], hot_cycle: List[int], n_combos: int = 10) -> List[List[int]]:
|
| 82 |
+
keys = list(range(1, TOTAL_NUMBERS+1))
|
| 83 |
+
weights = [freq_last.get(k, 0) + (1.5 if k in hot_cycle else 0.0) + 0.01 for k in keys]
|
| 84 |
+
combos, tries = [], 0
|
| 85 |
+
while len(combos) < n_combos and tries < 10000:
|
| 86 |
+
c = _weighted_choice(keys, weights, COMBINATION_SIZE)
|
| 87 |
+
c = sorted(c)
|
| 88 |
+
if _validate_combo(c) and _meets_heuristics(c) and c not in combos:
|
| 89 |
+
combos.append(c)
|
| 90 |
+
tries += 1
|
| 91 |
+
return combos
|
| 92 |
+
|
| 93 |
+
def estrategia_patrones_detectados(pairs_top: List[Dict], trios_top: List[Dict], n_combos: int = 10) -> List[List[int]]:
|
| 94 |
+
combos, tries = [], 0
|
| 95 |
+
pairs = [tuple(p['pair']) for p in pairs_top]
|
| 96 |
+
trios = [tuple(t['trio']) for t in trios_top]
|
| 97 |
+
while len(combos) < n_combos and tries < 12000:
|
| 98 |
+
base = set()
|
| 99 |
+
if trios and random.random() < 0.6:
|
| 100 |
+
base.update(random.choice(trios))
|
| 101 |
+
guard = 0
|
| 102 |
+
while len(base) < 4 and pairs and guard < 10:
|
| 103 |
+
cand = random.choice(pairs)
|
| 104 |
+
if not (set(cand) & base):
|
| 105 |
+
base.update(cand)
|
| 106 |
+
guard += 1
|
| 107 |
+
rest = [x for x in range(1, TOTAL_NUMBERS+1) if x not in base]
|
| 108 |
+
random.shuffle(rest)
|
| 109 |
+
while len(base) < 6 and rest:
|
| 110 |
+
base.add(rest.pop())
|
| 111 |
+
c = sorted(list(base))[:6]
|
| 112 |
+
if _validate_combo(c) and _meets_heuristics(c) and c not in combos:
|
| 113 |
+
combos.append(c)
|
| 114 |
+
tries += 1
|
| 115 |
+
return combos
|
| 116 |
+
|
| 117 |
+
def estrategia_random_ponderado(freq_abs: Dict[int,int], n_combos: int = 10) -> List[List[int]]:
|
| 118 |
+
keys = list(range(1, TOTAL_NUMBERS+1))
|
| 119 |
+
raw = [freq_abs.get(k, 0) + 0.01 for k in keys]
|
| 120 |
+
combos, tries = [], 0
|
| 121 |
+
while len(combos) < n_combos and tries < 8000:
|
| 122 |
+
c = _weighted_choice(keys, raw, COMBINATION_SIZE)
|
| 123 |
+
c = sorted(c)
|
| 124 |
+
if _validate_combo(c) and _meets_heuristics(c) and c not in combos:
|
| 125 |
+
combos.append(c)
|
| 126 |
+
tries += 1
|
| 127 |
+
return combos
|
| 128 |
+
|
| 129 |
+
# Scoring ML
|
| 130 |
+
def score_combo_ml(combo: List[int], probs: Dict[int, float]) -> float:
|
| 131 |
+
eps = 1e-9
|
| 132 |
+
ll = sum(np.log(max(probs.get(x, eps), eps)) for x in combo) * 10.0
|
| 133 |
+
suma = sum(combo)
|
| 134 |
+
penalty_sum = abs(135 - suma) * 1.0
|
| 135 |
+
ev = sum(1 for x in combo if x % 2 == 0)
|
| 136 |
+
od = COMBINATION_SIZE - ev
|
| 137 |
+
penalty_parity = abs(ev - od) * 2.0
|
| 138 |
+
consec = _consecutivos(combo)
|
| 139 |
+
penalty_consec = max(0, consec - 1) * 3.0
|
| 140 |
+
buckets = [0,0,0,0,0]
|
| 141 |
+
for x in combo:
|
| 142 |
+
if 1 <= x <= 9: buckets[0]+=1
|
| 143 |
+
elif 10 <= x <= 18: buckets[1]+=1
|
| 144 |
+
elif 19 <= x <= 27: buckets[2]+=1
|
| 145 |
+
elif 28 <= x <= 36: buckets[3]+=1
|
| 146 |
+
elif 37 <= x <= 45: buckets[4]+=1
|
| 147 |
+
cobertura = sum(1 for b in buckets if b > 0)
|
| 148 |
+
penalty_bucket = 0.0
|
| 149 |
+
if max(buckets) > 3:
|
| 150 |
+
penalty_bucket += (max(buckets) - 3) * 2.0
|
| 151 |
+
if cobertura < 3:
|
| 152 |
+
penalty_bucket += (3 - cobertura) * 2.0
|
| 153 |
+
return float(ll - penalty_sum - penalty_parity - penalty_consec - penalty_bucket)
|
| 154 |
+
|
| 155 |
+
def rankear_combos_ml(combos: List[List[int]], probs: Dict[int, float]):
|
| 156 |
+
scored = []
|
| 157 |
+
seen = set()
|
| 158 |
+
for c in combos:
|
| 159 |
+
t = tuple(sorted(c))
|
| 160 |
+
if t in seen:
|
| 161 |
+
continue
|
| 162 |
+
seen.add(t)
|
| 163 |
+
scored.append((list(t), score_combo_ml(list(t), probs)))
|
| 164 |
+
scored.sort(key=lambda x: x[1], reverse=True)
|
| 165 |
+
return scored
|
proyecto_tinka_ml/proyecto_tinka_ml/main.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Interfaz de consola (menú) para el sistema Tinka con opción ML."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import pandas as pd
|
| 5 |
+
|
| 6 |
+
from config import DATA_DIR, COMBOS_FILE
|
| 7 |
+
from db_connector import get_data, refresh_cache
|
| 8 |
+
from analizador import analisis_completo, analisis_frecuencias, analisis_temporal
|
| 9 |
+
from generador import (
|
| 10 |
+
estrategia_frecuencia_pura,
|
| 11 |
+
estrategia_equilibrio_hot_cold,
|
| 12 |
+
estrategia_temporal_inteligente,
|
| 13 |
+
estrategia_patrones_detectados,
|
| 14 |
+
estrategia_random_ponderado,
|
| 15 |
+
rankear_combos_ml,
|
| 16 |
+
)
|
| 17 |
+
from utils import parse_numbers, save_json, load_json
|
| 18 |
+
from visualizador import render_ascii_hist, html_report
|
| 19 |
+
from ml import (
|
| 20 |
+
beta_binomial_posteriors,
|
| 21 |
+
beta_binomial_posteriors_ewma,
|
| 22 |
+
blend_probabilities,
|
| 23 |
+
save_probabilities,
|
| 24 |
+
thompson_sampling_pool,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
def pause():
|
| 28 |
+
input("\nPresiona ENTER para continuar...")
|
| 29 |
+
|
| 30 |
+
def _int_input_default(prompt: str, default: int) -> int:
|
| 31 |
+
s = input(prompt).strip()
|
| 32 |
+
if not s:
|
| 33 |
+
return default
|
| 34 |
+
try:
|
| 35 |
+
v = int(s)
|
| 36 |
+
if v <= 0: return default
|
| 37 |
+
return v
|
| 38 |
+
except Exception:
|
| 39 |
+
return default
|
| 40 |
+
|
| 41 |
+
def show_dashboard(df: pd.DataFrame):
|
| 42 |
+
stats = analisis_completo(df)
|
| 43 |
+
print("\n=== DASHBOARD RÁPIDO ===\n")
|
| 44 |
+
frec = stats['frecuencias']
|
| 45 |
+
print("Top 15 calientes:", frec['hot_15'])
|
| 46 |
+
print("Top 15 fríos :", frec['cold_15'])
|
| 47 |
+
print("En racha (ult10):", frec['en_racha_ult10'])
|
| 48 |
+
print("Dormidos (>20) :", frec['dormidos_mas20'])
|
| 49 |
+
print("\nHistograma (ASCII):\n")
|
| 50 |
+
print(render_ascii_hist(frec['freq_abs']))
|
| 51 |
+
print("\nChi-cuadrado:", stats['chi_cuadrado'])
|
| 52 |
+
out_html = DATA_DIR / "reporte.html"
|
| 53 |
+
out_html.write_text(html_report(stats), encoding="utf-8")
|
| 54 |
+
print(f"\nReporte HTML exportado en: {out_html}")
|
| 55 |
+
|
| 56 |
+
def analisis_por_numero(df: pd.DataFrame):
|
| 57 |
+
try:
|
| 58 |
+
n = int(input("Número (1-45): ").strip())
|
| 59 |
+
except Exception:
|
| 60 |
+
print("Entrada inválida"); return
|
| 61 |
+
if not (1 <= n <= 45):
|
| 62 |
+
print("Fuera de rango"); return
|
| 63 |
+
frec = analisis_frecuencias(df)
|
| 64 |
+
fa = frec['freq_abs'].get(n, 0)
|
| 65 |
+
fr = frec['freq_rel'].get(n, 0.0)
|
| 66 |
+
ult10 = n in frec['en_racha_ult10']
|
| 67 |
+
dorm = n in frec['dormidos_mas20']
|
| 68 |
+
print(f"\nNúmero {n}: freq_abs={fa}, freq_rel={fr:.4f}, en_racha_ult10={ult10}, dormido>20={dorm}\n")
|
| 69 |
+
|
| 70 |
+
def _imprimir_combos(combos):
|
| 71 |
+
for i, c in enumerate(combos, 1):
|
| 72 |
+
print(f"{i:02d})", " ".join(f"{x:02d}" for x in c))
|
| 73 |
+
|
| 74 |
+
def generar_combinaciones(df: pd.DataFrame):
|
| 75 |
+
stats = analisis_completo(df)
|
| 76 |
+
frec = stats['frecuencias']
|
| 77 |
+
cooc = stats['coocurrencias']
|
| 78 |
+
temp = analisis_temporal(df)
|
| 79 |
+
last50 = temp['ventanas']['50']
|
| 80 |
+
|
| 81 |
+
print("\nEstrategias:")
|
| 82 |
+
print(" 1) Frecuencia pura")
|
| 83 |
+
print(" 2) Equilibrio caliente-frío")
|
| 84 |
+
print(" 3) Temporal inteligente (últimos 50)")
|
| 85 |
+
print(" 4) Patrones detectados (pares/tríos)")
|
| 86 |
+
print(" 5) Random ponderado")
|
| 87 |
+
op = input("Elige 1-5: ").strip()
|
| 88 |
+
|
| 89 |
+
n = _int_input_default("¿Cuántas combinaciones quieres generar? [10 por defecto]: ", 10)
|
| 90 |
+
|
| 91 |
+
combos = []
|
| 92 |
+
if op == "1":
|
| 93 |
+
combos = estrategia_frecuencia_pura(frec['freq_abs'], n_combos=n)
|
| 94 |
+
etiqueta = "frecuencia_pura"
|
| 95 |
+
elif op == "2":
|
| 96 |
+
combos = estrategia_equilibrio_hot_cold(frec['freq_abs'], frec['hot_15'], frec['cold_15'], n_combos=n)
|
| 97 |
+
etiqueta = "equilibrio_hot_cold"
|
| 98 |
+
elif op == "3":
|
| 99 |
+
combos = estrategia_temporal_inteligente(last50['freq_abs'], last50['hot_15'], n_combos=n)
|
| 100 |
+
etiqueta = "temporal_inteligente_50"
|
| 101 |
+
elif op == "4":
|
| 102 |
+
combos = estrategia_patrones_detectados(cooc['pairs_top20'], cooc['trios_top20'], n_combos=n)
|
| 103 |
+
etiqueta = "patrones_detectados"
|
| 104 |
+
elif op == "5":
|
| 105 |
+
combos = estrategia_random_ponderado(frec['freq_abs'], n_combos=n)
|
| 106 |
+
etiqueta = "random_ponderado"
|
| 107 |
+
else:
|
| 108 |
+
print("Opción no válida"); return
|
| 109 |
+
|
| 110 |
+
if not combos:
|
| 111 |
+
print("\nNo se pudieron generar combinaciones."); return
|
| 112 |
+
|
| 113 |
+
print("\n=== Combinaciones sugeridas ===\n")
|
| 114 |
+
_imprimir_combos(combos)
|
| 115 |
+
|
| 116 |
+
record = load_json(COMBOS_FILE, default=[])
|
| 117 |
+
record.append({"estrategia": etiqueta, "n": n, "combos": combos})
|
| 118 |
+
save_json(COMBOS_FILE, record)
|
| 119 |
+
print(f"\nGuardado en {COMBOS_FILE}")
|
| 120 |
+
|
| 121 |
+
def comparar_mi_combinacion(df: pd.DataFrame):
|
| 122 |
+
s = input("Ingresa tus 6 números separados por espacio: ").strip()
|
| 123 |
+
arr = sorted(parse_numbers(s))
|
| 124 |
+
if len(arr) != 6 or min(arr) < 1 or max(arr) > 45 or len(set(arr)) != 6:
|
| 125 |
+
print("Entrada inválida"); return
|
| 126 |
+
stats = analisis_completo(df)
|
| 127 |
+
frec = stats['frecuencias']
|
| 128 |
+
hot = set(frec['hot_15'])
|
| 129 |
+
cold = set(frec['cold_15'])
|
| 130 |
+
score = sum(frec['freq_abs'].get(x,0) for x in arr)
|
| 131 |
+
suma = sum(arr)
|
| 132 |
+
ev = sum(1 for x in arr if x % 2 == 0)
|
| 133 |
+
od = 6 - ev
|
| 134 |
+
in_hot = [x for x in arr if x in hot]
|
| 135 |
+
in_cold = [x for x in arr if x in cold]
|
| 136 |
+
print("\n=== Evaluación ===")
|
| 137 |
+
print("Tu combinación :", " ".join(f"{x:02d}" for x in arr))
|
| 138 |
+
print("Suma :", suma, "(recomendado 90-180)")
|
| 139 |
+
print("Pares/Impares :", ev, "/", od)
|
| 140 |
+
print("En HOT :", in_hot)
|
| 141 |
+
print("En COLD :", in_cold)
|
| 142 |
+
print("Puntuación (sum freq_abs):", score)
|
| 143 |
+
|
| 144 |
+
def ver_mejores_historicas(df: pd.DataFrame):
|
| 145 |
+
frec = analisis_frecuencias(df)['freq_abs']
|
| 146 |
+
rows = []
|
| 147 |
+
for _, r in df.iterrows():
|
| 148 |
+
arr = sorted(parse_numbers(r['numeros']))
|
| 149 |
+
s = sum(arr)
|
| 150 |
+
score = sum(frec.get(x,0) for x in arr)
|
| 151 |
+
penal = abs(135 - s)
|
| 152 |
+
rows.append({"id_sorteo": int(r['id_sorteo']), "fecha": str(r['fecha_sorteo']),
|
| 153 |
+
"combo": " ".join(f"{x:02d}" for x in arr),
|
| 154 |
+
"suma": s, "score": score - penal})
|
| 155 |
+
top = sorted(rows, key=lambda x: x['score'], reverse=True)[:10]
|
| 156 |
+
print("\n=== Top 10 históricas por score heurístico ===\n")
|
| 157 |
+
for i, row in enumerate(top, 1):
|
| 158 |
+
print(f"{i:02d}) {row['fecha']} id={row['id_sorteo']} {row['combo']} score={row['score']:.2f}")
|
| 159 |
+
|
| 160 |
+
def exportar_analisis(df: pd.DataFrame):
|
| 161 |
+
html = html_report(analisis_completo(df))
|
| 162 |
+
out = DATA_DIR / "reporte.html"
|
| 163 |
+
out.write_text(html, encoding="utf-8")
|
| 164 |
+
print(f"Reporte exportado en {out}")
|
| 165 |
+
|
| 166 |
+
def actualizar_cache():
|
| 167 |
+
df = refresh_cache()
|
| 168 |
+
print(f"Cache actualizado. Registros: {len(df)}")
|
| 169 |
+
|
| 170 |
+
# -- Opción 8: Recomendación automática (ML)
|
| 171 |
+
def recomendacion_ml_menu(df: pd.DataFrame):
|
| 172 |
+
print("\nCalculando probabilidades bayesianas (global + reciente)...")
|
| 173 |
+
posts_global = beta_binomial_posteriors(df, prior_strength=30.0, p0=(6.0/45.0))
|
| 174 |
+
posts_recent = beta_binomial_posteriors_ewma(df, halflife_draws=50, prior_strength=15.0, p0=(6.0/45.0))
|
| 175 |
+
probs_blend = blend_probabilities(posts_global, posts_recent, w_recent=0.30)
|
| 176 |
+
save_probabilities(posts_global, posts_recent, probs_blend)
|
| 177 |
+
|
| 178 |
+
n = _int_input_default("¿Cuántas recomendaciones quieres? [1 por defecto]: ", 1)
|
| 179 |
+
|
| 180 |
+
stats = analisis_completo(df)
|
| 181 |
+
frec = stats['frecuencias']
|
| 182 |
+
cooc = stats['coocurrencias']
|
| 183 |
+
temp = analisis_temporal(df)
|
| 184 |
+
last50 = temp['ventanas']['50']
|
| 185 |
+
|
| 186 |
+
pool = []
|
| 187 |
+
N = max(10, n * 12)
|
| 188 |
+
pool += estrategia_equilibrio_hot_cold(frec['freq_abs'], frec['hot_15'], frec['cold_15'], n_combos=N)
|
| 189 |
+
pool += estrategia_temporal_inteligente(last50['freq_abs'], last50['hot_15'], n_combos=N)
|
| 190 |
+
pool += estrategia_random_ponderado(frec['freq_abs'], n_combos=N // 2)
|
| 191 |
+
pool += estrategia_patrones_detectados(cooc['pairs_top20'], cooc['trios_top20'], n_combos=max(5, n*6))
|
| 192 |
+
|
| 193 |
+
# Thompson Sampling extra (con posts recientes para mayor reactividad)
|
| 194 |
+
from ml import thompson_sampling_pool
|
| 195 |
+
pool += thompson_sampling_pool(posts_recent, n_combos=max(10, n*10), k=6)
|
| 196 |
+
|
| 197 |
+
if not pool:
|
| 198 |
+
print("No se pudieron generar candidatas. Actualiza la base y reintenta."); return
|
| 199 |
+
|
| 200 |
+
ranked = rankear_combos_ml(pool, probs_blend)
|
| 201 |
+
topn = ranked[:n]
|
| 202 |
+
|
| 203 |
+
print("\n=== Recomendación automática (ML) ===\n")
|
| 204 |
+
for i, (combo, score) in enumerate(topn, 1):
|
| 205 |
+
print(f"{i:02d})", " ".join(f"{x:02d}" for x in combo), f" score={score:.2f}")
|
| 206 |
+
|
| 207 |
+
record = load_json(COMBOS_FILE, default=[])
|
| 208 |
+
record.append({"estrategia": "auto_ml", "n": n,
|
| 209 |
+
"ranked": [{"combo": c, "score": float(s)} for (c, s) in topn]})
|
| 210 |
+
save_json(COMBOS_FILE, record)
|
| 211 |
+
print(f"\nGuardado en {COMBOS_FILE}")
|
| 212 |
+
|
| 213 |
+
def main():
|
| 214 |
+
print("""====================================================
|
| 215 |
+
SISTEMA DE ANÁLISIS Y PREDICCIÓN — TINKA
|
| 216 |
+
(Educativo/Estadístico) — by tu asesor
|
| 217 |
+
====================================================
|
| 218 |
+
AVISO: La lotería es aleatoria. Este sistema NO garantiza aciertos.
|
| 219 |
+
Usa la información con responsabilidad.
|
| 220 |
+
""" )
|
| 221 |
+
df = get_data(use_cache=True)
|
| 222 |
+
if len(df)==0:
|
| 223 |
+
print("No hay datos en la base. Ejecuta tu scraper primero.")
|
| 224 |
+
return
|
| 225 |
+
while True:
|
| 226 |
+
print("""Menú:
|
| 227 |
+
1) Ver dashboard completo de estadísticas
|
| 228 |
+
2) Análisis específico por número
|
| 229 |
+
3) Generar combinaciones (elegir estrategia y cantidad)
|
| 230 |
+
4) Comparar mi combinación vs estadísticas
|
| 231 |
+
5) Ver mejores combinaciones históricas
|
| 232 |
+
6) Exportar análisis a HTML
|
| 233 |
+
7) Actualizar datos desde BD (refresh cache)
|
| 234 |
+
8) Recomendación automática (ML)
|
| 235 |
+
0) Salir
|
| 236 |
+
""" )
|
| 237 |
+
op = input("Elige opción: ").strip()
|
| 238 |
+
if op == "1": show_dashboard(df); pause()
|
| 239 |
+
elif op == "2": analisis_por_numero(df); pause()
|
| 240 |
+
elif op == "3": generar_combinaciones(df); pause()
|
| 241 |
+
elif op == "4": comparar_mi_combinacion(df); pause()
|
| 242 |
+
elif op == "5": ver_mejores_historicas(df); pause()
|
| 243 |
+
elif op == "6": exportar_analisis(df); pause()
|
| 244 |
+
elif op == "7": df = refresh_cache(); print("Datos recargados."); pause()
|
| 245 |
+
elif op == "8": recomendacion_ml_menu(df); pause()
|
| 246 |
+
elif op == "0": print("¡Hasta luego!"); break
|
| 247 |
+
else: print("Opción inválida")
|
| 248 |
+
|
| 249 |
+
if __name__ == "__main__":
|
| 250 |
+
main()
|
proyecto_tinka_ml/proyecto_tinka_ml/ml.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ML ligero: Beta-Binomial, EWMA y Thompson Sampling."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
from typing import Dict, List, Tuple
|
| 4 |
+
from collections import defaultdict
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pandas as pd
|
| 7 |
+
from config import DATA_DIR
|
| 8 |
+
from utils import parse_numbers, save_json, load_json
|
| 9 |
+
|
| 10 |
+
PROBS_FILE = DATA_DIR / "probabilidades.json"
|
| 11 |
+
|
| 12 |
+
def _counts_from_df(df: pd.DataFrame) -> Dict[int, int]:
|
| 13 |
+
cnt = defaultdict(int)
|
| 14 |
+
for _, r in df.iterrows():
|
| 15 |
+
for n in parse_numbers(r['numeros']):
|
| 16 |
+
cnt[int(n)] += 1
|
| 17 |
+
return dict(cnt)
|
| 18 |
+
|
| 19 |
+
def _counts_ewma(df: pd.DataFrame, halflife_draws: int = 50) -> Dict[int, float]:
|
| 20 |
+
if len(df) == 0:
|
| 21 |
+
return {i: 0.0 for i in range(1, 46)}
|
| 22 |
+
gamma = 0.5 ** (1.0 / max(1, halflife_draws))
|
| 23 |
+
w = 1.0
|
| 24 |
+
cnt = {i: 0.0 for i in range(1, 46)}
|
| 25 |
+
total_weight = 0.0
|
| 26 |
+
for _, r in df.iloc[::-1].iterrows():
|
| 27 |
+
nums = set(parse_numbers(r['numeros']))
|
| 28 |
+
for i in range(1, 46):
|
| 29 |
+
if i in nums:
|
| 30 |
+
cnt[i] += w
|
| 31 |
+
total_weight += w
|
| 32 |
+
w *= gamma
|
| 33 |
+
cnt['__total_weight__'] = total_weight
|
| 34 |
+
return cnt
|
| 35 |
+
|
| 36 |
+
def beta_binomial_posteriors(df: pd.DataFrame, prior_strength: float = 30.0, p0: float = 6.0/45.0):
|
| 37 |
+
s = _counts_from_df(df)
|
| 38 |
+
D = float(len(df))
|
| 39 |
+
a0 = prior_strength * p0
|
| 40 |
+
b0 = prior_strength * (1.0 - p0)
|
| 41 |
+
posts = {}
|
| 42 |
+
for i in range(1, 46):
|
| 43 |
+
successes = float(s.get(i, 0))
|
| 44 |
+
failures = max(0.0, D - successes)
|
| 45 |
+
alpha = a0 + successes
|
| 46 |
+
beta = b0 + failures
|
| 47 |
+
posts[i] = {"alpha": alpha, "beta": beta, "p": alpha/(alpha+beta)}
|
| 48 |
+
return posts
|
| 49 |
+
|
| 50 |
+
def beta_binomial_posteriors_ewma(df: pd.DataFrame, halflife_draws: int = 50, prior_strength: float = 15.0, p0: float = 6.0/45.0):
|
| 51 |
+
ew = _counts_ewma(df, halflife_draws=halflife_draws)
|
| 52 |
+
total_w = float(ew.get('__total_weight__', 0.0))
|
| 53 |
+
a0 = prior_strength * p0
|
| 54 |
+
b0 = prior_strength * (1.0 - p0)
|
| 55 |
+
posts = {}
|
| 56 |
+
for i in range(1, 46):
|
| 57 |
+
successes = float(ew.get(i, 0.0))
|
| 58 |
+
failures = max(0.0, total_w - successes)
|
| 59 |
+
alpha = a0 + successes
|
| 60 |
+
beta = b0 + failures
|
| 61 |
+
posts[i] = {"alpha": alpha, "beta": beta, "p": alpha/(alpha+beta)}
|
| 62 |
+
return posts
|
| 63 |
+
|
| 64 |
+
def blend_probabilities(global_posts, recent_posts, w_recent: float = 0.30):
|
| 65 |
+
w_recent = max(0.0, min(1.0, w_recent))
|
| 66 |
+
out = {}
|
| 67 |
+
for i in range(1, 46):
|
| 68 |
+
pg = float(global_posts[i]["p"])
|
| 69 |
+
pr = float(recent_posts[i]["p"])
|
| 70 |
+
out[i] = (1.0 - w_recent) * pg + w_recent * pr
|
| 71 |
+
return out
|
| 72 |
+
|
| 73 |
+
def save_probabilities(probs_global, probs_recent, probs_blend):
|
| 74 |
+
data = {"global": probs_global, "recent": probs_recent, "blend": probs_blend}
|
| 75 |
+
save_json(PROBS_FILE, data)
|
| 76 |
+
|
| 77 |
+
def load_probabilities():
|
| 78 |
+
return load_json(PROBS_FILE, default=None)
|
| 79 |
+
|
| 80 |
+
def thompson_sampling_combination(posts, k: int = 6):
|
| 81 |
+
draws = []
|
| 82 |
+
for i in range(1, 46):
|
| 83 |
+
a = float(posts[i]['alpha']); b = float(posts[i]['beta'])
|
| 84 |
+
a = max(a, 1e-3); b = max(b, 1e-3)
|
| 85 |
+
theta = np.random.beta(a, b)
|
| 86 |
+
draws.append((i, float(theta)))
|
| 87 |
+
draws.sort(key=lambda x: x[1], reverse=True)
|
| 88 |
+
return sorted([i for (i, t) in draws[:k]])
|
| 89 |
+
|
| 90 |
+
def thompson_sampling_pool(posts, n_combos: int = 10, k: int = 6):
|
| 91 |
+
combos = []
|
| 92 |
+
seen = set()
|
| 93 |
+
tries = 0
|
| 94 |
+
while len(combos) < n_combos and tries < n_combos * 200:
|
| 95 |
+
c = tuple(thompson_sampling_combination(posts, k=k))
|
| 96 |
+
if c not in seen:
|
| 97 |
+
combos.append(list(c))
|
| 98 |
+
seen.add(c)
|
| 99 |
+
tries += 1
|
| 100 |
+
return combos
|
proyecto_tinka_ml/proyecto_tinka_ml/requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pandas
|
| 2 |
+
numpy
|
| 3 |
+
mysql-connector-python
|
| 4 |
+
matplotlib
|
| 5 |
+
scipy
|
| 6 |
+
SQLAlchemy
|
proyecto_tinka_ml/proyecto_tinka_ml/utils.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Funciones auxiliares y utilidades comunes."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
from typing import List
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from math import sqrt
|
| 6 |
+
import json, os
|
| 7 |
+
|
| 8 |
+
def ensure_dirs(path: Path) -> None:
|
| 9 |
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
| 10 |
+
|
| 11 |
+
def parse_numbers(s: str) -> List[int]:
|
| 12 |
+
nums = []
|
| 13 |
+
for token in s.replace(',', ' ').split():
|
| 14 |
+
token = token.strip()
|
| 15 |
+
if token.isdigit():
|
| 16 |
+
nums.append(int(token))
|
| 17 |
+
return nums
|
| 18 |
+
|
| 19 |
+
def is_prime(n: int) -> bool:
|
| 20 |
+
if n < 2: return False
|
| 21 |
+
if n in (2,3): return True
|
| 22 |
+
if n % 2 == 0: return False
|
| 23 |
+
r = int(sqrt(n)); f = 3
|
| 24 |
+
while f <= r:
|
| 25 |
+
if n % f == 0: return False
|
| 26 |
+
f += 2
|
| 27 |
+
return True
|
| 28 |
+
|
| 29 |
+
def ascii_bar(label: str, value: int, max_value: int, width: int = 40) -> str:
|
| 30 |
+
if max_value <= 0:
|
| 31 |
+
bar = ""
|
| 32 |
+
else:
|
| 33 |
+
fill = int((value / max_value) * width)
|
| 34 |
+
bar = "█" * fill + "·" * (width - fill)
|
| 35 |
+
return f"{label:>2}: {bar} {value}"
|
| 36 |
+
|
| 37 |
+
def save_json(path: Path, data) -> None:
|
| 38 |
+
ensure_dirs(path)
|
| 39 |
+
tmp = str(path) + ".tmp"
|
| 40 |
+
with open(tmp, "w", encoding="utf-8") as f:
|
| 41 |
+
json.dump(data, f, ensure_ascii=False, indent=2, default=str)
|
| 42 |
+
os.replace(tmp, path)
|
| 43 |
+
|
| 44 |
+
def load_json(path: Path, default=None):
|
| 45 |
+
p = Path(path)
|
| 46 |
+
if not p.exists():
|
| 47 |
+
return default
|
| 48 |
+
try:
|
| 49 |
+
txt = p.read_text(encoding="utf-8").strip()
|
| 50 |
+
if not txt:
|
| 51 |
+
return default
|
| 52 |
+
return json.loads(txt)
|
| 53 |
+
except Exception:
|
| 54 |
+
return default
|
proyecto_tinka_ml/proyecto_tinka_ml/visualizador.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Visualizaciones básicas: ASCII y PNG/HTML."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
from typing import Dict
|
| 4 |
+
import base64
|
| 5 |
+
from io import BytesIO
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
+
from utils import ascii_bar
|
| 8 |
+
|
| 9 |
+
def render_ascii_hist(freq_abs: Dict[int,int]) -> str:
|
| 10 |
+
maxv = max(freq_abs.values()) if freq_abs else 0
|
| 11 |
+
lines = []
|
| 12 |
+
for n, v in sorted(freq_abs.items()):
|
| 13 |
+
lines.append(ascii_bar(f"{n:02d}", v, maxv, width=34))
|
| 14 |
+
return "\n".join(lines)
|
| 15 |
+
|
| 16 |
+
def plot_freq_png(freq_abs: Dict[int,int]) -> bytes:
|
| 17 |
+
xs = list(sorted(freq_abs.keys()))
|
| 18 |
+
ys = [freq_abs[k] for k in xs]
|
| 19 |
+
fig, ax = plt.subplots(figsize=(10,4))
|
| 20 |
+
ax.bar(xs, ys)
|
| 21 |
+
ax.set_title("Frecuencia absoluta de números (1-45)")
|
| 22 |
+
ax.set_xlabel("Número")
|
| 23 |
+
ax.set_ylabel("Frecuencia")
|
| 24 |
+
buf = BytesIO()
|
| 25 |
+
fig.tight_layout()
|
| 26 |
+
fig.savefig(buf, format="png", dpi=150)
|
| 27 |
+
plt.close(fig)
|
| 28 |
+
return buf.getvalue()
|
| 29 |
+
|
| 30 |
+
def html_report(stats: Dict) -> str:
|
| 31 |
+
png = plot_freq_png(stats['frecuencias']['freq_abs'])
|
| 32 |
+
b64 = base64.b64encode(png).decode("ascii")
|
| 33 |
+
chi2 = stats['chi_cuadrado']
|
| 34 |
+
html = f"""<!DOCTYPE html>
|
| 35 |
+
<html lang="es">
|
| 36 |
+
<head>
|
| 37 |
+
<meta charset="utf-8"/>
|
| 38 |
+
<title>Reporte Tinka</title>
|
| 39 |
+
<style>
|
| 40 |
+
body {{ font-family: Arial, Helvetica, sans-serif; margin: 20px; }}
|
| 41 |
+
h1, h2 {{ margin: 0.4em 0; }}
|
| 42 |
+
pre {{ background: #f6f8fa; padding: 12px; overflow: auto; }}
|
| 43 |
+
.small {{ color:#555; font-size: 0.9em; }}
|
| 44 |
+
</style>
|
| 45 |
+
</head>
|
| 46 |
+
<body>
|
| 47 |
+
<h1>Reporte de Análisis — Tinka</h1>
|
| 48 |
+
<p class="small">Este reporte se genera a partir de los resultados históricos presentes en tu base de datos.</p>
|
| 49 |
+
|
| 50 |
+
<h2>Frecuencias</h2>
|
| 51 |
+
<img src="data:image/png;base64,{b64}" alt="Frecuencias"/>
|
| 52 |
+
<pre>{render_ascii_hist(stats['frecuencias']['freq_abs'])}</pre>
|
| 53 |
+
|
| 54 |
+
<h2>Chi-cuadrado</h2>
|
| 55 |
+
<p>Chi2 = {chi2.get('chi2')} p-value = {chi2.get('p_value')}</p>
|
| 56 |
+
<p class="small">Si p-value es muy pequeño (< 0.05), indica desviación respecto a uniformidad; recuerda que los sorteos son procesos aleatorios.</p>
|
| 57 |
+
|
| 58 |
+
<h2>Disclaimer</h2>
|
| 59 |
+
<p class="small">Este sistema es informativo y educativo. La lotería es aleatoria. Juega con responsabilidad.</p>
|
| 60 |
+
</body>
|
| 61 |
+
</html>
|
| 62 |
+
"""
|
| 63 |
+
return html
|