Klausurschnitt-Vorjahresvergleich beim Hochstufen
CI / build-and-test (push) Canceled after 0s

LearningGroup.PreviousGroupId wird von GroupRolloverService jetzt automatisch
gesetzt; neue Karte "Vorjahresvergleich" im Kurs-Tab Übersicht zeigt den
gepoolten Klausurdurchschnitt neben dem der Vorgängergruppe, sobald dort
Ergebnisse vorliegen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 02:33:07 +02:00
co-authored by Claude Sonnet 5
parent b7c7d46ee4
commit 84c03962ef
8 changed files with 162 additions and 10 deletions
@@ -33,12 +33,15 @@ public partial class GroupOverviewViewModel : ObservableObject
private readonly ILessonRepository _lessons;
private readonly IExamRepository _exams;
private readonly IExamResultRepository _examResults;
private readonly IParticipationSessionRepository _sessions;
private readonly IParticipationRepository _entries;
private readonly IStudentRepository _students;
private readonly IDocumentationRepository _documentation;
private readonly IWorkTaskRepository _tasks;
private readonly IGroupRepository _groups;
private readonly AttendanceBalanceService _attendanceBalance;
private readonly GradingService _grading;
private readonly SchoolYearService _schoolYear;
private Guid _groupId;
@@ -48,6 +51,8 @@ public partial class GroupOverviewViewModel : ObservableObject
[ObservableProperty] private string _nextLessonLabel = "";
[ObservableProperty] private bool _hasNextExam;
[ObservableProperty] private string _nextExamLabel = "";
[ObservableProperty] private bool _hasYearComparison;
[ObservableProperty] private string _yearComparisonLabel = "";
[ObservableProperty] private string _participationHintLabel = "";
[ObservableProperty] private bool _participationHintIsStale;
[ObservableProperty] private bool _hasOpenHomeworkCheck;
@@ -105,13 +110,14 @@ public partial class GroupOverviewViewModel : ObservableObject
}
public GroupOverviewViewModel(ILessonRepository lessons, IExamRepository exams,
IParticipationSessionRepository sessions, IParticipationRepository entries,
IStudentRepository students, IDocumentationRepository documentation, IWorkTaskRepository tasks,
AttendanceBalanceService attendanceBalance, SchoolYearService schoolYear)
IExamResultRepository examResults, IParticipationSessionRepository sessions,
IParticipationRepository entries, IStudentRepository students,
IDocumentationRepository documentation, IWorkTaskRepository tasks, IGroupRepository groups,
AttendanceBalanceService attendanceBalance, GradingService grading, SchoolYearService schoolYear)
{
_lessons = lessons; _exams = exams; _sessions = sessions;
_lessons = lessons; _exams = exams; _examResults = examResults; _sessions = sessions;
_entries = entries; _students = students; _documentation = documentation; _tasks = tasks;
_attendanceBalance = attendanceBalance; _schoolYear = schoolYear;
_groups = groups; _attendanceBalance = attendanceBalance; _grading = grading; _schoolYear = schoolYear;
}
public void Initialize(Guid groupId, string groupName)
@@ -126,6 +132,7 @@ public partial class GroupOverviewViewModel : ObservableObject
var today = DateOnly.FromDateTime(DateTime.Today);
LoadNextLesson(today);
LoadNextExam(today);
LoadYearComparison();
LoadParticipationHint(today);
LoadOpenHomeworkCheck(today);
LoadOpenExcuses(today);
@@ -156,6 +163,38 @@ public partial class GroupOverviewViewModel : ObservableObject
NextExamLabel = next is null ? "" : $"{next.Date:dd.MM.yyyy} — {next.Title}";
}
// ── Klausurschnitt-Vorjahresvergleich (Nutzer-Feedback) ──────────────────
//
// Setzt voraus, dass diese Gruppe über GroupRolloverService aus einer Vorgängergruppe
// hochgestuft wurde (LearningGroup.PreviousGroupId) — vor Einführung dieses Felds
// hochgestufte Gruppen bleiben unverknüpft und zeigen keinen Vergleich. Nur sichtbar, wenn
// im Vorjahr tatsächlich Klausurergebnisse vorliegen; ohne die ist kein Vergleich möglich,
// auch wenn eine Verknüpfung besteht.
private void LoadYearComparison()
{
var group = _groups.GetById(_groupId);
var previous = group?.PreviousGroupId is Guid previousId ? ComputeExamAverage(previousId) : null;
if (previous is null) { HasYearComparison = false; return; }
var current = ComputeExamAverage(_groupId);
YearComparisonLabel = current is null
? $"Vorjahr: Ø {previous.Value.Average:0.0} ({previous.Value.Count} Klausuren) — dieses Jahr noch keine Klausur."
: $"Dieses Jahr: Ø {current.Value.Average:0.0} ({current.Value.Count} Klausuren) · " +
$"Vorjahr: Ø {previous.Value.Average:0.0} ({previous.Value.Count} Klausuren)";
HasYearComparison = true;
}
private (double Average, int Count)? ComputeExamAverage(Guid groupId)
{
var grades = _exams.GetByGroup(groupId)
.SelectMany(e => _examResults.GetByExam(e.Id))
.Where(r => !r.Absent && !string.IsNullOrWhiteSpace(r.Grade))
.Select(r => (Grade: r.Grade!, Weight: 1.0))
.ToList();
return grades.Count == 0 ? null : (_grading.WeightedAverage(grades), grades.Count);
}
/// Erinnert nicht an eine Note, sondern schlicht daran, überhaupt wieder eine Sitzung
/// anzulegen — genau das vergisst man in Kursen, die man seltener unterrichtet, zuerst.
private void LoadParticipationHint(DateOnly today)
@@ -66,6 +66,14 @@
</StackPanel>
</Border>
<!-- Klausurschnitt-Vorjahresvergleich (Nutzer-Feedback) -->
<Border Classes="card" IsVisible="{Binding HasYearComparison}">
<StackPanel>
<TextBlock Text="VORJAHRESVERGLEICH" Classes="cardTitle"/>
<TextBlock Text="{Binding YearComparisonLabel}" TextWrapping="Wrap" Classes="cardBody"/>
</StackPanel>
</Border>
<!-- Mitarbeit -->
<Border Classes="card">
<StackPanel>