from datetime import datetime, timezone from sqlalchemy import func, select from sqlalchemy.orm import Session from app.db.models import Exercise, TrainingPlan, TrainingRecord from app.schemas.models import TrainingPlanCreateRequest, TrainingRecordCreateRequest from app.services.videos import verify_child_owner DEFAULT_EXERCISES = [ {"id": 1, "name": "肩胛稳定训练", "durationMinutes": 5}, {"id": 2, "name": "颈后肌群拉伸", "sets": 3}, ] def plan_to_dict(plan: TrainingPlan) -> dict: detail = plan.plan_detail or {} records_count = detail.get("completedDays", 0) current_day = min(plan.cycle_days, records_count + 1) return { "id": plan.id, "status": plan.status, "detail": { **detail, "goal": plan.goal, "cycleDays": plan.cycle_days, "currentDay": current_day, "childId": plan.child_id, "reportId": plan.report_id, }, "startedAt": plan.started_at.isoformat() if plan.started_at else None, "endedAt": plan.ended_at.isoformat() if plan.ended_at else None, } def build_plan_detail(body: TrainingPlanCreateRequest) -> dict: exercises = DEFAULT_EXERCISES if body.exercise_ids: exercises = [{"id": eid, "name": f"动作 #{eid}"} for eid in body.exercise_ids] return { "goal": body.goal, "cycleDays": body.cycle_days, "currentDay": 1, "completedDays": 0, "exercises": exercises, "constraints": body.constraints or {}, } def get_plan_for_user(db: Session, user_id: int, plan_id: int) -> TrainingPlan | None: plan = db.get(TrainingPlan, plan_id) if not plan or plan.status == "cancelled": return None if not verify_child_owner(db, user_id, plan.child_id): return None return plan def get_active_plan_for_child(db: Session, user_id: int, child_id: int) -> TrainingPlan | None: if not verify_child_owner(db, user_id, child_id): return None return db.scalar( select(TrainingPlan) .where(TrainingPlan.child_id == child_id, TrainingPlan.status == "active") .order_by(TrainingPlan.id.desc()) ) def list_plans_for_child(db: Session, user_id: int, child_id: int) -> list[TrainingPlan] | None: if not verify_child_owner(db, user_id, child_id): return None return list( db.scalars( select(TrainingPlan) .where(TrainingPlan.child_id == child_id, TrainingPlan.status != "cancelled") .order_by(TrainingPlan.id.desc()) .limit(10) ).all() ) def create_plan(db: Session, user_id: int, body: TrainingPlanCreateRequest) -> TrainingPlan | None: if not verify_child_owner(db, user_id, body.child_id): return None now = datetime.now(timezone.utc) plan = TrainingPlan( child_id=body.child_id, report_id=body.report_id, goal=body.goal, cycle_days=body.cycle_days, status="active", plan_detail=build_plan_detail(body), started_at=now, ) db.add(plan) db.commit() db.refresh(plan) return plan def create_record( db: Session, user_id: int, plan_id: int, body: TrainingRecordCreateRequest ) -> TrainingRecord | None: plan = get_plan_for_user(db, user_id, plan_id) if not plan: return None feedback = {"media": body.media} if body.media else None record = TrainingRecord( plan_id=plan_id, child_id=plan.child_id, exercise_id=body.exercise_id, completed=body.completed, score=body.score, duration_seconds=body.duration_seconds, feedback=feedback, note=body.note, ) db.add(record) detail = dict(plan.plan_detail or {}) detail["completedDays"] = detail.get("completedDays", 0) + (1 if body.completed else 0) detail["lastScore"] = body.score detail["lastRecordId"] = None plan.plan_detail = detail db.commit() db.refresh(record) detail["lastRecordId"] = record.id plan.plan_detail = detail db.commit() return record def ensure_default_exercises(db: Session) -> None: existing = db.scalar(select(func.count()).select_from(Exercise)) if existing: return for item in DEFAULT_EXERCISES: db.add( Exercise( name=item["name"], category="posture", target_issue="头前伸/高低肩", duration_seconds=(item.get("durationMinutes", 3) * 60), ) ) db.commit()