diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 2276652..190cbc2 100644 Binary files a/config/__pycache__/__init__.cpython-311.pyc and b/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 281cd8f..510ba17 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 27d8853..57e1289 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index 05ab9ff..c718f3a 100644 Binary files a/config/__pycache__/wsgi.cpython-311.pyc and b/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/cookies.txt b/cookies.txt new file mode 100644 index 0000000..c31d989 --- /dev/null +++ b/cookies.txt @@ -0,0 +1,4 @@ +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc index 9ecccf5..aae0670 100644 Binary files a/core/__pycache__/__init__.cpython-311.pyc and b/core/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 0071a6c..1f878c9 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/apps.cpython-311.pyc b/core/__pycache__/apps.cpython-311.pyc index 2f054bd..60bbdc2 100644 Binary files a/core/__pycache__/apps.cpython-311.pyc and b/core/__pycache__/apps.cpython-311.pyc differ diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 553b7b9..876926e 100644 Binary files a/core/__pycache__/context_processors.cpython-311.pyc and b/core/__pycache__/context_processors.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc index c041035..521b7cb 100644 Binary files a/core/__pycache__/forms.cpython-311.pyc and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index db9c55c..fcced8f 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index e74af67..a25b933 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 4f8400b..5f9fd13 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/context_processors.py b/core/context_processors.py index 0bf87c3..aa66118 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -1,13 +1,17 @@ import os import time +from django.utils import timezone + + def project_context(request): - """ - Adds project-specific environment variables to the template context globally. - """ + """Adds project-specific environment variables to the template context globally.""" return { - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), + "project_name": os.getenv("PROJECT_NAME", "Loterias BR Pro"), + "project_description": os.getenv("PROJECT_DESCRIPTION", "Gerador inteligente de números para loterias do Brasil com histórico recente e indicadores estatísticos."), "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + "is_admin": bool(request.session.get("admin_auth")), + "current_time": timezone.now(), # Used for cache-busting static assets "deployment_timestamp": int(time.time()), } diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc index 353005b..8331b89 100644 Binary files a/core/migrations/__pycache__/0001_initial.cpython-311.pyc and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0002_lottery_ai_predictions_lottery_analysis_window.cpython-311.pyc b/core/migrations/__pycache__/0002_lottery_ai_predictions_lottery_analysis_window.cpython-311.pyc index fa35d95..d8f312c 100644 Binary files a/core/migrations/__pycache__/0002_lottery_ai_predictions_lottery_analysis_window.cpython-311.pyc and b/core/migrations/__pycache__/0002_lottery_ai_predictions_lottery_analysis_window.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0003_alter_lottery_name.cpython-311.pyc b/core/migrations/__pycache__/0003_alter_lottery_name.cpython-311.pyc index 553580d..f4a99b4 100644 Binary files a/core/migrations/__pycache__/0003_alter_lottery_name.cpython-311.pyc and b/core/migrations/__pycache__/0003_alter_lottery_name.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/__init__.cpython-311.pyc b/core/migrations/__pycache__/__init__.cpython-311.pyc index 8cc8e92..dff4d5e 100644 Binary files a/core/migrations/__pycache__/__init__.cpython-311.pyc and b/core/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/templates/core/index.html b/core/templates/core/index.html index bb29bb6..a2897fa 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -57,8 +57,8 @@
- 5 loterias - Mega-Sena, Quina, Dupla Sena, Lotomania, Lotofacil + {{ lottery_count }} loterias + {{ lottery_overview }}
Analise recencia @@ -81,7 +81,7 @@
{{ form.lottery_type }} -
Baseado no historico recente simulado ate o admin importar dados reais.
+
Baseado no histórico recente já restaurado no banco e nas configurações atuais de cada loteria.
{{ form.lottery_type.errors }}
@@ -258,17 +258,11 @@ document.addEventListener('DOMContentLoaded', function() { let selectedNumbers = new Set(); let currentMax = 60; - const lotteryConfigs = { - 'mega_sena': 60, - 'quina': 80, - 'dupla_sena': 50, - 'lotomania': 100, - 'lotofacil': 25 - }; + const lotteryConfigs = {{ lottery_configs_json|safe }}; function renderGrid() { const type = lotterySelect.value; - currentMax = lotteryConfigs[type] || 60; + currentMax = (lotteryConfigs[type] && lotteryConfigs[type].max_number) || 60; grid.innerHTML = ''; selectedNumbers.clear(); updateUI(); @@ -301,8 +295,9 @@ document.addEventListener('DOMContentLoaded', function() { renderGrid(); // Initial load btnLive.onclick = async () => { - if (selectedNumbers.size < 6) { - alert('Por favor, selecione pelo menos 6 números.'); + const minimumRequired = (lotteryConfigs[lotterySelect.value] && lotteryConfigs[lotterySelect.value].numbers_to_draw) || 6; + if (selectedNumbers.size < minimumRequired) { + alert(`Por favor, selecione pelo menos ${minimumRequired} números para esta loteria.`); return; } diff --git a/core/views.py b/core/views.py index 484c64f..c8ab532 100644 --- a/core/views.py +++ b/core/views.py @@ -1,72 +1,259 @@ -from django.shortcuts import render, get_object_or_404, redirect -from django.http import JsonResponse, HttpResponse -from .models import Lottery, DrawResult, AdminAccess from collections import Counter -import random -from django import get_version -import json import csv +import json +import math +import random + +from django import get_version +from django.http import HttpResponse, JsonResponse +from django.shortcuts import get_object_or_404, redirect, render + +from .forms import LotterySimulatorForm +from .models import AdminAccess, DrawResult, Lottery + + +def _format_int(value): + return f"{int(value):,}".replace(",", ".") + + +def _format_percent(value): + if value <= 0: + return "0%" + if value < 0.000001: + return "< 0,000001%" + return f"{value:.6f}%".replace(".", ",") + + +def _parse_annulled_numbers(lottery): + return [int(number.strip()) for number in lottery.annulled_numbers.split(",") if number.strip().isdigit()] + + +def _lottery_queryset(): + return Lottery.objects.all().order_by("id") + + +def _lottery_choices(): + return [(lottery.name, lottery.get_name_display()) for lottery in _lottery_queryset()] + + +def _get_recent_draws(lottery, draws_to_consider=0): + queryset = DrawResult.objects.filter(lottery=lottery).order_by("-draw_number") + if draws_to_consider: + queryset = queryset[:draws_to_consider] + return [[int(number) for number in draw.numbers.split(",") if number] for draw in queryset] + + +def _rank_numbers(lottery, draw_matrix): + total_draws = len(draw_matrix) + if not total_draws: + ordered = list(range(1, lottery.max_number + 1)) + return ordered, ordered[: lottery.numbers_to_draw], ordered[-lottery.numbers_to_draw :] + + counts = Counter(number for draw in draw_matrix for number in draw) + last_seen = {} + for index, draw in enumerate(draw_matrix): + for number in draw: + last_seen.setdefault(number, index) + + scored = [] + for number in range(1, lottery.max_number + 1): + frequency_score = counts.get(number, 0) / total_draws + delay_score = last_seen.get(number, total_draws) / total_draws + total_score = (frequency_score * 0.7) + (delay_score * 0.3) + scored.append((number, total_score)) + + scored.sort(key=lambda item: (-item[1], item[0])) + ranked_numbers = [number for number, _ in scored] + hot_numbers = ranked_numbers[: lottery.numbers_to_draw] + cold_numbers = [number for number, _ in sorted(scored, key=lambda item: (item[1], item[0]))[: lottery.numbers_to_draw]] + return ranked_numbers, hot_numbers, cold_numbers + + +def _generate_suggestions(lottery, ranked_numbers, games_requested, annulled_numbers): + if not ranked_numbers: + ranked_numbers = list(range(1, lottery.max_number + 1)) + + filtered_pool = [number for number in ranked_numbers if number not in annulled_numbers] + if len(filtered_pool) < lottery.numbers_to_draw: + filtered_pool = ranked_numbers[:] + + pool_size = min(len(filtered_pool), max(lottery.numbers_to_draw * 4, lottery.numbers_to_draw)) + candidate_pool = filtered_pool[:pool_size] + display_total = min(games_requested, 12) if games_requested < 1_000_000_000_000 else 6 + + suggestions = [] + seen = set() + attempt = 0 + while len(suggestions) < display_total and attempt < display_total * 10: + generator = random.Random(f"{lottery.name}:{games_requested}:{attempt}:{','.join(map(str, candidate_pool[:20]))}") + sample = sorted(generator.sample(candidate_pool, k=min(lottery.numbers_to_draw, len(candidate_pool)))) + key = tuple(sample) + if key not in seen and len(sample) == lottery.numbers_to_draw: + seen.add(key) + suggestions.append(sample) + attempt += 1 + + if not suggestions: + suggestions.append(sorted(candidate_pool[: lottery.numbers_to_draw])) + + return suggestions + + +def _build_lottery_cards(lotteries): + tagline_map = { + "mega_sena": "6 dezenas no volante principal", + "quina": "5 dezenas com universo amplo", + "dupla_sena": "duas chances por concurso", + "lotomania": "análise ampla para dezenas recorrentes", + "lotofacil": "equilíbrio para 15 dezenas", + "timemania": "histórico recente com foco estatístico", + "dia_de_sorte": "recorte curto com leitura de frequência", + "federal": "modelo estatístico interno por sequência", + "super_sete": "sequências compactas por coluna", + "maismilionaria": "base principal de 6 dezenas", + } + + cards = [] + for lottery in lotteries: + try: + combinations = math.comb(lottery.max_number, lottery.numbers_to_draw) + odds = f"1 em {_format_int(combinations)}" + except ValueError: + odds = "Configuração personalizada" + + cards.append({ + "label": lottery.get_name_display(), + "tagline": tagline_map.get(lottery.name, "Análise estatística por histórico real"), + "picks": lottery.numbers_to_draw, + "range_max": lottery.max_number, + "odds": odds, + }) + return cards + + +def _build_home_result(form): + if not form.is_valid(): + return None + + lottery = get_object_or_404(Lottery, name=form.cleaned_data["lottery_type"]) + draws_to_consider = int(form.cleaned_data["draws_to_consider"]) + games_requested = int(form.cleaned_data["games_to_generate"]) + draw_matrix = _get_recent_draws(lottery, draws_to_consider) + ranked_numbers, hot_numbers, cold_numbers = _rank_numbers(lottery, draw_matrix) + annulled_numbers = _parse_annulled_numbers(lottery) + reclaimed_numbers = [number for number in hot_numbers if number in annulled_numbers] + total_combinations = math.comb(lottery.max_number, lottery.numbers_to_draw) + probability = 100 / total_combinations if total_combinations else 0 + suggestions = _generate_suggestions(lottery, ranked_numbers, games_requested, annulled_numbers) + + trillion_labels = { + 1000000000000: "1 trilhão", + 10000000000000: "10 trilhões", + 30000000000000: "30 trilhões", + 60000000000000: "60 trilhões", + } + + return { + "lottery": lottery.get_name_display(), + "draws_used": len(draw_matrix), + "total_combinations": _format_int(total_combinations), + "odds": f"1 em {_format_int(total_combinations)}", + "percent": _format_percent(probability), + "hot_numbers": hot_numbers, + "cold_numbers": cold_numbers, + "annulled_numbers": annulled_numbers, + "reclaimed_numbers": reclaimed_numbers, + "suggestions": suggestions, + "is_trillion": games_requested >= 1000000000000, + "trillion_label": trillion_labels.get(games_requested, _format_int(games_requested)), + } + def home(request): - """Página inicial com resumo das estatísticas de IA.""" - loterias = Lottery.objects.all() + """Página inicial com resumo das estatísticas e simulador principal.""" + lotteries = list(_lottery_queryset()) + form = LotterySimulatorForm( + request.POST or None, + lottery_choices=[(lottery.name, lottery.get_name_display()) for lottery in lotteries], + ) + result = _build_home_result(form) if request.method == "POST" else None + stats = [] - for lottery in loterias: - last_draw = DrawResult.objects.filter(lottery=lottery).order_by('-draw_number').first() + for lottery in lotteries: + last_draw = DrawResult.objects.filter(lottery=lottery).order_by("-draw_number").first() stats.append({ - 'name': lottery.get_name_display(), - 'key': lottery.name, - 'last_draw': last_draw.draw_number if last_draw else "N/A", - 'last_numbers': last_draw.numbers.split(',') if last_draw else [], - 'max_number': lottery.max_number + "name": lottery.get_name_display(), + "key": lottery.name, + "last_draw": last_draw.draw_number if last_draw else "N/A", + "last_numbers": last_draw.numbers.split(",") if last_draw else [], + "max_number": lottery.max_number, + "numbers_to_draw": lottery.numbers_to_draw, }) - return render(request, "core/index.html", {"stats": stats, "django_version": get_version()}) + + overview_names = [lottery.get_name_display() for lottery in lotteries[:4]] + lottery_configs = { + lottery.name: { + "max_number": lottery.max_number, + "numbers_to_draw": lottery.numbers_to_draw, + } + for lottery in lotteries + } + + context = { + "form": form, + "result": result, + "stats": stats, + "django_version": get_version(), + "lottery_cards": _build_lottery_cards(lotteries), + "lottery_count": len(lotteries), + "lottery_overview": ", ".join(overview_names) + (" e mais" if len(lotteries) > 4 else ""), + "lottery_configs_json": json.dumps(lottery_configs), + } + return render(request, "core/index.html", context) + def lottery_info_api(request, lottery_key): - """ - Supercomputador de Elite 99.9% - Sincronizado com 10.000 concursos. - Calcula dezenas de elite baseadas em convergência matemática de alto desempenho. - """ + """Retorna indicadores estatísticos da loteria selecionada para o gerador sequencial.""" lottery = get_object_or_404(Lottery, name=lottery_key) - annulled = [int(n) for n in lottery.annulled_numbers.split(',') if n] - - draws_db = DrawResult.objects.filter(lottery=lottery).order_by('draw_number') - real_draws = [[int(n) for n in d.numbers.split(',')] for d in draws_db] + annulled = _parse_annulled_numbers(lottery) + + draws_db = DrawResult.objects.filter(lottery=lottery).order_by("draw_number") + real_draws = [[int(n) for n in draw.numbers.split(",")] for draw in draws_db] last_real_num = draws_db.last().draw_number if draws_db.exists() else 0 - + random.seed(f"{lottery_key}_supercomputer") all_draws = list(real_draws) max_num = lottery.max_number n_draw = lottery.numbers_to_draw - + while len(all_draws) < 10000: - hist_flat = [n for d in all_draws[-50:] for n in d] + hist_flat = [number for draw in all_draws[-50:] for number in draw] counts = Counter(hist_flat) candidates = [] - for n in range(1, max_num + 1): - score = 100 - (counts.get(n, 0) * 2) - candidates.append((n, score + random.randint(1, 10))) - candidates.sort(key=lambda x: x[1], reverse=True) - all_draws.append(sorted([c[0] for c in candidates[:n_draw]])) + for number in range(1, max_num + 1): + score = 100 - (counts.get(number, 0) * 2) + candidates.append((number, score + random.randint(1, 10))) + candidates.sort(key=lambda item: item[1], reverse=True) + all_draws.append(sorted(candidate[0] for candidate in candidates[:n_draw])) - global_counts = Counter(n for d in all_draws for n in d) + global_counts = Counter(number for draw in all_draws for number in draw) last_seen_real = {} - if real_draws: - for i, d in enumerate(reversed(real_draws)): - for n in d: - if n not in last_seen_real: last_seen_real[n] = i - - elite_candidates = [] - for n in range(1, max_num + 1): - if n in annulled: continue - freq_score = (global_counts.get(n, 0) / 10000.0) * 100 - delay_score = last_seen_real.get(n, len(real_draws)) * 1.5 - total_score = (freq_score * 0.4) + (delay_score * 0.6) - elite_candidates.append({'num': n, 'score': total_score}) + for index, draw in enumerate(reversed(real_draws)): + for number in draw: + last_seen_real.setdefault(number, index) - elite_candidates.sort(key=lambda x: x['score'], reverse=True) - elite_greens = [c['num'] for c in elite_candidates[:20]] - next_prediction = sorted([c['num'] for c in elite_candidates[:n_draw]]) + elite_candidates = [] + for number in range(1, max_num + 1): + if number in annulled: + continue + freq_score = (global_counts.get(number, 0) / 10000.0) * 100 + delay_score = last_seen_real.get(number, len(real_draws)) * 1.5 + total_score = (freq_score * 0.4) + (delay_score * 0.6) + elite_candidates.append({"num": number, "score": total_score}) + + elite_candidates.sort(key=lambda item: item["score"], reverse=True) + elite_greens = [candidate["num"] for candidate in elite_candidates[:20]] + next_prediction = sorted(candidate["num"] for candidate in elite_candidates[:n_draw]) return JsonResponse({ "name": lottery.get_name_display(), @@ -74,78 +261,112 @@ def lottery_info_api(request, lottery_key): "numbers_to_draw": lottery.numbers_to_draw, "elite_greens": elite_greens, "annulled_numbers": annulled, - "reclaimed_numbers": [n for n in elite_greens if n in annulled][:5], + "reclaimed_numbers": [number for number in elite_greens if number in annulled][:5], "supercomputer_sync": 10000, "last_real_contest": last_real_num, "next_prediction": next_prediction, - "precision": "99.9%" + "precision": "99.9%", }) + def sequential_generator(request): - loterias = Lottery.objects.all() + loterias = _lottery_queryset() return render(request, "core/sequential_generator.html", {"loterias": loterias}) + def lottery_results(request): - loterias = Lottery.objects.all() + loterias = _lottery_queryset() results_data = [] for lottery in loterias: - all_draws = DrawResult.objects.filter(lottery=lottery).order_by('-draw_number') + all_draws = DrawResult.objects.filter(lottery=lottery).order_by("-draw_number") if all_draws.exists(): current_draw = all_draws.first() - current_numbers = [n.strip().zfill(2) for n in current_draw.numbers.split(',')] + current_numbers = [number.strip().zfill(2) for number in current_draw.numbers.split(",")] results_data.append({ - 'lottery': lottery, - 'last_draw': current_draw, - 'current_numbers': current_numbers, - 'prediction': ["??"] * lottery.numbers_to_draw + "lottery": lottery, + "last_draw": current_draw, + "current_numbers": current_numbers, + "prediction": ["??"] * lottery.numbers_to_draw, }) return render(request, "core/results_ia.html", {"results": results_data}) + def hits_report(request): return render(request, "core/hits_report.html") + def admin_login(request): if request.method == "POST": key = request.POST.get("private_key") if AdminAccess.objects.filter(private_key=key).exists() or key == "admin123": - request.session['admin_auth'] = True - return redirect('admin_dashboard') + request.session["admin_auth"] = True + return redirect("admin_dashboard") return render(request, "core/admin_login.html") + def admin_logout(request): request.session.flush() - return redirect('home') + return redirect("home") + def admin_dashboard(request): - if not request.session.get('admin_auth'): return redirect('admin_login') - loterias = Lottery.objects.all() + if not request.session.get("admin_auth"): + return redirect("admin_login") + loterias = _lottery_queryset() return render(request, "core/admin_dashboard.html", {"loterias": loterias}) + def edit_lottery(request, lottery_id): - if not request.session.get('admin_auth'): return redirect('admin_login') + if not request.session.get("admin_auth"): + return redirect("admin_login") lottery = get_object_or_404(Lottery, id=lottery_id) if request.method == "POST": lottery.annulled_numbers = request.POST.get("annulled_numbers", "") lottery.save() - return redirect('admin_dashboard') + return redirect("admin_dashboard") return render(request, "core/edit_lottery.html", {"lottery": lottery}) + def full_report(request): return render(request, "core/full_report.html") + def download_funnel(request, lottery_id): lottery = get_object_or_404(Lottery, id=lottery_id) - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = f'attachment; filename="funnel_{lottery.name}.csv"' + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = f'attachment; filename="funnel_{lottery.name}.csv"' writer = csv.writer(response) - writer.writerow(['Number', 'Status']) - annulled = lottery.annulled_numbers.split(',') - for n in range(1, lottery.max_number + 1): - writer.writerow([n, 'Annulled' if str(n) in annulled else 'Active']) + writer.writerow(["Number", "Status"]) + annulled = lottery.annulled_numbers.split(",") + for number in range(1, lottery.max_number + 1): + writer.writerow([number, "Annulled" if str(number) in annulled else "Active"]) return response + def ai_auto_predict(request, lottery_id=None): return JsonResponse({"status": "success", "message": "Neural re-calibration complete."}) + def live_math(request): - return JsonResponse({"status": "live", "data": random.sample(range(1, 61), 6)}) + if request.method != "POST": + return JsonResponse({"error": "Método não permitido."}, status=405) + + payload = json.loads(request.body or "{}") + lottery_key = payload.get("lottery") + numbers = [int(number) for number in payload.get("numbers", []) if str(number).isdigit()] + lottery = get_object_or_404(Lottery, name=lottery_key) + + draw_matrix = _get_recent_draws(lottery, 50) + ranked_numbers, hot_numbers, _ = _rank_numbers(lottery, draw_matrix) + top_window = set(ranked_numbers[: max(lottery.numbers_to_draw * 2, lottery.numbers_to_draw)]) + overlap_score = sum(1 for number in numbers if number in top_window) + hot_overlap = sum(1 for number in numbers if number in hot_numbers) + diversity_bonus = 5 if len(set(number % 2 for number in numbers)) > 1 else 0 + score = min(99, 35 + (overlap_score * 8) + (hot_overlap * 10) + diversity_bonus) + + return JsonResponse({ + "status": "success" if score >= 70 else "warning", + "score": score, + "numbers_checked": len(numbers), + "lottery": lottery.get_name_display(), + }) diff --git a/restore_app.py b/restore_app.py index 597b18a..ce553a2 100644 --- a/restore_app.py +++ b/restore_app.py @@ -1,87 +1,107 @@ import os -import django -import requests from datetime import datetime -# Setup Django environment -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +import django +import requests + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") django.setup() -from core.models import Lottery, DrawResult, AdminAccess +from core.models import AdminAccess, DrawResult, Lottery + + +LOTTERIES = [ + {"name": "mega_sena", "api_name": "megasena", "min": 1, "max": 60, "draw": 6}, + {"name": "quina", "api_name": "quina", "min": 1, "max": 80, "draw": 5}, + {"name": "dupla_sena", "api_name": "duplasena", "min": 1, "max": 50, "draw": 6}, + {"name": "lotomania", "api_name": "lotomania", "min": 0, "max": 99, "draw": 50}, + {"name": "lotofacil", "api_name": "lotofacil", "min": 1, "max": 25, "draw": 15}, + {"name": "timemania", "api_name": "timemania", "min": 1, "max": 80, "draw": 7}, + {"name": "dia_de_sorte", "api_name": "diadesorte", "min": 1, "max": 31, "draw": 7}, + {"name": "federal", "api_name": "federal", "min": 0, "max": 99999, "draw": 5}, + {"name": "super_sete", "api_name": "supersete", "min": 0, "max": 9, "draw": 7}, + {"name": "maismilionaria", "api_name": "maismilionaria", "min": 1, "max": 50, "draw": 6}, +] + def restore_lotteries(): - lotteries_data = [ - ('mega_sena', 1, 60, 6), - ('quina', 1, 80, 5), - ('dupla_sena', 1, 50, 6), - ('lotomania', 0, 99, 50), - ('lotofacil', 1, 25, 15), - ('timemania', 1, 80, 7), - ('dia_de_sorte', 1, 31, 7), - ('federal', 0, 99999, 5), - ('super_sete', 0, 9, 7), - ('maismilionaria', 1, 50, 6), - ] - - for name, min_n, max_n, to_draw in lotteries_data: - Lottery.objects.get_or_create( - name=name, + created = 0 + for item in LOTTERIES: + lottery, was_created = Lottery.objects.get_or_create( + name=item["name"], defaults={ - 'min_number': min_n, - 'max_number': max_n, - 'numbers_to_draw': to_draw - } + "min_number": item["min"], + "max_number": item["max"], + "numbers_to_draw": item["draw"], + }, ) - print("Loterias inicializadas.") + if not was_created: + changed = False + for field, value in { + "min_number": item["min"], + "max_number": item["max"], + "numbers_to_draw": item["draw"], + }.items(): + if getattr(lottery, field) != value: + setattr(lottery, field, value) + changed = True + if changed: + lottery.save(update_fields=["min_number", "max_number", "numbers_to_draw"]) + created += int(was_created) + print(f"Loterias prontas. Novas criadas: {created}") + def restore_admin(): - if not AdminAccess.objects.exists(): - admin = AdminAccess.objects.create(private_key='admin123') - print(f"Chave Admin criada: {admin.private_key}") - else: - print(f"Chave Admin existente: {AdminAccess.objects.first().private_key}") + admin, created = AdminAccess.objects.get_or_create(private_key="admin123") + print(f"Chave admin pronta: {admin.private_key} ({'nova' if created else 'existente'})") -def sync_all_latest(): - from core.views import sync_results - sync_results() - print("Sincronização dos últimos resultados concluída.") -def sync_specific(lottery_name, contest_number): - mapping = { - 'mega_sena': 'megasena', - 'lotofacil': 'lotofacil', - } - api_name = mapping.get(lottery_name) - if not api_name: return +def sync_history(draws_per_lottery=40): + session = requests.Session() + inserted = 0 + + for item in LOTTERIES: + lottery = Lottery.objects.get(name=item["name"]) + latest_url = f"https://loteriascaixa-api.herokuapp.com/api/{item['api_name']}/latest" + latest_response = session.get(latest_url, timeout=15) + latest_response.raise_for_status() + latest_data = latest_response.json() + latest_contest = int(latest_data["concurso"]) + + current_inserted = 0 + for contest in range(latest_contest, max(latest_contest - draws_per_lottery, 0), -1): + contest_url = f"https://loteriascaixa-api.herokuapp.com/api/{item['api_name']}/{contest}" + response = session.get(contest_url, timeout=15) + if response.status_code != 200: + continue - lottery = Lottery.objects.get(name=lottery_name) - try: - url = f"https://loteriascaixa-api.herokuapp.com/api/{api_name}/{contest_number}" - response = requests.get(url, timeout=5) - if response.status_code == 200: data = response.json() - draw_number = int(data.get('concurso')) - if not DrawResult.objects.filter(lottery=lottery, draw_number=draw_number).exists(): - date_str = data.get('data') - draw_date = datetime.strptime(date_str, "%d/%m/%Y").date() - numbers_list = data.get('dezenas', []) - numbers_str = ",".join([str(int(n)) for n in numbers_list]) - DrawResult.objects.create( - lottery=lottery, - draw_number=draw_number, - draw_date=draw_date, - numbers=numbers_str - ) - print(f"Sincronizado específico: {lottery_name} Concurso {draw_number}") - except Exception as e: - print(f"Erro ao sincronizar {lottery_name} {contest_number}: {e}") + dezenas = data.get("dezenas") or [] + if not dezenas: + continue -if __name__ == "__main__": + draw_date = datetime.strptime(data["data"], "%d/%m/%Y").date() + numbers_str = ",".join(str(int(number)) for number in dezenas) + + _, created = DrawResult.objects.get_or_create( + lottery=lottery, + draw_number=int(data["concurso"]), + defaults={"draw_date": draw_date, "numbers": numbers_str}, + ) + inserted += int(created) + current_inserted += int(created) + + print(f"{item['name']}: ultimo={latest_contest}, novos={current_inserted}") + + print(f"Total de resultados inseridos agora: {inserted}") + + +def main(): restore_lotteries() restore_admin() - sync_all_latest() - # Concursos solicitados na história - for contest in [2976, 2977]: - sync_specific('mega_sena', contest) - sync_specific('lotofacil', contest) - print("Restauração concluída!") + sync_history(draws_per_lottery=40) + print("Restauração concluída com sucesso.") + + +if __name__ == "__main__": + main()