Spaces:
Sleeping
Sleeping
File size: 8,818 Bytes
6253f86 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | """
utils/progress_tracker.py β Daily progress tracking logic for FitPlan Pro.
Handles: checkbox state management, completion detection, stats calculation.
"""
from utils.db import save_progress, get_progress, get_all_progress
from utils.streak_manager import update_streak
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Checkbox state helpers
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def build_workout_checks(exercises):
"""
Build initial empty checkbox dict from exercise list.
exercises: list of dicts with 'name' key
Returns: {"Push-ups": False, "Squats": False, ...}
"""
return {ex["name"]: False for ex in exercises if "name" in ex}
def build_dietary_checks(dietary):
"""
Build initial empty checkbox dict from dietary dict.
dietary: {"breakfast": "...", "lunch": "...", ...}
Returns: {"breakfast": False, "lunch": False, ...}
"""
return {meal: False for meal in dietary if dietary[meal]}
def merge_checks(existing, new_keys):
"""
Merge saved checkbox state with current exercise/meal list.
Preserves saved True/False values; adds False for new keys.
"""
merged = {}
for key in new_keys:
merged[key] = existing.get(key, False)
return merged
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Core tracking functions
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def load_day_progress(username, plan_id, day_number, exercises, dietary):
"""
Load saved progress for a day and merge with current plan data.
Returns: (workout_checks, dietary_checks, day_completed)
"""
saved = get_progress(username, plan_id, day_number)
workout_keys = [ex["name"] for ex in exercises if "name" in ex]
dietary_keys = list(dietary.keys()) if dietary else []
workout_checks = merge_checks(saved["workout_checks"], workout_keys)
dietary_checks = merge_checks(saved["dietary_checks"], dietary_keys)
return workout_checks, dietary_checks, saved["day_completed"]
def toggle_workout_item(username, plan_id, day_number,
exercise_name, checked,
current_workout_checks, current_dietary_checks):
"""
Toggle a single workout checkbox and save to DB.
Returns: (updated_workout_checks, day_completed, new_streak)
"""
current_workout_checks[exercise_name] = checked
day_completed = save_progress(
username, plan_id, day_number,
current_workout_checks, current_dietary_checks
)
new_streak = update_streak(username, day_completed)
return current_workout_checks, day_completed, new_streak
def toggle_dietary_item(username, plan_id, day_number,
meal_name, checked,
current_workout_checks, current_dietary_checks):
"""
Toggle a single dietary checkbox and save to DB.
Returns: (updated_dietary_checks, day_completed, new_streak)
"""
current_dietary_checks[meal_name] = checked
day_completed = save_progress(
username, plan_id, day_number,
current_workout_checks, current_dietary_checks
)
new_streak = update_streak(username, day_completed)
return current_dietary_checks, day_completed, new_streak
def mark_day_complete(username, plan_id, day_number,
exercises, dietary):
"""
Mark ALL items in a day as complete at once.
Returns: (workout_checks, dietary_checks, new_streak)
"""
workout_checks = {ex["name"]: True for ex in exercises if "name" in ex}
dietary_checks = {meal: True for meal in dietary if dietary[meal]}
save_progress(username, plan_id, day_number, workout_checks, dietary_checks)
new_streak = update_streak(username, True)
return workout_checks, dietary_checks, new_streak
def unmark_day(username, plan_id, day_number, exercises, dietary):
"""
Unmark ALL items in a day (reset to unchecked).
Returns: (workout_checks, dietary_checks)
"""
workout_checks = {ex["name"]: False for ex in exercises if "name" in ex}
dietary_checks = {meal: False for meal in dietary if dietary[meal]}
save_progress(username, plan_id, day_number, workout_checks, dietary_checks)
update_streak(username, False)
return workout_checks, dietary_checks
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Stats / Summary
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def get_plan_stats(username, plan_id, total_days):
"""
Calculate overall plan completion stats.
Returns dict with summary numbers.
"""
all_progress = get_all_progress(username, plan_id)
completed_days = sum(1 for p in all_progress if p["day_completed"])
total_workout_tasks = 0
done_workout_tasks = 0
total_dietary_tasks = 0
done_dietary_tasks = 0
for p in all_progress:
wc = p["workout_checks"]
dc = p["dietary_checks"]
total_workout_tasks += len(wc)
done_workout_tasks += sum(1 for v in wc.values() if v)
total_dietary_tasks += len(dc)
done_dietary_tasks += sum(1 for v in dc.values() if v)
total_tasks = total_workout_tasks + total_dietary_tasks
done_tasks = done_workout_tasks + done_dietary_tasks
return {
"completed_days": completed_days,
"total_days": total_days,
"pending_days": total_days - completed_days,
"progress_pct": round((completed_days / total_days * 100), 1) if total_days else 0,
"total_workout_tasks": total_workout_tasks,
"done_workout_tasks": done_workout_tasks,
"total_dietary_tasks": total_dietary_tasks,
"done_dietary_tasks": done_dietary_tasks,
"total_tasks": total_tasks,
"done_tasks": done_tasks,
"overall_task_pct": round((done_tasks / total_tasks * 100), 1) if total_tasks else 0
}
def get_today_day_number(plan_created_at, days_per_week=5):
"""
Calculate which day of the plan today corresponds to.
Based on days since plan creation and training frequency.
Returns: day_number (1-indexed), or 1 if before start.
"""
from datetime import datetime, timezone
import math
now = datetime.now(timezone.utc).timestamp()
days_elapsed = (now - plan_created_at) / 86400 # seconds per day
# Account for rest days: only training days count
# e.g. 5 days/week = train Mon-Fri, rest Sat-Sun
training_day = math.floor(days_elapsed * (days_per_week / 7)) + 1
return max(1, int(training_day))
def get_completion_heatmap(username, plan_id, total_days):
"""
Build a completion status list for calendar/heatmap display.
Returns: list of dicts [{day_number, status, date}]
status: 'completed' | 'partial' | 'pending'
"""
all_progress = get_all_progress(username, plan_id)
progress_map = {p["day_number"]: p for p in all_progress}
heatmap = []
for day in range(1, total_days + 1):
p = progress_map.get(day)
if not p:
status = "pending"
elif p["day_completed"]:
status = "completed"
else:
wc = p["workout_checks"]
dc = p["dietary_checks"]
any_done = any(wc.values()) or any(dc.values())
status = "partial" if any_done else "pending"
heatmap.append({
"day_number": day,
"status": status,
"date": p["date"] if p else None
})
return heatmap |