initial commit
This commit is contained in:
commit
39fdd43d8e
127 files changed
+13919
No files matched your search
@@ -0,0 +1,197 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import AccessoryState, Workout, WorkoutExercise, WorkoutSet
|
||||
from app.services.program import load_program
|
||||
from app.services.weights import bench_weight
|
||||
|
||||
|
||||
WEEKDAY_NAMES = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
|
||||
|
||||
|
||||
def next_dates_for_weekdays(start: date, weekdays: list[int], count: int) -> list[date]:
|
||||
"""Return `count` dates on selected weekdays starting from start (inclusive if matches)."""
|
||||
selected = set(weekdays)
|
||||
dates: list[date] = []
|
||||
cursor = start
|
||||
# safety limit
|
||||
for _ in range(400):
|
||||
if cursor.weekday() in selected:
|
||||
dates.append(cursor)
|
||||
if len(dates) >= count:
|
||||
break
|
||||
cursor += timedelta(days=1)
|
||||
if len(dates) < count:
|
||||
raise ValueError("Не удалось построить расписание")
|
||||
return dates
|
||||
|
||||
|
||||
def clear_cycle_workouts(db: Session, cycle_number: int) -> None:
|
||||
workouts = db.query(Workout).filter(Workout.cycle_number == cycle_number).all()
|
||||
for w in workouts:
|
||||
db.delete(w)
|
||||
db.query(AccessoryState).filter(AccessoryState.cycle_number == cycle_number).delete()
|
||||
db.flush()
|
||||
|
||||
|
||||
def generate_schedule(
|
||||
db: Session,
|
||||
*,
|
||||
one_rm_kg: float,
|
||||
training_weekdays: list[int],
|
||||
start_date: date,
|
||||
cycle_number: int,
|
||||
) -> list[Workout]:
|
||||
program = load_program()
|
||||
total_days = sum(len(w["days"]) for w in program["weeks"])
|
||||
dates = next_dates_for_weekdays(start_date, training_weekdays, total_days)
|
||||
|
||||
# Map chronological day_index within week order from template (day 0,1,2 of each week)
|
||||
date_idx = 0
|
||||
created: list[Workout] = []
|
||||
|
||||
for week in program["weeks"]:
|
||||
for day_index, day in enumerate(week["days"]):
|
||||
scheduled = dates[date_idx]
|
||||
date_idx += 1
|
||||
workout = Workout(
|
||||
cycle_number=cycle_number,
|
||||
week_number=week["week"],
|
||||
day_index=day_index,
|
||||
scheduled_date=scheduled,
|
||||
status="planned",
|
||||
)
|
||||
db.add(workout)
|
||||
db.flush()
|
||||
|
||||
for order_index, ex in enumerate(day["exercises"]):
|
||||
kind = ex["kind"]
|
||||
accessory_state = None
|
||||
needs_weight = False
|
||||
prescribed = None
|
||||
locked = False
|
||||
|
||||
if kind == "accessory":
|
||||
accessory_state = (
|
||||
db.query(AccessoryState)
|
||||
.filter(
|
||||
AccessoryState.cycle_number == cycle_number,
|
||||
AccessoryState.exercise_name == ex["name"],
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if accessory_state and accessory_state.locked and accessory_state.weight_kg is not None:
|
||||
prescribed = accessory_state.weight_kg
|
||||
locked = True
|
||||
elif accessory_state and accessory_state.pending_weight_kg is not None:
|
||||
prescribed = accessory_state.pending_weight_kg
|
||||
needs_weight = False
|
||||
elif accessory_state and accessory_state.weight_kg is not None and not accessory_state.locked:
|
||||
prescribed = accessory_state.weight_kg
|
||||
needs_weight = False
|
||||
else:
|
||||
needs_weight = True
|
||||
|
||||
we = WorkoutExercise(
|
||||
workout_id=workout.id,
|
||||
order_index=order_index,
|
||||
name=ex["name"],
|
||||
kind=kind,
|
||||
prescribed_weight_kg=prescribed,
|
||||
weight_locked=locked,
|
||||
needs_weight_input=needs_weight,
|
||||
)
|
||||
db.add(we)
|
||||
db.flush()
|
||||
|
||||
for set_index, s in enumerate(ex["sets"]):
|
||||
if kind == "bench":
|
||||
tw = bench_weight(one_rm_kg, s["percent"])
|
||||
ws = WorkoutSet(
|
||||
exercise_id=we.id,
|
||||
set_index=set_index,
|
||||
target_percent=s["percent"],
|
||||
target_reps=s["reps"],
|
||||
target_weight_kg=tw,
|
||||
is_failure=False,
|
||||
)
|
||||
elif kind == "failure":
|
||||
ws = WorkoutSet(
|
||||
exercise_id=we.id,
|
||||
set_index=set_index,
|
||||
target_reps=None,
|
||||
target_weight_kg=None,
|
||||
is_failure=True,
|
||||
)
|
||||
else:
|
||||
reps = s.get("reps") if isinstance(s, dict) else None
|
||||
ws = WorkoutSet(
|
||||
exercise_id=we.id,
|
||||
set_index=set_index,
|
||||
target_reps=reps,
|
||||
target_weight_kg=prescribed,
|
||||
is_failure=False,
|
||||
)
|
||||
db.add(ws)
|
||||
|
||||
created.append(workout)
|
||||
|
||||
db.flush()
|
||||
return created
|
||||
|
||||
|
||||
def apply_accessory_weight_to_future(
|
||||
db: Session,
|
||||
*,
|
||||
cycle_number: int,
|
||||
exercise_name: str,
|
||||
weight_kg: float,
|
||||
from_date: date,
|
||||
lock: bool,
|
||||
) -> None:
|
||||
state = (
|
||||
db.query(AccessoryState)
|
||||
.filter(
|
||||
AccessoryState.cycle_number == cycle_number,
|
||||
AccessoryState.exercise_name == exercise_name,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not state:
|
||||
state = AccessoryState(cycle_number=cycle_number, exercise_name=exercise_name)
|
||||
db.add(state)
|
||||
|
||||
if lock:
|
||||
state.weight_kg = weight_kg
|
||||
state.locked = True
|
||||
state.pending_weight_kg = None
|
||||
else:
|
||||
state.pending_weight_kg = weight_kg
|
||||
state.weight_kg = weight_kg
|
||||
state.locked = False
|
||||
|
||||
# Update future workouts' exercises of same name
|
||||
future = (
|
||||
db.query(WorkoutExercise)
|
||||
.join(Workout)
|
||||
.filter(
|
||||
Workout.cycle_number == cycle_number,
|
||||
Workout.scheduled_date >= from_date,
|
||||
Workout.status != "completed",
|
||||
WorkoutExercise.name == exercise_name,
|
||||
WorkoutExercise.kind == "accessory",
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for we in future:
|
||||
if we.sets and all(s.completed for s in we.sets):
|
||||
continue
|
||||
we.prescribed_weight_kg = weight_kg
|
||||
we.weight_locked = lock
|
||||
we.needs_weight_input = False
|
||||
for s in we.sets:
|
||||
if not s.completed:
|
||||
s.target_weight_kg = weight_kg
|
||||
Reference in new issue
Block a user