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>
This commit is contained in:
Benjamin Admin
2026-02-09 09:51:32 +01:00
parent f7487ee240
commit bfdaf63ba9
2009 changed files with 749983 additions and 1731 deletions

View File

@@ -0,0 +1,933 @@
"""
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 });
}
"""