Files
2026-10-11 14:12:10 +03:00

194 lines
4.7 KiB
Python

from __future__ import annotations
from datetime import date, datetime
from typing import Any, Literal, Optional
from pydantic import BaseModel, Field, field_validator
Weekday = Literal[0, 1, 2, 3, 4, 5, 6] # Mon=0 .. Sun=6
class SetupPasswordRequest(BaseModel):
password: str = Field(min_length=4, max_length=128)
class LoginRequest(BaseModel):
password: str
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
needs_setup: bool = False
onboarded: bool = False
class StatusResponse(BaseModel):
has_password: bool
onboarded: bool
phase: str = "active"
class OnboardingRequest(BaseModel):
one_rm_kg: float = Field(gt=0)
training_weekdays: list[int] = Field(min_length=3, max_length=3)
start_date: date
@field_validator("training_weekdays")
@classmethod
def validate_days(cls, v: list[int]) -> list[int]:
if len(set(v)) != 3:
raise ValueError("Нужно выбрать 3 разных дня")
for d in v:
if d < 0 or d > 6:
raise ValueError("День недели должен быть 0–6")
sorted_days = sorted(v)
# check circular gap of at least 1 day between consecutive selected days
for i in range(3):
a = sorted_days[i]
b = sorted_days[(i + 1) % 3]
if i < 2:
gap = b - a - 1
else:
gap = (a + 7) - b - 1
if gap < 1:
raise ValueError("Между выбранными днями нужен минимум 1 день перерыва")
return sorted(v)
class ProfileResponse(BaseModel):
one_rm_kg: Optional[float]
training_weekdays: Optional[list[int]]
start_date: Optional[date]
onboarded: bool
phase: str
rest_until: Optional[datetime]
probe_available_at: Optional[datetime]
cycle_number: int
rest_countdown_seconds: Optional[int] = None
can_probe: bool = False
class SetResponse(BaseModel):
id: int
set_index: int
target_percent: Optional[float]
target_reps: Optional[int]
target_weight_kg: Optional[float]
is_failure: bool
completed: bool
actual_reps: Optional[int]
actual_weight_kg: Optional[float]
class ExerciseResponse(BaseModel):
id: int
order_index: int
name: str
kind: str
prescribed_weight_kg: Optional[float]
weight_locked: bool
needs_weight_input: bool
pending_prompt: Optional[str]
weight_step_kg: float = 1.25
sets: list[SetResponse]
class WorkoutSummary(BaseModel):
id: int
cycle_number: int
week_number: int
day_index: int
scheduled_date: date
status: str
exercise_count: int = 0
class WorkoutDetail(BaseModel):
id: int
cycle_number: int
week_number: int
day_index: int
scheduled_date: date
status: str
started_at: Optional[datetime]
paused_at: Optional[datetime]
completed_at: Optional[datetime]
current_exercise_index: int
current_set_index: int
rest_started_at: Optional[datetime]
rest_elapsed_seconds: int = 0
can_start: bool = False
can_resume: bool = False
pause_expires_at: Optional[datetime] = None
exercises: list[ExerciseResponse]
class CalendarResponse(BaseModel):
year: int
month: int
workouts: list[WorkoutSummary]
profile: ProfileResponse
class CompleteSetRequest(BaseModel):
actual_reps: Optional[int] = None
actual_weight_kg: Optional[float] = None
class SetWeightRequest(BaseModel):
weight_kg: float = Field(gt=0)
class AdjustSetRequest(BaseModel):
actual_weight_kg: float = Field(gt=0)
actual_reps: int = Field(ge=0)
class PromptChoiceRequest(BaseModel):
choice: Literal["easy_yes", "easy_no"]
weight_kg: Optional[float] = None
increase_kg: Optional[float] = None
decrease_kg: Optional[float] = None
class StartRestRequest(BaseModel):
rest_days: int = Field(ge=3, le=5)
class ProbeRequest(BaseModel):
one_rm_kg: float = Field(gt=0)
start_date: date
training_weekdays: Optional[list[int]] = None
class ExportResponse(BaseModel):
version: int = 1
exported_at: datetime
data: dict[str, Any]
class ImportRequest(BaseModel):
version: int
data: dict[str, Any]
class MessageResponse(BaseModel):
message: str
workout: Optional[WorkoutDetail] = None
profile: Optional[ProfileResponse] = None
class MoveWorkoutRequest(BaseModel):
scheduled_date: date
class TrainingDaysRequest(BaseModel):
training_weekdays: list[int] = Field(min_length=3, max_length=3)
@field_validator("training_weekdays")
@classmethod
def validate_days(cls, v: list[int]) -> list[int]:
return OnboardingRequest.validate_days(v)