import json from openai import AsyncOpenAI from app.config import settings SYSTEM_PROMPT = """Você é um nutricionista especialista brasileiro que analisa rótulos de alimentos. Responda SEMPRE em JSON válido com esta estrutura exata: { "score": , "summary": "", "positives": ["", ...], "negatives": ["", ...], "nutrition": { "calorias": "", "gordura_total": "", "gordura_saturada": "", "acucar": "", "sodio": "", "fibras": "", "proteinas": "", "carboidratos": "" }, "nutrition_verdict": "", "ingredients": [ { "name": "", "popular_name": "", "explanation": "", "classification": "", "reason": "" } ], "recipe": { "title": "", "description": "", "prep_time": "", "calories": "", "ingredients_list": ["", ...], "steps": ["", ...], "tip": "" }, "substitutions": [ { "name": "", "brand": "", "reason": "", "estimated_score": } ] } REGRAS PARA SUBSTITUIÇÕES: - SOMENTE inclua "substitutions" se o score for < 50 - Sugira 3 produtos REAIS brasileiros que sejam alternativas mais saudáveis - Se score >= 50, retorne "substitutions": null Para a receita: - Se score > 70: sugira receita usando o produto - Se score <= 70: sugira alternativa saudável Critérios para o score: - 90-100: Natural, minimamente processado - 70-89: Bom, poucos aditivos - 50-69: Médio, processado mas aceitável - 30-49: Ruim, ultraprocessado - 0-29: Péssimo, alto em açúcar/sódio/gordura trans Use linguagem simples e direta.""" HEALTH_PROFILE_PROMPTS = { "normal": "", "crianca": "\n⚠️ PERFIL: CRIANÇA. Alerte sobre: excesso de açúcar, corantes artificiais, cafeína, sódio alto. Seja mais rigoroso com ultraprocessados.", "gestante": "\n⚠️ PERFIL: GESTANTE. Alerte sobre: cafeína, adoçantes artificiais, sódio excessivo, conservantes. Priorize folato, ferro, cálcio.", "diabetico": "\n⚠️ PERFIL: DIABÉTICO. Alerte sobre: açúcares, carboidratos refinados, índice glicêmico alto. Valorize fibras e proteínas.", "hipertenso": "\n⚠️ PERFIL: HIPERTENSO. Alerte sobre: sódio (>400mg é ALTO), glutamato monossódico, conservantes com sódio. Limite: <2g sódio/dia.", } async def analyze_product(product_data: dict, user_context: dict = None) -> dict: if not settings.OPENAI_API_KEY: return _mock_analysis(product_data) client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) nutrition_info = product_data.get('nutrition', {}) nutrition_str = json.dumps(nutrition_info, ensure_ascii=False) if nutrition_info else 'Não disponível' system = SYSTEM_PROMPT extra = "" if user_context: hp = user_context.get("health_profile", "normal") system += HEALTH_PROFILE_PROMPTS.get(hp, "") allergies = user_context.get("allergies", []) if allergies: extra = f"\n\n⚠️ ALERGIAS DO USUÁRIO: {', '.join(allergies)}. Destaque QUALQUER ingrediente que possa conter esses alérgenos." user_msg = f"""Produto: {product_data.get('name', 'Desconhecido')} Marca: {product_data.get('brand', '')} Categoria: {product_data.get('category', '')} Ingredientes: {product_data.get('ingredients_text', 'Não disponível')} Nutri-Score: {product_data.get('nutri_score', 'N/A')} NOVA: {product_data.get('nova_group', 'N/A')} Dados Nutricionais: {nutrition_str}{extra} Analise este produto completo.""" try: resp = await client.chat.completions.create( model=settings.OPENAI_MODEL, messages=[ {"role": "system", "content": system}, {"role": "user", "content": user_msg} ], response_format={"type": "json_object"}, temperature=0.3, timeout=30, ) return json.loads(resp.choices[0].message.content) except Exception as e: print(f"OpenAI error: {e}") return _mock_analysis(product_data) def _mock_analysis(product_data: dict) -> dict: ingredients = product_data.get("ingredients_text", "") score = 50 if any(w in ingredients.lower() for w in ["açúcar", "sugar", "xarope", "glucose"]): score -= 15 if any(w in ingredients.lower() for w in ["hidrogenada", "trans"]): score -= 20 if product_data.get("nova_group") == 4: score -= 10 ns = product_data.get("nutri_score", "") if ns == "e": score -= 10 elif ns == "d": score -= 5 elif ns == "a": score += 15 elif ns == "b": score += 10 score = max(0, min(100, score)) return { "score": score, "summary": f"Análise baseada em regras para {product_data.get('name', 'este produto')}. Configure OPENAI_API_KEY para análise completa com IA.", "positives": ["Dados nutricionais disponíveis"], "negatives": ["Análise IA indisponível - usando fallback"], "nutrition": {}, "nutrition_verdict": "", "ingredients": [], "recipe": None, "substitutions": None, } PHOTO_PROMPT = """Analise esta foto de um produto alimentar/suplemento. A foto pode mostrar o rótulo, tabela nutricional, ingredientes ou embalagem. IMPORTANTE: Mesmo que a foto esteja parcial, TENTE extrair o máximo de informações. NUNCA retorne erro se conseguir identificar o produto. Responda em JSON com este formato: { "product_name": "", "brand": "", "category": "", "ingredients_text": "", "nutri_score": "", "nova_group": <1-4 ou null>, "score": , "summary": "", "positives": ["..."], "negatives": ["..."], "nutrition": {"calorias":"...","acucar":"...","gordura_total":"...","gordura_saturada":"...","sodio":"...","carboidratos":"...","fibras":"...","proteinas":"..."}, "nutrition_verdict": "", "ingredients": [{"name":"...","popular_name":"...","explanation":"...","classification":"good|warning|bad","reason":"..."}], "recipe": {"title":"...","description":"...","prep_time":"...","calories":"...","ingredients_list":["..."],"steps":["..."],"tip":"..."}, "substitutions": null } Se score < 50, inclua "substitutions" com 3 alternativas reais brasileiras. SOMENTE retorne {"error": "mensagem"} se NÃO for alimento.""" async def analyze_photo(b64_image: str, user_context: dict = None) -> dict: if not settings.OPENAI_API_KEY: return None client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) system = "Voce eh um nutricionista brasileiro expert em analise de rotulos alimentares." if user_context: hp = user_context.get("health_profile", "normal") system += HEALTH_PROFILE_PROMPTS.get(hp, "") prompt = PHOTO_PROMPT if user_context and user_context.get("allergies"): prompt += f"\n\n⚠️ ALERGIAS: {', '.join(user_context['allergies'])}. Destaque alérgenos!" for detail_level in ["low", "auto"]: try: resp = await client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": system}, {"role": "user", "content": [ {"type": "text", "text": prompt}, {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64_image}", "detail": detail_level}} ]} ], response_format={"type": "json_object"}, temperature=0.3, timeout=30, max_tokens=1500, ) result = json.loads(resp.choices[0].message.content) if result.get("error"): continue return result except Exception as e: print(f"OpenAI photo error (detail={detail_level}): {e}") continue return None