2Fitplan / database.py
Shrey0405's picture
Update database.py
eb25553 verified
Raw History Blame Contribute Delete
13.5 kB
import sqlite3
import json
from datetime import date, datetime
conn = sqlite3.connect("fitplan.db", check_same_thread=False)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("PRAGMA foreign_keys = ON")
# ── TABLE 1: Core authentication ─────────────────────────────────────────────
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
""")
# ── TABLE 2: Health onboarding & safety profile ───────────────────────────────
cursor.execute("""
CREATE TABLE IF NOT EXISTS user_onboarding (
email TEXT PRIMARY KEY,
conditions TEXT, -- JSON list: e.g. ["Lower Back Pain", "Hypertension"]
pain_points TEXT, -- JSON list: e.g. ["Knee", "Shoulder"]
past_surgeries TEXT,
pregnancy_status TEXT DEFAULT 'None',
allergens TEXT, -- JSON list: e.g. ["Dairy", "Gluten"]
dietary_type TEXT DEFAULT 'Pure Veg',
regional_cuisine TEXT DEFAULT 'Generic Indian',
leisure_sports TEXT, -- JSON list for Sunday recovery activity choices
onboarding_complete INTEGER DEFAULT 0,
FOREIGN KEY(email) REFERENCES users(email)
)
""")
# ── TABLE 3: Fluctuating biometrics & training parameters ─────────────────────
cursor.execute("""
CREATE TABLE IF NOT EXISTS user_metrics (
email TEXT PRIMARY KEY,
name TEXT,
gender TEXT,
height REAL,
weight REAL,
age INTEGER,
goal TEXT,
level TEXT,
equipment TEXT, -- JSON list
bmi REAL,
bmi_status TEXT,
updated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY(email) REFERENCES users(email)
)
""")
# ── TABLE 4: Weekly training + nutrition blueprint ────────────────────────────
cursor.execute("""
CREATE TABLE IF NOT EXISTS weekly_blueprints (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL,
week_start TEXT NOT NULL, -- ISO date: YYYY-MM-DD (Monday)
training_matrix TEXT, -- Full 7-day workout plan text
nutrition_matrix TEXT, -- Full 7-day diet plan text
raw_full_plan TEXT, -- Original unsplit LLM output
generated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY(email) REFERENCES users(email)
)
""")
# ── TABLE 5: Engagement, streaks & daily completion logs ──────────────────────
cursor.execute("""
CREATE TABLE IF NOT EXISTS engagement_matrix (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL,
log_date TEXT NOT NULL, -- ISO date: YYYY-MM-DD
day_number INTEGER NOT NULL, -- 1–7 within the weekly cycle
workout_done INTEGER DEFAULT 0, -- 0 or 1
diet_done INTEGER DEFAULT 0,
flexspace_note TEXT, -- Any realtime alteration applied
flexspace_patch TEXT, -- LLM-returned patch text
current_streak INTEGER DEFAULT 0,
UNIQUE(email, log_date),
FOREIGN KEY(email) REFERENCES users(email)
)
""")
conn.commit()
# ─────────────────────────────────────────────────────────────────────────────
# AUTH HELPERS
# ─────────────────────────────────────────────────────────────────────────────
def add_user(name: str, email: str, password: str) -> bool:
try:
cursor.execute(
"INSERT INTO users (name, email, password) VALUES (?, ?, ?)",
(name, email, password)
)
conn.commit()
return True
except Exception:
return False
def user_exists(email: str):
cursor.execute("SELECT id FROM users WHERE email=?", (email,))
return cursor.fetchone()
def verify_login(email: str, password: str):
cursor.execute(
"SELECT id, name, email FROM users WHERE email=? AND password=?",
(email, password)
)
return cursor.fetchone()
# ─────────────────────────────────────────────────────────────────────────────
# ONBOARDING HELPERS
# ─────────────────────────────────────────────────────────────────────────────
def save_onboarding(email, conditions, pain_points, past_surgeries,
pregnancy_status, allergens, dietary_type,
regional_cuisine, leisure_sports):
cursor.execute("""
INSERT INTO user_onboarding
(email, conditions, pain_points, past_surgeries, pregnancy_status,
allergens, dietary_type, regional_cuisine, leisure_sports, onboarding_complete)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
ON CONFLICT(email) DO UPDATE SET
conditions=excluded.conditions,
pain_points=excluded.pain_points,
past_surgeries=excluded.past_surgeries,
pregnancy_status=excluded.pregnancy_status,
allergens=excluded.allergens,
dietary_type=excluded.dietary_type,
regional_cuisine=excluded.regional_cuisine,
leisure_sports=excluded.leisure_sports,
onboarding_complete=1
""", (
email,
json.dumps(conditions),
json.dumps(pain_points),
past_surgeries,
pregnancy_status,
json.dumps(allergens),
dietary_type,
regional_cuisine,
json.dumps(leisure_sports)
))
conn.commit()
def get_onboarding(email) -> dict | None:
cursor.execute("SELECT * FROM user_onboarding WHERE email=?", (email,))
row = cursor.fetchone()
if not row:
return None
d = dict(row)
for key in ("conditions", "pain_points", "allergens", "leisure_sports"):
try:
d[key] = json.loads(d[key]) if d[key] else []
except Exception:
d[key] = []
return d
def onboarding_complete(email) -> bool:
cursor.execute(
"SELECT onboarding_complete FROM user_onboarding WHERE email=?", (email,)
)
row = cursor.fetchone()
return bool(row and row[0])
# ─────────────────────────────────────────────────────────────────────────────
# METRICS HELPERS
# ─────────────────────────────────────────────────────────────────────────────
def save_metrics(email, name, gender, height, weight, age,
goal, level, equipment, bmi, bmi_status):
cursor.execute("""
INSERT INTO user_metrics
(email, name, gender, height, weight, age, goal, level,
equipment, bmi, bmi_status, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
ON CONFLICT(email) DO UPDATE SET
name=excluded.name, gender=excluded.gender,
height=excluded.height, weight=excluded.weight,
age=excluded.age, goal=excluded.goal,
level=excluded.level, equipment=excluded.equipment,
bmi=excluded.bmi, bmi_status=excluded.bmi_status,
updated_at=datetime('now')
""", (
email, name, gender, height, weight, age, goal, level,
json.dumps(equipment), bmi, bmi_status
))
conn.commit()
def get_metrics(email) -> dict | None:
cursor.execute("SELECT * FROM user_metrics WHERE email=?", (email,))
row = cursor.fetchone()
if not row:
return None
d = dict(row)
try:
d["equipment"] = json.loads(d["equipment"]) if d["equipment"] else []
except Exception:
d["equipment"] = []
return d
# ─────────────────────────────────────────────────────────────────────────────
# BLUEPRINT HELPERS
# ─────────────────────────────────────────────────────────────────────────────
def _current_week_monday() -> str:
today = date.today()
monday = today - __import__("datetime").timedelta(days=today.weekday())
return monday.isoformat()
def save_blueprint(email, training_matrix, nutrition_matrix, raw_full_plan):
week_start = _current_week_monday()
cursor.execute("""
INSERT INTO weekly_blueprints
(email, week_start, training_matrix, nutrition_matrix, raw_full_plan)
VALUES (?, ?, ?, ?, ?)
""", (email, week_start, training_matrix, nutrition_matrix, raw_full_plan))
conn.commit()
def get_latest_blueprint(email) -> dict | None:
cursor.execute("""
SELECT * FROM weekly_blueprints
WHERE email=?
ORDER BY generated_at DESC
LIMIT 1
""", (email,))
row = cursor.fetchone()
return dict(row) if row else None
def update_blueprint_patch(email, day_number, field, patch_text):
"""Applies a FlexSpace patch to a specific day cell in the latest blueprint."""
bp = get_latest_blueprint(email)
if not bp:
return
target_col = "training_matrix" if field == "workout" else "nutrition_matrix"
existing = bp[target_col] or ""
marker = f"[DAY_{day_number}_PATCH]"
patched = f"{existing}\n\n{marker}\n{patch_text}"
cursor.execute(f"""
UPDATE weekly_blueprints
SET {target_col}=?
WHERE id=?
""", (patched, bp["id"]))
conn.commit()
# ─────────────────────────────────────────────────────────────────────────────
# ENGAGEMENT / STREAK HELPERS
# ─────────────────────────────────────────────────────────────────────────────
def _calc_streak(email) -> int:
cursor.execute("""
SELECT log_date FROM engagement_matrix
WHERE email=? AND workout_done=1
ORDER BY log_date DESC
""", (email,))
rows = [r[0] for r in cursor.fetchall()]
if not rows:
return 0
streak = 0
check = date.today()
for d_str in rows:
d = date.fromisoformat(d_str)
if d == check or d == check - __import__("datetime").timedelta(days=1):
streak += 1
check = d - __import__("datetime").timedelta(days=1)
else:
break
return streak
def log_day_completion(email, day_number, workout_done=True, diet_done=False):
today = date.today().isoformat()
streak = _calc_streak(email) + (1 if workout_done else 0)
cursor.execute("""
INSERT INTO engagement_matrix
(email, log_date, day_number, workout_done, diet_done, current_streak)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(email, log_date) DO UPDATE SET
workout_done=excluded.workout_done,
diet_done=excluded.diet_done,
current_streak=excluded.current_streak
""", (email, today, day_number, int(workout_done), int(diet_done), streak))
conn.commit()
return streak
def log_flexspace(email, day_number, note, patch_text):
today = date.today().isoformat()
cursor.execute("""
INSERT INTO engagement_matrix (email, log_date, day_number, flexspace_note, flexspace_patch)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(email, log_date) DO UPDATE SET
flexspace_note=excluded.flexspace_note,
flexspace_patch=excluded.flexspace_patch
""", (email, today, day_number, note, patch_text))
conn.commit()
def get_streak(email) -> int:
cursor.execute("""
SELECT current_streak FROM engagement_matrix
WHERE email=?
ORDER BY log_date DESC LIMIT 1
""", (email,))
row = cursor.fetchone()
return row[0] if row else 0
def get_completed_days(email) -> list[str]:
cursor.execute("""
SELECT log_date FROM engagement_matrix
WHERE email=? AND workout_done=1
""", (email,))
return [r[0] for r in cursor.fetchall()]
def get_today_log(email) -> dict | None:
today = date.today().isoformat()
cursor.execute(
"SELECT * FROM engagement_matrix WHERE email=? AND log_date=?",
(email, today)
)
row = cursor.fetchone()
return dict(row) if row else None