131 lines
5.6 KiB
Python
131 lines
5.6 KiB
Python
# 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(
|
|
'<tr>'
|
|
'<td valign="top" style="padding:0 10px 8px 0;font-size:15px;line-height:1.6;color:#2563eb;">•</td>'
|
|
f'<td style="padding:0 0 8px 0;font-size:15px;line-height:1.6;color:#374151;">{html.escape(title)}</td>'
|
|
'</tr>'
|
|
for title in contextTitles[:3]
|
|
)
|
|
topicsBlock = (
|
|
'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" '
|
|
'style="border-collapse:separate;border-spacing:0;border:1px solid #e5e7eb;border-radius:12px;'
|
|
'background-color:#ffffff;margin:0 0 16px 0;">'
|
|
'<tr><td style="padding:18px 20px;">'
|
|
'<div style="font-size:12px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;'
|
|
'color:#1d4ed8;margin:0 0 8px 0;">Aktive Coaching-Themen</div>'
|
|
f'<table role="presentation" cellpadding="0" cellspacing="0" style="border-collapse:collapse;">{rows}</table>'
|
|
'</td></tr></table>'
|
|
)
|
|
streakBlock = (
|
|
'<table role="presentation" width="100%" cellpadding="0" cellspacing="0" '
|
|
'style="border-collapse:separate;border-spacing:0;border:1px solid #dbeafe;border-radius:12px;'
|
|
'background:linear-gradient(135deg,#eff6ff,#f8fbff);">'
|
|
'<tr><td style="padding:18px 20px;">'
|
|
'<div style="font-size:12px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;'
|
|
'color:#1d4ed8;margin:0 0 8px 0;">Dein Rhythmus</div>'
|
|
f'<div style="font-size:15px;line-height:1.7;color:#374151;">Aktueller Streak: '
|
|
f'<strong>{int(streakDays or 0)} Tage</strong></div>'
|
|
'</td></tr></table>'
|
|
)
|
|
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}")
|