""" core/routines.py - Shared business logic for routines This module contains reusable functions for routine operations that can be called from API routes, bot commands, or scheduler. """ import uuid from datetime import datetime, date import core.postgres as postgres def start_session(routine_id, user_uuid): """ Create and start a new routine session. Returns the session object or None if routine not found. """ routine = postgres.select_one("routines", {"id": routine_id, "user_uuid": user_uuid}) if not routine: return None active_session = postgres.select_one( "routine_sessions", {"user_uuid": user_uuid, "status": "active"} ) if active_session: return {"error": "already_active", "session_id": active_session["id"]} steps = postgres.select( "routine_steps", {"routine_id": routine_id}, order_by="position" ) if not steps: return {"error": "no_steps"} session = { "id": str(uuid.uuid4()), "routine_id": routine_id, "user_uuid": user_uuid, "status": "active", "current_step_index": 0, } result = postgres.insert("routine_sessions", session) return {"session": result, "current_step": steps[0]} def pause_session(session_id, user_uuid): """Pause an active session.""" session = postgres.select_one( "routine_sessions", {"id": session_id, "user_uuid": user_uuid} ) if not session: return None if session.get("status") != "active": return {"error": "not_active"} result = postgres.update( "routine_sessions", {"status": "paused", "paused_at": datetime.now().isoformat()}, {"id": session_id} ) return result def resume_session(session_id, user_uuid): """Resume a paused session.""" session = postgres.select_one( "routine_sessions", {"id": session_id, "user_uuid": user_uuid} ) if not session: return None if session.get("status") != "paused": return {"error": "not_paused"} result = postgres.update( "routine_sessions", {"status": "active", "paused_at": None}, {"id": session_id} ) return result def abort_session(session_id, user_uuid, reason=None): """Abort a session with optional reason.""" session = postgres.select_one( "routine_sessions", {"id": session_id, "user_uuid": user_uuid} ) if not session: return None result = postgres.update( "routine_sessions", { "status": "aborted", "abort_reason": reason or "Aborted by user", "completed_at": datetime.now().isoformat() }, {"id": session_id} ) return result def complete_session(session_id, user_uuid): """Mark a session as completed and update streak.""" session = postgres.select_one( "routine_sessions", {"id": session_id, "user_uuid": user_uuid} ) if not session: return None completed_at = datetime.now() result = postgres.update( "routine_sessions", {"status": "completed", "completed_at": completed_at.isoformat()}, {"id": session_id} ) _update_streak(user_uuid, session["routine_id"]) return result def clone_template(template_id, user_uuid): """Clone a template to user's routines.""" template = postgres.select_one("routine_templates", {"id": template_id}) if not template: return None template_steps = postgres.select( "routine_template_steps", {"template_id": template_id}, order_by="position" ) new_routine = { "id": str(uuid.uuid4()), "user_uuid": user_uuid, "name": template["name"], "description": template.get("description"), "icon": template.get("icon"), } routine = postgres.insert("routines", new_routine) for step in template_steps: new_step = { "id": str(uuid.uuid4()), "routine_id": routine["id"], "name": step["name"], "instructions": step.get("instructions"), "step_type": step.get("step_type", "generic"), "duration_minutes": step.get("duration_minutes"), "media_url": step.get("media_url"), "position": step["position"], } postgres.insert("routine_steps", new_step) return routine def _update_streak(user_uuid, routine_id): """Update streak after completing a session. Resets if day was missed.""" today = date.today() streak = postgres.select_one( "routine_streaks", {"user_uuid": user_uuid, "routine_id": routine_id} ) if not streak: new_streak = { "id": str(uuid.uuid4()), "user_uuid": user_uuid, "routine_id": routine_id, "current_streak": 1, "longest_streak": 1, "last_completed_date": today.isoformat(), } return postgres.insert("routine_streaks", new_streak) last_completed = streak.get("last_completed_date") if last_completed: if isinstance(last_completed, str): last_completed = date.fromisoformat(last_completed) days_diff = (today - last_completed).days if days_diff == 0: return streak elif days_diff == 1: new_streak = streak["current_streak"] + 1 else: new_streak = 1 else: new_streak = 1 longest = max(streak["longest_streak"], new_streak) postgres.update( "routine_streaks", { "current_streak": new_streak, "longest_streak": longest, "last_completed_date": today.isoformat(), }, {"id": streak["id"]} ) return streak def calculate_streak(user_uuid, routine_id): """Get current streak for a routine.""" streak = postgres.select_one( "routine_streaks", {"user_uuid": user_uuid, "routine_id": routine_id} ) return streak def get_active_session(user_uuid): """Get user's currently active session.""" return postgres.select_one( "routine_sessions", {"user_uuid": user_uuid, "status": "active"} )