import math from flask import Blueprint, render_template, request, redirect, url_for, flash from app.auth import login_required, get_current_user from app.db import query, query_one, execute, execute_returning bp = Blueprint("checkin", __name__) def calculate_bmi(weight_kg, height_cm): """Calculate BMI from weight (kg) and height (cm).""" if not weight_kg or not height_cm or float(height_cm) == 0: return None h_m = float(height_cm) / 100.0 return round(float(weight_kg) / (h_m * h_m), 1) def check_milestones(user_id, user): """Check and award any new milestones after a check-in.""" checkins = query( "SELECT weight_kg, checked_in_at FROM checkins WHERE user_id = %s ORDER BY checked_in_at ASC", (user_id,), ) if not checkins: return starting = float(user.get("starting_weight_kg") or checkins[0]["weight_kg"]) current = float(checkins[-1]["weight_kg"]) total_lost = starting - current count = len(checkins) milestone_checks = [ ("first_checkin", count >= 1), ("5_checkins", count >= 5), ("10_checkins", count >= 10), ("25_checkins", count >= 25), ("lost_1kg", total_lost >= 1), ("lost_2kg", total_lost >= 2), ("lost_5kg", total_lost >= 5), ("lost_10kg", total_lost >= 10), ("lost_15kg", total_lost >= 15), ("lost_20kg", total_lost >= 20), ] for key, achieved in milestone_checks: if achieved: try: execute( "INSERT INTO milestones (user_id, milestone_key) VALUES (%s, %s) ON CONFLICT DO NOTHING", (user_id, key), ) except Exception: pass @bp.route("/checkin", methods=["GET"]) @login_required def index(): user = get_current_user() checkins = query( "SELECT * FROM checkins WHERE user_id = %s ORDER BY checked_in_at DESC", (user["id"],), ) return render_template("checkin.html", user=user, checkins=checkins) @bp.route("/checkin", methods=["POST"]) @login_required def create(): user = get_current_user() weight_kg = request.form.get("weight_kg") notes = request.form.get("notes", "").strip() if not weight_kg: flash("Weight is required.", "error") return redirect(url_for("checkin.index")) try: weight_kg = float(weight_kg) except ValueError: flash("Invalid weight value.", "error") return redirect(url_for("checkin.index")) bmi = calculate_bmi(weight_kg, user.get("height_cm")) checkin = execute_returning( """INSERT INTO checkins (user_id, weight_kg, bmi, notes) VALUES (%s, %s, %s, %s) RETURNING *""", (user["id"], weight_kg, bmi, notes or None), ) # Check milestones check_milestones(user["id"], user) # If HTMX request, return just the new row if request.headers.get("HX-Request"): return render_template("partials/checkin_row.html", c=checkin, user=user) flash("Check-in recorded!", "success") return redirect(url_for("checkin.index")) @bp.route("/checkin/", methods=["DELETE"]) @login_required def delete(checkin_id): user = get_current_user() execute( "DELETE FROM checkins WHERE id = %s AND user_id = %s", (checkin_id, user["id"]), ) if request.headers.get("HX-Request"): return "" flash("Check-in deleted.", "info") return redirect(url_for("checkin.index"))