# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
CommCoach Scheduler Service.
Handles daily reminders and scheduled email summaries.
"""
import logging
import html
from typing import Dict, Any, List
logger = logging.getLogger(__name__)
def _buildReminderHtmlBlock(contextTitles: List[str], streakDays: int) -> str:
rows = "".join(
'
'
'| • | '
f'{html.escape(title)} | '
'
'
for title in contextTitles[:3]
)
topicsBlock = (
''
'| '
' Aktive Coaching-Themen '
f''
' |
'
)
streakBlock = (
''
'| '
' Dein Rhythmus '
f'Aktueller Streak: '
f'{int(streakDays or 0)} Tage '
' |
'
)
return topicsBlock + streakBlock
def registerScheduledJobs(eventManagement):
"""Register CommCoach scheduled jobs with the event management system."""
try:
eventManagement.registerCron(
jobId="commcoach_daily_reminder",
func=_runDailyReminders,
cronKwargs={"hour": 8, "minute": 0},
)
logger.info("CommCoach scheduler: daily reminder job registered at 08:00")
except Exception as e:
logger.error(f"CommCoach scheduler: failed to register jobs: {e}")
async def _runDailyReminders():
"""Send daily coaching reminders to users who have opted in."""
try:
from modules.shared.configuration import APP_CONFIG
from modules.connectors.connectorDbPostgre import DatabaseConnector
from .datamodelCommcoach import CoachingUserProfile, CoachingContextStatus
from modules.interfaces.interfaceMessaging import getInterface as getMessagingInterface
from modules.shared.notifyMandateAdmins import _renderHtmlEmail, _resolveMandateName
dbHost = APP_CONFIG.get("DB_HOST", "_no_config_default_data")
db = DatabaseConnector(
dbHost=dbHost,
dbDatabase="poweron_commcoach",
dbUser=APP_CONFIG.get("DB_USER"),
dbPassword=APP_CONFIG.get("DB_PASSWORD_SECRET"),
dbPort=int(APP_CONFIG.get("DB_PORT", 5432)),
userId="system",
)
profiles = db.getRecordset(CoachingUserProfile, recordFilter={"dailyReminderEnabled": True})
if not profiles:
return
messaging = getMessagingInterface()
from modules.interfaces.interfaceDbApp import getRootInterface
rootInterface = getRootInterface()
sentCount = 0
for profile in profiles:
try:
userId = profile.get("userId")
user = rootInterface.getUser(userId)
if not user or not user.email:
continue
# Check if user has active contexts
from .datamodelCommcoach import CoachingContext
contexts = db.getRecordset(CoachingContext, recordFilter={
"userId": userId,
"status": CoachingContextStatus.ACTIVE.value,
})
if not contexts:
continue
contextTitles = [c.get("title", "Unbenannt") for c in contexts[:3]]
contextList = ", ".join(contextTitles)
subject = "Dein tägliches Coaching wartet"
mandateName = _resolveMandateName(profile.get("mandateId"))
htmlMessage = _renderHtmlEmail(
"Zeit für dein tägliches Coaching",
[
f"Du hast aktuell {len(contexts)} aktive Coaching-Themen.",
"Schon 10 Minuten reichen oft, um einen Gedanken zu klären, eine nächste Aktion festzulegen oder ein Gespräch vorzubereiten.",
f"Im Fokus: {contextList}",
],
mandateName,
footerNote="Diese Erinnerung wurde automatisch auf Basis deiner CommCoach-Einstellungen versendet.",
rawHtmlBlock=_buildReminderHtmlBlock(contextTitles, int(profile.get("streakDays", 0) or 0)),
)
messaging.send("email", user.email, subject, htmlMessage)
sentCount += 1
except Exception as e:
logger.warning(f"Failed to send reminder to user {profile.get('userId')}: {e}")
if sentCount > 0:
logger.info(f"CommCoach scheduler: sent {sentCount} daily reminders")
except Exception as e:
logger.error(f"CommCoach daily reminders failed: {e}")