feat: add Phase 5 Progression & Analytics — smart suggestions, dashboard, schedule

Add auto-progression engine (ProgressionService) with rep increase, weight
increase, deload, and felt-easy acceleration rules. Add AnalyticsService for
user stats, exercise progress charts, and volume-by-day data. New dashboard
and schedule routes with Chart.js visualizations. Progression badges shown
inline on workout day view. Navigation updated with Dashboard and Schedule links.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 12:26:23 -06:00
parent e35b78ae87
commit 134542b66f
19 changed files with 1209 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}Dashboard -- SneakySwole{% endblock %}
{% block head_extra %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
{% endblock %}
{% block content %}
<hgroup>
<h1>Progress Dashboard</h1>
{% if active_profile %}
<p>{{ active_profile.display_name }}'s training overview</p>
{% else %}
<p>No profile selected -- <a href="/profiles">select one</a></p>
{% endif %}
</hgroup>
{% if stats %}
<!-- Summary Stats -->
<div class="grid">
{% include "partials/stats_card.html" %}
</div>
<!-- Volume by Day Chart -->
<article>
<header><h3>Volume by Workout Day</h3></header>
{% include "partials/volume_chart.html" %}
</article>
<!-- Exercise Progress Links -->
<article>
<header><h3>Per-Exercise Progress</h3></header>
<ul>
{% for exercise in exercises %}
<li>
<a href="/dashboard/exercise/{{ exercise.id }}">
{{ exercise.name }}
</a>
<small> -- {{ exercise.workout_day }} Day</small>
</li>
{% endfor %}
</ul>
</article>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,48 @@
{% extends "base.html" %}
{% block title %}{{ exercise.name }} Progress -- SneakySwole{% endblock %}
{% block head_extra %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
{% endblock %}
{% block content %}
<hgroup>
<h1>{{ exercise.name }}</h1>
<p>{{ exercise.muscle_group }} | {{ exercise.workout_day }} Day</p>
</hgroup>
<!-- Progression Suggestion -->
{% if suggestion %}
<article>
<header><h3>Next Workout Suggestion</h3></header>
<p>{{ suggestion.message }}</p>
<div class="grid">
<div>
<small>Suggested Reps</small>
<p style="font-size:1.5rem; font-weight:700;">
{{ suggestion.suggested_reps }}
</p>
</div>
<div>
<small>Suggested Weight</small>
<p style="font-size:1.5rem; font-weight:700;">
{{ suggestion.suggested_weight }}
</p>
</div>
<div>
<small>Progression Type</small>
<p><mark>{{ suggestion.progression_type }}</mark></p>
</div>
</div>
</article>
{% endif %}
<!-- Progress Chart -->
<article>
<header><h3>Rep and Weight Trends</h3></header>
{% include "partials/progress_chart.html" %}
</article>
<a href="/dashboard" role="button" class="outline">Back to Dashboard</a>
{% endblock %}

View File

@@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block title %}4-Week Schedule -- SneakySwole{% endblock %}
{% block content %}
<hgroup>
<h1>4-Week Schedule</h1>
{% if active_profile %}
<p>Schedule for: <strong>{{ active_profile.display_name }}</strong></p>
{% else %}
<p>No profile selected -- <a href="/profiles">select one</a></p>
{% endif %}
</hgroup>
{% for week in weeks %}
<article>
<header>
<h3>Week {{ week.week_number }}</h3>
</header>
<div class="grid">
{% for day in week.days %}
<div style="text-align:center;
padding:1rem;
border-radius:0.5rem;
{% if day.is_today %}
border: 2px solid var(--pico-primary);
{% endif %}
{% if day.is_completed %}
background: rgba(99, 102, 241, 0.15);
{% endif %}">
<strong>{{ day.workout_day.name }}</strong>
<br>
<small>{{ day.date.strftime('%b %d') }}</small>
{% if day.is_completed %}
<br><mark>Done</mark>
{% endif %}
{% if day.is_today %}
<br><small><strong>Today</strong></small>
{% endif %}
<br>
<a href="/workouts/{{ day.workout_day.name|lower|replace(' ', '-') }}"
style="font-size:0.8rem;">
Start
</a>
</div>
{% endfor %}
</div>
</article>
{% endfor %}
{% endblock %}