Files
breakpilot-lehrer/klausur-service/backend/mail/ai_service.py
Benjamin Admin 34da9f4cda [split-required] Split 700-870 LOC files across all services
backend-lehrer (11 files):
- llm_gateway/routes/schools.py (867 → 5), recording_api.py (848 → 6)
- messenger_api.py (840 → 5), print_generator.py (824 → 5)
- unit_analytics_api.py (751 → 5), classroom/routes/context.py (726 → 4)
- llm_gateway/routes/edu_search_seeds.py (710 → 4)

klausur-service (12 files):
- ocr_labeling_api.py (845 → 4), metrics_db.py (833 → 4)
- legal_corpus_api.py (790 → 4), page_crop.py (758 → 3)
- mail/ai_service.py (747 → 4), github_crawler.py (767 → 3)
- trocr_service.py (730 → 4), full_compliance_pipeline.py (723 → 4)
- dsfa_rag_api.py (715 → 4), ocr_pipeline_auto.py (705 → 4)

website (6 pages):
- audit-checklist (867 → 8), content (806 → 6)
- screen-flow (790 → 4), scraper (789 → 5)
- zeugnisse (776 → 5), modules (745 → 4)

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

197 lines
6.0 KiB
Python

"""
AI Email Analysis Service — Barrel Re-export
Split into:
- mail/ai_sender.py — Sender classification (domain + LLM)
- mail/ai_deadline.py — Deadline extraction (regex + LLM)
- mail/ai_category.py — Category classification + response suggestions
The AIEmailService class and get_ai_email_service() are defined here
to maintain the original public API.
"""
import logging
from typing import Optional, List, Tuple
from datetime import datetime
import httpx
from .models import (
EmailCategory,
SenderType,
TaskPriority,
SenderClassification,
DeadlineExtraction,
EmailAnalysisResult,
ResponseSuggestion,
get_priority_from_sender_type,
)
from .mail_db import update_email_ai_analysis
from .ai_sender import classify_sender, LLM_GATEWAY_URL
from .ai_deadline import extract_deadlines
from .ai_category import classify_category, suggest_response
logger = logging.getLogger(__name__)
class AIEmailService:
"""
AI-powered email analysis service.
Features:
- Domain-based sender classification (fast, no LLM)
- LLM-based sender classification (fallback)
- Deadline extraction using regex + LLM
- Category classification
- Response suggestions
"""
def __init__(self):
self._http_client = None
async def get_http_client(self) -> httpx.AsyncClient:
"""Get or create HTTP client for LLM gateway."""
if self._http_client is None:
self._http_client = httpx.AsyncClient(timeout=30.0)
return self._http_client
async def classify_sender(
self,
sender_email: str,
sender_name: Optional[str] = None,
subject: Optional[str] = None,
body_preview: Optional[str] = None,
) -> SenderClassification:
"""Classify the sender of an email."""
client = await self.get_http_client()
return await classify_sender(
client, sender_email, sender_name, subject, body_preview
)
async def extract_deadlines(
self,
subject: str,
body_text: str,
) -> List[DeadlineExtraction]:
"""Extract deadlines from email content."""
client = await self.get_http_client()
return await extract_deadlines(client, subject, body_text)
async def classify_category(
self,
subject: str,
body_preview: str,
sender_type: SenderType,
) -> Tuple[EmailCategory, float]:
"""Classify email into a category."""
client = await self.get_http_client()
return await classify_category(client, subject, body_preview, sender_type)
async def analyze_email(
self,
email_id: str,
sender_email: str,
sender_name: Optional[str],
subject: str,
body_text: Optional[str],
body_preview: Optional[str],
) -> EmailAnalysisResult:
"""Run full analysis pipeline on an email."""
# 1. Classify sender
sender_classification = await self.classify_sender(
sender_email, sender_name, subject, body_preview
)
# 2. Extract deadlines
deadlines = await self.extract_deadlines(subject, body_text or "")
# 3. Classify category
category, category_confidence = await self.classify_category(
subject, body_preview or "", sender_classification.sender_type
)
# 4. Determine priority
suggested_priority = get_priority_from_sender_type(sender_classification.sender_type)
# Upgrade priority if deadlines are found
if deadlines:
nearest_deadline = min(d.deadline_date for d in deadlines)
days_until = (nearest_deadline - datetime.now()).days
if days_until <= 1:
suggested_priority = TaskPriority.URGENT
elif days_until <= 3:
suggested_priority = TaskPriority.HIGH
elif days_until <= 7:
suggested_priority = max(suggested_priority, TaskPriority.MEDIUM)
# 5. Summary (optional)
summary = None
# 6. Determine if task should be auto-created
auto_create_task = (
len(deadlines) > 0 or
sender_classification.sender_type in [
SenderType.KULTUSMINISTERIUM,
SenderType.LANDESSCHULBEHOERDE,
SenderType.RLSB,
]
)
# 7. Store analysis in database
await update_email_ai_analysis(
email_id=email_id,
category=category.value,
sender_type=sender_classification.sender_type.value,
sender_authority_name=sender_classification.authority_name,
detected_deadlines=[
{
"date": d.deadline_date.isoformat(),
"description": d.description,
"is_firm": d.is_firm,
}
for d in deadlines
],
suggested_priority=suggested_priority.value,
ai_summary=summary,
)
return EmailAnalysisResult(
email_id=email_id,
category=category,
category_confidence=category_confidence,
sender_classification=sender_classification,
deadlines=deadlines,
suggested_priority=suggested_priority,
summary=summary,
suggested_actions=[],
auto_create_task=auto_create_task,
)
async def suggest_response(
self,
subject: str,
body_text: str,
sender_type: SenderType,
category: EmailCategory,
) -> List[ResponseSuggestion]:
"""Generate response suggestions for an email."""
client = await self.get_http_client()
return await suggest_response(
client, subject, body_text, sender_type, category
)
# Global instance
_ai_service: Optional[AIEmailService] = None
def get_ai_email_service() -> AIEmailService:
"""Get or create the global AIEmailService instance."""
global _ai_service
if _ai_service is None:
_ai_service = AIEmailService()
return _ai_service