Visão Geral do Projeto
O FoodSmart Kitchen é uma plataforma integrada de receitas e compras inteligentes, desenvolvida com Python 3.11 e Django 2.0.7. O sistema cria um ciclo completo de "compras → receitas → saúde", oferecendo uma solução "o que comprar – como preparar – comer bem".
Informações do Projeto
- Curso: Recolha e Fusão de Dados
- Grupo: Sempre Unido
- Repositório: https://gitee.com/ding41/buy-menu
Arquitetura do Sistema
O sistema adota a arquitetura B/S com o seguinte fluxo fechado:
- Recolha de dados multi-fonte
- Análise e fusão de dados
- Algoritmo de recomendação
- Apresentação Web
- Interação do utilizador
- Feedback comportamental
Stack Tecnológica
- Backend: Django 2.0.7, Python 3.11
- Base de dados: SQLite
- Frontend: HTML, CSS, JavaScript, jQuery
- Outros: Pillow (processamento de imagens), django-haystack (pesquisa)
Funcionalidades Principais
Administração
- Gestão completa do ciclo de vida dos produtos
- Gestão de receitas e associação a ingredientes
- Visualização de dados comportamentais dos utilizadores
Funcionalidades para o Utilizador
Navegação e Carrinho de Compras
- Navegação por categorias e pesquisa por palavras-chave
- Seleção de porção e modo de preparo
- Atualização de preços em tempo real no carrinho
Recomendação Bidirecional de Receitas
- Produto → Receita: com base em etiquetas associadas aos ingredientes
- Receita → Produto: com base nos ingredientes necessários na receita
- Suporte a "adicionar tudo ao carrinho"
Análise Inteligente com IA
- Análise da combinação nutricional dos ingredientes no carrinho
- Preferências alimentares (baixo açúcar, baixo sal, etc.)
- Assistente IA para sugestões de preparo e culinária
Implementação Detalhdaa
1. Sistema de Utilizadores (Backend)
Utilizamos o sistema de autenticação do Django, complementado por um decorador personalizado para proteção de páginas.
Registo
def registar_utilizador(request):
nome = request.POST.get('user_name')
senha = request.POST.get('pwd')
confirmar = request.POST.get('confirm_pwd')
email = request.POST.get('email')
if senha != confirmar:
return redirect('/user/register/')
from django.contrib.auth.hashers import make_password
senha_cripto = make_password(senha)
from .models import UserProfile
UserProfile.objects.create(
username=nome,
password=senha_cripto,
email=email
)
contexto = {
'title': 'Login',
'username': nome,
}
return render(request, 'auth/login.html', contexto)
def verificar_nome(request):
nome = request.GET.get('uname')
existe = UserProfile.objects.filter(username=nome).exists()
return JsonResponse({'count': 1 if existe else 0})
Login
from django.contrib.auth import authenticate, login
def processar_login(request):
nome = request.POST.get('username')
senha = request.POST.get('pwd')
lembrar = request.POST.get('jizhu', 0)
utilizador = authenticate(username=nome, password=senha)
if utilizador is not None:
login(request, utilizador)
url = request.COOKIES.get('url', '/')
resposta = redirect(url)
if lembrar:
resposta.set_cookie('username', nome)
else:
resposta.set_cookie('username', '', max_age=-1)
request.session['user_id'] = utilizador.id
request.session['user_name'] = nome
return resposta
else:
contexto = {
'title': 'Login',
'error_name': 0,
'error_pwd': 1,
'nome': nome,
}
return render(request, 'auth/login.html', contexto)
Decorador de Login
from django.shortcuts import redirect
def requer_login(view_func):
def wrapper(request, *args, **kwargs):
if request.session.get('user_id'):
return view_func(request, *args, **kwargs)
else:
red = redirect('/user/login/')
red.set_cookie('url', request.get_full_path())
return red
return wrapper
2. Análise Inteligente de Ingredientes com IA
Integração com a API Deepseek para analisar a combinação nutricional dos itens no carrinho.
Classe de Análise
import requests, logging
logger = logging.getLogger(__name__)
class AnalisadorAlimentos:
def __init__(self, chave_api):
self.chave = chave_api
self.url = "https://api.deepseek.com/v1/chat/completions"
self.headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {chave_api}"
}
def _chamar_api(self, mensagens, modelo="deepseek-chat"):
try:
dados = {
"model": modelo,
"messages": mensagens,
"temperature": 0.7,
"max_tokens": 2000
}
resp = requests.post(self.url, headers=self.headers, json=dados, timeout=30)
resp.raise_for_status()
resultado = resp.json()
return resultado["choices"][0]["message"]["content"]
except requests.exceptions.RequestException as e:
logger.error(f"Falha na API: {e}")
return "O serviço de IA está temporariamente indisponível."
except Exception as e:
logger.error(f"Erro inesperado: {e}")
return "Ocorreu um erro durante a análise."
def analisar_combinacao(self, itens_carrinho):
if not itens_carrinho:
return {"sucesso": False, "mensagem": "Carrinho vazio", "analise": None}
lista_alimentos = []
for item in itens_carrinho:
nome = item.get('goods_name', '')
qtd = item.get('count', 1)
cat = item.get('category', '')
if nome:
lista_alimentos.append(f"{nome} ({qtd} un.) – {cat}")
texto_alimentos = "\n".join(lista_alimentos)
prompt = f"""
Como nutricionista e especialista em segurança alimentar, analise os seguintes ingredientes no carrinho:
{texto_alimentos}
Dimensões a analisar:
1. Composição nutricional (macronutrientes, micronutrientes)
2. Segurança alimentar (interações, armazenamento)
3. Público-alvo adequado/inadequado
4. Recomendações de saúde e sugestões de receitas
"""
mensagens = [
{"role": "system", "content": "És nutricionista especializado em combinação de alimentos."},
{"role": "user", "content": prompt}
]
analise = self._chamar_api(mensagens)
return {
"sucesso": True,
"mensagem": "Análise concluída",
"analise": analise,
"qtd_itens": len(itens_carrinho),
"alimentos": lista_alimentos
}
Configuração e Modo de Depuração
# config.py
CONFIG_IA = {
'max_itens': 20,
'temperature': 0.7,
'max_tokens': 2000,
}
MODO_DEBUG = False # True usa analisador simulado
def obter_analisador():
if MODO_DEBUG or not CHAVE_API or CHAVE_API == "sk-placeholder":
return AnalisadorSimulado()
return AnalisadorAlimentos(CHAVE_API)
3. Associação entre Receitas e Ingredientes
Modelos de dados que ligam receitas a produtos (ingredientes) e vice-versa.
Modelos
from django.db import models
class Receita(models.Model):
titulo = models.CharField(max_length=100, verbose_name="Nome da Receita")
imagem = models.CharField(max_length=100, blank=True, null=True)
video = models.CharField(max_length=200, blank=True, null=True)
descricao = models.CharField(max_length=500, blank=True, null=True)
ingredientes_texto = models.CharField(max_length=500, blank=True, null=True) # ingredientes
quantidades = models.CharField(max_length=500, blank=True, null=True) # quantidades
passos_texto = models.TextField(blank=True, null=True)
passos_imagens = models.TextField(blank=True, null=True)
classificacao = models.FloatField(default=0)
class Meta:
db_table = 'receitas'
class Produto(models.Model):
nome = models.CharField(max_length=20, verbose_name="Nome do Produto", unique=True)
imagem = models.ImageField(upload_to='produtos/%Y/%m', blank=True)
preco = models.DecimalField(max_digits=5, decimal_places=2)
unidade = models.CharField(max_length=20, default='500g')
cliques = models.IntegerField(default=0)
descricao_curta = models.CharField(max_length=200)
stock = models.IntegerField(default=0)
descricao = models.TextField(max_length=200)
categoria = models.ForeignKey('Categoria', on_delete=models.CASCADE)
class ReceitaProduto(models.Model):
receita = models.ForeignKey(Receita, on_delete=models.CASCADE, related_name='ingredientes')
produto = models.ForeignKey(Produto, on_delete=models.CASCADE, related_name='receitas')
quantidade_usada = models.CharField(max_length=50, blank=True, null=True)
class Meta:
db_table = 'receita_produto'
unique_together = ('receita', 'produto')
Vistas da API
from django.shortcuts import get_object_or_404
from django.http import JsonResponse
from .models import Receita, ReceitaProduto
def detalhe_receita(request, receita_id):
receita = get_object_or_404(Receita, pk=receita_id)
associacoes = ReceitaProduto.objects.filter(receita_id=receita_id).select_related('produto')
produtos = []
for assoc in associacoes:
p = assoc.produto
produtos.append({
'id': p.id,
'nome': p.nome,
'preco': str(p.preco),
'imagem': str(p.imagem) if p.imagem else '',
'unidade': p.unidade,
'stock': p.stock,
'quantidade': assoc.quantidade_usada or '',
})
passos = []
if receita.passos_texto:
textos = receita.passos_texto.split('#')
imagens = (receita.passos_imagens or '').split('#')
for i, txt in enumerate(textos):
if txt.strip():
passos.append({
'texto': txt.strip(),
'imagem': imagens[i] if i < len(imagens) else ''
})
return JsonResponse({
'sucesso': True,
'receita': {
'id': receita.id,
'titulo': receita.titulo,
'imagem': receita.imagem or '',
'descricao': receita.descricao or '',
'video': receita.video or '',
'classificacao': receita.classificacao,
'ingredientes': [] , # omitido para brevidade
'passos': passos,
},
'produtos': produtos,
})
4. Personalização – Preferências de Saúde
Interfaec que permite ao utilizador definir preferências (ex: baixo açúcar, baixo colesterol) que influenciam as recomendações da IA.
Modelo de Utilizador Estendido
class UserProfile(models.Model):
username = models.CharField(max_length=20, unique=True)
password = models.CharField(max_length=40)
email = models.EmailField(unique=True)
morada = models.CharField(max_length=100, default='')
telefone = models.CharField(max_length=11, default='')
preferencia_saude = models.CharField(max_length=255, default='', blank=True)
class Meta:
verbose_name = "Perfil do Utilizador"
class HistoricoVisualizacao(models.Model):
utilizador = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
produto = models.ForeignKey(Produto, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
Vista para Guardar Preferências
from django.http import JsonResponse
import json
@requer_login
def guardar_preferencia(request):
if request.method == 'POST':
dados = json.loads(request.body) if request.content_type == 'application/json' else request.POST
preferencia = dados.get('preferences', '')
utilizador = UserProfile.objects.get(id=request.session['user_id'])
utilizador.preferencia_saude = preferencia
utilizador.save()
return JsonResponse({'sucesso': True, 'mensagem': 'Preferência guardada'})
return JsonResponse({'sucesso': False, 'mensagem': 'Método não permitido'})
HTML do Diálogo de Preferências (simplificado)
<div id="modal_preferencias" class="modal" style="display:none;">
<div class="modal-conteudo">
<h3>🥗 Definições de Alimentação Personalizada</h3>
<p>Sobre o que se preocupa? A IA irá focar-se nesses aspetos.</p>
<div class="opcoes">
<span class="tag" data-valor="baixo_acucar">🍬 Baixo Açúcar</span>
<span class="tag" data-valor="baixo_colesterol">🥩 Baixo Colesterol</span>
<span class="tag" data-valor="baixo_sal">🧂 Baixo Sal</span>
<span class="tag" data-valor="alto_proteina">💪 Alta Proteína</span>
<span class="tag" data-valor="baixas_calorias">🥗 Baixas Calorias</span>
<span class="tag" data-valor="vegetariano">🥬 Vegetariano</span>
</div>
<input type="hidden" id="preferencias_selecionadas" value="">
<button onclick="salvarPreferencias()">Confirmar</button>
<a href="javascript:fecharModal()">Agora não</a>
</div>
</div>
<script>
$(function(){
var prefAtual = "{{ user_profile.preferencia_saude|default:'' }}";
if (prefAtual === "") {
setTimeout(function(){ $('#modal_preferencias').fadeIn(); }, 500);
}
$('.tag').click(function(){
$(this).toggleClass('ativa');
atualizarSelecao();
});
});
function atualizarSelecao() {
var selecionadas = [];
$('.tag.ativa').each(function(){
selecionadas.push($(this).data('valor'));
});
$('#preferencias_selecionadas').val(selecionadas.join(','));
}
function salvarPreferencias() {
var prefs = $('#preferencias_selecionadas').val();
if (!prefs) { alert("Seleciona pelo menos uma opção."); return; }
$.ajax({
url: '/user/guardar_preferencia/',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ preferences: prefs }),
success: function(res) {
if (res.sucesso) {
alert("✅ Preferências guardadas!");
fecharModal();
setTimeout(function(){ location.reload(); }, 500);
} else {
alert("Erro: " + res.mensagem);
}
}
});
}
</script>
Implantação no Servidor Cloud (Huawei)
O projeto foi implantado num servidor Ubuntu na nuvem Huawei. Os passos principais:
- Conexão ao servidor via SSH
- Instalação do painel BT (Webmin-like) para gestão simplificada
- Instalação do Python 3.11 via PPA (deadsnakes)
- Criação de ambiente virtual com virtualenv
- Upload dos ficheiros do projeto (ex: via SCP ou git)
- Alteração do ficheiro
settings.pypara permitir acesso de qualquer IP (ALLOWED_HOSTS = ['*']) - Instalação das dependências com
pip install -r requirements.txt - Início do servidor:
nohup python manage.py runserver 0.0.0.0:8080 &
Por questões de segurança, foi necessário desativar a firewall do servidor (ou abrir a porta 8080) no painel de controlo da nuvem.
Reflexões Pessoais
Ao desenvolver este projeto, aprofundei o conhecimento prático do Django, especialmente na gestão de sessões, autenticação e criação de decoradores personalizados. A integração da API do Deepseek ensinou-me a lidar com chamadas externas e a implementar uma lógica de fallback robusta. O trabalho em equipa foi crucial – a definição clara de interfaces entre frontend e backend, e a coordenação nas bases de dados, mostraram a importância da comunicação e da responsabilidade individual no sucesso coletivo.