This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

934 lines
23 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
BreakPilot Studio - Notenbuch (Gradebook) Modul
Funktionen:
- Notenübersicht pro Klasse/Schüler
- Noteneingabe für verschiedene Prüfungsarten
- Durchschnittsberechnung
- Trend-Analyse
- Export nach Excel/PDF
- Integration mit Zeugnissen
"""
class GradebookModule:
"""Modul fuer digitale Notenverwaltung."""
@staticmethod
def get_css() -> str:
"""CSS fuer das Gradebook-Modul."""
return """
/* =============================================
GRADEBOOK MODULE - Notenbuch
============================================= */
/* Panel Layout */
.panel-gradebook {
display: none;
flex-direction: column;
height: 100%;
background: var(--bp-bg);
overflow: hidden;
}
.panel-gradebook.active {
display: flex;
}
/* Header */
.gradebook-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 32px;
border-bottom: 1px solid var(--bp-border);
background: var(--bp-surface);
}
.gradebook-title-section h1 {
font-size: 24px;
font-weight: 700;
color: var(--bp-text);
margin-bottom: 4px;
}
.gradebook-subtitle {
font-size: 14px;
color: var(--bp-text-muted);
}
.gradebook-actions {
display: flex;
gap: 12px;
}
/* Filter Bar */
.gradebook-filters {
display: flex;
gap: 16px;
padding: 16px 32px;
background: var(--bp-surface-elevated);
border-bottom: 1px solid var(--bp-border);
flex-wrap: wrap;
align-items: center;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.filter-group label {
font-size: 12px;
color: var(--bp-text-muted);
font-weight: 500;
}
.filter-group select,
.filter-group input {
padding: 8px 12px;
border: 1px solid var(--bp-border);
border-radius: 8px;
background: var(--bp-surface);
color: var(--bp-text);
font-size: 13px;
min-width: 150px;
}
.filter-group select:focus,
.filter-group input:focus {
outline: none;
border-color: var(--bp-primary);
}
/* Content Layout */
.gradebook-content {
flex: 1;
overflow: auto;
padding: 24px 32px;
}
/* Grade Table */
.gradebook-table-container {
background: var(--bp-surface);
border-radius: 12px;
border: 1px solid var(--bp-border);
overflow: hidden;
}
.gradebook-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.gradebook-table thead {
background: var(--bp-surface-elevated);
position: sticky;
top: 0;
z-index: 10;
}
.gradebook-table th {
padding: 12px 16px;
text-align: left;
font-weight: 600;
color: var(--bp-text);
border-bottom: 2px solid var(--bp-border);
white-space: nowrap;
}
.gradebook-table th.grade-column {
text-align: center;
min-width: 80px;
}
.gradebook-table th.average-column {
text-align: center;
background: var(--bp-primary-soft);
}
.gradebook-table tbody tr {
border-bottom: 1px solid var(--bp-border);
transition: background 0.2s;
}
.gradebook-table tbody tr:hover {
background: var(--bp-surface-elevated);
}
.gradebook-table td {
padding: 12px 16px;
color: var(--bp-text);
}
.gradebook-table td.grade-cell {
text-align: center;
}
.gradebook-table td.average-cell {
text-align: center;
font-weight: 600;
background: var(--bp-primary-soft);
}
/* Student Name Column */
.student-name {
display: flex;
align-items: center;
gap: 12px;
}
.student-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--bp-primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
}
.student-info {
display: flex;
flex-direction: column;
}
.student-info .name {
font-weight: 500;
}
.student-info .class {
font-size: 11px;
color: var(--bp-text-muted);
}
/* Grade Input */
.grade-input {
width: 50px;
padding: 6px 8px;
border: 1px solid transparent;
border-radius: 6px;
background: var(--bp-surface);
color: var(--bp-text);
text-align: center;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.grade-input:hover {
border-color: var(--bp-border);
}
.grade-input:focus {
outline: none;
border-color: var(--bp-primary);
background: var(--bp-surface-elevated);
}
/* Grade Colors */
.grade-1 { color: #22c55e; }
.grade-2 { color: #84cc16; }
.grade-3 { color: #eab308; }
.grade-4 { color: #f97316; }
.grade-5 { color: #ef4444; }
.grade-6 { color: #dc2626; }
.grade-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
}
.grade-badge.grade-1 { background: rgba(34, 197, 94, 0.15); }
.grade-badge.grade-2 { background: rgba(132, 204, 22, 0.15); }
.grade-badge.grade-3 { background: rgba(234, 179, 8, 0.15); }
.grade-badge.grade-4 { background: rgba(249, 115, 22, 0.15); }
.grade-badge.grade-5 { background: rgba(239, 68, 68, 0.15); }
.grade-badge.grade-6 { background: rgba(220, 38, 38, 0.15); }
/* Trend Indicator */
.trend-indicator {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
margin-left: 8px;
}
.trend-up { color: var(--bp-success); }
.trend-down { color: var(--bp-danger); }
.trend-stable { color: var(--bp-text-muted); }
/* Statistics Panel */
.gradebook-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
padding: 20px;
}
.stat-card .stat-icon {
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
margin-bottom: 12px;
}
.stat-card .stat-icon.blue {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}
.stat-card .stat-icon.green {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
}
.stat-card .stat-icon.yellow {
background: rgba(234, 179, 8, 0.15);
color: #eab308;
}
.stat-card .stat-icon.red {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
.stat-card .stat-value {
font-size: 28px;
font-weight: 700;
color: var(--bp-text);
margin-bottom: 4px;
}
.stat-card .stat-label {
font-size: 13px;
color: var(--bp-text-muted);
}
/* Add Grade Modal */
.add-grade-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.add-grade-modal.active {
display: flex;
}
.add-grade-content {
background: var(--bp-surface);
border-radius: 16px;
padding: 24px;
width: 100%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
}
.add-grade-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.add-grade-header h2 {
font-size: 18px;
font-weight: 600;
}
.add-grade-form {
display: flex;
flex-direction: column;
gap: 16px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.form-group label {
font-size: 13px;
font-weight: 500;
color: var(--bp-text-muted);
}
.form-group input,
.form-group select,
.form-group textarea {
padding: 10px 12px;
border: 1px solid var(--bp-border);
border-radius: 8px;
background: var(--bp-surface-elevated);
color: var(--bp-text);
font-size: 14px;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--bp-primary);
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 8px;
}
/* Exam Types */
.exam-type-selector {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.exam-type-btn {
padding: 8px 16px;
border: 1px solid var(--bp-border);
border-radius: 20px;
background: var(--bp-surface);
color: var(--bp-text-muted);
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.exam-type-btn:hover {
border-color: var(--bp-primary);
color: var(--bp-text);
}
.exam-type-btn.active {
background: var(--bp-primary);
border-color: var(--bp-primary);
color: white;
}
/* Chart Container */
.gradebook-chart {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
}
.gradebook-chart h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
}
.chart-placeholder {
height: 200px;
background: var(--bp-surface-elevated);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: var(--bp-text-muted);
}
/* Responsive */
@media (max-width: 768px) {
.gradebook-filters {
flex-direction: column;
align-items: stretch;
}
.filter-group {
width: 100%;
}
.filter-group select {
width: 100%;
}
.form-row {
grid-template-columns: 1fr;
}
.gradebook-stats {
grid-template-columns: 1fr;
}
}
/* Print Styles */
@media print {
.gradebook-header,
.gradebook-filters,
.gradebook-actions,
.add-grade-modal {
display: none !important;
}
.gradebook-content {
padding: 0;
}
.gradebook-table {
font-size: 11px;
}
}
"""
@staticmethod
def get_html() -> str:
"""HTML fuer das Gradebook-Panel."""
return """
<!-- Gradebook Panel -->
<div id="panel-gradebook" class="panel-gradebook">
<!-- Header -->
<div class="gradebook-header">
<div class="gradebook-title-section">
<h1>Notenbuch</h1>
<div class="gradebook-subtitle">Notenübersicht und -verwaltung</div>
</div>
<div class="gradebook-actions">
<button class="btn btn-secondary" onclick="exportGradebook()">
<span class="icon">📥</span> Exportieren
</button>
<button class="btn btn-primary" onclick="openAddGradeModal()">
<span class="icon"></span> Note eintragen
</button>
</div>
</div>
<!-- Filters -->
<div class="gradebook-filters">
<div class="filter-group">
<label>Schuljahr</label>
<select id="gradebook-year" onchange="loadGradebook()">
<option value="2024/2025">2024/2025</option>
<option value="2023/2024">2023/2024</option>
</select>
</div>
<div class="filter-group">
<label>Klasse</label>
<select id="gradebook-class" onchange="loadGradebook()">
<option value="">Alle Klassen</option>
<option value="5a">5a</option>
<option value="5b">5b</option>
<option value="6a">6a</option>
<option value="6b">6b</option>
</select>
</div>
<div class="filter-group">
<label>Fach</label>
<select id="gradebook-subject" onchange="loadGradebook()">
<option value="">Alle Fächer</option>
<option value="deutsch">Deutsch</option>
<option value="mathematik">Mathematik</option>
<option value="englisch">Englisch</option>
<option value="biologie">Biologie</option>
<option value="geschichte">Geschichte</option>
</select>
</div>
<div class="filter-group">
<label>Halbjahr</label>
<select id="gradebook-semester" onchange="loadGradebook()">
<option value="1">1. Halbjahr</option>
<option value="2">2. Halbjahr</option>
</select>
</div>
</div>
<!-- Content -->
<div class="gradebook-content">
<!-- Statistics -->
<div class="gradebook-stats">
<div class="stat-card">
<div class="stat-icon blue">📊</div>
<div class="stat-value" id="stat-average">2.4</div>
<div class="stat-label">Klassendurchschnitt</div>
</div>
<div class="stat-card">
<div class="stat-icon green">✓</div>
<div class="stat-value" id="stat-passed">24</div>
<div class="stat-label">Schüler bestanden</div>
</div>
<div class="stat-card">
<div class="stat-icon yellow">📝</div>
<div class="stat-value" id="stat-exams">8</div>
<div class="stat-label">Leistungsnachweise</div>
</div>
<div class="stat-card">
<div class="stat-icon red">⚠️</div>
<div class="stat-value" id="stat-warning">3</div>
<div class="stat-label">Gefährdete Schüler</div>
</div>
</div>
<!-- Grade Distribution Chart -->
<div class="gradebook-chart">
<h3>Notenverteilung</h3>
<div class="chart-placeholder" id="grade-distribution-chart">
Hier wird das Notenverteilungsdiagramm angezeigt
</div>
</div>
<!-- Grade Table -->
<div class="gradebook-table-container">
<table class="gradebook-table">
<thead>
<tr>
<th>Schüler/in</th>
<th class="grade-column">Klausur 1</th>
<th class="grade-column">Test 1</th>
<th class="grade-column">Klausur 2</th>
<th class="grade-column">Mündlich</th>
<th class="grade-column">Test 2</th>
<th class="grade-column average-column">Schnitt</th>
<th class="grade-column">Trend</th>
</tr>
</thead>
<tbody id="gradebook-tbody">
<!-- Dynamisch generiert -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Add Grade Modal -->
<div class="add-grade-modal" id="add-grade-modal">
<div class="add-grade-content">
<div class="add-grade-header">
<h2>Note eintragen</h2>
<button class="btn-icon" onclick="closeAddGradeModal()">✕</button>
</div>
<form class="add-grade-form" onsubmit="saveGrade(event)">
<div class="form-row">
<div class="form-group">
<label>Schüler/in</label>
<select id="grade-student" required>
<option value="">Auswählen...</option>
</select>
</div>
<div class="form-group">
<label>Fach</label>
<select id="grade-subject" required>
<option value="">Auswählen...</option>
<option value="deutsch">Deutsch</option>
<option value="mathematik">Mathematik</option>
<option value="englisch">Englisch</option>
</select>
</div>
</div>
<div class="form-group">
<label>Art des Leistungsnachweises</label>
<div class="exam-type-selector">
<button type="button" class="exam-type-btn active" data-type="klausur">Klausur</button>
<button type="button" class="exam-type-btn" data-type="test">Test</button>
<button type="button" class="exam-type-btn" data-type="muendlich">Mündlich</button>
<button type="button" class="exam-type-btn" data-type="hausaufgabe">Hausaufgabe</button>
<button type="button" class="exam-type-btn" data-type="referat">Referat</button>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Note</label>
<select id="grade-value" required>
<option value="">Auswählen...</option>
<option value="1">1 (sehr gut)</option>
<option value="2">2 (gut)</option>
<option value="3">3 (befriedigend)</option>
<option value="4">4 (ausreichend)</option>
<option value="5">5 (mangelhaft)</option>
<option value="6">6 (ungenügend)</option>
</select>
</div>
<div class="form-group">
<label>Datum</label>
<input type="date" id="grade-date" required>
</div>
</div>
<div class="form-group">
<label>Gewichtung (%)</label>
<input type="number" id="grade-weight" min="0" max="100" value="100" required>
</div>
<div class="form-group">
<label>Bemerkung (optional)</label>
<textarea id="grade-comment" rows="2" placeholder="z.B. Nachschreibeklausur"></textarea>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="closeAddGradeModal()">Abbrechen</button>
<button type="submit" class="btn btn-primary">Speichern</button>
</div>
</form>
</div>
</div>
"""
@staticmethod
def get_js() -> str:
"""JavaScript fuer das Gradebook-Modul."""
return """
// =============================================
// GRADEBOOK MODULE - JavaScript
// =============================================
// Sample data (in production: from API)
const gradebookData = {
students: [
{ id: 1, name: 'Anna Beispiel', class: '5a', grades: { k1: 2, t1: 1, k2: 2, m: 2, t2: 2 }, avg: 1.8 },
{ id: 2, name: 'Ben Schmidt', class: '5a', grades: { k1: 3, t1: 3, k2: 3, m: 2, t2: 3 }, avg: 2.8 },
{ id: 3, name: 'Clara Weber', class: '5a', grades: { k1: 1, t1: 2, k2: 1, m: 1, t2: 1 }, avg: 1.2 },
{ id: 4, name: 'David Müller', class: '5a', grades: { k1: 4, t1: 4, k2: 5, m: 3, t2: 4 }, avg: 4.0 },
{ id: 5, name: 'Emma Fischer', class: '5a', grades: { k1: 2, t1: 2, k2: 2, m: 2, t2: 2 }, avg: 2.0 },
{ id: 6, name: 'Felix Wagner', class: '5a', grades: { k1: 3, t1: 2, k2: 3, m: 3, t2: 2 }, avg: 2.6 },
{ id: 7, name: 'Greta Hoffmann', class: '5a', grades: { k1: 1, t1: 1, k2: 1, m: 2, t2: 1 }, avg: 1.2 },
{ id: 8, name: 'Hans Bauer', class: '5a', grades: { k1: 5, t1: 4, k2: 5, m: 4, t2: 5 }, avg: 4.6 },
]
};
// Initialize Gradebook
function initGradebook() {
loadGradebook();
initExamTypeSelector();
setTodayDate();
populateStudentSelect();
}
// Load Gradebook Data
function loadGradebook() {
const tbody = document.getElementById('gradebook-tbody');
if (!tbody) return;
tbody.innerHTML = '';
gradebookData.students.forEach(student => {
const row = createStudentRow(student);
tbody.appendChild(row);
});
updateStatistics();
}
// Create Student Row
function createStudentRow(student) {
const row = document.createElement('tr');
// Get initials
const initials = student.name.split(' ').map(n => n[0]).join('');
// Calculate trend
const grades = Object.values(student.grades);
const recent = grades.slice(-2);
const trend = recent.length >= 2
? (recent[1] < recent[0] ? 'up' : recent[1] > recent[0] ? 'down' : 'stable')
: 'stable';
const trendIcon = trend === 'up' ? '' : trend === 'down' ? '' : '';
const trendClass = trend === 'up' ? 'trend-up' : trend === 'down' ? 'trend-down' : 'trend-stable';
row.innerHTML = `
<td>
<div class="student-name">
<div class="student-avatar">${initials}</div>
<div class="student-info">
<span class="name">${student.name}</span>
<span class="class">${student.class}</span>
</div>
</div>
</td>
<td class="grade-cell">
<span class="grade-badge grade-${student.grades.k1}">${student.grades.k1}</span>
</td>
<td class="grade-cell">
<span class="grade-badge grade-${student.grades.t1}">${student.grades.t1}</span>
</td>
<td class="grade-cell">
<span class="grade-badge grade-${student.grades.k2}">${student.grades.k2}</span>
</td>
<td class="grade-cell">
<span class="grade-badge grade-${student.grades.m}">${student.grades.m}</span>
</td>
<td class="grade-cell">
<span class="grade-badge grade-${student.grades.t2}">${student.grades.t2}</span>
</td>
<td class="average-cell">
<span class="grade-${Math.round(student.avg)}">${student.avg.toFixed(1)}</span>
</td>
<td class="grade-cell">
<span class="trend-indicator ${trendClass}">${trendIcon}</span>
</td>
`;
return row;
}
// Update Statistics
function updateStatistics() {
const students = gradebookData.students;
const averages = students.map(s => s.avg);
const classAvg = (averages.reduce((a, b) => a + b, 0) / averages.length).toFixed(1);
const passed = students.filter(s => s.avg <= 4.0).length;
const warning = students.filter(s => s.avg > 4.0).length;
document.getElementById('stat-average').textContent = classAvg;
document.getElementById('stat-passed').textContent = passed;
document.getElementById('stat-warning').textContent = warning;
}
// Exam Type Selector
function initExamTypeSelector() {
const buttons = document.querySelectorAll('.exam-type-btn');
buttons.forEach(btn => {
btn.addEventListener('click', () => {
buttons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
}
// Set Today's Date
function setTodayDate() {
const dateInput = document.getElementById('grade-date');
if (dateInput) {
dateInput.value = new Date().toISOString().split('T')[0];
}
}
// Populate Student Select
function populateStudentSelect() {
const select = document.getElementById('grade-student');
if (!select) return;
select.innerHTML = '<option value="">Auswählen...</option>';
gradebookData.students.forEach(student => {
const option = document.createElement('option');
option.value = student.id;
option.textContent = `${student.name} (${student.class})`;
select.appendChild(option);
});
}
// Open Add Grade Modal
function openAddGradeModal() {
const modal = document.getElementById('add-grade-modal');
if (modal) {
modal.classList.add('active');
setTodayDate();
}
}
// Close Add Grade Modal
function closeAddGradeModal() {
const modal = document.getElementById('add-grade-modal');
if (modal) {
modal.classList.remove('active');
}
}
// Save Grade
function saveGrade(event) {
event.preventDefault();
const studentId = document.getElementById('grade-student').value;
const subject = document.getElementById('grade-subject').value;
const grade = document.getElementById('grade-value').value;
const date = document.getElementById('grade-date').value;
const weight = document.getElementById('grade-weight').value;
const comment = document.getElementById('grade-comment').value;
const examType = document.querySelector('.exam-type-btn.active')?.dataset.type || 'klausur';
// In production: API call
console.log('Saving grade:', {
studentId, subject, grade, date, weight, comment, examType
});
// Show success message
alert('Note wurde gespeichert!');
closeAddGradeModal();
// Reload table
loadGradebook();
}
// Export Gradebook
function exportGradebook() {
const format = prompt('Export-Format wählen (excel/pdf/csv):', 'excel');
if (format) {
// In production: API call to generate export
console.log('Exporting gradebook as:', format);
alert(`Export als ${format.toUpperCase()} wird erstellt...`);
}
}
// Initialize on panel activation
document.addEventListener('DOMContentLoaded', () => {
// Check if gradebook panel is active
const panel = document.getElementById('panel-gradebook');
if (panel && panel.classList.contains('active')) {
initGradebook();
}
});
// Also initialize when panel becomes active
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const panel = document.getElementById('panel-gradebook');
if (panel && panel.classList.contains('active')) {
initGradebook();
}
}
});
});
const gradebookPanel = document.getElementById('panel-gradebook');
if (gradebookPanel) {
observer.observe(gradebookPanel, { attributes: true });
}
"""