ui update and some backend functionality adding in accordance with research on adhd and ux design
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user