From 36fc77f98e0e19ddf237694cc5820ddf9d4a1ecf Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 18 Feb 2026 01:25:04 +0000 Subject: [PATCH] =?UTF-8?q?CONFIGURA=C3=87=C3=95ES=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/__pycache__/models.cpython-311.pyc | Bin 209 -> 3224 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 773 bytes core/__pycache__/views.cpython-311.pyc | Bin 9022 -> 11862 bytes core/migrations/0001_initial.py | 49 ++++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 2645 bytes core/models.py | 43 +++- core/templates/core/admin_dashboard.html | 21 ++ core/templates/core/admin_login.html | 25 ++ core/templates/core/edit_lottery.html | 70 ++++++ core/templates/core/index.html | 21 +- core/urls.py | 9 +- core/views.py | 229 ++++++++---------- 12 files changed, 334 insertions(+), 133 deletions(-) create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/templates/core/admin_dashboard.html create mode 100644 core/templates/core/admin_login.html create mode 100644 core/templates/core/edit_lottery.html diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 158fc718fd5eeff6e9d276a208cf2a0fe544a224..9668fb5282adcf93f8de66c1dabda5a2fde5050a 100644 GIT binary patch literal 3224 zcmaJ@O>7fa5Z<-d|Bn9%32G^<(vaW+wxATD{*Xoqr7ht8BvQeb)#CT;Y*_Dxx4S^1 z9(?E_haPgE;!sthRmG2>QVy+zda9HQhaT)|rCRUW6E`C`acO6E?Idql@^hT)5JpUG$+X4}Ys0($mEQ-hn^guZf5Y7oC_>Dj$ zDlP_l8}9=5aC26cJgpFDgjQ z2$S0a70MzJ$^jylg8_j`i#<8`K!P;980l?(Bn?;|_4H#sISD=r-4($%xcpI$lkhh} zIYA;|Rg@>DNnJChD-~*4&M8otW_5Li8mg?yl?C-Wm6w=yT_vi#@w=f_RC!5dAVYOi zR!x{{2Gt8s|Anpd`5=$i)tib=jhel{!;Ya{b107xT2&q02Je%qW1EU$-cVFRDw{^Z z<^Wvy0-lcmfmbJfEnqPG@xAZ^1~E`tNuQ_7a)nXVrbIRk8J-#MP8HRDAaco~dey=w z%P$MX1+z|z&K1Y7og%$SD{wk1-Z0r!YYBWPR!l~V{tGEAE%S(?ST<9Xl|9>{Qs^#X zOsyP{hR{x*xHJ6o!Djjd{-Yw+ZN$wUT0hy$ zo^EANyV=u?Pdkzrdb=a+B>t&*;O<%c1SI`gT!YO0Nkm-H&?2DLACSuuksw1M6x+}j z@wB3wTH!~LfgFzYYup9Zhdp(n9Dk)W;Yl%u-XKx1Aa)nh?;)n3H_AgK4j4(11SDOO zM=qGQP1&;Z4oGKAqpH;$rf%HZ_>EdJwU%gQcw8cDLNrTB4;U3oBK&4?I zh_yX`r^o+FkGIkjZhE2_o$#`+lut7RBin-nRoPx3eCWbw^YdrFysXS${`~BmhcSi~ zg-ZxOp8UkHX^nOf7l+cP7*72PWq#r6@@fBWDNw5jQ*ZFtJiTfAD9xyb;pjRg-Hn!q zMD{vbumwhRfFZ#=vwjA$9gI^=X{r+l?+tf^9%)?cq=e-D)l+Wb^*e0+gD1nyM5&c1 zxrtI^ww)bEbdKNuqM0qWvPCyrY<${VY-K0h>;%k5@@rLhB;Oi2;*K0?eAWpI`wp*N zcSk4J1CPg>qf@QXDR&f(9!6YGBCZFC?*N*;JFfo#Nk6UuOWV2=4a01+H<7@j-~h4* zx>JA7c(|1o6L5$(2Bb;vEKDD7cy$AEcw1*ndHoo=8;K0yIZdK~=M0HKUdDNR7T)QX z)Pk0@GU?w{Vp=kE6%8;9oMkebYQO-NXH@N`THql}1Pj4t8VYo9YC|?Ls$(PaGMt6M zE&vqz-xt1KmwUbsB>mwdff^J=5(KlPm+39D3Kyx3r62;pMqKa&p5D#4mH07lM&RgD zJ~RLWp7y&gA9Kd*|WZ7=%i;Ek6}ypD?)7ZZTOWY(ybm0gSBgQz&^r|$Z%Qr zfNYDqbi64YZ>J8fR^8OWJCXHMzn*D+{#7$|sg=6qrY<$kwR4BCjUQ^|Cf(d5^zMwj z7IrgoD>LS1#u}frQwMPGf%|VZQ-xNl;HCLqT&5IQdorVk)331;?~h;xc*{Gn8}Gy-gc7J>bLqcTz|18Je(BK+uiBf Q$gTa)JlTu?(eR@3AJN+Pvj6}9 literal 209 zcmZ3^%ge<81ZVzC$+QI0k3k$5V1hC}3jrC^8B!Qh7;_kM8KW2(8B&;n88n$+f)r>n z-eSwmPf5)w_S0m##hH?on3tZfmy%S(43u2S@EN4$m!5t`er~FMX;NukNvVEnMQUL1iU_iikopElxEBU^uw7LNGh%9SU1A zGcC`cr82m`!g1z==Dy>Hdw68X<;j7=EG(>atUHyS)e5P>j%>dx>XCEL@f>r{piIFX5&J6X*E&SnsZYj)^ delta 223 zcmZo=yUnD&oR^o20SM0gnUa|Wq#uJgFu)3Be72dWu9}>}x`u5TGXukFAclZ+#weB) z_Fx80j+Y<-O~zX+1&JjYFBzenjQrfxTMR`YV?c6wiMgqMnyk0DQ?e5C()0C7i*kyK zK!)97Ez3+TFD_!8{GBm?iw`IQGNIUOav+nG#RCTM3#jM=8v`q6gG+}{N5~Ae3oP;% WS>&&<$bVpF;%92$2Eig8pmqSI)HOZ; diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index fd109e1b8d09e4dae700c6e2063943a39673037e..8923acc41527ed2ce6b1229599656daa191c8b96 100644 GIT binary patch literal 11862 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 HQ2T!YV48W| literal 9022 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 K4&FfZl=%-&!^(94 literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 71a8362..4b08aec 100644 --- a/core/models.py +++ b/core/models.py @@ -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}" diff --git a/core/templates/core/admin_dashboard.html b/core/templates/core/admin_dashboard.html new file mode 100644 index 0000000..1465dd6 --- /dev/null +++ b/core/templates/core/admin_dashboard.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% block content %} +
+ +
+ {% for lottery in loterias %} +
+
+

{{ lottery.get_name_display }}

+

Números: 1 a {{ lottery.max_number }}

+
+ Editar Números +
+
+ {% endfor %} +
+
+{% endblock %} diff --git a/core/templates/core/admin_login.html b/core/templates/core/admin_login.html new file mode 100644 index 0000000..1730ba4 --- /dev/null +++ b/core/templates/core/admin_login.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% block content %} +
+
+
+
+

Acesso Administrativo

+

Insira sua Private Key para acessar as configurações.

+
+ {% csrf_token %} +
+ +
+ +
+ {% if messages %} + {% for message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} +
+
+
+
+{% endblock %} diff --git a/core/templates/core/edit_lottery.html b/core/templates/core/edit_lottery.html new file mode 100644 index 0000000..73147b0 --- /dev/null +++ b/core/templates/core/edit_lottery.html @@ -0,0 +1,70 @@ +{% extends "base.html" %} +{% block content %} +
+ + +
+
+

Editor: {{ lottery.get_name_display }}

+

Selecione os números para ANULAR no próximo sorteio.

+
+ +
+ {% csrf_token %} +
+ {% for n in numbers %} +
+ + +
+ {% endfor %} +
+ +
+ +
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 35b56fc..0db060f 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -18,7 +18,11 @@ - + {% if is_admin %} + + {% else %} + + {% endif %} @@ -126,19 +130,22 @@
-
Quentes no recorte
+
Probabilidades Quentes (Verde)
{% 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)