File size: 3,829 Bytes
557ee65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
103
104
105
106
107
108
109
110
from typing import List, Optional, Literal
from datetime import date, timedelta, datetime
import uuid
from domain.training.run import Run
from domain.training.planned_session import PlannedSession
from domain.runner.goal import Goal
from domain.runner.profile import RunnerProfile


def match_run_to_session(run: Run, sessions: List[PlannedSession]) -> Optional[uuid.UUID]:
    """
    Matches a run to a planned session based on date and distance.
    Rules:
    - abs(run_date - planned_date) <= 1 day
    - run_distance >= 0.8 * target_distance
    - Match only one session (first match wins)
    """
    run_date = run.start_time.date()
    run_dist_km = run.total_distance_m / 1000.0

    for session in sessions:
        if session.completed_run_id is not None:
            continue

        date_diff = abs((run_date - session.planned_date).days)
        if date_diff <= 1 and run_dist_km >= (0.8 * session.target_distance_km):
            return session.id

    return None


def classify_week(sessions: List[PlannedSession], current_volume: float, goal_volume: float) -> str:
    """
    Classifies the week's structure based on completed sessions.
    Returns canonical string: strong_week, structured_but_light, rebuild_week, reset_week.
    """
    if not sessions:
        return "reset_week"

    long_run_completed = any(s.session_type == "long_run" and s.completed_run_id for s in sessions)
    weekday_sessions = [s for s in sessions if s.session_type == "weekday"]
    weekdays_completed = sum(1 for s in weekday_sessions if s.completed_run_id)

    # Heuristics
    if long_run_completed:
        if weekdays_completed >= 2 or current_volume >= goal_volume:
            return "strong_week"
        else:
            return "structured_but_light"
    else:
        if weekdays_completed >= 2 or (current_volume > 0 and current_volume >= 0.5 * goal_volume):
            return "rebuild_week"
        else:
            return "reset_week"


def generate_week_template(
    runner_id: uuid.UUID,
    week_start: date,
    goal: Optional[Goal],
    profile: Optional[RunnerProfile],
    previous_sessions: List[PlannedSession] = None,
) -> List[PlannedSession]:
    """
    Generates a week template (list of PlannedSession).
    Logic:
    - Default 3 weekday + 1 long run
    - Long run distance based on goal (e.g. 30–40% weekly volume)
    - Weekday runs evenly distributed
    - Respect baseline weekly distance
    """
    # 1. Determine Target Weekly Volume
    baseline = profile.baseline_weekly_km if (profile and profile.baseline_weekly_km is not None) else 20.0
    target_volume = baseline
    if goal and goal.type == "volume" and goal.target_value is not None:
        target_volume = goal.target_value

    # Simple growth logic if previous week was successful (optional/not requested but good to have)
    # For now, stay simple as per requirements.

    # 2. Distribute Volume
    long_run_dist = target_volume * 0.35
    weekday_total = target_volume - long_run_dist
    weekday_count = 3
    weekday_dist = weekday_total / weekday_count

    # 3. Assign Dates
    # Mon (0), Tue (1), Wed (2), Thu (3), Fri (4), Sat (5), Sun (6)
    # We'll use Tue, Thu, Fri for weekdays and Sun for long run
    template_dates = {
        1: ("weekday", weekday_dist),
        3: ("weekday", weekday_dist),
        4: ("weekday", weekday_dist),
        6: ("long_run", long_run_dist),
    }

    sessions = []
    for day_offset, (s_type, dist) in template_dates.items():
        sessions.append(
            PlannedSession(
                runner_id=runner_id,
                week_start_date=week_start,
                session_type=s_type,
                planned_date=week_start + timedelta(days=day_offset),
                target_distance_km=round(dist, 1),
            )
        )

    return sessions