runner-ai-intelligence / src /domain /runner_positioning.py
avfranco's picture
HF Space deploy snapshot (minimal allow-list)
557ee65
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
)