v2: Program research, Groq extraction, and workout tracking (REVIEW — not deployed)

Backend:
- New models: ProgramCatalog, CatalogWorkout, CrawlQueue, WorkoutLog
- Program.py: added catalog_id, current_week, current_day columns
- program_research.py: ttp-crawler integration, Groq Llama 3.3 70B extraction, known sources map, validation
- crawl_scheduler.py: priority queue with off-peak/weekend scheduling
- catalog.py router: research, catalog browse, adopt, schedule, complete, progress endpoints
- Points: 75pts/workout, 50pts weekly bonus, 200pts completion bonus
- config.py: added groq_api_key setting
- main.py: catalog router, background scheduler, v2 migration

Frontend:
- ProgramSearch.jsx: search, research submission, catalog browsing, adopt programs
- ProgramDetail.jsx: schedule view, today's workout, completion modal, progress tracking
- App.jsx: Programs nav tab, new routes
- api.js: catalog and tracking API functions

Config:
- docker-compose.yml: GROQ_API_KEY env var

NOT DEPLOYED — requires Groq API key and Scot review
This commit is contained in:
claude 2026-04-03 16:22:53 +00:00
parent a81ca9c275
commit 93f24fff83
13 changed files with 1738 additions and 9 deletions

View file

@ -41,12 +41,29 @@ async def lifespan(app: FastAPI):
except Exception as e:
logger.warning(f"Resource seeding failed (non-fatal): {e}")
# Start background crawl queue scheduler
crawl_task = None
try:
from app.services.crawl_scheduler import crawl_queue_loop
crawl_task = asyncio.create_task(crawl_queue_loop())
logger.info("Crawl queue scheduler started")
except Exception as e:
logger.warning(f"Crawl scheduler failed to start (non-fatal): {e}")
logger.info("Fitness Rewards backend started")
yield
# Shutdown
if crawl_task:
crawl_task.cancel()
try:
await crawl_task
except asyncio.CancelledError:
pass
logger.info("Fitness Rewards backend shutting down")
app = FastAPI(title="Fitness Rewards", version="0.1.0", lifespan=lifespan)
app = FastAPI(title="Fitness Rewards", version="0.2.0", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
@ -64,6 +81,7 @@ from app.routers.food import router as food_router
from app.routers.points import router as points_router, rewards_router
from app.routers.integrations import router as integrations_router
from app.routers.programs import router as programs_router
from app.routers.catalog import router as catalog_router
app.include_router(auth_router)
app.include_router(onboarding_router)
@ -73,11 +91,12 @@ app.include_router(points_router)
app.include_router(rewards_router)
app.include_router(integrations_router)
app.include_router(programs_router)
app.include_router(catalog_router)
@app.get("/api/health")
async def health():
return {"status": "ok", "version": "0.1.0"}
return {"status": "ok", "version": "0.2.0"}
@ -87,7 +106,7 @@ async def run_migrations():
from sqlalchemy import text
async with engine.begin() as conn:
# Add equipment_list JSONB column if missing
# v1: Add equipment_list JSONB column if missing
await conn.execute(text("""
DO $$ BEGIN
ALTER TABLE user_profiles ADD COLUMN IF NOT EXISTS equipment_list JSONB DEFAULT '[]';
@ -95,6 +114,16 @@ async def run_migrations():
END $$;
"""))
# v2: Add catalog columns to programs table
await conn.execute(text("""
DO $$ BEGIN
ALTER TABLE programs ADD COLUMN IF NOT EXISTS catalog_id UUID REFERENCES program_catalog(id);
ALTER TABLE programs ADD COLUMN IF NOT EXISTS current_week INTEGER DEFAULT 1;
ALTER TABLE programs ADD COLUMN IF NOT EXISTS current_day INTEGER DEFAULT 1;
EXCEPTION WHEN undefined_table THEN NULL;
END $$;
"""))
async def seed_resources():
"""Seed the resource library with curated fitness programs."""
@ -232,4 +261,4 @@ async def seed_resources():
for r in resources:
db.add(r)
await db.commit()
await db.commit()