[split-required] Split 500-850 LOC files (batch 2)

backend-lehrer (10 files):
- game/database.py (785 → 5), correction_api.py (683 → 4)
- classroom_engine/antizipation.py (676 → 5)
- llm_gateway schools/edu_search already done in prior batch

klausur-service (12 files):
- orientation_crop_api.py (694 → 5), pdf_export.py (677 → 4)
- zeugnis_crawler.py (676 → 5), grid_editor_api.py (671 → 5)
- eh_templates.py (658 → 5), mail/api.py (651 → 5)
- qdrant_service.py (638 → 5), training_api.py (625 → 4)

website (6 pages):
- middleware (696 → 8), mail (733 → 6), consent (628 → 8)
- compliance/risks (622 → 5), export (502 → 5), brandbook (629 → 7)

studio-v2 (3 components):
- B2BMigrationWizard (848 → 3), CleanupPanel (765 → 2)
- dashboard-experimental (739 → 2)

admin-lehrer (4 files):
- uebersetzungen (769 → 4), manager (670 → 2)
- ChunkBrowserQA (675 → 6), dsfa/page (674 → 5)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-25 08:24:01 +02:00
parent 34da9f4cda
commit b4613e26f3
118 changed files with 15258 additions and 14680 deletions

View File

@@ -0,0 +1,218 @@
# ==============================================
# Breakpilot Drive - Game Session & Quiz DB Methods
# ==============================================
# Methods for saving/querying game sessions, quiz answers, and basic leaderboard.
import json
import logging
from typing import Optional, List, Dict, Any
logger = logging.getLogger(__name__)
class SessionsMixin:
"""Mixin providing game session and quiz database methods for GameDatabase."""
async def save_game_session(
self,
student_id: str,
game_mode: str,
duration_seconds: int,
distance_traveled: float,
score: int,
questions_answered: int,
questions_correct: int,
difficulty_level: int,
metadata: Optional[Dict[str, Any]] = None,
) -> Optional[str]:
"""Save a game session and return the session ID."""
await self._ensure_connected()
if not self._pool:
return None
try:
async with self._pool.acquire() as conn:
row = await conn.fetchrow(
"""
INSERT INTO game_sessions (
student_id, game_mode, duration_seconds, distance_traveled,
score, questions_answered, questions_correct, difficulty_level,
started_at, ended_at, metadata
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8,
NOW() - make_interval(secs => $3), NOW(), $9)
RETURNING id
""",
student_id, game_mode, duration_seconds, distance_traveled,
score, questions_answered, questions_correct, difficulty_level,
json.dumps(metadata) if metadata else None
)
if row:
return str(row["id"])
except Exception as e:
logger.error(f"Failed to save game session: {e}")
return None
async def get_user_sessions(
self,
student_id: str,
limit: int = 10
) -> List[Dict[str, Any]]:
"""Get recent game sessions for a user."""
await self._ensure_connected()
if not self._pool:
return []
try:
async with self._pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT id, student_id, game_mode, duration_seconds, distance_traveled,
score, questions_answered, questions_correct, difficulty_level,
started_at, ended_at, metadata
FROM game_sessions
WHERE student_id = $1
ORDER BY ended_at DESC
LIMIT $2
""",
student_id, limit
)
return [
{
"session_id": str(row["id"]),
"user_id": str(row["student_id"]),
"game_mode": row["game_mode"],
"duration_seconds": row["duration_seconds"],
"distance_traveled": float(row["distance_traveled"]) if row["distance_traveled"] else 0.0,
"score": row["score"],
"questions_answered": row["questions_answered"],
"questions_correct": row["questions_correct"],
"difficulty_level": row["difficulty_level"],
"started_at": row["started_at"].isoformat() if row["started_at"] else None,
"ended_at": row["ended_at"].isoformat() if row["ended_at"] else None,
}
for row in rows
]
except Exception as e:
logger.error(f"Failed to get user sessions: {e}")
return []
async def get_leaderboard(
self,
timeframe: str = "day",
limit: int = 10
) -> List[Dict[str, Any]]:
"""Get leaderboard data."""
await self._ensure_connected()
if not self._pool:
return []
# Timeframe filter
timeframe_sql = {
"day": "ended_at > NOW() - INTERVAL '1 day'",
"week": "ended_at > NOW() - INTERVAL '7 days'",
"month": "ended_at > NOW() - INTERVAL '30 days'",
"all": "1=1",
}.get(timeframe, "1=1")
try:
async with self._pool.acquire() as conn:
rows = await conn.fetch(
f"""
SELECT student_id, SUM(score) as total_score
FROM game_sessions
WHERE {timeframe_sql}
GROUP BY student_id
ORDER BY total_score DESC
LIMIT $1
""",
limit
)
return [
{
"rank": i + 1,
"user_id": str(row["student_id"]),
"total_score": int(row["total_score"]),
}
for i, row in enumerate(rows)
]
except Exception as e:
logger.error(f"Failed to get leaderboard: {e}")
return []
async def save_quiz_answer(
self,
session_id: str,
question_id: str,
subject: str,
difficulty: int,
is_correct: bool,
answer_time_ms: int,
) -> bool:
"""Save an individual quiz answer."""
await self._ensure_connected()
if not self._pool:
return False
try:
async with self._pool.acquire() as conn:
await conn.execute(
"""
INSERT INTO game_quiz_answers (
session_id, question_id, subject, difficulty,
is_correct, answer_time_ms
) VALUES ($1, $2, $3, $4, $5, $6)
""",
session_id, question_id, subject, difficulty,
is_correct, answer_time_ms
)
return True
except Exception as e:
logger.error(f"Failed to save quiz answer: {e}")
return False
async def get_subject_stats(
self,
student_id: str
) -> Dict[str, Dict[str, Any]]:
"""Get per-subject statistics for a student."""
await self._ensure_connected()
if not self._pool:
return {}
try:
async with self._pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT
qa.subject,
COUNT(*) as total,
SUM(CASE WHEN qa.is_correct THEN 1 ELSE 0 END) as correct,
AVG(qa.answer_time_ms) as avg_time_ms
FROM game_quiz_answers qa
JOIN game_sessions gs ON qa.session_id = gs.id
WHERE gs.student_id = $1
GROUP BY qa.subject
""",
student_id
)
return {
row["subject"]: {
"total": row["total"],
"correct": row["correct"],
"accuracy": row["correct"] / row["total"] if row["total"] > 0 else 0.0,
"avg_time_ms": int(row["avg_time_ms"]) if row["avg_time_ms"] else 0,
}
for row in rows
}
except Exception as e:
logger.error(f"Failed to get subject stats: {e}")
return {}