Health_Tracker / app.py
uumerrr684's picture
Update app.py
67d68ab verified
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())