"""A1 — Stage-bound practice exercises (Activity Set).""" from __future__ import annotations from datetime import datetime, timezone from typing import Any, Optional from services.recurring import ( create_recurring_element, list_recurring_for_initiative, update_recurring_element, ) # Starter exercise sets per stage title (Spagat reference path). STAGE_PRACTICE_SETS: dict[str, list[tuple[str, str]]] = { "Stufe 1 — Basis": [ ("Vorbeuge-Dehnung", "Täglich 3×30 Sek. — Hüfte und Beinrückseite"), ("Hüftöffner (Schmetterling)", "Täglich sitzend, leichter Druck"), ], "Stufe 2 — Aufbau": [ ("Seitlicher Spagat — links", "Täglich halten, optional Messwert (Grad)"), ("Seitlicher Spagat — rechts", "Täglich halten, optional Messwert (Grad)"), ("Aktive Vorbeuge", "Täglich 3× kurz halten"), ], "Stufe 3 — Ziel": [ ("Voller Spagat", "Täglich Zielposition — Messwert dokumentieren"), ], "Dehnung — Einstieg": [ ("Vorbeuge halten", "Täglich 3×30 Sek."), ], "Hüftrotation — Einstieg": [ ("Hüftkreisen im Stand", "Täglich 10 Wiederholungen je Richtung"), ], "Dehnung — nach Join": [ ("Enddehnung Mawashi-Vorbereitung", "Täglich 2×45 Sek."), ], "Hüftrotation — nach Join": [ ("Explosive Hüftrotation", "Täglich 3×8 Wiederholungen"), ], } def get_active_maturity_stages( *, tenant_id: str, initiative_id: str ) -> list[dict[str, Any]]: from services.roadmap import list_roadmap_items_for_initiative stages = [ item for item in list_roadmap_items_for_initiative( tenant_id=tenant_id, initiative_id=initiative_id ) if item.get("item_type") == "maturity_stage" and item.get("status") == "active" ] stages.sort(key=lambda s: (s.get("sort_order", 0), s.get("title", ""))) return stages def get_active_maturity_stage( *, tenant_id: str, initiative_id: str ) -> Optional[dict[str, Any]]: stages = get_active_maturity_stages( tenant_id=tenant_id, initiative_id=initiative_id ) return stages[0] if stages else None def list_practices_for_stage( *, tenant_id: str, initiative_id: str, stage_id: str, ) -> list[dict[str, Any]]: items = list_recurring_for_initiative( tenant_id=tenant_id, initiative_id=initiative_id ) return [ item for item in items if item.get("roadmap_item_id") == stage_id ] def pause_all_practices( *, tenant_id: str, initiative_id: str, user_id: Optional[str] = None, ) -> list[str]: paused: list[str] = [] for item in list_recurring_for_initiative( tenant_id=tenant_id, initiative_id=initiative_id ): if item.get("status") == "active": update_recurring_element( tenant_id=tenant_id, recurring_id=item["id"], status="paused", user_id=user_id, ) paused.append(item["title"]) return paused def pause_practices_on_same_lane_except_stage( *, tenant_id: str, initiative_id: str, keep_stage_id: str, user_id: Optional[str] = None, ) -> list[str]: from services.progression_graph import progression_lanes_for_initiative lanes = progression_lanes_for_initiative( tenant_id=tenant_id, initiative_id=initiative_id ) keep_lane = lanes.get(str(keep_stage_id)) if not keep_lane: return pause_all_practices( tenant_id=tenant_id, initiative_id=initiative_id, user_id=user_id ) lane_gate_ids = { gate_id for gate_id, lane in lanes.items() if lane == keep_lane } paused: list[str] = [] for item in list_recurring_for_initiative( tenant_id=tenant_id, initiative_id=initiative_id ): if item.get("status") != "active": continue if item.get("roadmap_item_id") == keep_stage_id: continue if str(item.get("roadmap_item_id") or "") not in lane_gate_ids: continue update_recurring_element( tenant_id=tenant_id, recurring_id=item["id"], status="paused", user_id=user_id, ) paused.append(item["title"]) return paused def pause_practices_except_stage( *, tenant_id: str, initiative_id: str, keep_stage_id: str, strand_project_id: Optional[str] = None, user_id: Optional[str] = None, ) -> list[str]: del strand_project_id return pause_practices_on_same_lane_except_stage( tenant_id=tenant_id, initiative_id=initiative_id, keep_stage_id=keep_stage_id, user_id=user_id, ) def seed_practices_for_stage( *, tenant_id: str, initiative_id: str, stage: dict[str, Any], user_id: Optional[str] = None, ) -> list[dict[str, Any]]: """Idempotent: create missing exercises for a stage; activate existing.""" stage_id = stage["id"] stage_title = stage.get("title") or "" defs = STAGE_PRACTICE_SETS.get(stage_title) if not defs: defs = [ ( f"Übung — {stage_title}", f"Tägliche Routine für {stage_title}", ) ] existing = list_practices_for_stage( tenant_id=tenant_id, initiative_id=initiative_id, stage_id=stage_id, ) existing_by_title = {item["title"]: item for item in existing} created: list[dict[str, Any]] = [] now = datetime.now(timezone.utc) for title, description in defs: if title in existing_by_title: item = existing_by_title[title] if item.get("status") != "active": updated = update_recurring_element( tenant_id=tenant_id, recurring_id=item["id"], status="active", next_due_at=now, user_id=user_id, ) if updated: created.append(updated) else: created.append(item) continue row = create_recurring_element( tenant_id=tenant_id, initiative_id=initiative_id, title=title, description=description, status="active", interval_days=1, next_due_at=now, roadmap_item_id=stage_id, user_id=user_id, ) created.append(row) return created def activate_stage_practice_set( *, tenant_id: str, initiative_id: str, stage: dict[str, Any], user_id: Optional[str] = None, ) -> dict[str, Any]: paused = pause_practices_on_same_lane_except_stage( tenant_id=tenant_id, initiative_id=initiative_id, keep_stage_id=stage["id"], user_id=user_id, ) exercises = seed_practices_for_stage( tenant_id=tenant_id, initiative_id=initiative_id, stage=stage, user_id=user_id, ) return { "stage_id": stage["id"], "stage_title": stage.get("title"), "paused_practices": paused, "active_practices": [e["title"] for e in exercises], "practice_ids": [e["id"] for e in exercises], }