Files
breakpilot-lehrer/backend-lehrer/alerts_agent/api/digests_email.py
Benjamin Admin bd4b956e3c [split-required] Split final 43 files (500-668 LOC) to complete refactoring
klausur-service (11 files):
- cv_gutter_repair, ocr_pipeline_regression, upload_api
- ocr_pipeline_sessions, smart_spell, nru_worksheet_generator
- ocr_pipeline_overlays, mail/aggregator, zeugnis_api
- cv_syllable_detect, self_rag

backend-lehrer (17 files):
- classroom_engine/suggestions, generators/quiz_generator
- worksheets_api, llm_gateway/comparison, state_engine_api
- classroom/models (→ 4 submodules), services/file_processor
- alerts_agent/api/wizard+digests+routes, content_generators/pdf
- classroom/routes/sessions, llm_gateway/inference
- classroom_engine/analytics, auth/keycloak_auth
- alerts_agent/processing/rule_engine, ai_processor/print_versions

agent-core (5 files):
- brain/memory_store, brain/knowledge_graph, brain/context_manager
- orchestrator/supervisor, sessions/session_manager

admin-lehrer (5 components):
- GridOverlay, StepGridReview, DevOpsPipelineSidebar
- DataFlowDiagram, sbom/wizard/page

website (2 files):
- DependencyMap, lehrer/abitur-archiv

Other: nibis_ingestion, grid_detection_service, export-doclayout-onnx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 09:41:42 +02:00

147 lines
4.3 KiB
Python

"""
Alert Digests - PDF-Generierung und E-Mail-Versand.
"""
import io
import logging
from ..db.models import AlertDigestDB
logger = logging.getLogger(__name__)
async def generate_pdf_from_html(html_content: str) -> bytes:
"""
Generiere PDF aus HTML.
Verwendet WeasyPrint oder wkhtmltopdf als Fallback.
"""
try:
from weasyprint import HTML
pdf_bytes = HTML(string=html_content).write_pdf()
return pdf_bytes
except ImportError:
pass
try:
import pdfkit
pdf_bytes = pdfkit.from_string(html_content, False)
return pdf_bytes
except ImportError:
pass
try:
from xhtml2pdf import pisa
result = io.BytesIO()
pisa.CreatePDF(io.StringIO(html_content), dest=result)
return result.getvalue()
except ImportError:
pass
raise ImportError(
"Keine PDF-Bibliothek verfuegbar. "
"Installieren Sie: pip install weasyprint oder pip install pdfkit oder pip install xhtml2pdf"
)
async def send_digest_by_email(digest: AlertDigestDB, recipient_email: str):
"""
Versende Digest per E-Mail.
Verwendet:
- Lokalen SMTP-Server (Postfix/Sendmail)
- SMTP-Relay (z.B. SES, Mailgun)
- SendGrid API
"""
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
msg = MIMEMultipart('alternative')
msg['Subject'] = f"Wochenbericht: {digest.period_start.strftime('%d.%m.%Y')} - {digest.period_end.strftime('%d.%m.%Y')}"
msg['From'] = os.getenv('SMTP_FROM', 'alerts@breakpilot.app')
msg['To'] = recipient_email
text_content = f"""
BreakPilot Alerts - Wochenbericht
Zeitraum: {digest.period_start.strftime('%d.%m.%Y')} - {digest.period_end.strftime('%d.%m.%Y')}
Gesamt: {digest.total_alerts} Meldungen
Kritisch: {digest.critical_count}
Dringend: {digest.urgent_count}
Oeffnen Sie die HTML-Version fuer die vollstaendige Uebersicht.
---
Diese E-Mail wurde automatisch von BreakPilot Alerts generiert.
"""
msg.attach(MIMEText(text_content, 'plain', 'utf-8'))
if digest.summary_html:
msg.attach(MIMEText(digest.summary_html, 'html', 'utf-8'))
try:
pdf_bytes = await generate_pdf_from_html(digest.summary_html)
pdf_attachment = MIMEApplication(pdf_bytes, _subtype='pdf')
pdf_attachment.add_header(
'Content-Disposition', 'attachment',
filename=f"wochenbericht_{digest.period_start.strftime('%Y%m%d')}.pdf"
)
msg.attach(pdf_attachment)
except Exception:
pass # PDF-Anhang ist optional
smtp_host = os.getenv('SMTP_HOST', 'localhost')
smtp_port = int(os.getenv('SMTP_PORT', '25'))
smtp_user = os.getenv('SMTP_USER', '')
smtp_pass = os.getenv('SMTP_PASS', '')
try:
if smtp_port == 465:
server = smtplib.SMTP_SSL(smtp_host, smtp_port)
else:
server = smtplib.SMTP(smtp_host, smtp_port)
if smtp_port == 587:
server.starttls()
if smtp_user and smtp_pass:
server.login(smtp_user, smtp_pass)
server.send_message(msg)
server.quit()
except Exception as e:
sendgrid_key = os.getenv('SENDGRID_API_KEY')
if sendgrid_key:
await send_via_sendgrid(msg, sendgrid_key)
else:
raise e
async def send_via_sendgrid(msg, api_key: str):
"""Fallback: SendGrid API."""
import httpx
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.sendgrid.com/v3/mail/send",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
},
json={
"personalizations": [{"to": [{"email": msg['To']}]}],
"from": {"email": msg['From']},
"subject": msg['Subject'],
"content": [
{"type": "text/plain", "value": msg.get_payload(0).get_payload()},
{"type": "text/html", "value": msg.get_payload(1).get_payload() if len(msg.get_payload()) > 1 else ""}
]
}
)
if response.status_code >= 400:
raise Exception(f"SendGrid error: {response.status_code}")