| | import os, uuid, random, json |
| | from dataclasses import dataclass |
| | from typing import Dict, List, Tuple |
| | import numpy as np |
| | import pandas as pd |
| | import gradio as gr |
| | from pydub import AudioSegment |
| | import logging |
| | from time import sleep |
| | from simple_salesforce import Salesforce |
| | from dotenv import load_dotenv |
| | from datetime import datetime |
| | import tempfile |
| |
|
| | |
| | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
| | logger = logging.getLogger(__name__) |
| |
|
| | |
| | USE_ASR = True |
| | WHISPER_MODEL_NAME = os.getenv("WHISPER_MODEL", "base") |
| | if USE_ASR: |
| | import whisper |
| | _whisper_model = whisper.load_model(WHISPER_MODEL_NAME) |
| |
|
| | |
| | lang_map = { |
| | "English": "en", "Hindi": "hi", "Telugu": "te", "Spanish": "es", |
| | } |
| |
|
| | |
| | def get_output_template(lang: str) -> str: |
| | templates = { |
| | "English": ( |
| | "🌾 Personalized Weekly Diet Plan\n" |
| | "────────────────────────────\n" |
| | "👤 Age: {age} | Gender: {gender}\n" |
| | "📏 Height: {height} cm | ⚖️ Weight: {weight} kg\n" |
| | "💼 Occupation: {occupation}\n" |
| | "🏃 Activity: {activity_level} | 🎯 Goal: {goal}\n" |
| | "⚕️ Health: {health_conditions}\n" |
| | "🍽️ Preferences: {dietary_preferences}\n" |
| | "⚠️ Allergies: {allergies}\n" |
| | "💰 Budget: ₹{budget}/day\n" |
| | ), |
| | "Hindi": ( |
| | "🌾 साप्ताहिक व्यक्तिगत आहार योजना\n" |
| | "────────────────────────────\n" |
| | "👤 आयु: {age} | लिंग: {gender}\n" |
| | "📏 ऊँचाई: {height} सेमी | ⚖️ वज़न: {weight} किग्रा\n" |
| | "💼 पेशा: {occupation}\n" |
| | "🏃 गतिविधि: {activity_level} | 🎯 लक्ष्य: {goal}\n" |
| | "⚕️ स्वास्थ्य: {health_conditions}\n" |
| | "🍽️ पसंद: {dietary_preferences}\n" |
| | "⚠️ एलर्जी: {allergies}\n" |
| | "💰 बजट: ₹{budget}/दिन\n" |
| | ), |
| | "Telugu": ( |
| | "🌾 వారాంత వ్యక్తిగత ఆహార ప్రణాళిక\n" |
| | "────────────────────────────\n" |
| | "👤 వయసు: {age} | లింగం: {gender}\n" |
| | "📏 ఎత్తు: {height} సెం.మీ | ⚖️ బరువు: {weight} కి.గ్రా\n" |
| | "💼 వృత్తి: {occupation}\n" |
| | "🏃 చర్య స్థాయి: {activity_level} | 🎯 లక్ష్యం: {goal}\n" |
| | "⚕️ ఆరోగ్యం: {health_conditions}\n" |
| | "🍽️ అభిరుచులు: {dietary_preferences}\n" |
| | "⚠️ అలర్జీలు: {allergies}\n" |
| | "💰 బడ్జెట్: ₹{budget}/రోజు\n" |
| | ), |
| | "Spanish": ( |
| | "🌾 Plan de dieta semanal personalizado\n" |
| | "────────────────────────────\n" |
| | "👤 Edad: {age} | Género: {gender}\n" |
| | "📏 Altura: {height} cm | ⚖️ Peso: {weight} kg\n" |
| | "💼 Ocupación: {occupation}\n" |
| | "🏃 Actividad: {activity_level} | 🎯 Objetivo: {goal}\n" |
| | "⚕️ Salud: {health_conditions}\n" |
| | "🍽️ Preferencias: {dietary_preferences}\n" |
| | "⚠️ Alergias: {allergies}\n" |
| | "💰 Presupuesto: ₹{budget}/día\n" |
| | ), |
| | } |
| | return templates.get(lang, templates["English"]) |
| |
|
| | |
| | FOODS = { |
| | "grains": [ |
| | {"name": "rice", "kcal": 130, "protein": 2.7, "carbs": 28, "fat": 0.3}, |
| | {"name": "wheat roti", "kcal": 110, "protein": 3.0, "carbs": 18, "fat": 2.0}, |
| | {"name": "millet (jowar)", "kcal": 119, "protein": 3.5, "carbs": 23, "fat": 1.0}, |
| | {"name": "oats", "kcal": 389, "protein": 16.9, "carbs": 66, "fat": 6.9}, |
| | ], |
| | "vegetables": [ |
| | {"name": "spinach", "kcal": 23, "protein": 2.9, "carbs": 3.6, "fat": 0.4, "vitc": 28}, |
| | {"name": "carrot", "kcal": 41, "protein": 0.9, "carbs": 10, "fat": 0.2, "vitc": 6}, |
| | {"name": "beetroot", "kcal": 43, "protein": 1.6, "carbs": 10, "fat": 0.2, "vitc": 4}, |
| | {"name": "cabbage", "kcal": 25, "protein": 1.3, "carbs": 6, "fat": 0.1, "vitc": 36}, |
| | {"name": "tomato", "kcal": 18, "protein": 0.9, "carbs": 3.9, "fat": 0.2, "vitc": 14}, |
| | ], |
| | "proteins": [ |
| | {"name": "lentils (dal)", "kcal": 116, "protein": 9.0, "carbs": 20, "fat": 0.4, "veg": True}, |
| | {"name": "chickpeas", "kcal": 164, "protein": 8.9, "carbs": 27, "fat": 2.6, "veg": True}, |
| | {"name": "paneer", "kcal": 265, "protein": 18, "carbs": 6, "fat": 20, "veg": True}, |
| | {"name": "eggs", "kcal": 155, "protein": 13, "carbs": 1.1, "fat": 11}, |
| | {"name": "chicken", "kcal": 239, "protein": 27, "carbs": 0, "fat": 14}, |
| | {"name": "tofu", "kcal": 76, "protein": 8, "carbs": 2, "fat": 4.8, "veg": True}, |
| | {"name": "fish", "kcal": 206, "protein": 22, "carbs": 0, "fat": 12}, |
| | ], |
| | "fruits": [ |
| | {"name": "banana", "kcal": 89, "protein": 1.1, "carbs": 23, "fat": 0.3, "vitc": 9}, |
| | {"name": "orange", "kcal": 47, "protein": 0.9, "carbs": 12, "fat": 0.1, "vitc": 53}, |
| | {"name": "apple", "kcal": 52, "protein": 0.3, "carbs": 14, "fat": 0.2, "vitc": 5}, |
| | {"name": "guava", "kcal": 68, "protein": 2.6, "carbs": 14, "fat": 1, "vitc": 228}, |
| | ], |
| | "fats": [ |
| | {"name": "groundnut oil", "kcal": 884, "protein": 0, "carbs": 0, "fat": 100}, |
| | {"name": "ghee", "kcal": 900, "protein": 0, "carbs": 0, "fat": 100}, |
| | {"name": "olive oil", "kcal": 884, "protein": 0, "carbs": 0, "fat": 100}, |
| | ], |
| | "dairy": [ |
| | {"name": "milk", "kcal": 60, "protein": 3.2, "carbs": 5, "fat": 3.3}, |
| | {"name": "curd", "kcal": 61, "protein": 3.5, "carbs": 4.7, "fat": 3.3}, |
| | {"name": "buttermilk", "kcal": 40, "protein": 3.3, "carbs": 4.8, "fat": 1.0}, |
| | ], |
| | } |
| |
|
| | |
| | FOOD_NAME_I18N = { |
| | "Hindi": { |
| | "rice": "चावल", "wheat roti": "गेहूँ रोटी", "millet (jowar)": "ज्वार", |
| | "oats": "ओट्स", "spinach": "पालक", "carrot": "गाजर", "beetroot": "चुकंदर", |
| | "cabbage": "पत्ता गोभी", "tomato": "टमाटर", "lentils (dal)": "दाल", |
| | "chickpeas": "काबुली चना", "paneer": "पनीर", "eggs": "अंडे", "chicken": "चिकन", |
| | "tofu": "टोफू", "fish": "मछली", "banana": "केला", "orange": "संतरा", |
| | "apple": "सेब", "guava": "अमरूद", "groundnut oil": "मूंगफली तेल", |
| | "ghee": "घी", "olive oil": "जैतून तेल", "milk": "दूध", "curd": "दही", |
| | "buttermilk": "छाछ", |
| | }, |
| | "Telugu": { |
| | "rice": "బియ్యం", "wheat roti": "గోధుమ రొట్టి", "millet (jowar)": "జొన్న", |
| | "oats": "ఓట్స్", "spinach": "పాలకూర", "carrot": "క్యారెట్", "beetroot": "బీట్రూట్", |
| | "cabbage": "కాబేజీ", "tomato": "టమోటా", "lentils (dal)": "పప్పు", |
| | "chickpeas": "సెనగలు", "paneer": "పనీర్", "eggs": "గుడ్లు", "chicken": "చికెన్", |
| | "tofu": "టోఫు", "fish": "చేప", "banana": "అరటి", "orange": "నారింజ", |
| | "apple": "ఆపిల్", "guava": "జామ", "groundnut oil": "పల్లీల నూనె", |
| | "ghee": "నెయ్యి", "olive oil": "ఆలివ్ ఆయిల్", "milk": "పాలు", "curd": "పెరుగు", |
| | "buttermilk": "మజ్జిగ", |
| | }, |
| | "Spanish": { |
| | "rice": "arroz", "wheat roti": "roti de trigo", "millet (jowar)": "mijo", |
| | "oats": "avena", "spinach": "espinaca", "carrot": "zanahoria", "beetroot": "remolacha", |
| | "cabbage": "col", "tomato": "tomate", "lentils (dal)": "lentejas", |
| | "chickpeas": "garbanzos", "paneer": "paneer", "eggs": "huevos", "chicken": "pollo", |
| | "tofu": "tofu", "fish": "pescado", "banana": "plátano", "orange": "naranja", |
| | "apple": "manzana", "guava": "guayaba", "groundnut oil": "aceite de cacahuete", |
| | "ghee": "ghee", "olive oil": "aceite de oliva", "milk": "leche", "curd": "yogur", |
| | "buttermilk": "suero de leche", |
| | } |
| | } |
| | def tfood(name: str, lang: str) -> str: |
| | return FOOD_NAME_I18N.get(lang, {}).get(name, name) |
| |
|
| | |
| | def mifflin_st_jeor_bmr(sex: str, weight: float, height: float, age: int) -> float: |
| | if sex.lower().startswith("m"): |
| | return 10*weight + 6.25*height - 5*age + 5 |
| | elif sex.lower().startswith("f"): |
| | return 10*weight + 6.25*height - 5*age - 161 |
| | else: |
| | return 10*weight + 6.25*height - 5*age - 78 |
| |
|
| | def activity_factor(level: str) -> float: |
| | return {"Low": 1.2, "Moderate": 1.55, "High": 1.725}.get(level, 1.4) |
| |
|
| | def goal_adjustment(goal: str) -> float: |
| | return {"Lose": 0.85, "Maintain": 1.0, "Gain": 1.15}.get(goal, 1.0) |
| |
|
| | def macro_targets(kcal: float, health_notes: str) -> Tuple[float, float, float]: |
| | p_pct, c_pct, f_pct = 0.25, 0.50, 0.25 |
| | ln = health_notes.lower() |
| | if "diabetes" in ln or "sugar" in ln: |
| | c_pct = 0.40; f_pct = 0.30; p_pct = 0.30 |
| | if "kidney" in ln or "renal" in ln: |
| | p_pct = 0.18; c_pct = 0.52; f_pct = 0.30 |
| | if "cholesterol" in ln or "lipid" in ln: |
| | f_pct = 0.20; c_pct = 0.55; p_pct = 0.25 |
| | protein_g = (kcal * p_pct) / 4.0 |
| | carbs_g = (kcal * c_pct) / 4.0 |
| | fat_g = (kcal * f_pct) / 9.0 |
| | return protein_g, carbs_g, fat_g |
| |
|
| | |
| | def transcribe_audio(path: str, lang_ui: str) -> str: |
| | if not USE_ASR or not path or not os.path.exists(path): |
| | return "" |
| | tmp = f"tmp_{uuid.uuid4().hex}.wav" |
| | try: |
| | seg = AudioSegment.from_file(path) |
| | seg = seg.set_frame_rate(16000).set_channels(1).set_sample_width(2) |
| | seg.export(tmp, format="wav") |
| | result = _whisper_model.transcribe(tmp, language=lang_map.get(lang_ui, "en"), fp16=False) |
| | return result.get("text", "").strip() |
| | except Exception as e: |
| | return f"(ASR error: {e})" |
| | finally: |
| | try: |
| | if os.path.exists(tmp): |
| | os.remove(tmp) |
| | except: |
| | pass |
| |
|
| | |
| | @dataclass |
| | class Person: |
| | age: int |
| | gender: str |
| | weight: float |
| | height: float |
| | occupation: str |
| | activity: str |
| | goal: str |
| | budget: float |
| | health: str |
| | prefs: str |
| | allergies: str |
| | lang: str |
| |
|
| | def choose_foods_for_day(veg: bool, allergies: List[str], rng: random.Random): |
| | def ok(item): |
| | nm = item["name"].lower() |
| | return all(a not in nm for a in allergies) |
| |
|
| | grains = [f for f in FOODS["grains"] if ok(f)] |
| | proteins = [f for f in FOODS["proteins"] if ok(f) and (veg is False or f.get("veg", False))] |
| | vegetables = [f for f in FOODS["vegetables"] if ok(f)] |
| | fruits = [f for f in FOODS["fruits"] if ok(f)] |
| | dairy = [f for f in FOODS["dairy"] if ok(f)] |
| | fats = [f for f in FOODS["fats"] if ok(f)] |
| |
|
| | grain = rng.choice(grains) if grains else FOODS["grains"][0] |
| | protein = rng.choice(proteins) if proteins else FOODS["proteins"][0] |
| | vegs = rng.sample(vegetables, k=min(2, len(vegetables))) if vegetables else [FOODS["vegetables"][0]] |
| | fruit = rng.choice(fruits) if fruits else FOODS["fruits"][0] |
| | dairy_pick = rng.choice(dairy) if dairy else FOODS["dairy"][0] |
| | fat_pick = rng.choice(fats) if fats else FOODS["fats"][0] |
| |
|
| | return grain, protein, vegs, fruit, dairy_pick, fat_pick |
| |
|
| | def meal_strings(lang: str, grain, protein, vegs, fruit, dairy, fat): |
| | b = f"{grain['name']} porridge + {dairy['name']}, {fruit['name']}" |
| | l = f"{protein['name']} + {grain['name']} + {vegs[0]['name']}" |
| | d = f"{vegs[0]['name']} curry + {vegs[1]['name'] if len(vegs)>1 else fruit['name']} salad + {protein['name']}" |
| | b = " + ".join([tfood(part.strip(), lang) for part in b.split("+")]) |
| | l = " + ".join([tfood(part.strip(), lang) for part in l.split("+")]) |
| | d = " + ".join([tfood(part.strip(), lang) for part in d.split("+")]) |
| | return b, l, d |
| |
|
| | def scale_to_targets(meal_df: pd.DataFrame, target_kcal: float, rng: random.Random) -> pd.DataFrame: |
| | df = meal_df.copy() |
| | df["grams"] = 100.0 |
| | current_kcal = df["kcal"].sum() |
| | if current_kcal == 0: |
| | return df |
| | factor = target_kcal / current_kcal |
| | factor = max(0.6, min(1.8, factor)) |
| | factor *= rng.uniform(0.95, 1.05) |
| | df["grams"] *= factor |
| | for col in ["kcal", "protein", "carbs", "fat"]: |
| | df[col] = df[col] * df["grams"] / 100.0 |
| | return df |
| |
|
| | def build_week_plan(p: Person, seed: int = 1337): |
| | rng = random.Random(seed) |
| | bmr = mifflin_st_jeor_bmr(p.gender, p.weight, p.height, p.age) |
| | tdee = bmr * activity_factor(p.activity) |
| | target_kcal = tdee * goal_adjustment(p.goal) |
| | protein_g, carbs_g, fat_g = macro_targets(target_kcal, p.health or "") |
| |
|
| | kcal_split = np.array([0.30, 0.40, 0.30]) |
| | kcal_meals = (kcal_split * target_kcal).tolist() |
| |
|
| | veg = ("veg" in (p.prefs or "").lower()) and ("non-veg" not in (p.prefs or "").lower()) |
| | allergies = [a.strip().lower() for a in (p.allergies or "").split(",") if a.strip()] |
| |
|
| | rows = [] |
| | readable_summary = [] |
| | for day in range(1, 8): |
| | grain, protein, vegs, fruit, dairy, fat = choose_foods_for_day(veg, allergies, rng) |
| |
|
| | breakfast_items = [grain, dairy, fruit] |
| | lunch_items = [protein, grain, vegs[0]] |
| | dinner_items = [vegs[0], vegs[1] if len(vegs) > 1 else fruit, protein] |
| |
|
| | def items_to_df(items): |
| | return pd.DataFrame([{k: it.get(k, 0) if k != "name" else it["name"] |
| | for k in ["name", "kcal", "protein", "carbs", "fat"]} |
| | for it in items]) |
| |
|
| | bdf = scale_to_targets(items_to_df(breakfast_items), kcal_meals[0], rng) |
| | ldf = scale_to_targets(items_to_df(lunch_items), kcal_meals[1], rng) |
| | ddf = scale_to_targets(items_to_df(dinner_items), kcal_meals[2], rng) |
| |
|
| | def summarize(df): |
| | return { |
| | "kcal": round(df["kcal"].sum(), 0), |
| | "protein": round(df["protein"].sum(), 1), |
| | "carbs": round(df["carbs"].sum(), 1), |
| | "fat": round(df["fat"].sum(), 1), |
| | "desc": ", ".join([tfood(n, p.lang) for n in df["name"].tolist()]) |
| | } |
| |
|
| | bsum, lsum, dsum = summarize(bdf), summarize(ldf), summarize(ddf) |
| | daily = { |
| | "Day": f"Day {day}", |
| | "Breakfast": bsum["desc"], |
| | "B_kcal": bsum["kcal"], "B_protein": bsum["protein"], "B_carbs": bsum["carbs"], "B_fat": bsum["fat"], |
| | "Lunch": lsum["desc"], |
| | "L_kcal": lsum["kcal"], "L_protein": lsum["protein"], "L_carbs": lsum["carbs"], "L_fat": lsum["fat"], |
| | "Dinner": dsum["desc"], |
| | "D_kcal": dsum["kcal"], "D_protein": dsum["protein"], "D_carbs": dsum["carbs"], "D_fat": dsum["fat"], |
| | "Total_kcal": round(bsum["kcal"] + lsum["kcal"] + dsum["kcal"], 0), |
| | "Total_protein": round(bsum["protein"] + lsum["protein"] + dsum["protein"], 1), |
| | "Total_carbs": round(bsum["carbs"] + lsum["carbs"] + dsum["carbs"], 1), |
| | "Total_fat": round(bsum["fat"] + lsum["fat"] + dsum["fat"], 1), |
| | } |
| | rows.append(daily) |
| |
|
| | readable_summary.append(f"\n📅 Day {day} Nutrition Summary") |
| | readable_summary.append("-" * 50) |
| | readable_summary.append(f"Breakfast: {bsum['desc']}") |
| | readable_summary.append(f" - Calories: {bsum['kcal']} kcal | Protein: {bsum['protein']}g | Carbs: {bsum['carbs']}g | Fat: {bsum['fat']}g") |
| | readable_summary.append(f"Lunch: {lsum['desc']}") |
| | readable_summary.append(f" - Calories: {lsum['kcal']} kcal | Protein: {lsum['protein']}g | Carbs: {lsum['carbs']}g | Fat: {lsum['fat']}g") |
| | readable_summary.append(f"Dinner: {dsum['desc']}") |
| | readable_summary.append(f" - Calories: {dsum['kcal']} kcal | Protein: {dsum['protein']}g | Carbs: {dsum['carbs']}g | Fat: {dsum['fat']}g") |
| | readable_summary.append(f"Total Daily: {daily['Total_kcal']} kcal | Protein: {daily['Total_protein']}g | Carbs: {daily['Total_carbs']}g | Fat: {daily['Total_fat']}g") |
| | readable_summary.append("-" * 50) |
| |
|
| | week_df = pd.DataFrame(rows) |
| | header = get_output_template(p.lang).format( |
| | age=p.age, gender=p.gender, height=p.height, weight=p.weight, |
| | occupation=p.occupation, activity_level=p.activity, goal=p.goal, |
| | health_conditions=p.health or "None", dietary_preferences=p.prefs or "None", |
| | allergies=p.allergies or "None", budget=p.budget, |
| | ) |
| |
|
| | targets = ( |
| | f"🎯 Targets (approx.): {int(target_kcal)} kcal/day | " |
| | f"Protein ~ {int(protein_g)} g | Carbs ~ {int(carbs_g)} g | Fat ~ {int(fat_g)} g" |
| | ) |
| | readable_summary = "\n".join(readable_summary) |
| | return header + "\n" + targets + "\n\n" + readable_summary, week_df |
| |
|
| | |
| | load_dotenv() |
| | SF_USERNAME = os.getenv("SF_USERNAME") |
| | SF_PASSWORD = os.getenv("SF_PASSWORD") |
| | SF_TOKEN = os.getenv("SF_SECURITY_TOKEN") |
| | SF_INSTANCE_URL = os.getenv("SF_INSTANCE_URL", "https://sathkruthatechnologysoluti4-dev-ed.develop.my.salesforce.com") |
| |
|
| | def connect_to_salesforce(max_retries=5): |
| | global sf |
| | for attempt in range(max_retries): |
| | try: |
| | if not all([SF_USERNAME, SF_PASSWORD, SF_TOKEN]): |
| | return False, "❌ Missing Salesforce credentials. Please set SF_USERNAME, SF_PASSWORD, and SF_TOKEN as HF Secrets." |
| | sf = Salesforce( |
| | username=SF_USERNAME, |
| | password=SF_PASSWORD, |
| | security_token=SF_TOKEN, |
| | instance_url=SF_INSTANCE_URL |
| | ) |
| | logger.info("✅ Connected to Salesforce at %s", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) |
| | return True, "" |
| | except Exception as e: |
| | logger.error("❌ Salesforce connection attempt %d failed: %s", attempt + 1, str(e)) |
| | if attempt < max_retries - 1: |
| | sleep(2 ** attempt) |
| | else: |
| | return False, f"❌ All connection attempts failed: {e}" |
| | return False, "" |
| |
|
| | def check_field_accessibility(object_name: str, field_name: str) -> bool: |
| | try: |
| | describe_result = sf.__getattr__(object_name).describe() |
| | field_info = next((f for f in describe_result["fields"] if f["name"] == field_name), None) |
| | if field_info: |
| | logger.info("Field %s.%s accessibility: read=%s, write=%s", object_name, field_name, field_info.get("readable"), field_info.get("updateable")) |
| | return field_info.get("updateable", False) |
| | logger.warning("Field %s.%s not found in object description", object_name, field_name) |
| | return False |
| | except Exception as e: |
| | logger.error("❌ Failed to check field accessibility for %s.%s: %s", object_name, field_name, str(e)) |
| | return False |
| |
|
| | def prepare_salesforce_data(header: str, week_df: pd.DataFrame) -> Dict: |
| | nutritional_data = week_df.to_dict(orient="records") |
| | record = { |
| | "Name": f"Diet Plan {uuid.uuid4().hex[:6]}", |
| | "NutritionalSummary__c": json.dumps(nutritional_data)[:130000], |
| | "PlanDate__c": pd.Timestamp.now().strftime('%Y-%m-%d'), |
| | "Meals__c": header[:32000], |
| | } |
| | logger.info("Salesforce fields being sent: %s", record.keys()) |
| | return record |
| |
|
| | def create_local_food_source_record(diet_plan_id: str, local_foods: Dict) -> str: |
| | try: |
| | |
| | location = { |
| | "latitude": random.uniform(10.0, 40.0), |
| | "longitude": random.uniform(-130.0, -70.0) |
| | } |
| | |
| | type_value = random.choice(["Farmers Market", "CSA", "Food Pantry"]) |
| | |
| | |
| | diet_plan_accessible = check_field_accessibility("LocalFoodSource__c", "DietPlan__c") |
| | location_accessible = check_field_accessibility("LocalFoodSource__c", "Location__c") |
| | |
| | if not diet_plan_accessible: |
| | logger.warning("⚠️ Field 'DietPlan__c' not found or not writable on LocalFoodSource__c, skipping lookup.") |
| | if not location_accessible: |
| | logger.warning("⚠️ Field 'Location__c' not writable on LocalFoodSource__c. Check field-level security.") |
| |
|
| | record = {} |
| | if diet_plan_accessible: |
| | record["DietPlan__c"] = diet_plan_id |
| | if location_accessible: |
| | record["Location__c"] = location |
| | record["Type__c"] = type_value |
| | record["AvailableFoods__c"] = json.dumps(local_foods)[:131072] |
| | |
| | logger.info("Attempting to create LocalFoodSource with fields: %s", record.keys()) |
| | res = sf.LocalFoodSource__c.create(record) |
| | if res.get('success'): |
| | logger.info("✅ Saved LocalFoodSource record (Id: %s) at %s", res.get('id'), datetime.now().strftime('%Y-%m-%d %H:%M:%S')) |
| | return f"✅ Saved LocalFoodSource (Id: {res.get('id')})" |
| | else: |
| | logger.error("❌ Failed to save LocalFoodSource: %s", res.get('errors')) |
| | return f"❌ Failed to save LocalFoodSource: {res.get('errors')}" |
| | except Exception as e: |
| | logger.error("❌ Failed to save LocalFoodSource: %s", str(e)) |
| | return f"❌ Failed to save LocalFoodSource: {str(e)}" |
| |
|
| | def create_feedback_record(diet_plan_id: str, feedback: str) -> str: |
| | try: |
| | record = { |
| | "DietPlan__c": diet_plan_id, |
| | "Comments__c": feedback[:32000] if feedback else "No feedback provided", |
| | } |
| | res = sf.Feedback__c.create(record) |
| | if res.get('success'): |
| | logger.info("✅ Saved Feedback record (Id: %s) at %s", res.get('id'), datetime.now().strftime('%Y-%m-%d %H:%M:%S')) |
| | return f"✅ Saved Feedback (Id: {res.get('id')})" |
| | else: |
| | logger.error("❌ Failed to save Feedback: %s", res.get('errors')) |
| | return f"❌ Failed to save Feedback: {res.get('errors')}" |
| | except Exception as e: |
| | logger.error("❌ Failed to save Feedback: %s", str(e)) |
| | return f"❌ Failed to save Feedback: {str(e)}" |
| |
|
| | def save_plan_to_salesforce(person: Person, header: str, week_df: pd.DataFrame, feedback: str = "") -> str: |
| | success, msg = connect_to_salesforce() |
| | if not success: |
| | return msg |
| |
|
| | try: |
| | |
| | if not check_field_accessibility("DietPlan__c", "Name"): |
| | return "❌ Insufficient permissions to update 'Name' field on DietPlan__c. Please check field-level security with your Salesforce admin." |
| |
|
| | |
| | diet_plan_record = prepare_salesforce_data(header, week_df) |
| | res = sf.DietPlan__c.create(diet_plan_record) |
| | if not res.get('success'): |
| | logger.error("❌ Salesforce create failed: %s", res.get('errors')) |
| | return f"❌ Failed to save in Salesforce: {res.get('errors')}" |
| |
|
| | diet_plan_id = res.get('id') |
| | logger.info("✅ Saved DietPlan record (Id: %s) at %s", diet_plan_id, datetime.now().strftime('%Y-%m-%d %H:%M:%S')) |
| |
|
| | |
| | local_food_source_status = create_local_food_source_record(diet_plan_id, FOODS) |
| |
|
| | |
| | feedback_status = create_feedback_record(diet_plan_id, feedback) |
| |
|
| | return f"✅ Saved DietPlan (Id: {diet_plan_id})\n{local_food_source_status}\n{feedback_status}" |
| | except Exception as e: |
| | logger.error("❌ Failed to save in Salesforce: %s", str(e)) |
| | return f"❌ Failed to save in Salesforce: {str(e)}" |
| |
|
| | |
| | def full_pipeline( |
| | health_audio, prefs_audio, allergy_audio, |
| | age, gender, weight, height, occupation, activity, goal, budget, lang, |
| | health_text_ui, prefs_text_ui, allergy_text_ui, seed, feedback |
| | ): |
| | logger.info("Pipeline started at %s", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) |
| | trans_health = transcribe_audio(health_audio, lang) if health_audio else "" |
| | trans_prefs = transcribe_audio(prefs_audio, lang) if prefs_audio else "" |
| | trans_allergy = transcribe_audio(allergy_audio, lang) if allergy_audio else "" |
| |
|
| | health = (health_text_ui or "").strip() or trans_health |
| | prefs = (prefs_text_ui or "").strip() or trans_prefs |
| | allergy = (allergy_text_ui or "").strip() or trans_allergy |
| |
|
| | p = Person( |
| | age=int(age), gender=gender, weight=float(weight), height=float(height), |
| | occupation=occupation or "-", activity=activity, goal=goal, budget=float(budget), |
| | health=health, prefs=prefs, allergies=allergy, lang=lang |
| | ) |
| | header, week_df = build_week_plan(p, seed=int(seed or 1337)) |
| |
|
| | |
| | with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as tmp_file: |
| | week_df.to_csv(tmp_file.name, index=False, encoding="utf-8") |
| | csv_path = tmp_file.name |
| |
|
| | sf_status = save_plan_to_salesforce(p, header, week_df, feedback) |
| |
|
| | logger.info("Pipeline completed at %s", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) |
| | return header + "\n\n" + sf_status, week_df, csv_path, trans_health, trans_prefs, trans_allergy |
| |
|
| | |
| | with gr.Blocks(theme=gr.themes.Soft(primary_hue="green")) as demo: |
| | gr.Markdown("## 🥗 Personalized Diet Plan Generator (Offline, Multilingual)") |
| | with gr.Row(): |
| | lang_sel = gr.Dropdown(choices=list(lang_map.keys()), value="English", label="Language") |
| |
|
| | with gr.Row(): |
| | age = gr.Number(value=30, label="Age") |
| | gender = gr.Radio(["Male", "Female", "Other"], value="Male", label="Gender") |
| | height = gr.Number(value=170, label="Height (cm)") |
| | weight = gr.Number(value=70, label="Weight (kg)") |
| |
|
| | with gr.Row(): |
| | occupation = gr.Textbox(label="Occupation", placeholder="e.g., Student / Desk job / Field work") |
| | activity = gr.Radio(["Low", "Moderate", "High"], value="Moderate", label="Activity Level") |
| | goal = gr.Radio(["Lose", "Maintain", "Gain"], value="Maintain", label="Goal") |
| | budget = gr.Number(value=200, label="Daily Budget (₹)") |
| | seed = gr.Number(value=1337, label="Random Seed (for reproducible plans)") |
| |
|
| | gr.Markdown("### Optional: Type or Speak your health info, preferences, and allergies") |
| | with gr.Row(): |
| | health_text = gr.Textbox(label="Health conditions (typed)", placeholder="e.g., Diabetes, high cholesterol") |
| | prefs_text = gr.Textbox(label="Dietary preferences (typed)", placeholder="e.g., Veg, Non-veg, High protein") |
| | allergy_text = gr.Textbox(label="Allergies (typed, comma-separated)", placeholder="e.g., peanut, milk") |
| |
|
| | with gr.Row(): |
| | health_audio = gr.Audio(label="🎤 Speak Health Conditions (optional)", type="filepath") |
| | prefs_audio = gr.Audio(label="🎤 Speak Dietary Preferences (optional)", type="filepath") |
| | allergy_audio = gr.Audio(label="🎤 Speak Allergies (optional)", type="filepath") |
| |
|
| | btn = gr.Button("🎯 Generate 7-Day Plan with Feedback") |
| |
|
| | header_out = gr.Markdown() |
| | week_table = gr.Dataframe(wrap=True, interactive=False, label="Weekly Plan (scroll sideways for macros)") |
| | csv_file = gr.File(label="Download CSV") |
| | with gr.Accordion("ASR transcription (from voice inputs)", open=False): |
| | asr_h = gr.Textbox(label="Health (ASR)") |
| | asr_p = gr.Textbox(label="Preferences (ASR)") |
| | asr_a = gr.Textbox(label="Allergies (ASR)") |
| |
|
| | with gr.Row(): |
| | feedback_input = gr.Textbox(label="Feedback", placeholder="Provide your feedback on the diet plan", lines=3) |
| |
|
| | btn.click( |
| | full_pipeline, |
| | inputs=[ |
| | health_audio, prefs_audio, allergy_audio, |
| | age, gender, weight, height, occupation, activity, goal, budget, lang_sel, |
| | health_text, prefs_text, allergy_text, seed, feedback_input |
| | ], |
| | outputs=[header_out, week_table, csv_file, asr_h, asr_p, asr_a] |
| | ) |
| |
|
| | if __name__ == "__main__": |
| | demo.launch() |