Add supplements, kettlebell, calendar, push notifications, and PWA support

- Supplement tracking: CRUD endpoints, /today, /logs, Supplements page
- Kettlebell workouts: session tracking, analytics endpoint, ActiveSession page
- Calendar module: events CRUD, calendar components
- Push notifications: VAPID keys, PushSubscription model, APScheduler reminders,
  service worker with push/notificationclick handlers, Profile notifications UI
- PWA: vite-plugin-pwa, manifest, icons, service worker generation
- Frontend: TypeScript types, API modules, ConfirmModal, toast notifications
- Auth fixes: password hashing, nutrition endpoint auth
- CLAUDE.md: project documentation and development guide

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-03-20 18:57:03 -06:00
parent bd91eb4171
commit f279907ae3
61 changed files with 9256 additions and 85 deletions

View File

@@ -0,0 +1,57 @@
import dspy
from pydantic import BaseModel, Field
class ExerciseBlock(BaseModel):
order: int = Field(description="Position in workout sequence")
name: str = Field(description="Exercise name")
description: str = Field(description="Brief description of the movement")
sets: int = Field(description="Number of sets")
reps: int = Field(description="Number of reps per set (0 if timed)")
duration_seconds: int = Field(description="Duration in seconds per set (0 if rep-based)")
weight_kg: float = Field(description="Prescribed weight in kg")
rest_seconds: int = Field(description="Rest time between sets in seconds")
coaching_tip: str = Field(description="Key coaching cue for this exercise")
class KettlebellSessionOutput(BaseModel):
reasoning: str = Field(description="Step-by-step reasoning for session design choices")
title: str = Field(description="Session title")
focus: str = Field(description="Session focus e.g. strength, conditioning, mobility")
total_duration_min: int = Field(description="Estimated total workout duration in minutes")
difficulty: str = Field(description="Difficulty level: beginner, intermediate, advanced")
exercises: list[ExerciseBlock] = Field(description="Ordered list of exercises in the session")
notes: str = Field(description="Coaching notes and any special instructions for the session")
class GenerateKettlebellSession(dspy.Signature):
"""Generate a personalized kettlebell workout session based on user profile and preferences.
Think step-by-step: assess user fitness level, pick movements appropriate to the focus and
difficulty, assign weights respecting progressive overload principles from available weights,
sequence exercises for proper warm-up and fatigue management, and ensure total work time
(sets × reps/duration + rest periods) fits within the requested duration.
"""
user_profile: str = dspy.InputField(desc="User details including age, weight, fitness level, and goals")
available_weights_kg: str = dspy.InputField(desc="Comma-separated list of available kettlebell weights in kg")
focus: str = dspy.InputField(desc="Session focus: strength, conditioning, mobility, fat loss, etc.")
duration_minutes: int = dspy.InputField(desc="Target session duration in minutes")
session: KettlebellSessionOutput = dspy.OutputField(desc="Complete structured kettlebell session")
class KettlebellModule(dspy.Module):
def __init__(self):
super().__init__()
self.generate = dspy.ChainOfThought(GenerateKettlebellSession)
def forward(self, user_profile: str, available_weights_kg: str, focus: str, duration_minutes: int):
return self.generate(
user_profile=user_profile,
available_weights_kg=available_weights_kg,
focus=focus,
duration_minutes=duration_minutes,
)
kettlebell_module = KettlebellModule()