backend-lehrer (5 files): - alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3) - teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3) - mail/mail_db.py (987 → 6) klausur-service (5 files): - legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4) - ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2) - KorrekturPage.tsx (956 → 6) website (5 pages): - mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7) - ocr-labeling (946 → 7), audit-workspace (871 → 4) studio-v2 (5 files + 1 deleted): - page.tsx (946 → 5), MessagesContext.tsx (925 → 4) - korrektur (914 → 6), worksheet-cleanup (899 → 6) - useVocabWorksheet.ts (888 → 3) - Deleted dead page-original.tsx (934 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
227 lines
7.4 KiB
Python
227 lines
7.4 KiB
Python
"""
|
|
Repository für Alert Profiles (Nutzer-Profile für Relevanz-Scoring).
|
|
"""
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import Optional, List, Dict, Any
|
|
from sqlalchemy.orm import Session as DBSession
|
|
from sqlalchemy.orm.attributes import flag_modified
|
|
|
|
from .models import AlertProfileDB
|
|
|
|
|
|
class ProfileRepository:
|
|
"""Repository für Alert Profiles (Nutzer-Profile für Relevanz-Scoring)."""
|
|
|
|
def __init__(self, db: DBSession):
|
|
self.db = db
|
|
|
|
# ==================== CREATE / GET-OR-CREATE ====================
|
|
|
|
def get_or_create(self, user_id: str = None) -> AlertProfileDB:
|
|
"""Holt oder erstellt ein Profil."""
|
|
profile = self.get_by_user_id(user_id)
|
|
if profile:
|
|
return profile
|
|
|
|
# Neues Profil erstellen
|
|
profile = AlertProfileDB(
|
|
id=str(uuid.uuid4()),
|
|
user_id=user_id,
|
|
name="Default" if not user_id else f"Profile {user_id[:8]}",
|
|
)
|
|
self.db.add(profile)
|
|
self.db.commit()
|
|
self.db.refresh(profile)
|
|
return profile
|
|
|
|
def create_default_education_profile(self, user_id: str = None) -> AlertProfileDB:
|
|
"""Erstellt ein Standard-Profil für Bildungsthemen."""
|
|
profile = AlertProfileDB(
|
|
id=str(uuid.uuid4()),
|
|
user_id=user_id,
|
|
name="Bildung Default",
|
|
priorities=[
|
|
{
|
|
"label": "Inklusion",
|
|
"weight": 0.9,
|
|
"keywords": ["inklusiv", "Förderbedarf", "Behinderung", "Barrierefreiheit"],
|
|
"description": "Inklusive Bildung, Förderschulen, Nachteilsausgleich"
|
|
},
|
|
{
|
|
"label": "Datenschutz Schule",
|
|
"weight": 0.85,
|
|
"keywords": ["DSGVO", "Schülerfotos", "Einwilligung", "personenbezogene Daten"],
|
|
"description": "DSGVO in Schulen, Datenschutz bei Klassenfotos"
|
|
},
|
|
{
|
|
"label": "Schulrecht Bayern",
|
|
"weight": 0.8,
|
|
"keywords": ["BayEUG", "Schulordnung", "Kultusministerium", "Bayern"],
|
|
"description": "Bayerisches Schulrecht, Verordnungen"
|
|
},
|
|
{
|
|
"label": "Digitalisierung Schule",
|
|
"weight": 0.7,
|
|
"keywords": ["DigitalPakt", "Tablet-Klasse", "Lernplattform"],
|
|
"description": "Digitale Medien im Unterricht"
|
|
},
|
|
],
|
|
exclusions=["Stellenanzeige", "Praktikum gesucht", "Werbung", "Pressemitteilung"],
|
|
policies={
|
|
"prefer_german_sources": True,
|
|
"max_age_days": 30,
|
|
"min_content_length": 100,
|
|
}
|
|
)
|
|
self.db.add(profile)
|
|
self.db.commit()
|
|
self.db.refresh(profile)
|
|
return profile
|
|
|
|
# ==================== READ ====================
|
|
|
|
def get_by_id(self, profile_id: str) -> Optional[AlertProfileDB]:
|
|
"""Holt ein Profil nach ID."""
|
|
return self.db.query(AlertProfileDB).filter(
|
|
AlertProfileDB.id == profile_id
|
|
).first()
|
|
|
|
def get_by_user_id(self, user_id: str) -> Optional[AlertProfileDB]:
|
|
"""Holt ein Profil nach User-ID."""
|
|
if not user_id:
|
|
# Default-Profil ohne User
|
|
return self.db.query(AlertProfileDB).filter(
|
|
AlertProfileDB.user_id.is_(None)
|
|
).first()
|
|
|
|
return self.db.query(AlertProfileDB).filter(
|
|
AlertProfileDB.user_id == user_id
|
|
).first()
|
|
|
|
# ==================== UPDATE ====================
|
|
|
|
def update_priorities(
|
|
self,
|
|
profile_id: str,
|
|
priorities: List[Dict],
|
|
) -> Optional[AlertProfileDB]:
|
|
"""Aktualisiert die Prioritäten eines Profils."""
|
|
profile = self.get_by_id(profile_id)
|
|
if not profile:
|
|
return None
|
|
|
|
profile.priorities = priorities
|
|
self.db.commit()
|
|
self.db.refresh(profile)
|
|
return profile
|
|
|
|
def update_exclusions(
|
|
self,
|
|
profile_id: str,
|
|
exclusions: List[str],
|
|
) -> Optional[AlertProfileDB]:
|
|
"""Aktualisiert die Ausschlüsse eines Profils."""
|
|
profile = self.get_by_id(profile_id)
|
|
if not profile:
|
|
return None
|
|
|
|
profile.exclusions = exclusions
|
|
self.db.commit()
|
|
self.db.refresh(profile)
|
|
return profile
|
|
|
|
def add_feedback(
|
|
self,
|
|
profile_id: str,
|
|
title: str,
|
|
url: str,
|
|
is_relevant: bool,
|
|
reason: str = "",
|
|
) -> Optional[AlertProfileDB]:
|
|
"""Fügt Feedback als Beispiel hinzu."""
|
|
profile = self.get_by_id(profile_id)
|
|
if not profile:
|
|
return None
|
|
|
|
example = {
|
|
"title": title,
|
|
"url": url,
|
|
"reason": reason,
|
|
"added_at": datetime.utcnow().isoformat(),
|
|
}
|
|
|
|
if is_relevant:
|
|
examples = list(profile.positive_examples or [])
|
|
examples.append(example)
|
|
profile.positive_examples = examples[-20:] # Max 20
|
|
profile.total_kept += 1
|
|
flag_modified(profile, "positive_examples")
|
|
else:
|
|
examples = list(profile.negative_examples or [])
|
|
examples.append(example)
|
|
profile.negative_examples = examples[-20:] # Max 20
|
|
profile.total_dropped += 1
|
|
flag_modified(profile, "negative_examples")
|
|
|
|
profile.total_scored += 1
|
|
self.db.commit()
|
|
self.db.refresh(profile)
|
|
return profile
|
|
|
|
def update_stats(
|
|
self,
|
|
profile_id: str,
|
|
kept: int = 0,
|
|
dropped: int = 0,
|
|
) -> Optional[AlertProfileDB]:
|
|
"""Aktualisiert die Statistiken eines Profils."""
|
|
profile = self.get_by_id(profile_id)
|
|
if not profile:
|
|
return None
|
|
|
|
profile.total_scored += kept + dropped
|
|
profile.total_kept += kept
|
|
profile.total_dropped += dropped
|
|
|
|
self.db.commit()
|
|
self.db.refresh(profile)
|
|
return profile
|
|
|
|
# ==================== DELETE ====================
|
|
|
|
def delete(self, profile_id: str) -> bool:
|
|
"""Löscht ein Profil."""
|
|
profile = self.get_by_id(profile_id)
|
|
if not profile:
|
|
return False
|
|
|
|
self.db.delete(profile)
|
|
self.db.commit()
|
|
return True
|
|
|
|
# ==================== CONVERSION ====================
|
|
|
|
def to_dict(self, profile: AlertProfileDB) -> Dict[str, Any]:
|
|
"""Konvertiert DB-Model zu Dictionary."""
|
|
return {
|
|
"id": profile.id,
|
|
"user_id": profile.user_id,
|
|
"name": profile.name,
|
|
"priorities": profile.priorities,
|
|
"exclusions": profile.exclusions,
|
|
"policies": profile.policies,
|
|
"examples": {
|
|
"positive": len(profile.positive_examples or []),
|
|
"negative": len(profile.negative_examples or []),
|
|
},
|
|
"stats": {
|
|
"total_scored": profile.total_scored,
|
|
"total_kept": profile.total_kept,
|
|
"total_dropped": profile.total_dropped,
|
|
"accuracy_estimate": profile.accuracy_estimate,
|
|
},
|
|
"created_at": profile.created_at.isoformat() if profile.created_at else None,
|
|
"updated_at": profile.updated_at.isoformat() if profile.updated_at else None,
|
|
}
|