workout-tracker / tracker /models.py
ronheichman's picture
Deploy workout tracker
e0cc987 verified
Raw
History Blame Contribute Delete
7.87 kB
from __future__ import annotations
from datetime import datetime
import sqlmodel
from pydantic import BaseModel, Field
from sqlalchemy import Column
from sqlalchemy.types import JSON
from sqlmodel import SQLModel
from tracker.constants import MealLogSource, MealPattern, MealSlot
class TrackerUser(SQLModel, table=True):
"""Dashboard user owning tracker rows."""
__tablename__ = "tracker_users"
user_id: int | None = sqlmodel.Field(default=None, primary_key=True)
email: str = sqlmodel.Field(unique=True, index=True)
label: str = sqlmodel.Field(default="")
role: str | None = sqlmodel.Field(default=None)
created_at: datetime = sqlmodel.Field(default_factory=datetime.now)
class Exercise(SQLModel, table=True):
"""Exercise catalog entry, seeded from wger/exercemus data."""
__tablename__ = "exercises"
exercise_id: int | None = sqlmodel.Field(primary_key=True)
name: str = sqlmodel.Field(unique=True, index=True)
muscle_group: str = sqlmodel.Field()
equipment: str = sqlmodel.Field()
wger_id: int | None = sqlmodel.Field(index=True)
starting_bodyweight_fraction: float | None = sqlmodel.Field(default=None)
starting_load_kg: float | None = sqlmodel.Field(default=None)
starting_load_reference: str = sqlmodel.Field(default="")
class Program(SQLModel, table=True):
"""Named workout program template."""
__tablename__ = "programs"
program_id: int | None = sqlmodel.Field(primary_key=True)
user_id: int | None = sqlmodel.Field(default=None, index=True)
name: str = sqlmodel.Field(unique=True)
description: str = sqlmodel.Field()
created_at: datetime = sqlmodel.Field(default_factory=datetime.now)
class ProgramExercise(SQLModel, table=True):
"""An exercise within a program day, with rep/set targets."""
__tablename__ = "program_exercises"
program_id: int = sqlmodel.Field(foreign_key="programs.program_id", primary_key=True)
day_label: str = sqlmodel.Field(primary_key=True)
exercise_id: int = sqlmodel.Field(foreign_key="exercises.exercise_id", primary_key=True)
order_index: int = sqlmodel.Field()
target_sets: int = sqlmodel.Field()
target_rep_min: int = sqlmodel.Field()
target_rep_max: int = sqlmodel.Field()
weight_increment_kg: float = sqlmodel.Field()
rest_seconds: int = sqlmodel.Field()
superset_group: int | None = sqlmodel.Field(index=True)
starting_weight_kg: float | None = sqlmodel.Field(default=None)
class WorkoutLog(SQLModel, table=True):
"""Per-set workout log entry."""
__tablename__ = "workout_logs"
log_id: int | None = sqlmodel.Field(primary_key=True)
user_id: int | None = sqlmodel.Field(default=None, index=True)
exercise_id: int = sqlmodel.Field(foreign_key="exercises.exercise_id", index=True)
program_id: int = sqlmodel.Field(foreign_key="programs.program_id")
day_label: str = sqlmodel.Field()
logged_at: datetime = sqlmodel.Field(default_factory=datetime.now, index=True)
set_number: int = sqlmodel.Field()
reps_target: int = sqlmodel.Field()
reps_done: int = sqlmodel.Field()
weight_kg: float = sqlmodel.Field()
completed: bool = sqlmodel.Field()
class BodyWeightLog(SQLModel, table=True):
"""Daily body weight measurement."""
__tablename__ = "body_weight_logs"
log_id: int | None = sqlmodel.Field(primary_key=True)
user_id: int | None = sqlmodel.Field(default=None, index=True)
logged_at: datetime = sqlmodel.Field(default_factory=datetime.now, index=True)
weight_kg: float = sqlmodel.Field()
class RunLog(SQLModel, table=True):
"""Running session for a specific program day (same scope as strength WorkoutLog batches)."""
__tablename__ = "run_logs"
run_log_id: int | None = sqlmodel.Field(default=None, primary_key=True)
user_id: int | None = sqlmodel.Field(default=None, index=True)
program_id: int | None = sqlmodel.Field(default=None, foreign_key="programs.program_id", index=True)
day_label: str | None = sqlmodel.Field(default=None, index=True)
logged_at: datetime = sqlmodel.Field(default_factory=datetime.now, index=True)
distance_km: float = sqlmodel.Field()
duration_minutes: float = sqlmodel.Field()
notes: str = sqlmodel.Field(default="")
class PlannedMeal(SQLModel, table=True):
"""A planned meal row in tracker.db (recipe id references parser corpus)."""
__tablename__ = "planned_meals"
planned_meal_id: int | None = sqlmodel.Field(default=None, primary_key=True)
user_id: int | None = sqlmodel.Field(default=None, index=True)
plan_date: str = sqlmodel.Field(index=True)
meal_slot: str = sqlmodel.Field(index=True)
recipe_id: int | None = sqlmodel.Field(default=None, index=True)
notes: str = sqlmodel.Field(default="")
planned_add_ins: list[str] = sqlmodel.Field(
default_factory=list,
sa_column=Column(JSON),
)
class MealLogEntry(SQLModel, table=True):
"""Logged meal adherence or manual food entry."""
__tablename__ = "meal_log_entries"
meal_log_id: int | None = sqlmodel.Field(default=None, primary_key=True)
user_id: int | None = sqlmodel.Field(default=None, index=True)
logged_at: datetime = sqlmodel.Field(default_factory=datetime.now, index=True)
log_date: str = sqlmodel.Field(index=True)
meal_slot: str = sqlmodel.Field(index=True)
description: str = sqlmodel.Field()
kcal: int | None = None
protein_g: float | None = None
recipe_id: int | None = sqlmodel.Field(default=None, index=True)
source: str = sqlmodel.Field()
class RecipeSearchDefaults(BaseModel):
"""Default filters for recipe search (agent profile)."""
max_active_minutes: int | None = None
max_total_minutes: int | None = None
min_calories: int | None = None
max_calories: int | None = None
min_protein: float | None = None
search_limit: int | None = None
class AgentProfileYaml(BaseModel):
"""Optional fields loaded from agent_profile.yaml (partial)."""
pantry_user_id: int | None = None
meal_pattern: MealPattern | None = None
default_timezone: str | None = None
costco_event_label: str | None = None
costco_duration_minutes: int | None = None
weekly_climbing_sessions_target: int | None = None
calorie_policy: str | None = None
recipe_search_defaults: RecipeSearchDefaults | None = None
raw_addins_defaults: list[str] = Field(default_factory=list)
class ResolvedAgentProfile(BaseModel):
"""Merged agent profile for API and MCP (config + YAML)."""
pantry_user_id: int
active_program_id: int | None
meal_pattern: MealPattern
default_timezone: str
costco_event_label: str
costco_duration_minutes: int
weekly_climbing_sessions_target: int | None
calorie_policy: str
recipe_search_defaults: RecipeSearchDefaults
raw_addins_defaults: list[str]
parser_data_dir: str
default_calendar_id: str
chores_calendar_id: str | None
workout_calendar_id: str | None
tracker_app_url: str
class MealPlanUpsertInput(BaseModel):
plan_date: str
meal_slot: MealSlot
recipe_id: int | None = None
notes: str = ""
planned_add_ins: list[str] = Field(default_factory=list)
class MealPlanRow(BaseModel):
planned_meal_id: int
plan_date: str
meal_slot: MealSlot
recipe_id: int | None
notes: str
planned_add_ins: list[str]
class MealLogCreateInput(BaseModel):
log_date: str
meal_slot: MealSlot
description: str
kcal: int | None = None
protein_g: float | None = None
recipe_id: int | None = None
source: MealLogSource
class MealLogRow(BaseModel):
meal_log_id: int
logged_at: datetime
log_date: str
meal_slot: MealSlot
description: str
kcal: int | None
protein_g: float | None
recipe_id: int | None
source: MealLogSource