ui update and some backend functionality adding in accordance with research on adhd and ux design

This commit is contained in:
2026-02-14 17:21:37 -06:00
parent 4d3a9fbd54
commit fb480eacb2
32 changed files with 9549 additions and 248 deletions

View File

@@ -6,10 +6,13 @@ Routines have ordered steps. Users start sessions to walk through them.
import os
import uuid
from datetime import datetime
import flask
import jwt
import core.auth as auth
import core.postgres as postgres
import core.routines as routines_core
import core.tz as tz
def _get_user_uuid(token):
@@ -32,6 +35,111 @@ def _auth(request):
return user_uuid
def _record_step_result(session_id, step_id, step_index, result, session):
"""Record a per-step result (completed or skipped)."""
try:
# Compute duration from previous step completion or session start
prev_results = postgres.select(
"routine_step_results",
where={"session_id": session_id},
order_by="completed_at DESC",
limit=1,
)
now = tz.user_now()
if prev_results:
last_completed = prev_results[0].get("completed_at")
if last_completed:
if isinstance(last_completed, str):
last_completed = datetime.fromisoformat(last_completed)
# Make naive datetimes comparable with aware ones
if last_completed.tzinfo is None:
duration_seconds = int((now.replace(tzinfo=None) - last_completed).total_seconds())
else:
duration_seconds = int((now - last_completed).total_seconds())
else:
duration_seconds = None
else:
created_at = session.get("created_at")
if created_at:
if isinstance(created_at, str):
created_at = datetime.fromisoformat(created_at)
if created_at.tzinfo is None:
duration_seconds = int((now.replace(tzinfo=None) - created_at).total_seconds())
else:
duration_seconds = int((now - created_at).total_seconds())
else:
duration_seconds = None
postgres.insert("routine_step_results", {
"id": str(uuid.uuid4()),
"session_id": session_id,
"step_id": step_id,
"step_index": step_index,
"result": result,
"duration_seconds": duration_seconds,
"completed_at": now.isoformat(),
})
except Exception:
pass # Don't fail the step completion if tracking fails
def _complete_session_with_celebration(session_id, user_uuid, session):
"""Complete a session and return celebration data."""
now = tz.user_now()
created_at = session.get("created_at")
if created_at:
if isinstance(created_at, str):
created_at = datetime.fromisoformat(created_at)
# Handle naive vs aware datetime comparison
if created_at.tzinfo is None:
duration_minutes = round((now.replace(tzinfo=None) - created_at).total_seconds() / 60, 1)
else:
duration_minutes = round((now - created_at).total_seconds() / 60, 1)
else:
duration_minutes = 0
# Update session as completed with duration
postgres.update("routine_sessions", {
"status": "completed",
"completed_at": now.isoformat(),
"actual_duration_minutes": int(duration_minutes),
}, {"id": session_id})
# Update streak (returns streak with optional 'milestone' key)
streak_result = routines_core._update_streak(user_uuid, session["routine_id"])
# Get streak data
streak = postgres.select_one("routine_streaks", {
"user_uuid": user_uuid,
"routine_id": session["routine_id"],
})
streak_milestone = streak_result.get("milestone") if streak_result else None
# Count step results for this session
step_results = postgres.select("routine_step_results", {"session_id": session_id})
steps_completed = sum(1 for r in step_results if r.get("result") == "completed")
steps_skipped = sum(1 for r in step_results if r.get("result") == "skipped")
# Total completions for this routine
all_completed = postgres.select("routine_sessions", {
"routine_id": session["routine_id"],
"user_uuid": user_uuid,
"status": "completed",
})
result = {
"streak_current": streak["current_streak"] if streak else 1,
"streak_longest": streak["longest_streak"] if streak else 1,
"session_duration_minutes": duration_minutes,
"total_completions": len(all_completed),
"steps_completed": steps_completed,
"steps_skipped": steps_skipped,
}
if streak_milestone:
result["streak_milestone"] = streak_milestone
return result
def register(app):
# ── Routines CRUD ─────────────────────────────────────────────
@@ -89,7 +197,7 @@ def register(app):
existing = postgres.select_one("routines", {"id": routine_id, "user_uuid": user_uuid})
if not existing:
return flask.jsonify({"error": "not found"}), 404
allowed = ["name", "description", "icon"]
allowed = ["name", "description", "icon", "location", "environment_prompts", "habit_stack_after"]
updates = {k: v for k, v in data.items() if k in allowed}
if not updates:
return flask.jsonify({"error": "no valid fields to update"}), 400
@@ -257,6 +365,8 @@ def register(app):
if not user_uuid:
return flask.jsonify({"error": "unauthorized"}), 401
session = postgres.select_one("routine_sessions", {"user_uuid": user_uuid, "status": "active"})
if not session:
session = postgres.select_one("routine_sessions", {"user_uuid": user_uuid, "status": "paused"})
if not session:
return flask.jsonify({"error": "no active session"}), 404
routine = postgres.select_one("routines", {"id": session["routine_id"]})
@@ -277,18 +387,33 @@ def register(app):
session = postgres.select_one("routine_sessions", {"id": session_id, "user_uuid": user_uuid})
if not session:
return flask.jsonify({"error": "not found"}), 404
if session["status"] != "active":
if session["status"] not in ("active", "paused"):
return flask.jsonify({"error": "session not active"}), 400
# Auto-resume if paused
if session["status"] == "paused":
postgres.update("routine_sessions", {"status": "active", "paused_at": None}, {"id": session_id})
data = flask.request.get_json() or {}
steps = postgres.select(
"routine_steps",
where={"routine_id": session["routine_id"]},
order_by="position",
)
next_index = session["current_step_index"] + 1
current_index = session["current_step_index"]
current_step = steps[current_index] if current_index < len(steps) else None
# Record step result
if current_step:
_record_step_result(session_id, current_step["id"], current_index, "completed", session)
next_index = current_index + 1
if next_index >= len(steps):
postgres.update("routine_sessions", {"status": "completed"}, {"id": session_id})
return flask.jsonify({"session": {"status": "completed"}, "next_step": None}), 200
# Session complete — compute celebration data
celebration = _complete_session_with_celebration(session_id, user_uuid, session)
return flask.jsonify({
"session": {"status": "completed"},
"next_step": None,
"celebration": celebration,
}), 200
postgres.update("routine_sessions", {"current_step_index": next_index}, {"id": session_id})
return flask.jsonify({"session": {"current_step_index": next_index}, "next_step": steps[next_index]}), 200
@@ -301,17 +426,31 @@ def register(app):
session = postgres.select_one("routine_sessions", {"id": session_id, "user_uuid": user_uuid})
if not session:
return flask.jsonify({"error": "not found"}), 404
if session["status"] != "active":
if session["status"] not in ("active", "paused"):
return flask.jsonify({"error": "session not active"}), 400
# Auto-resume if paused
if session["status"] == "paused":
postgres.update("routine_sessions", {"status": "active", "paused_at": None}, {"id": session_id})
steps = postgres.select(
"routine_steps",
where={"routine_id": session["routine_id"]},
order_by="position",
)
next_index = session["current_step_index"] + 1
current_index = session["current_step_index"]
current_step = steps[current_index] if current_index < len(steps) else None
# Record step result as skipped
if current_step:
_record_step_result(session_id, current_step["id"], current_index, "skipped", session)
next_index = current_index + 1
if next_index >= len(steps):
postgres.update("routine_sessions", {"status": "completed"}, {"id": session_id})
return flask.jsonify({"session": {"status": "completed"}, "next_step": None}), 200
celebration = _complete_session_with_celebration(session_id, user_uuid, session)
return flask.jsonify({
"session": {"status": "completed"},
"next_step": None,
"celebration": celebration,
}), 200
postgres.update("routine_sessions", {"current_step_index": next_index}, {"id": session_id})
return flask.jsonify({"session": {"current_step_index": next_index}, "next_step": steps[next_index]}), 200