{% for number in result.hot_numbers %}
- {{ number }}
+ {{ number|stringformat:"02d" }}
{% endfor %}
-
-
Frias no recorte
+
+
Números Anulados (Vermelho)
- {% for number in result.cold_numbers %}
- {{ number }}
+ {% for number in result.annulled_numbers %}
+ {{ number|stringformat:"02d" }}
{% endfor %}
+ {% if not result.annulled_numbers %}
+ Nenhum numero anulado pelo admin.
+ {% endif %}
diff --git a/core/urls.py b/core/urls.py
index 6299e3d..4f93d0c 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -1,7 +1,10 @@
from django.urls import path
-
-from .views import home
+from . import views
urlpatterns = [
- path("", home, name="home"),
+ path('', views.home, name='home'),
+ path('admin-loto/', views.admin_login, name='admin_login'),
+ path('admin-loto/logout/', views.admin_logout, name='admin_logout'),
+ path('admin-loto/dashboard/', views.admin_dashboard, name='admin_dashboard'),
+ path('admin-loto/edit//', views.edit_lottery, name='edit_lottery'),
]
diff --git a/core/views.py b/core/views.py
index bd09fd5..5e0dbed 100644
--- a/core/views.py
+++ b/core/views.py
@@ -5,121 +5,95 @@ import random
from collections import Counter
from django import get_version as django_version
-from django.shortcuts import render
+from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
+from django.contrib import messages
from .forms import LotterySimulatorForm
+from .models import Lottery, DrawResult, AdminAccess
+def check_admin(request):
+ """Verifica se a chave privada na sessão é válida."""
+ key = request.session.get('admin_key')
+ return AdminAccess.objects.filter(private_key=key).exists()
-LOTTERY_CONFIGS = {
- "mega_sena": {
- "label": "Mega-Sena",
- "range_max": 60,
- "picks": 6,
- "sample_draws": 15,
- "seed": 1982,
- "tagline": "6 dezenas entre 60",
- },
- "quina": {
- "label": "Quina",
- "range_max": 80,
- "picks": 5,
- "sample_draws": 15,
- "seed": 1971,
- "tagline": "5 dezenas entre 80",
- },
- "dupla_sena": {
- "label": "Dupla Sena",
- "range_max": 50,
- "picks": 6,
- "sample_draws": 15,
- "seed": 1990,
- "tagline": "6 dezenas entre 50",
- },
- "lotomania": {
- "label": "Lotomania",
- "range_max": 100,
- "picks": 50,
- "sample_draws": 12,
- "seed": 1999,
- "tagline": "50 dezenas entre 100",
- },
- "lotofacil": {
- "label": "Lotofacil",
- "range_max": 25,
- "picks": 15,
- "sample_draws": 20,
- "seed": 1994,
- "tagline": "15 dezenas entre 25",
- },
-}
+def admin_login(request):
+ """Tela de login simples para a Chave Privada."""
+ if request.method == 'POST':
+ key = request.POST.get('private_key')
+ if AdminAccess.objects.filter(private_key=key).exists():
+ request.session['admin_key'] = key
+ return redirect('admin_dashboard')
+ else:
+ messages.error(request, "Chave Privada Inválida!")
+ return render(request, 'core/admin_login.html')
+def admin_logout(request):
+ request.session.flush()
+ return redirect('home')
-def _generate_sample_draws(config):
- rng = random.Random(config["seed"])
- draws = []
- population = list(range(1, config["range_max"] + 1))
- for _ in range(config["sample_draws"]):
- draws.append(sorted(rng.sample(population, config["picks"])))
- return draws
+def admin_dashboard(request):
+ """Painel principal do administrador."""
+ if not check_admin(request):
+ return redirect('admin_login')
+
+ loterias = Lottery.objects.all()
+ return render(request, 'core/admin_dashboard.html', {'loterias': loterias})
+def edit_lottery(request, lottery_id):
+ """Editor específico para cada jogo (anular números)."""
+ if not check_admin(request):
+ return redirect('admin_login')
+
+ lottery = get_object_or_404(Lottery, id=lottery_id)
+ # Ajuste para Lotomania (0-99 se necessário, mas mantendo 1-100 por padrão)
+ numbers = range(1, lottery.max_number + 1)
+ annulled = [int(n) for n in lottery.annulled_numbers.split(',') if n]
-def _weighted_unique_sample(numbers, weights, picks, rng):
- available = list(zip(numbers, weights))
- selection = []
- for _ in range(picks):
- total_weight = sum(weight for _, weight in available)
- if total_weight <= 0:
- choice = rng.choice(available)
- selection.append(choice[0])
- available.remove(choice)
- continue
- pick = rng.uniform(0, total_weight)
- cumulative = 0
- for index, (number, weight) in enumerate(available):
- cumulative += weight
- if cumulative >= pick:
- selection.append(number)
- del available[index]
- break
- return selection
+ if request.method == 'POST':
+ selected_numbers = request.POST.getlist('numbers')
+ lottery.annulled_numbers = ",".join(selected_numbers)
+ lottery.save()
+ messages.success(request, f"Configurações da {lottery.get_name_display()} salvas!")
+ return redirect('admin_dashboard')
+ return render(request, 'core/edit_lottery.html', {
+ 'lottery': lottery,
+ 'numbers': numbers,
+ 'annulled': annulled
+ })
def _format_odds(total_combinations):
if total_combinations >= 1_000_000_000_000:
return f"1 em {total_combinations:.2e}"
return f"1 em {total_combinations:,}".replace(",", ".")
-
def _format_percent(odds):
percent = 100 / odds
if percent < 0.000001:
return "<0,000001%"
return f"{percent:.6f}%".replace(".", ",")
-
def home(request):
"""Render the landing screen with lottery simulator and insights."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
- lottery_choices = [
- (key, config["label"]) for key, config in LOTTERY_CONFIGS.items()
- ]
+ loterias_db = Lottery.objects.all()
+ lottery_choices = [(l.name, l.get_name_display()) for l in loterias_db]
+
lottery_cards = []
- for key, config in LOTTERY_CONFIGS.items():
- total_combinations = math.comb(config["range_max"], config["picks"])
- lottery_cards.append(
- {
- "key": key,
- "label": config["label"],
- "tagline": config["tagline"],
- "range_max": config["range_max"],
- "picks": config["picks"],
- "odds": _format_odds(total_combinations),
- }
- )
+ for l in loterias_db:
+ total_combinations = math.comb(l.max_number, l.numbers_to_draw)
+ lottery_cards.append({
+ "key": l.name,
+ "label": l.get_name_display(),
+ "tagline": f"{l.numbers_to_draw} dezenas entre {l.max_number}",
+ "range_max": l.max_number,
+ "picks": l.numbers_to_draw,
+ "odds": _format_odds(total_combinations),
+ })
form = LotterySimulatorForm(
request.POST or None,
@@ -131,64 +105,75 @@ def home(request):
lottery_key = form.cleaned_data["lottery_type"]
draws_to_consider = form.cleaned_data["draws_to_consider"]
games_to_generate = form.cleaned_data["games_to_generate"]
- config = LOTTERY_CONFIGS[lottery_key]
- draws = _generate_sample_draws(config)
- draws_to_consider = min(draws_to_consider, len(draws))
- recent_draws = draws[-draws_to_consider:]
- frequency = Counter(
- number for draw in recent_draws for number in draw
- )
- numbers = list(range(1, config["range_max"] + 1))
+
+ lottery_obj = Lottery.objects.get(name=lottery_key)
+ annulled = [int(n) for n in lottery_obj.annulled_numbers.split(',') if n]
+
+ # Busca sorteios reais no banco
+ draws_db = DrawResult.objects.filter(lottery=lottery_obj)[:draws_to_consider]
+ draw_lists = []
+ for d in draws_db:
+ draw_lists.append([int(n) for n in d.numbers.split(',')])
+
+ # Se não houver sorteios reais, usa aleatórios para manter a app funcionando
+ if not draw_lists:
+ rng_mock = random.Random(42)
+ population = list(range(1, lottery_obj.max_number + 1))
+ for _ in range(draws_to_consider):
+ draw_lists.append(rng_mock.sample(population, lottery_obj.numbers_to_draw))
+
+ frequency = Counter(number for draw in draw_lists for number in draw)
+ numbers = [n for n in range(1, lottery_obj.max_number + 1) if n not in annulled]
+
+ # Números Quentes (Verde) e Frios (Vermelho)
+ # Quentes: Maior frequência e não anulados
+ hot_candidates = frequency.most_common(15)
+ hot_numbers = [n for n, c in hot_candidates if n not in annulled][:8]
+
+ # Pesos para geração baseados na frequência
weights = [frequency.get(number, 0) + 1 for number in numbers]
rng = random.Random(f"{lottery_key}-{draws_to_consider}-{games_to_generate}")
suggestions = []
for _ in range(games_to_generate):
- suggestion = _weighted_unique_sample(
- numbers, weights, config["picks"], rng
- )
- suggestions.append(sorted(suggestion))
+ if len(numbers) >= lottery_obj.numbers_to_draw:
+ # Simulação ponderada simples
+ indices = list(range(len(numbers)))
+ ws = [frequency.get(numbers[i], 0) + 1 for i in indices]
+ selected_indices = random.choices(indices, weights=ws, k=lottery_obj.numbers_to_draw)
+ # Garante que não repete números no mesmo jogo
+ game = []
+ temp_indices = indices.copy()
+ for _p in range(lottery_obj.numbers_to_draw):
+ ws_temp = [frequency.get(numbers[i], 0) + 1 for i in temp_indices]
+ idx = random.choices(range(len(temp_indices)), weights=ws_temp, k=1)[0]
+ game.append(numbers[temp_indices[idx]])
+ del temp_indices[idx]
+ suggestions.append(sorted(game))
- total_combinations = math.comb(config["range_max"], config["picks"])
- hot_numbers = [
- number for number, _ in frequency.most_common(8)
- ]
- cold_numbers = [
- number
- for number, _ in sorted(
- frequency.items(), key=lambda item: item[1]
- )
- ]
- missing_numbers = [
- number for number in numbers if number not in frequency
- ]
- cold_numbers = (missing_numbers + cold_numbers)[:8]
+ total_combinations = math.comb(lottery_obj.max_number, lottery_obj.numbers_to_draw)
+
result = {
- "lottery": config["label"],
- "draws_used": draws_to_consider,
+ "lottery": lottery_obj.get_name_display(),
+ "draws_used": len(draw_lists),
"total_combinations": f"{total_combinations:,}".replace(",", "."),
"odds": _format_odds(total_combinations),
"percent": _format_percent(total_combinations),
"suggestions": suggestions,
"hot_numbers": hot_numbers,
- "cold_numbers": cold_numbers,
+ "annulled_numbers": annulled,
}
context = {
"project_name": "LotoPulse",
- "project_description": (
- "Analise loterias brasileiras, gere jogos e acompanhe "
- "probabilidades com base nos sorteios mais recentes."
- ),
+ "project_description": "Análise matemática e editor de probabilidades para Loterias Caixa.",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
- "host_name": host_name,
- "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
- "deployment_timestamp": now.strftime("%Y%m%d%H%M%S"),
"form": form,
"result": result,
"lottery_cards": lottery_cards,
+ "is_admin": check_admin(request),
}
return render(request, "core/index.html", context)