This commit is contained in:
xds
2026-03-16 15:43:20 +03:00
parent 002e4cca31
commit 1d76f29244
14 changed files with 546 additions and 89 deletions

View File

@@ -25,6 +25,7 @@ from backend.app.services.zones import calculate_power_zones, calculate_hr_zones
from backend.app.services.power_curve import calculate_power_curve
from backend.app.services.intervals import detect_intervals
from backend.app.services.ai_summary import generate_summary
from backend.app.services.coaching import link_activity_to_plan
router = APIRouter()
@@ -48,31 +49,38 @@ async def upload_activity(
file_path.write_bytes(content)
# 1. Parse FIT
activity, data_points = parse_fit_file(content, rider.id, str(file_path))
activity, data_points, exercise_sets = parse_fit_file(content, rider.id, str(file_path))
if exercise_sets:
activity.exercise_sets = exercise_sets
# Auto-link to training plan
await link_activity_to_plan(activity, rider.id, session)
session.add(activity)
await session.flush()
# 2. Save data points
for dp in data_points:
dp.activity_id = activity.id
session.add_all(data_points)
# 2. Save data points (if any — strength workouts may have none)
if data_points:
for dp in data_points:
dp.activity_id = activity.id
session.add_all(data_points)
# 3. Calculate & save metrics (with FTP if available)
metrics = calculate_metrics(data_points, activity, ftp=rider.ftp)
if metrics:
session.add(metrics)
# 3. Calculate & save metrics (with FTP if available)
metrics = calculate_metrics(data_points, activity, ftp=rider.ftp)
if metrics:
session.add(metrics)
# 4. Detect & save intervals
intervals = detect_intervals(data_points, ftp=rider.ftp)
for interval in intervals:
interval.activity_id = activity.id
session.add_all(intervals)
# 4. Detect & save intervals
intervals = detect_intervals(data_points, ftp=rider.ftp)
for interval in intervals:
interval.activity_id = activity.id
session.add_all(intervals)
# 5. Calculate & save power curve
curve_data = calculate_power_curve(data_points)
if curve_data:
pc = PowerCurve(activity_id=activity.id, curve_data=curve_data)
session.add(pc)
# 5. Calculate & save power curve
curve_data = calculate_power_curve(data_points)
if curve_data:
pc = PowerCurve(activity_id=activity.id, curve_data=curve_data)
session.add(pc)
await session.commit()
await session.refresh(activity)

View File

@@ -9,6 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.core.auth import get_current_rider
from backend.app.core.database import get_session
from backend.app.models.activity import Activity
from backend.app.models.coaching import CoachingChat
from backend.app.models.rider import Rider
from backend.app.models.training import TrainingPlan
@@ -243,11 +244,74 @@ async def get_today(
rider: Rider = Depends(get_current_rider),
session: AsyncSession = Depends(get_session),
):
"""Get today's planned workout."""
"""Get today's planned workout with linked activity if any."""
workout = await get_today_workout(rider, session)
if not workout:
return None
# Check if there's already a linked activity for today
linked_query = (
select(Activity)
.where(Activity.training_plan_id == uuid.UUID(workout["plan_id"]))
.where(Activity.plan_week == workout["week_number"])
.where(Activity.plan_day == workout["day"])
)
linked_result = await session.execute(linked_query)
linked = linked_result.scalar_one_or_none()
workout["linked_activity_id"] = str(linked.id) if linked else None
workout["completed"] = linked is not None
return workout
# --- Activity-Plan linking ---
class LinkRequest(BaseModel):
activity_id: str
plan_id: str
week: int
day: str
@router.post("/link")
async def link_activity(
body: LinkRequest,
rider: Rider = Depends(get_current_rider),
session: AsyncSession = Depends(get_session),
):
"""Manually link an activity to a planned workout day."""
activity = await session.get(Activity, uuid.UUID(body.activity_id))
if not activity or activity.rider_id != rider.id:
raise HTTPException(status_code=404, detail="Activity not found")
plan = await session.get(TrainingPlan, uuid.UUID(body.plan_id))
if not plan or plan.rider_id != rider.id:
raise HTTPException(status_code=404, detail="Plan not found")
activity.training_plan_id = plan.id
activity.plan_week = body.week
activity.plan_day = body.day
await session.commit()
return {"ok": True}
@router.post("/unlink/{activity_id}")
async def unlink_activity(
activity_id: uuid.UUID,
rider: Rider = Depends(get_current_rider),
session: AsyncSession = Depends(get_session),
):
"""Remove link between activity and planned workout."""
activity = await session.get(Activity, activity_id)
if not activity or activity.rider_id != rider.id:
raise HTTPException(status_code=404, detail="Activity not found")
activity.training_plan_id = None
activity.plan_week = None
activity.plan_day = None
await session.commit()
return {"ok": True}
# --- Adjustment chat ---
@router.post("/plan/adjust")