runner-ai-intelligence / src /domain /training /weekly_snapshot_builder.py
avfranco's picture
HF Space deploy snapshot (minimal allow-list)
d64fd55
from typing import List, Optional
from datetime import date
import uuid
from domain.training.run import Run
from domain.training.weekly_snapshot import WeeklySnapshot
class WeeklySnapshotBuilder:
"""
Pure domain component to build WeeklySnapshot objects from raw runs.
Deterministic, no side effects, no repository dependencies.
"""
def build(
self,
runner_id: uuid.UUID,
week_start_date: date,
runs: List[Run]
) -> WeeklySnapshot:
"""
Aggregates a list of runs into a weekly snapshot for a specific week.
"""
if not runs:
return WeeklySnapshot.from_metrics(
runner_id=runner_id,
week_start_date=week_start_date,
total_distance_km=0.0,
avg_pace_sec_per_km=0.0,
total_time_sec=0,
run_count=0,
consistency_score=0.0,
)
total_distance_m = sum(run.total_distance_m for run in runs)
total_duration_s = sum(run.total_duration_s for run in runs)
total_distance_km = total_distance_m / 1000.0
avg_pace_sec_per_km = 0.0
if total_distance_km > 0:
avg_pace_sec_per_km = total_duration_s / total_distance_km
hr_values = [run.avg_hr_bpm for run in runs if run.avg_hr_bpm is not None]
avg_hr = sum(hr_values) / len(hr_values) if hr_values else None
max_hr_values = [run.max_hr_bpm for run in runs if run.max_hr_bpm is not None]
max_hr = max(max_hr_values) if max_hr_values else None
# Simple consistency metric: 5 runs = 100% (min 1, max 5)
consistency_score = float(min(len(runs) * 20, 100))
return WeeklySnapshot.from_metrics(
runner_id=runner_id,
week_start_date=week_start_date,
total_distance_km=total_distance_km,
avg_pace_sec_per_km=avg_pace_sec_per_km,
total_time_sec=int(total_duration_s),
run_count=len(runs),
consistency_score=consistency_score,
avg_hr=avg_hr,
max_hr=max_hr,
)