Code Splitting Results: - goals.py: 1339 → 655 lines (-684 lines, -51%) - Created 4 new routers: * goal_types.py (426 lines) - Goal Type Definitions CRUD * goal_progress.py (155 lines) - Progress tracking * training_phases.py (107 lines) - Training phases * fitness_tests.py (94 lines) - Fitness tests Benefits: ✅ Improved maintainability (smaller, focused files) ✅ Better context window efficiency for AI tools ✅ Clearer separation of concerns ✅ Easier testing and debugging All routers registered in main.py. Backward compatible - no API changes.
108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
"""
|
|
Training Phases Router - Training Phase Detection & Management
|
|
|
|
Endpoints for managing training phases:
|
|
- List training phases
|
|
- Create manual training phases
|
|
- Update phase status (accept/reject auto-detected phases)
|
|
|
|
Part of v9h Goal System.
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from typing import Optional
|
|
from datetime import date
|
|
|
|
from db import get_db, get_cursor, r2d
|
|
from auth import require_auth
|
|
|
|
router = APIRouter(prefix="/api/goals", tags=["training-phases"])
|
|
|
|
# ============================================================================
|
|
# Pydantic Models
|
|
# ============================================================================
|
|
|
|
class TrainingPhaseCreate(BaseModel):
|
|
"""Create training phase (manual or auto-detected)"""
|
|
phase_type: str # calorie_deficit, calorie_surplus, deload, maintenance, periodization
|
|
start_date: date
|
|
end_date: Optional[date] = None
|
|
notes: Optional[str] = None
|
|
|
|
# ============================================================================
|
|
# Endpoints
|
|
# ============================================================================
|
|
|
|
@router.get("/phases")
|
|
def list_training_phases(session: dict = Depends(require_auth)):
|
|
"""List training phases"""
|
|
pid = session['profile_id']
|
|
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
cur.execute("""
|
|
SELECT id, phase_type, detected_automatically, confidence_score,
|
|
status, start_date, end_date, duration_days,
|
|
detection_params, notes, created_at
|
|
FROM training_phases
|
|
WHERE profile_id = %s
|
|
ORDER BY start_date DESC
|
|
""", (pid,))
|
|
|
|
return [r2d(row) for row in cur.fetchall()]
|
|
|
|
@router.post("/phases")
|
|
def create_training_phase(data: TrainingPhaseCreate, session: dict = Depends(require_auth)):
|
|
"""Create training phase (manual)"""
|
|
pid = session['profile_id']
|
|
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
|
|
duration = None
|
|
if data.end_date:
|
|
duration = (data.end_date - data.start_date).days
|
|
|
|
cur.execute("""
|
|
INSERT INTO training_phases (
|
|
profile_id, phase_type, detected_automatically,
|
|
status, start_date, end_date, duration_days, notes
|
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
RETURNING id
|
|
""", (
|
|
pid, data.phase_type, False,
|
|
'active', data.start_date, data.end_date, duration, data.notes
|
|
))
|
|
|
|
phase_id = cur.fetchone()['id']
|
|
|
|
return {"id": phase_id, "message": "Trainingsphase erstellt"}
|
|
|
|
@router.put("/phases/{phase_id}/status")
|
|
def update_phase_status(
|
|
phase_id: str,
|
|
status: str,
|
|
session: dict = Depends(require_auth)
|
|
):
|
|
"""Update training phase status (accept/reject auto-detected phases)"""
|
|
pid = session['profile_id']
|
|
|
|
valid_statuses = ['suggested', 'accepted', 'active', 'completed', 'rejected']
|
|
if status not in valid_statuses:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Ungültiger Status. Erlaubt: {', '.join(valid_statuses)}"
|
|
)
|
|
|
|
with get_db() as conn:
|
|
cur = get_cursor(conn)
|
|
cur.execute(
|
|
"UPDATE training_phases SET status = %s WHERE id = %s AND profile_id = %s",
|
|
(status, phase_id, pid)
|
|
)
|
|
|
|
if cur.rowcount == 0:
|
|
raise HTTPException(status_code=404, detail="Trainingsphase nicht gefunden")
|
|
|
|
return {"message": "Status aktualisiert"}
|