"""Private training preferences and session observations.""" from flask import Blueprint, abort, flash, jsonify, redirect, render_template, request, url_for from flask_login import current_user, login_required from .extensions import db from .models import Activity training = Blueprint("training", __name__) @training.get("/fitness") @login_required def fitness(): from .fitness import history return render_template("fitness.html", data=history(current_user)) @training.get("/fitness/data") @login_required def fitness_data(): from .fitness import history return jsonify(history(current_user)) @training.app_context_processor def fitness_helpers(): from .fitness import effort from .heart_rate import zones return {"run_effort": effort, "fitness_zones": zones} @training.post("/training/zones") @login_required def zones(): from datetime import date, datetime from zoneinfo import ZoneInfo mode = request.form.get("mode") if mode not in {"auto", "max", "custom"}: abort(400) config = {"mode": mode} try: birthday = request.form.get("birthday", "").strip() if birthday: birthday_date = date.fromisoformat(birthday) today = datetime.now(ZoneInfo(current_user.timezone)).date() age = today.year - birthday_date.year - ((today.month, today.day) < (birthday_date.month, birthday_date.day)) if not 5 <= age <= 110: abort(400) config["birthday"] = birthday if mode == "max": maximum = int(request.form.get("max_hr", "")) if not 100 <= maximum <= 230: abort(400) config["max_hr"] = maximum if mode == "custom": boundaries = [int(request.form.get(f"z{i}", "")) for i in range(2, 6)] if not 30 <= boundaries[0] < boundaries[1] < boundaries[2] < boundaries[3] <= 230: abort(400) config["boundaries"] = boundaries except (ValueError, TypeError): abort(400) current_user.hr_zones = config db.session.commit() flash("Fitness zones saved. Your full fitness history recalculates automatically.") return redirect(url_for("training.fitness")) @training.post("/training/bands") @login_required def bands(): raw = [request.form.get("lower", "").strip(), request.form.get("upper", "").strip()] if not any(raw): current_user.hr_bands = None else: try: low, high = [int(v) for v in raw] except ValueError: abort(400) if not 30 <= low < high <= 240: abort(400) current_user.hr_bands = [low, high] db.session.commit() flash("Personal heart-rate bands saved.") return redirect(url_for("web.settings")) @training.post("/activities//context") @login_required def context(activity_id): row = db.first_or_404(db.select(Activity).where(Activity.id == activity_id, Activity.owner_id == current_user.id)) values = {} for field, low in (("perceived_effort", 1), ("fatigue", 0)): raw = request.form.get(field, "").strip() if raw: try: value = int(raw) except ValueError: abort(400) if not low <= value <= 10: abort(400) values[field] = value notes = request.form.get("notes", "").strip() if len(notes) > 2000: abort(400) values["notes"] = notes row.session_context = values db.session.commit() flash("Private session context saved.") return redirect(url_for("web.activity", activity_id=row.id))