21 lines
3.5 KiB
HTML
21 lines
3.5 KiB
HTML
{% extends 'base.html' %}
|
||
{% block title %}Running fitness · Stride{% endblock %}
|
||
{% block head %}<link rel="stylesheet" href="{{ url_for('static', filename='fitness.css') }}">{% endblock %}
|
||
{% block content %}
|
||
<div class="fitness-page">
|
||
<span class="eyebrow">YOUR TRAINING OVER TIME</span><h1>Running fitness</h1>
|
||
<p>Accumulated running effort, fatigue and form. Scores describe training load, alongside your performance trends.</p>
|
||
<p>{{ data.zones.source }} · <a href="{{ url_for('web.settings') }}">Edit fitness zones</a></p>
|
||
<p>{% for label in data.zones.labels %}Z{{ loop.index }}: {{ label }} bpm{{ ' · ' if not loop.last }}{% endfor %}</p>
|
||
{% if data.series %}
|
||
{% set latest = data.series[-1] %}
|
||
<div class="stats-grid">{% for key in ['fitness', 'fatigue', 'form'] %}<article class="stat-card"><div class="stat-label">{{ key|upper }}</div><div class="stat-value">{{ '%.1f'|format(latest[key]) }}</div><p>{{ 'Provisional estimate' if latest.provisional else 'Running load estimate' }}</p></article>{% endfor %}</div>
|
||
<section class="panel"><div class="section-head"><h2>Fitness, fatigue & form</h2><label>Show <select id="fitness-range"><option value="90">90 days</option><option value="365">1 year</option><option value="all" selected>All history</option></select></label></div><div class="chart-wrap"><canvas id="fitness-chart" role="img" aria-label="Daily running fitness, fatigue and form"></canvas></div></section>
|
||
{% endif %}
|
||
<p>{{ data.runs|length }} historical runs · {{ data.unscored }} unscored. Runs missing sufficient HR remain unscored. On those days the model uses only known effort, so subsequent values may underestimate load.</p>
|
||
<details class="panel methodology"><summary>How these scores work</summary><p>Effort v1 adds recorded moving minutes in Z1–Z5 with weights 1, 2, 3, 4 and 5. At each boundary HR belongs to the higher zone. At least 80% of timer time must have usable HR; missing time is not extrapolated. Recording breaks, stops and suspect HR are excluded by the recording analysis.</p><p>Each calendar day in your timezone updates fitness by (daily effort − previous fitness) × (1 − exp(−1/42)); fatigue uses 7 instead of 42. Form is same-day fitness minus fatigue. Multiple runs are summed; rest days contribute zero. The full history is calculated before choosing the visible range.</p><p>The model starts at zero at your earliest imported run; its first 126 days are provisional. Missing runs and unscored sessions can understate load. Only running is included. Changing HR zones recalculates all history with those zones. These are Stride estimates, with their own scale, rather than exact Strava scores.</p></details>
|
||
<section class="panel"><div class="section-head"><h2>Run effort</h2></div><div class="activity-table-wrap"><table><thead><tr><th>Date</th><th>Run</th><th>Effort</th><th>HR coverage</th></tr></thead><tbody>{% for run in data.runs %}<tr><td>{{ run.day }}</td><td><a href="{{ url_for('web.activity', activity_id=run.id) }}">{{ run.title }}</a></td><td title="{{ run.reason }}">{{ '%.1f'|format(run.score) if run.score is not none else 'Unscored' }}</td><td>{{ run.coverage }}%</td></tr>{% else %}<tr><td colspan="4">Import runs to build your fitness history.</td></tr>{% endfor %}</tbody></table></div></section>
|
||
</div>
|
||
{% endblock %}
|
||
{% block scripts %}<script id="fitness-data" type="application/json" data-url="{{ url_for('training.fitness_data') }}">{{ data|tojson }}</script><script src="{{ url_for('static', filename='vendor/chart.umd.js') }}"></script><script src="{{ url_for('static', filename='fitness.js') }}"></script>{% endblock %}
|