-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpb_engine.py
More file actions
149 lines (128 loc) · 5.09 KB
/
Copy pathpb_engine.py
File metadata and controls
149 lines (128 loc) · 5.09 KB
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""
Personal Bests Engine
======================
Recalculates all PBs from scratch by querying the workouts table.
Called after every CSV import and every C2 API sync.
PB categories (matching C2 standard):
Fixed distance : 100m, 500m, 1000m, 2000m, 5000m, 10000m
Time-based : 30min, 60min
"""
from models import db, Workout, PersonalBest
# --------------------------------------------------------------------------- #
# Category definitions #
# --------------------------------------------------------------------------- #
DISTANCE_CATEGORIES = {
"100m": 100,
"500m": 500,
"1000m": 1000,
"2000m": 2000,
"5000m": 5000,
"10000m": 10000,
}
TIME_CATEGORIES = {
"30min": 30 * 60, # seconds
"60min": 60 * 60,
}
# --------------------------------------------------------------------------- #
# Helpers #
# --------------------------------------------------------------------------- #
def _best_for_exact_distance(target_meters: int) -> Workout | None:
"""
Find the fastest workout with distance_meters exactly equal to target.
Returns the Workout with the lowest time_seconds.
"""
return (
Workout.query
.filter(
Workout.workout_type == "rower",
Workout.distance_meters == target_meters,
Workout.time_seconds.isnot(None),
)
.order_by(Workout.time_seconds.asc())
.first()
)
def _best_for_time_piece(target_seconds: int) -> Workout | None:
"""
Find the workout with time_seconds exactly equal to target and highest distance.
Returns the Workout with the greatest distance_meters.
"""
return (
Workout.query
.filter(
Workout.workout_type == "rower",
Workout.time_seconds == target_seconds,
Workout.distance_meters.isnot(None),
)
.order_by(Workout.distance_meters.desc())
.first()
)
def _upsert_pb(category: str, value_seconds: int, value_meters, workout: Workout) -> None:
"""
Insert or update the PersonalBest row for a given category.
Stores previous_value for delta display.
"""
existing = PersonalBest.query.filter_by(category=category).first()
if existing is None:
pb = PersonalBest(
category = category,
value_seconds = value_seconds,
value_meters = value_meters,
workout_id = workout.id,
achieved_date = workout.workout_date,
previous_value = None,
)
db.session.add(pb)
else:
# Only update if this is genuinely better
is_better = False
if value_meters is not None:
# Time piece: higher metres = better
is_better = (existing.value_meters is None or value_meters > existing.value_meters)
else:
# Distance piece: lower seconds = better
is_better = (existing.value_seconds is None or value_seconds < existing.value_seconds)
if is_better:
existing.previous_value = existing.value_seconds
existing.value_seconds = value_seconds
existing.value_meters = value_meters
existing.workout_id = workout.id
existing.achieved_date = workout.workout_date
# --------------------------------------------------------------------------- #
# Public API #
# --------------------------------------------------------------------------- #
def recalculate_all_pbs() -> None:
"""
Recalculate all personal bests from the workouts table.
Existing PB rows are updated in place via _upsert_pb, which preserves
previous_value for delta display when a genuinely better result is
found. Categories with no matching workout at all are removed.
Safe to call repeatedly — idempotent.
"""
matched_categories = set()
# Fixed-distance PBs
for category, meters in DISTANCE_CATEGORIES.items():
best = _best_for_exact_distance(meters)
if best is not None:
matched_categories.add(category)
_upsert_pb(
category = category,
value_seconds = best.time_seconds,
value_meters = None,
workout = best,
)
# Time-piece PBs
for category, seconds in TIME_CATEGORIES.items():
best = _best_for_time_piece(seconds)
if best is not None:
matched_categories.add(category)
_upsert_pb(
category = category,
value_seconds = seconds,
value_meters = best.distance_meters,
workout = best,
)
# Drop PBs for categories that no longer have any matching workout
stale_categories = (set(DISTANCE_CATEGORIES) | set(TIME_CATEGORIES)) - matched_categories
if stale_categories:
PersonalBest.query.filter(PersonalBest.category.in_(stale_categories)).delete(synchronize_session=False)
db.session.commit()