Spaces:
Running
Running
File size: 3,603 Bytes
d64fd55 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | from typing import Optional, Literal
from pydantic import BaseModel
from datetime import date
from enum import Enum
class TrainingPhase(str, Enum):
BASE = "base"
BUILD = "build"
PEAK = "peak"
RECOVERY = "recovery"
PLATEAU = "plateau"
class RunnerPositioning(BaseModel):
"""
Domain model for representing a runner's positioning assessment.
This is a deterministic interpretation of snapshots, trends, and goals.
"""
week_start_date: date
# Core signals
health_signal: Literal["RECOVERING", "OPTIMAL", "OVERREACHING", "UNKNOWN"]
position_status: Literal["AHEAD", "ON_TRACK", "FALLING_BEHIND", "UNKNOWN"]
goal_trajectory: Literal["IMPROVING", "STABLE", "DECLINING", "UNKNOWN"]
recommended_focus: Literal["RECOVERY", "CONSISTENCY", "INTENSITY", "MAINTENANCE"]
training_phase: TrainingPhase
# Metadata
comparison_available: bool = False
signal_strength: float = 1.0
llm_used: bool = False
summary: Optional[str] = None
@classmethod
def compute(
cls,
week_start: date,
total_distance: float,
target_distance: Optional[float],
consistency_score: float,
pace_delta: float,
hr_delta: Optional[float],
distance_delta_pct: float,
comparison_available: bool,
training_phase: TrainingPhase = TrainingPhase.BASE
) -> "RunnerPositioning":
"""
Pure deterministic logic to compute positioning signals.
No imports from outside the domain layer allowed.
"""
# ... (logic remains same, passed as arg from service)
# 1. Health Signal
if distance_delta_pct > 20.0 or (hr_delta is not None and hr_delta > 5.0):
health_signal = "OVERREACHING"
elif consistency_score < 0.6:
health_signal = "RECOVERING"
elif consistency_score > 0.8 and (hr_delta is None or hr_delta <= 0):
health_signal = "OPTIMAL"
else:
health_signal = "UNKNOWN"
# 2. Position Status (relative to goal distance)
if target_distance and target_distance > 0:
diff_pct = (total_distance - target_distance) / target_distance
if diff_pct > 0.1:
position_status = "AHEAD"
elif diff_pct < -0.1:
position_status = "FALLING_BEHIND"
else:
position_status = "ON_TRACK"
else:
position_status = "UNKNOWN"
# 3. Goal Trajectory
if pace_delta < -5.0 and consistency_score > 0.7:
goal_trajectory = "IMPROVING"
elif abs(pace_delta) <= 5.0 and consistency_score > 0.6:
goal_trajectory = "STABLE"
elif pace_delta > 5.0 or consistency_score < 0.5:
goal_trajectory = "DECLINING"
else:
goal_trajectory = "UNKNOWN"
# 4. Recommended Focus
if health_signal == "OVERREACHING":
recommended_focus = "RECOVERY"
elif consistency_score < 0.7:
recommended_focus = "CONSISTENCY"
elif health_signal == "OPTIMAL" and goal_trajectory != "DECLINING":
recommended_focus = "INTENSITY"
else:
recommended_focus = "MAINTENANCE"
return cls(
week_start_date=week_start,
health_signal=health_signal,
position_status=position_status,
goal_trajectory=goal_trajectory,
recommended_focus=recommended_focus,
comparison_available=comparison_available,
training_phase=training_phase
)
|