[split-required] [guardrail-change] Enforce 500 LOC budget across all services

Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook)
and split all 44 files exceeding 500 LOC into domain-focused modules:

- consent-service (Go): models, handlers, services, database splits
- backend-core (Python): security_api, rbac_api, pdf_service, auth splits
- admin-core (TypeScript): 5 page.tsx + sidebar extractions
- pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits
- voice-service (Python): enhanced_task_orchestrator split

Result: 0 violations, 36 exempted (pipeline, tests, pure-data files).
Go build verified clean. No behavior changes — pure structural splits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-27 00:09:30 +02:00
parent 5ef039a6bc
commit 92c86ec6ba
162 changed files with 23853 additions and 23034 deletions

View File

@@ -334,210 +334,3 @@ func (s *GradeService) GetClassGradesBySubject(ctx context.Context, classID, sub
return overviews, nil
}
// ========================================
// Grade Statistics
// ========================================
// GetStudentGradeAverage calculates the overall grade average for a student
func (s *GradeService) GetStudentGradeAverage(ctx context.Context, studentID, schoolYearID uuid.UUID, semester int) (float64, error) {
query := `
SELECT COALESCE(SUM(value * weight) / NULLIF(SUM(weight), 0), 0)
FROM grades
WHERE student_id = $1 AND school_year_id = $2 AND semester = $3 AND is_visible = true`
var average float64
err := s.db.Pool.QueryRow(ctx, query, studentID, schoolYearID, semester).Scan(&average)
if err != nil {
return 0, fmt.Errorf("failed to calculate average: %w", err)
}
return average, nil
}
// GetSubjectGradeStatistics gets grade statistics for a subject in a class
func (s *GradeService) GetSubjectGradeStatistics(ctx context.Context, classID, subjectID, schoolYearID uuid.UUID, semester int) (map[string]interface{}, error) {
query := `
SELECT
COUNT(DISTINCT g.student_id) as student_count,
AVG(g.value) as class_average,
MIN(g.value) as best_grade,
MAX(g.value) as worst_grade,
COUNT(*) as total_grades
FROM grades g
JOIN students s ON g.student_id = s.id
WHERE s.class_id = $1 AND g.subject_id = $2 AND g.school_year_id = $3 AND g.semester = $4 AND g.is_visible = true`
var studentCount, totalGrades int
var classAverage, bestGrade, worstGrade float64
err := s.db.Pool.QueryRow(ctx, query, classID, subjectID, schoolYearID, semester).Scan(
&studentCount, &classAverage, &bestGrade, &worstGrade, &totalGrades,
)
if err != nil {
return nil, fmt.Errorf("failed to get statistics: %w", err)
}
// Grade distribution (for German grades 1-6)
distributionQuery := `
SELECT
COUNT(CASE WHEN value >= 1 AND value < 1.5 THEN 1 END) as grade_1,
COUNT(CASE WHEN value >= 1.5 AND value < 2.5 THEN 1 END) as grade_2,
COUNT(CASE WHEN value >= 2.5 AND value < 3.5 THEN 1 END) as grade_3,
COUNT(CASE WHEN value >= 3.5 AND value < 4.5 THEN 1 END) as grade_4,
COUNT(CASE WHEN value >= 4.5 AND value < 5.5 THEN 1 END) as grade_5,
COUNT(CASE WHEN value >= 5.5 THEN 1 END) as grade_6
FROM grades g
JOIN students s ON g.student_id = s.id
WHERE s.class_id = $1 AND g.subject_id = $2 AND g.school_year_id = $3 AND g.semester = $4 AND g.is_visible = true AND g.type IN ('exam', 'test')`
var g1, g2, g3, g4, g5, g6 int
err = s.db.Pool.QueryRow(ctx, distributionQuery, classID, subjectID, schoolYearID, semester).Scan(
&g1, &g2, &g3, &g4, &g5, &g6,
)
if err != nil {
// Non-fatal, continue without distribution
g1, g2, g3, g4, g5, g6 = 0, 0, 0, 0, 0, 0
}
return map[string]interface{}{
"student_count": studentCount,
"class_average": classAverage,
"best_grade": bestGrade,
"worst_grade": worstGrade,
"total_grades": totalGrades,
"distribution": map[string]int{
"1": g1,
"2": g2,
"3": g3,
"4": g4,
"5": g5,
"6": g6,
},
}, nil
}
// ========================================
// Grade Comments
// ========================================
// AddGradeComment adds a comment to a grade
func (s *GradeService) AddGradeComment(ctx context.Context, gradeID, teacherID uuid.UUID, comment string, isPrivate bool) (*models.GradeComment, error) {
gradeComment := &models.GradeComment{
ID: uuid.New(),
GradeID: gradeID,
TeacherID: teacherID,
Comment: comment,
IsPrivate: isPrivate,
CreatedAt: time.Now(),
}
query := `
INSERT INTO grade_comments (id, grade_id, teacher_id, comment, is_private, created_at)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id`
err := s.db.Pool.QueryRow(ctx, query,
gradeComment.ID, gradeComment.GradeID, gradeComment.TeacherID,
gradeComment.Comment, gradeComment.IsPrivate, gradeComment.CreatedAt,
).Scan(&gradeComment.ID)
if err != nil {
return nil, fmt.Errorf("failed to add grade comment: %w", err)
}
return gradeComment, nil
}
// GetGradeComments gets comments for a grade
func (s *GradeService) GetGradeComments(ctx context.Context, gradeID uuid.UUID, includePrivate bool) ([]models.GradeComment, error) {
query := `
SELECT id, grade_id, teacher_id, comment, is_private, created_at
FROM grade_comments
WHERE grade_id = $1`
if !includePrivate {
query += ` AND is_private = false`
}
query += ` ORDER BY created_at DESC`
rows, err := s.db.Pool.Query(ctx, query, gradeID)
if err != nil {
return nil, fmt.Errorf("failed to get grade comments: %w", err)
}
defer rows.Close()
var comments []models.GradeComment
for rows.Next() {
var comment models.GradeComment
err := rows.Scan(
&comment.ID, &comment.GradeID, &comment.TeacherID,
&comment.Comment, &comment.IsPrivate, &comment.CreatedAt,
)
if err != nil {
return nil, fmt.Errorf("failed to scan grade comment: %w", err)
}
comments = append(comments, comment)
}
return comments, nil
}
// ========================================
// Parent Notifications
// ========================================
func (s *GradeService) notifyParentsOfNewGrade(ctx context.Context, grade *models.Grade) {
if s.matrix == nil {
return
}
// Get student info and Matrix room
var studentFirstName, studentLastName, matrixDMRoom string
err := s.db.Pool.QueryRow(ctx, `
SELECT first_name, last_name, matrix_dm_room
FROM students
WHERE id = $1`, grade.StudentID).Scan(&studentFirstName, &studentLastName, &matrixDMRoom)
if err != nil || matrixDMRoom == "" {
return
}
// Get subject name
var subjectName string
err = s.db.Pool.QueryRow(ctx, `SELECT name FROM subjects WHERE id = $1`, grade.SubjectID).Scan(&subjectName)
if err != nil {
return
}
studentName := studentFirstName + " " + studentLastName
gradeType := s.getGradeTypeDisplayName(grade.Type)
// Send Matrix notification
err = s.matrix.SendGradeNotification(ctx, matrixDMRoom, studentName, subjectName, gradeType, grade.Value)
if err != nil {
fmt.Printf("Failed to send grade notification: %v\n", err)
}
}
func (s *GradeService) getGradeTypeDisplayName(gradeType string) string {
switch gradeType {
case models.GradeTypeExam:
return "Klassenarbeit"
case models.GradeTypeTest:
return "Test"
case models.GradeTypeOral:
return "Mündliche Note"
case models.GradeTypeHomework:
return "Hausaufgabe"
case models.GradeTypeProject:
return "Projekt"
case models.GradeTypeParticipation:
return "Mitarbeit"
case models.GradeTypeSemester:
return "Halbjahreszeugnis"
case models.GradeTypeFinal:
return "Zeugnisnote"
default:
return gradeType
}
}