Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import sqlite3 | |
| import pandas as pd | |
| from datetime import datetime, timedelta | |
| import re | |
| from difflib import get_close_matches | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| import numpy as np | |
| # ============================================================================ | |
| # DATABASE SETUP | |
| # ============================================================================ | |
| def init_db(): | |
| conn = sqlite3.connect("health_tracker.db") | |
| c = conn.cursor() | |
| c.execute('''CREATE TABLE IF NOT EXISTS food_library | |
| (id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| food_name TEXT UNIQUE NOT NULL, | |
| calories TEXT, | |
| protein REAL, fat REAL, carbs REAL, fiber REAL, | |
| sodium REAL, cholesterol REAL, sugar REAL, saturated_fat REAL, | |
| vitamin_a REAL, vitamin_b1 REAL, vitamin_b2 REAL, vitamin_b3 REAL, vitamin_b5 REAL, | |
| vitamin_b6 REAL, vitamin_b12 REAL, vitamin_c REAL, vitamin_d REAL, vitamin_e REAL, | |
| vitamin_k REAL, folate REAL, calcium REAL, iron REAL, magnesium REAL, | |
| phosphorus REAL, potassium REAL, zinc REAL, copper REAL, manganese REAL, | |
| selenium REAL, iodine REAL, chromium REAL, molybdenum REAL, | |
| omega_3 REAL, omega_6 REAL, water REAL, ash REAL, | |
| created_date TEXT)''') | |
| c.execute('''CREATE TABLE IF NOT EXISTS meals | |
| (id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| date TEXT NOT NULL, | |
| time TEXT, | |
| food_name TEXT NOT NULL, | |
| portion TEXT, | |
| calories TEXT, protein REAL, fat REAL, carbs REAL, fiber REAL, | |
| sodium REAL, cholesterol REAL, sugar REAL, saturated_fat REAL, | |
| vitamin_a REAL, vitamin_b1 REAL, vitamin_b2 REAL, vitamin_b3 REAL, vitamin_b5 REAL, | |
| vitamin_b6 REAL, vitamin_b12 REAL, vitamin_c REAL, vitamin_d REAL, vitamin_e REAL, | |
| vitamin_k REAL, folate REAL, calcium REAL, iron REAL, magnesium REAL, | |
| phosphorus REAL, potassium REAL, zinc REAL, copper REAL, manganese REAL, | |
| selenium REAL, iodine REAL, chromium REAL, molybdenum REAL, | |
| omega_3 REAL, omega_6 REAL, water REAL, ash REAL, | |
| notes TEXT)''') | |
| conn.commit() | |
| conn.close() | |
| init_db() | |
| # ============================================================================ | |
| # PARSER | |
| # ============================================================================ | |
| def parse_nutrition_text(text): | |
| result = {} | |
| key_variants = { | |
| 'calories': ['calories', 'kcal', 'energy'], | |
| 'protein': ['protein', 'prot'], | |
| 'fat': ['fat'], | |
| 'saturated_fat': ['saturated fat', 'sat fat', 'saturated'], | |
| 'carbs': ['carbs', 'carbohydrates'], | |
| 'fiber': ['fiber', 'fibre'], | |
| 'sugar': ['sugar'], | |
| 'omega_3': ['omega-3', 'omega 3'], | |
| 'omega_6': ['omega-6', 'omega 6'], | |
| 'sodium': ['sodium'], | |
| 'potassium': ['potassium'], | |
| 'calcium': ['calcium'], | |
| 'magnesium': ['magnesium'], | |
| 'phosphorus': ['phosphorus'], | |
| 'iron': ['iron'], | |
| 'zinc': ['zinc'], | |
| 'copper': ['copper'], | |
| 'manganese': ['manganese'], | |
| 'selenium': ['selenium'], | |
| 'iodine': ['iodine'], | |
| 'chromium': ['chromium'], | |
| 'molybdenum': ['molybdenum'], | |
| 'vitamin_a': ['vitamin a', 'vit a', 'a'], | |
| 'vitamin_b1': ['b1', 'vitamin b1', 'thiamin'], | |
| 'vitamin_b2': ['b2', 'vitamin b2', 'riboflavin'], | |
| 'vitamin_b3': ['b3', 'vitamin b3', 'niacin'], | |
| 'vitamin_b5': ['b5', 'vitamin b5'], | |
| 'vitamin_b6': ['b6', 'vitamin b6'], | |
| 'vitamin_b12': ['b12', 'vitamin b12'], | |
| 'folate': ['folate', 'folic'], | |
| 'vitamin_c': ['vitamin c', 'vit c', 'c'], | |
| 'vitamin_d': ['vitamin d', 'vit d', 'd'], | |
| 'vitamin_e': ['vitamin e', 'vit e', 'e'], | |
| 'vitamin_k': ['vitamin k', 'vit k', 'k'], | |
| 'cholesterol': ['cholesterol'], | |
| 'water': ['water'], | |
| 'ash': ['ash'], | |
| } | |
| lines = [l.strip() for l in text.split('\n') if l.strip()] | |
| for line in lines: | |
| numbers = re.findall(r'\d+\.?\d*', line) | |
| if not numbers: | |
| continue | |
| val_str = numbers[-1] | |
| pos = line.rfind(val_str) | |
| label = line[:pos].strip().lower() | |
| label = re.sub(r'\s*\([^)]*\)', '', label).strip() | |
| all_keys = [] | |
| for k, vs in key_variants.items(): | |
| all_keys.extend(vs) | |
| matches = get_close_matches(label, all_keys, n=1, cutoff=0.65) | |
| if matches: | |
| found = matches[0] | |
| for canonical, variants in key_variants.items(): | |
| if found in variants: | |
| result[canonical] = float(val_str) | |
| break | |
| return result | |
| # ============================================================================ | |
| # DB HELPERS | |
| # ============================================================================ | |
| def get_db(): | |
| conn = sqlite3.connect("health_tracker.db") | |
| conn.row_factory = sqlite3.Row | |
| return conn | |
| def add_food_to_library(name, nutrition): | |
| conn = get_db() | |
| c = conn.cursor() | |
| try: | |
| cols = ', '.join(nutrition.keys()) | |
| vals = ', '.join(['?'] * len(nutrition)) | |
| c.execute(f"INSERT INTO food_library (food_name, created_date, {cols}) VALUES (?, ?, {vals})", | |
| (name, datetime.now().isoformat(), *nutrition.values())) | |
| conn.commit() | |
| return f"✅ Saved: {name}" | |
| except sqlite3.IntegrityError: | |
| return f"⚠️ {name} already exists" | |
| finally: | |
| conn.close() | |
| def get_all_foods(): | |
| conn = get_db() | |
| df = pd.read_sql_query("SELECT * FROM food_library ORDER BY food_name", conn) | |
| conn.close() | |
| return df | |
| def delete_food(fid): | |
| conn = get_db() | |
| c = conn.cursor() | |
| c.execute("DELETE FROM food_library WHERE id = ?", (fid,)) | |
| conn.commit() | |
| conn.close() | |
| return "Deleted" | |
| def log_meal(date_str, time_str, food_name, portion, nutrition): | |
| conn = get_db() | |
| c = conn.cursor() | |
| cols = ', '.join(nutrition.keys()) | |
| vals = ', '.join(['?'] * len(nutrition)) | |
| c.execute(f"INSERT INTO meals (date, time, food_name, portion, {cols}) VALUES (?, ?, ?, ?, {vals})", | |
| (date_str, time_str, food_name, portion, *nutrition.values())) | |
| conn.commit() | |
| conn.close() | |
| return "Meal logged" | |
| def get_meals(start_date, end_date): | |
| conn = get_db() | |
| df = pd.read_sql_query("SELECT * FROM meals WHERE date BETWEEN ? AND ? ORDER BY date DESC, time DESC", | |
| conn, params=(start_date, end_date)) | |
| conn.close() | |
| return df | |
| # ============================================================================ | |
| # GRADIO APP – fixed for 4.44.0 | |
| # ============================================================================ | |
| def parse_and_show(text): | |
| parsed = parse_nutrition_text(text) | |
| return parsed, "Parsed! Now enter name and save." | |
| def save_food(name, parsed): | |
| if not name.strip(): | |
| return "Enter food name", None | |
| if not parsed: | |
| return "No data parsed", None | |
| msg = add_food_to_library(name.strip(), parsed) | |
| return msg, None | |
| def refresh_foods(): | |
| df = get_all_foods() | |
| if df.empty: | |
| return "No foods yet", gr.update(choices=[]) | |
| choices = [f"{row['id']} - {row['food_name']}" for _, row in df.iterrows()] | |
| return df.to_string(index=False), gr.update(choices=choices) | |
| def delete_selected(selected): | |
| if not selected: | |
| return "Select food" | |
| fid = int(selected.split(" - ")[0]) | |
| msg = delete_food(fid) | |
| _, choices = refresh_foods() | |
| return msg, choices | |
| def log_meal_fn(food_choice, qty, date_obj, time_obj): | |
| if not food_choice: | |
| return "Select food" | |
| fid = int(food_choice.split(" - ")[0]) | |
| foods = get_all_foods() | |
| if foods.empty or fid not in foods['id'].values: | |
| return "Food not found" | |
| food_row = foods[foods['id'] == fid].iloc[0].to_dict() | |
| nutrition = {} | |
| for k in ['calories', 'protein', 'fat', 'carbs', 'fiber', 'sodium', 'cholesterol', 'sugar', 'saturated_fat', | |
| 'vitamin_a', 'vitamin_b1', 'vitamin_b2', 'vitamin_b3', 'vitamin_b5', 'vitamin_b6', 'vitamin_b12', | |
| 'vitamin_c', 'vitamin_d', 'vitamin_e', 'vitamin_k', 'folate', 'calcium', 'iron', 'magnesium', | |
| 'phosphorus', 'potassium', 'zinc', 'copper', 'manganese', 'selenium', 'iodine', 'chromium', | |
| 'molybdenum', 'omega_3', 'omega_6', 'water', 'ash']: | |
| val = food_row.get(k, 0) | |
| if k == 'calories': | |
| nutrition[k] = str(float(val) * qty if val else 0) | |
| else: | |
| nutrition[k] = float(val) * qty if val else 0 | |
| date_str = date_obj.strftime("%Y-%m-%d") if date_obj else datetime.now().strftime("%Y-%m-%d") | |
| time_str = time_obj.strftime("%H:%M") if time_obj else datetime.now().strftime("%H:%M") | |
| msg = log_meal(date_str, time_str, food_row['food_name'], f"{qty}x", nutrition) | |
| return msg | |
| def show_today(): | |
| today = datetime.now().strftime("%Y-%m-%d") | |
| df = get_meals(today, today) | |
| if df.empty: | |
| return "No meals today" | |
| df['calories'] = pd.to_numeric(df['calories'], errors='coerce').fillna(0) | |
| cals = df['calories'].sum() | |
| pro = df['protein'].sum() | |
| fat = df['fat'].sum() | |
| carb = df['carbs'].sum() | |
| summary = f"Today: {cals:.0f} kcal | Protein {pro:.1f}g | Fat {fat:.1f}g | Carbs {carb:.1f}g\n\n" | |
| return summary + df[['time', 'food_name', 'portion', 'calories']].to_string(index=False) | |
| with gr.Blocks(title="Nutrition Tracker") as app: | |
| gr.Markdown("# 🍛 Nutrition Tracker - Paste & Track") | |
| with gr.Tabs(): | |
| with gr.Tab("Add Food"): | |
| with gr.Row(): | |
| paste = gr.Textbox(label="Paste Nutrition Text", lines=15, placeholder="Calories (kcal) 340\nProtein (g) 13.00\n...") | |
| parse_btn = gr.Button("Parse") | |
| parsed_json = gr.JSON(label="Parsed Result") | |
| status_parse = gr.Textbox(label="Parse Status") | |
| food_name_in = gr.Textbox(label="Food Name") | |
| save_btn = gr.Button("Save Food", variant="primary") | |
| save_status = gr.Textbox(label="Save Status") | |
| parse_btn.click(parse_and_show, inputs=paste, outputs=[parsed_json, status_parse]) | |
| save_btn.click(save_food, inputs=[food_name_in, parsed_json], outputs=[save_status, parsed_json]) | |
| with gr.Tab("Log Meal"): | |
| food_dropdown = gr.Dropdown(label="Select Food", choices=[]) | |
| qty_input = gr.Number(label="Quantity", value=1.0, minimum=0.1) | |
| date_input = gr.DatePicker(label="Date") | |
| time_input = gr.TimePicker(label="Time") | |
| log_btn = gr.Button("Log Meal") | |
| log_status_out = gr.Textbox(label="Log Status") | |
| log_btn.click(log_meal_fn, inputs=[food_dropdown, qty_input, date_input, time_input], outputs=log_status_out) | |
| with gr.Tab("Today's Log"): | |
| today_text = gr.Textbox(label="Today's Summary", lines=12, interactive=False) | |
| refresh_today_btn = gr.Button("Refresh") | |
| refresh_today_btn.click(show_today, outputs=today_text) | |
| with gr.Tab("Food Library"): | |
| foods_text = gr.Textbox(label="Foods List", lines=15, interactive=False) | |
| delete_dropdown = gr.Dropdown(label="Delete Food", choices=[]) | |
| delete_btn = gr.Button("Delete", variant="stop") | |
| lib_status = gr.Textbox(label="Status") | |
| refresh_lib_btn = gr.Button("Refresh Library") | |
| refresh_lib_btn.click(refresh_foods, outputs=[foods_text, delete_dropdown]) | |
| delete_btn.click(delete_selected, inputs=delete_dropdown, outputs=[lib_status, delete_dropdown]) | |
| # Initial load | |
| app.load(refresh_foods, outputs=[foods_text, delete_dropdown]) | |
| app.load(refresh_foods, outputs=[food_dropdown]) # for log tab | |
| app.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft()) |