Compare commits
2 Commits
eada1a829b
...
78f4a53c49
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78f4a53c49 | ||
|
|
e156dd30cc |
89
db.py
89
db.py
@@ -466,6 +466,95 @@ class DataBase():
|
||||
|
||||
return result[0]['earliest_date'], result[0]['latest_date']
|
||||
|
||||
def get_topset_achievements(self, topset_id):
|
||||
# 1. Fetch current topset details
|
||||
current = self.execute("""
|
||||
SELECT
|
||||
t.weight, t.repetitions, t.exercise_id, w.person_id, w.start_date, w.workout_id,
|
||||
ROUND((100 * t.weight::NUMERIC::INTEGER) / (101.3 - 2.67123 * t.repetitions), 0)::NUMERIC::INTEGER AS estimated_1rm
|
||||
FROM topset t
|
||||
JOIN workout w ON t.workout_id = w.workout_id
|
||||
WHERE t.topset_id = %s
|
||||
""", [topset_id], one=True)
|
||||
|
||||
if not current:
|
||||
return {}
|
||||
|
||||
person_id = current['person_id']
|
||||
exercise_id = current['exercise_id']
|
||||
current_date = current['start_date']
|
||||
current_weight = current['weight']
|
||||
current_reps = current['repetitions']
|
||||
current_e1rm = current['estimated_1rm']
|
||||
|
||||
# 2. Fetch "Last Time" (previous workout's best set for this exercise)
|
||||
last_set = self.execute("""
|
||||
SELECT t.weight, t.repetitions
|
||||
FROM topset t
|
||||
JOIN workout w ON t.workout_id = w.workout_id
|
||||
WHERE w.person_id = %s AND t.exercise_id = %s AND w.start_date < %s
|
||||
ORDER BY w.start_date DESC, (100 * t.weight::NUMERIC::INTEGER) / (101.3 - 2.67123 * t.repetitions) DESC
|
||||
LIMIT 1
|
||||
""", [person_id, exercise_id, current_date], one=True)
|
||||
|
||||
# 3. Fetch All-Time Bests (strictly before current workout)
|
||||
best_stats = self.execute("""
|
||||
SELECT
|
||||
MAX(t.weight) as max_weight,
|
||||
MAX(ROUND((100 * t.weight::NUMERIC::INTEGER) / (101.3 - 2.67123 * t.repetitions), 0)) as max_e1rm,
|
||||
MAX(t.repetitions) FILTER (WHERE t.weight >= %s) as max_reps_at_weight
|
||||
FROM topset t
|
||||
JOIN workout w ON t.workout_id = w.workout_id
|
||||
WHERE w.person_id = %s AND t.exercise_id = %s AND w.start_date < %s
|
||||
""", [current_weight, person_id, exercise_id, current_date], one=True)
|
||||
|
||||
achievements = {
|
||||
'is_pr_weight': False,
|
||||
'is_pr_e1rm': False,
|
||||
'is_pr_reps': False,
|
||||
'weight_increase': 0,
|
||||
'rep_increase': 0,
|
||||
'stalled_sessions': 0
|
||||
}
|
||||
|
||||
# Calculate PRs
|
||||
if best_stats:
|
||||
if best_stats['max_weight'] and current_weight > best_stats['max_weight']:
|
||||
achievements['is_pr_weight'] = True
|
||||
if best_stats['max_e1rm'] and current_e1rm > best_stats['max_e1rm']:
|
||||
achievements['is_pr_e1rm'] = True
|
||||
if best_stats['max_reps_at_weight'] and current_reps > best_stats['max_reps_at_weight']:
|
||||
achievements['is_pr_reps'] = True
|
||||
|
||||
# Calculate Stalled Sessions
|
||||
# Count consecutive previous workouts for this exercise where weight and reps were identical to current
|
||||
previous_sets = self.execute("""
|
||||
SELECT t.weight, t.repetitions
|
||||
FROM topset t
|
||||
JOIN workout w ON t.workout_id = w.workout_id
|
||||
WHERE w.person_id = %s AND t.exercise_id = %s AND w.start_date < %s
|
||||
ORDER BY w.start_date DESC
|
||||
""", [person_id, exercise_id, current_date])
|
||||
|
||||
stalled_count = 0
|
||||
for s in previous_sets:
|
||||
if s['weight'] == current_weight and s['repetitions'] == current_reps:
|
||||
stalled_count += 1
|
||||
else:
|
||||
break
|
||||
|
||||
if stalled_count >= 1: # If it's the same as at least the previous session
|
||||
achievements['stalled_sessions'] = stalled_count
|
||||
|
||||
# Calculate Increases vs Last Time
|
||||
if last_set:
|
||||
if current_weight > last_set['weight']:
|
||||
achievements['weight_increase'] = current_weight - last_set['weight']
|
||||
elif current_weight == last_set['weight'] and current_reps > last_set['repetitions']:
|
||||
achievements['rep_increase'] = current_reps - last_set['repetitions']
|
||||
|
||||
return achievements
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -179,6 +179,12 @@ def get_workout_start_date(person_id, workout_id):
|
||||
start_date = workout.get('start_date') if workout else None
|
||||
return render_template('partials/start_date.html', person_id=person_id, workout_id=workout_id, start_date=start_date)
|
||||
|
||||
@workout_bp.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>/achievements", methods=['GET'])
|
||||
@validate_topset
|
||||
def get_topset_achievements(person_id, workout_id, topset_id):
|
||||
achievements = db.get_topset_achievements(topset_id)
|
||||
return render_template('partials/achievement_badges.html', achievements=achievements)
|
||||
|
||||
@workout_bp.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>", methods=['GET'])
|
||||
@validate_topset
|
||||
def get_topset(person_id, workout_id, topset_id):
|
||||
@@ -216,7 +222,7 @@ def update_topset(person_id, workout_id, topset_id):
|
||||
weight = request.form.get("weight")
|
||||
db.update_topset(exercise_id, repetitions, weight, topset_id)
|
||||
exercise = db.get_exercise(exercise_id)
|
||||
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=topset_id, exercise_name=exercise.get('name'), repetitions=repetitions, weight=weight)
|
||||
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=topset_id, exercise_id=exercise_id, exercise_name=exercise.get('name'), repetitions=repetitions, weight=weight)
|
||||
|
||||
@workout_bp.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>/delete", methods=['DELETE'])
|
||||
@login_required
|
||||
|
||||
File diff suppressed because one or more lines are too long
35
templates/partials/achievement_badges.html
Normal file
35
templates/partials/achievement_badges.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{% if achievements %}
|
||||
{% if achievements.is_pr_weight or achievements.is_pr_e1rm or achievements.is_pr_reps %}
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-gradient-to-r from-yellow-100 to-amber-200 px-2.5 py-0.5 text-xs font-bold text-amber-900 shadow-sm ring-1 ring-inset ring-amber-500/30 whitespace-nowrap"
|
||||
title="Personal Record">
|
||||
<svg class="mr-1 h-3 w-3 text-amber-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</svg>
|
||||
PR
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if achievements.weight_increase > 0 %}
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-bold text-green-800 shadow-sm ring-1 ring-inset ring-green-500/30 whitespace-nowrap"
|
||||
title="Weight increase vs last time">
|
||||
+{{ achievements.weight_increase }}kg
|
||||
</span>
|
||||
{% elif achievements.rep_increase > 0 %}
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-bold text-blue-800 shadow-sm ring-1 ring-inset ring-blue-500/30 whitespace-nowrap"
|
||||
title="Rep increase at same weight vs last time">
|
||||
+{{ achievements.rep_increase }} reps
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if achievements.stalled_sessions >= 1 %}
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-600 shadow-sm ring-1 ring-inset ring-slate-400/20 whitespace-nowrap"
|
||||
title="Weight and reps matched for {{ achievements.stalled_sessions + 1 }} sessions total">
|
||||
Stalled ({{ achievements.stalled_sessions + 1 }}x)
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -31,9 +31,14 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="p-4 whitespace-nowrap text-sm font-semibold text-gray-900">
|
||||
<td class="p-4 text-sm font-semibold text-gray-900">
|
||||
{% if is_edit|default(false, true) == false %}
|
||||
{{ repetitions }} x {{ weight }}kg
|
||||
<div class="flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||
<span class="whitespace-nowrap">{{ repetitions }} x {{ weight }}kg</span>
|
||||
<div hx-get="{{ url_for('workout.get_topset_achievements', person_id=person_id, workout_id=workout_id, topset_id=topset_id) }}"
|
||||
hx-trigger="load" hx-target="this" hx-swap="innerHTML" class="flex flex-wrap items-center gap-1">
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="flex items-center flex-col sm:flex-row">
|
||||
<input type="number"
|
||||
|
||||
Reference in New Issue
Block a user