Kairo-Jinkendo/backend/services/maturity_practice.py

249 lines
7.1 KiB
Python
Raw Normal View History

"""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 (
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", "3×30 Sek. — Hüfte und Beinrückseite"),
("Hüftöffner (Schmetterling)", "Sitzend, leichter Druck"),
],
"Stufe 2 — Aufbau": [
("Seitlicher Spagat — links", "Halten, optional Messwert (Grad)"),
("Seitlicher Spagat — rechts", "Halten, optional Messwert (Grad)"),
("Aktive Vorbeuge", "3× kurz halten"),
],
"Stufe 3 — Ziel": [
("Voller Spagat", "Zielposition — Messwert dokumentieren"),
],
"Dehnung — Einstieg": [
("Vorbeuge halten", "3×30 Sek."),
],
"Hüftrotation — Einstieg": [
("Hüftkreisen im Stand", "10 Wiederholungen je Richtung"),
],
"Dehnung — nach Join": [
("Enddehnung Mawashi-Vorbereitung", "2×45 Sek."),
],
"Hüftrotation — nach Join": [
("Explosive Hüftrotation", "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"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)
from services.scheduled_work import create_scheduled_action_at_gate
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_scheduled_action_at_gate(
tenant_id=tenant_id,
initiative_id=initiative_id,
gate_id=stage_id,
title=title,
description=description,
user_id=user_id,
schedule_kind="weekdays",
)
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],
}