Files
workout/scripts/preview_forecasts.py
T

73 lines
3.7 KiB
Python

"""Local, read-only UI preview using Peter's production sets in memory."""
from pathlib import Path
import sys
from datetime import date
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from flask import Flask, Blueprint, request, render_template, render_template_string
from dateutil.relativedelta import relativedelta
from scripts.audit_forecasts import load_person
from forecasting import e1rm
from utils import get_exercise_graph_model
root = Path(__file__).resolve().parents[1]
app = Flask(__name__, template_folder=str(root / 'templates'), static_folder=str(root / 'static'))
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.jinja_env.filters['strftime'] = lambda d: d.strftime('%d %b %Y')
data = load_person('Peter')
workout = Blueprint('workout', __name__)
def model(exercise_id):
(key, all_rows) = next((k,r) for k,r in data.items() if k[0] == exercise_id)
epoch = request.args.get('epoch', 'All')
minimum, maximum = request.args.get('min_date'), request.args.get('max_date')
minimum = date.fromisoformat(minimum) if minimum else None
maximum = date.fromisoformat(maximum) if maximum else None
if epoch in ('1M','3M','6M'):
minimum = date.today() - relativedelta(months=int(epoch[0]))
rows = [r for r in all_rows if (not minimum or r[0] >= minimum) and (not maximum or r[0] <= maximum)]
training = [dict(start_date=d, weight=w, repetitions=r) for d,w,r in all_rows if d <= max(x[0] for x in rows)]
m = get_exercise_graph_model(key[1], [round(float(e1rm(r[1],r[2])),1) for r in rows],
[r[2] for r in rows], [r[1] for r in rows], [r[0] for r in rows], ['']*len(rows),
epoch, 1007, exercise_id, forecast_enabled=request.args.get('forecast','true')=='true',
forecast_weeks=request.args.get('forecast_weeks',8,type=int), forecast_training_rows=training)
m.update(latest_topset_id=1, latest_workout_id=1,
topsets=[dict(start_date=d,weight=w,repetitions=r,workout_id=1,topset_id=i+1) for i,(d,w,r) in enumerate(reversed(rows[-5:]))],
offset=0, limit=5, source_topset_id=1)
return m
@app.route('/person/<int:person_id>/exercise/<int:exercise_id>/sparkline')
def get_exercise_progress_for_user(person_id,exercise_id):
return render_template('partials/sparkline.html', **model(exercise_id))
@workout.route('/person/<int:person_id>/exercise/<int:exercise_id>/history')
def get_exercise_history(person_id,exercise_id):
return render_template('partials/exercise_history.html', **model(exercise_id))
@workout.route('/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>/achievements')
def get_topset_achievements(**kwargs):
return ''
app.register_blueprint(workout)
@app.route('/')
def index():
exercise_id = request.args.get('exercise_id',1143,type=int)
graph = render_template('partials/sparkline.html', **model(exercise_id))
return render_template_string('''<!doctype html><html><head><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Workout forecast preview</title><link rel="stylesheet" href="/static/css/tailwind.css">
<link rel="stylesheet" href="/static/css/forecast.css"><script src="/static/js/htmx.min.js" defer></script>
<script src="/static/js/hyperscript.min.js" defer></script><script src="/static/js/forecast.js" defer></script></head>
<body style="background:#f5f7f8;font-family:system-ui;margin:0;padding:16px">
<main style="max-width:680px;margin:24px auto;background:white;border:1px solid #e2e8f0;border-radius:16px;padding:16px">
<p style="font-size:11px;color:#94a3b8;margin-bottom:16px">PETER / PROGRESS</p>{{ graph|safe }}</main></body></html>''', graph=graph)
if __name__ == '__main__':
app.run(port=5057, debug=False)