CONFIGURAÇÕES 2
This commit is contained in:
parent
9f27edfd4b
commit
36fc77f98e
Binary file not shown.
Binary file not shown.
Binary file not shown.
49
core/migrations/0001_initial.py
Normal file
49
core/migrations/0001_initial.py
Normal file
@ -0,0 +1,49 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-18 01:18
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AdminAccess',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('private_key', models.CharField(default=uuid.uuid4, max_length=255, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Lottery',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(choices=[('mega_sena', 'Mega-Sena'), ('quina', 'Quina'), ('dupla_sena', 'Dupla Sena'), ('lotomania', 'Lotomania'), ('lotofacil', 'Lotofácil')], max_length=50, unique=True)),
|
||||
('min_number', models.IntegerField(default=1)),
|
||||
('max_number', models.IntegerField()),
|
||||
('numbers_to_draw', models.IntegerField()),
|
||||
('annulled_numbers', models.TextField(blank=True, default='')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DrawResult',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('draw_number', models.IntegerField()),
|
||||
('draw_date', models.DateField()),
|
||||
('numbers', models.CharField(max_length=255)),
|
||||
('lottery', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='draws', to='core.lottery')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-draw_date'],
|
||||
'unique_together': {('lottery', 'draw_number')},
|
||||
},
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
@ -1,3 +1,44 @@
|
||||
from django.db import models
|
||||
import uuid
|
||||
|
||||
# Create your models here.
|
||||
class AdminAccess(models.Model):
|
||||
"""Armazena a chave privada única para acesso ao painel."""
|
||||
private_key = models.CharField(max_length=255, unique=True, default=uuid.uuid4)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Key created on {self.created_at}"
|
||||
|
||||
class Lottery(models.Model):
|
||||
"""Configurações específicas de cada tipo de loteria."""
|
||||
LOTTERY_TYPES = [
|
||||
('mega_sena', 'Mega-Sena'),
|
||||
('quina', 'Quina'),
|
||||
('dupla_sena', 'Dupla Sena'),
|
||||
('lotomania', 'Lotomania'),
|
||||
('lotofacil', 'Lotofácil'),
|
||||
]
|
||||
name = models.CharField(max_length=50, choices=LOTTERY_TYPES, unique=True)
|
||||
min_number = models.IntegerField(default=1)
|
||||
max_number = models.IntegerField()
|
||||
numbers_to_draw = models.IntegerField()
|
||||
|
||||
# Lista de números anulados manualmente (armazenado como string separada por vírgula)
|
||||
annulled_numbers = models.TextField(default="", blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.get_name_display()
|
||||
|
||||
class DrawResult(models.Model):
|
||||
"""Resultados reais dos sorteios da Caixa."""
|
||||
lottery = models.ForeignKey(Lottery, on_delete=models.CASCADE, related_name='draws')
|
||||
draw_number = models.IntegerField()
|
||||
draw_date = models.DateField()
|
||||
numbers = models.CharField(max_length=255) # Ex: "05,12,34,45,56,59"
|
||||
|
||||
class Meta:
|
||||
unique_together = ('lottery', 'draw_number')
|
||||
ordering = ['-draw_date']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.lottery.name} - Concurso {self.draw_number}"
|
||||
|
||||
21
core/templates/core/admin_dashboard.html
Normal file
21
core/templates/core/admin_dashboard.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-5 pt-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Painel Administrativo</h2>
|
||||
<a href="{% url 'admin_logout' %}" class="btn btn-ghost">Sair</a>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
{% for lottery in loterias %}
|
||||
<div class="col-md-4">
|
||||
<div class="card-soft p-4 h-100">
|
||||
<h3>{{ lottery.get_name_display }}</h3>
|
||||
<p class="text-muted">Números: 1 a {{ lottery.max_number }}</p>
|
||||
<hr>
|
||||
<a href="{% url 'edit_lottery' lottery.id %}" class="btn btn-brand w-100">Editar Números</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
25
core/templates/core/admin_login.html
Normal file
25
core/templates/core/admin_login.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-5 pt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card-glass p-5 text-center">
|
||||
<h2 class="mb-4">Acesso Administrativo</h2>
|
||||
<p class="text-muted">Insira sua Private Key para acessar as configurações.</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<input type="password" name="private_key" class="form-control form-control-lg text-center" placeholder="Chave Privada" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-brand btn-lg w-100">Acessar Painel</button>
|
||||
</form>
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-danger mt-3">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
70
core/templates/core/edit_lottery.html
Normal file
70
core/templates/core/edit_lottery.html
Normal file
@ -0,0 +1,70 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-5 pt-5">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'admin_dashboard' %}">Painel</a></li>
|
||||
<li class="breadcrumb-item active">{{ lottery.get_name_display }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="card-glass p-4 mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Editor: {{ lottery.get_name_display }}</h2>
|
||||
<p class="text-muted">Selecione os números para <strong>ANULAR</strong> no próximo sorteio.</p>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="lottery-grid mb-4">
|
||||
{% for n in numbers %}
|
||||
<div class="form-check number-toggle">
|
||||
<input class="form-check-input d-none" type="checkbox" name="numbers" value="{{ n }}" id="num_{{ n }}" {% if n in annulled %}checked{% endif %}>
|
||||
<label class="number-ball {% if n in annulled %}annulled{% endif %}" for="num_{{ n }}">
|
||||
{{ n|stringformat:"02d" }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-brand btn-lg">Salvar Configurações</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.lottery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
|
||||
gap: 15px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
.number-ball {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #0b1f2a;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border: 2px solid rgba(255,255,255,0.1);
|
||||
font-weight: bold;
|
||||
}
|
||||
.number-ball:hover {
|
||||
background: #0f766e;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.number-toggle input:checked + .number-ball {
|
||||
background: #dc3545 !important; /* Vermelho real para Anulado */
|
||||
border-color: #fff;
|
||||
box-shadow: 0 0 15px rgba(220, 53, 69, 0.5);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -18,7 +18,11 @@
|
||||
<li class="nav-item"><a class="nav-link" href="#simulador">Simulador</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#jogos">Jogos</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="#como-funciona">Como funciona</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/admin/">Admin</a></li>
|
||||
{% if is_admin %}
|
||||
<li class="nav-item"><a class="nav-link text-warning" href="{% url 'admin_dashboard' %}">Painel Admin</a></li>
|
||||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link" href="{% url 'admin_login' %}">Acesso Admin</a></li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-brand" href="#simulador">Gerar jogos</a>
|
||||
</li>
|
||||
@ -126,19 +130,22 @@
|
||||
</div>
|
||||
<div class="number-groups">
|
||||
<div>
|
||||
<div class="group-title">Quentes no recorte</div>
|
||||
<div class="group-title text-success">Probabilidades Quentes (Verde)</div>
|
||||
<div class="badge-grid">
|
||||
{% for number in result.hot_numbers %}
|
||||
<span class="badge badge-hot">{{ number }}</span>
|
||||
<span class="badge" style="background-color: #198754; font-size: 1.1rem;">{{ number|stringformat:"02d" }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="group-title">Frias no recorte</div>
|
||||
<div class="mt-3">
|
||||
<div class="group-title text-danger">Números Anulados (Vermelho)</div>
|
||||
<div class="badge-grid">
|
||||
{% for number in result.cold_numbers %}
|
||||
<span class="badge badge-cold">{{ number }}</span>
|
||||
{% for number in result.annulled_numbers %}
|
||||
<span class="badge" style="background-color: #dc3545; font-size: 1.1rem;">{{ number|stringformat:"02d" }}</span>
|
||||
{% endfor %}
|
||||
{% if not result.annulled_numbers %}
|
||||
<small class="text-muted opacity-50">Nenhum numero anulado pelo admin.</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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/<int:lottery_id>/', views.edit_lottery, name='edit_lottery'),
|
||||
]
|
||||
|
||||
229
core/views.py
229
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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user