initial commit
This commit is contained in:
commit
39fdd43d8e
127 files changed
+13919
No files matched your search
@@ -0,0 +1,114 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
|
||||
def _add(d: date, days: int) -> date:
|
||||
return d + timedelta(days=days)
|
||||
|
||||
|
||||
def _step(d: date, weekdays: list[int], direction: int) -> date:
|
||||
selected = set(weekdays)
|
||||
cursor = _add(d, direction)
|
||||
for _ in range(28):
|
||||
if cursor.weekday() in selected:
|
||||
return cursor
|
||||
cursor = _add(cursor, direction)
|
||||
raise HTTPException(400, "Не удалось найти тренировочный день")
|
||||
|
||||
|
||||
def _on_or_after(d: date, weekdays: list[int]) -> date:
|
||||
if d.weekday() in set(weekdays):
|
||||
return d
|
||||
return _step(_add(d, -1), weekdays, 1)
|
||||
|
||||
|
||||
def _order(week: int, day_index: int) -> int:
|
||||
return week * 10 + day_index
|
||||
|
||||
|
||||
def _next_training(after: date, weekdays: list[int], taken: set[date]) -> date:
|
||||
value = _step(after, weekdays, 1)
|
||||
while value in taken:
|
||||
value = _step(value, weekdays, 1)
|
||||
return value
|
||||
|
||||
|
||||
def _place(desired: date, previous: date | None, weekdays: list[int], taken: set[date]) -> date:
|
||||
value = desired
|
||||
if previous is not None and value <= previous:
|
||||
value = _next_training(previous, weekdays, taken)
|
||||
while value in taken:
|
||||
value = _next_training(value, weekdays, taken)
|
||||
return value
|
||||
|
||||
|
||||
def plan_move(workouts: list, moved_id: int, new_date: date, weekdays: list[int]) -> dict[int, date]:
|
||||
moved = next((w for w in workouts if w.id == moved_id), None)
|
||||
if moved is None:
|
||||
raise HTTPException(404, "Тренировка не найдена")
|
||||
if moved.status == "completed":
|
||||
raise HTTPException(400, "Выполненную тренировку переносить нельзя")
|
||||
if new_date < date.today():
|
||||
raise HTTPException(400, "В закрытый период переносить нельзя")
|
||||
|
||||
sorted_items = sorted(workouts, key=lambda w: (_order(w.week_number, w.day_index), w.id))
|
||||
completed = [w for w in sorted_items if w.status == "completed"]
|
||||
if any(w.scheduled_date == new_date for w in completed):
|
||||
raise HTTPException(400, "На эту дату уже есть выполненная тренировка")
|
||||
|
||||
moved_order = _order(moved.week_number, moved.day_index)
|
||||
earlier = [w for w in completed if _order(w.week_number, w.day_index) < moved_order]
|
||||
later = [w for w in completed if _order(w.week_number, w.day_index) > moved_order]
|
||||
if earlier and new_date <= earlier[-1].scheduled_date:
|
||||
raise HTTPException(400, "Дата должна быть позже уже выполненной тренировки")
|
||||
if later and new_date >= later[0].scheduled_date:
|
||||
raise HTTPException(400, "Дата должна быть раньше уже выполненной следующей тренировки")
|
||||
|
||||
taken = {w.scheduled_date for w in completed}
|
||||
assigned: dict[int, date] = {}
|
||||
index = 0
|
||||
while index < len(sorted_items):
|
||||
if sorted_items[index].status == "completed":
|
||||
index += 1
|
||||
continue
|
||||
start = index
|
||||
while index < len(sorted_items) and sorted_items[index].status != "completed":
|
||||
index += 1
|
||||
segment = sorted_items[start:index]
|
||||
prev_anchor = sorted_items[start - 1] if start > 0 else None
|
||||
next_anchor = sorted_items[index] if index < len(sorted_items) else None
|
||||
proposed = sorted(new_date if item.id == moved_id else item.scheduled_date for item in segment)
|
||||
dates: list[date] = []
|
||||
for raw in proposed:
|
||||
value = _place(raw, dates[-1] if dates else None, weekdays, taken)
|
||||
if prev_anchor is not None and value <= prev_anchor.scheduled_date:
|
||||
raise HTTPException(400, "Дата должна быть позже уже выполненной тренировки")
|
||||
if next_anchor is not None and value >= next_anchor.scheduled_date:
|
||||
raise HTTPException(400, "Дата должна быть раньше уже выполненной следующей тренировки")
|
||||
dates.append(value)
|
||||
taken.add(value)
|
||||
for item, value in zip(segment, dates):
|
||||
assigned[item.id] = value
|
||||
|
||||
return assigned
|
||||
|
||||
|
||||
def plan_weekday_change(workouts: list, weekdays: list[int], start_from: date) -> dict[int, date]:
|
||||
open_items = sorted(
|
||||
(w for w in workouts if w.status != "completed"),
|
||||
key=lambda w: _order(w.week_number, w.day_index),
|
||||
)
|
||||
taken = {w.scheduled_date for w in workouts if w.status == "completed"}
|
||||
assigned: dict[int, date] = {}
|
||||
cursor = start_from
|
||||
for item in open_items:
|
||||
date_value = _on_or_after(cursor, weekdays)
|
||||
while date_value in taken:
|
||||
date_value = _step(date_value, weekdays, 1)
|
||||
assigned[item.id] = date_value
|
||||
taken.add(date_value)
|
||||
cursor = _add(date_value, 1)
|
||||
return assigned
|
||||
Reference in new issue
Block a user