185 lines
6 KiB
Python
185 lines
6 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
CommCoach Gamification - Badge definitions and award logic.
|
|
Checks and awards badges after each session completion.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Dict, Any, List, Optional
|
|
from modules.shared.i18nRegistry import resolveText, t
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
BADGE_DEFINITIONS: Dict[str, Dict[str, Any]] = {
|
|
"first_session": {
|
|
"label": "Erste Session",
|
|
"description": "Deine erste Coaching-Session abgeschlossen",
|
|
"icon": "star",
|
|
},
|
|
"streak_3": {
|
|
"label": "3-Tage-Serie",
|
|
"description": "3 Tage in Folge eine Session absolviert",
|
|
"icon": "fire",
|
|
},
|
|
"streak_7": {
|
|
"label": "Wochenserie",
|
|
"description": "7 Tage in Folge eine Session absolviert",
|
|
"icon": "fire",
|
|
},
|
|
"streak_30": {
|
|
"label": "Monatsserie",
|
|
"description": "30 Tage in Folge eine Session absolviert",
|
|
"icon": "fire",
|
|
},
|
|
"sessions_5": {
|
|
"label": "Engagiert",
|
|
"description": "5 Sessions abgeschlossen",
|
|
"icon": "trophy",
|
|
},
|
|
"sessions_10": {
|
|
"label": "Fortgeschritten",
|
|
"description": "10 Sessions abgeschlossen",
|
|
"icon": "trophy",
|
|
},
|
|
"sessions_25": {
|
|
"label": "Experte",
|
|
"description": "25 Sessions abgeschlossen",
|
|
"icon": "trophy",
|
|
},
|
|
"sessions_50": {
|
|
"label": "Meister",
|
|
"description": "50 Sessions abgeschlossen",
|
|
"icon": "trophy",
|
|
},
|
|
"high_score": {
|
|
"label": "Bestleistung",
|
|
"description": "Durchschnittsscore über 80 in einer Session",
|
|
"icon": "medal",
|
|
},
|
|
"multi_context": {
|
|
"label": "Vielseitig",
|
|
"description": "3 verschiedene Coaching-Themen aktiv",
|
|
"icon": "layers",
|
|
},
|
|
"roleplay_first": {
|
|
"label": "Rollenspieler",
|
|
"description": "Erste Roleplay-Session mit einer Persona abgeschlossen",
|
|
"icon": "theater",
|
|
},
|
|
"all_dimensions": {
|
|
"label": "Ganzheitlich",
|
|
"description": "In allen 5 Kompetenz-Dimensionen bewertet",
|
|
"icon": "compass",
|
|
},
|
|
"task_completer": {
|
|
"label": "Umsetzer",
|
|
"description": "10 Coaching-Aufgaben erledigt",
|
|
"icon": "check-circle",
|
|
},
|
|
}
|
|
|
|
# Register all badge labels/descriptions at import time for i18n xx base set
|
|
t("Erste Session")
|
|
t("Deine erste Coaching-Session abgeschlossen")
|
|
t("3-Tage-Serie")
|
|
t("3 Tage in Folge eine Session absolviert")
|
|
t("Wochenserie")
|
|
t("7 Tage in Folge eine Session absolviert")
|
|
t("Monatsserie")
|
|
t("30 Tage in Folge eine Session absolviert")
|
|
t("Engagiert")
|
|
t("5 Sessions abgeschlossen")
|
|
t("Fortgeschritten")
|
|
t("10 Sessions abgeschlossen")
|
|
t("Experte")
|
|
t("25 Sessions abgeschlossen")
|
|
t("Meister")
|
|
t("50 Sessions abgeschlossen")
|
|
t("Bestleistung")
|
|
t("Durchschnittsscore über 80 in einer Session")
|
|
t("Vielseitig")
|
|
t("3 verschiedene Coaching-Themen aktiv")
|
|
t("Rollenspieler")
|
|
t("Erste Roleplay-Session mit einer Persona abgeschlossen")
|
|
t("Ganzheitlich")
|
|
t("In allen 5 Kompetenz-Dimensionen bewertet")
|
|
t("Umsetzer")
|
|
t("10 Coaching-Aufgaben erledigt")
|
|
|
|
|
|
async def checkAndAwardBadges(interface, userId: str, mandateId: str, instanceId: str,
|
|
session: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
|
|
"""Check badge conditions and award any newly earned badges. Returns list of newly awarded badges."""
|
|
awarded: List[Dict[str, Any]] = []
|
|
|
|
profile = interface.getProfile(userId, instanceId)
|
|
if not profile:
|
|
return awarded
|
|
|
|
totalSessions = profile.get("totalSessions", 0)
|
|
streakDays = profile.get("streakDays", 0)
|
|
|
|
badgesToCheck = [
|
|
("first_session", totalSessions >= 1),
|
|
("sessions_5", totalSessions >= 5),
|
|
("sessions_10", totalSessions >= 10),
|
|
("sessions_25", totalSessions >= 25),
|
|
("sessions_50", totalSessions >= 50),
|
|
("streak_3", streakDays >= 3),
|
|
("streak_7", streakDays >= 7),
|
|
("streak_30", streakDays >= 30),
|
|
]
|
|
|
|
if session and session.get("competenceScore"):
|
|
try:
|
|
score = float(session["competenceScore"])
|
|
if score >= 80:
|
|
badgesToCheck.append(("high_score", True))
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
if session and session.get("personaId") and session["personaId"] != "coach":
|
|
badgesToCheck.append(("roleplay_first", True))
|
|
|
|
try:
|
|
from .datamodelCommcoach import CoachingContextStatus
|
|
allContexts = interface.db.getRecordset(
|
|
interface.db.getRecordset.__self__.__class__.__mro__[0] # avoid import issues
|
|
) if False else []
|
|
except Exception:
|
|
allContexts = []
|
|
|
|
completedTasks = interface.getCompletedTaskCount(userId) if hasattr(interface, 'getCompletedTaskCount') else 0
|
|
if completedTasks >= 10:
|
|
badgesToCheck.append(("task_completer", True))
|
|
|
|
for badgeKey, condition in badgesToCheck:
|
|
if condition and not interface.hasBadge(userId, instanceId, badgeKey):
|
|
badgeData = {
|
|
"userId": userId,
|
|
"mandateId": mandateId,
|
|
"instanceId": instanceId,
|
|
"badgeKey": badgeKey,
|
|
}
|
|
newBadge = interface.awardBadge(badgeData)
|
|
definition = BADGE_DEFINITIONS.get(badgeKey, {})
|
|
newBadge["label"] = resolveText(definition.get("label", badgeKey))
|
|
newBadge["description"] = resolveText(definition.get("description", ""))
|
|
newBadge["icon"] = definition.get("icon", "star")
|
|
awarded.append(newBadge)
|
|
logger.info(f"Badge '{badgeKey}' awarded to user {userId}")
|
|
|
|
return awarded
|
|
|
|
|
|
def getBadgeDefinitions() -> Dict[str, Dict[str, Any]]:
|
|
"""Return all badge definitions for the frontend (labels resolved via i18n)."""
|
|
resolved = {}
|
|
for key, defn in BADGE_DEFINITIONS.items():
|
|
resolved[key] = {
|
|
**defn,
|
|
"label": resolveText(defn["label"]),
|
|
"description": resolveText(defn["description"]),
|
|
}
|
|
return resolved
|