using LehrerApp.Core.Models; namespace LehrerApp.Core.Services; /// Zählt den Korrekturfortschritt einer Klausur aus den vorhandenen Ergebnissen, statt einen /// eigenen "Korrektur läuft"-Status pflegen zu müssen — ein `ExamResult` gilt als erledigt, sobald /// entweder Punkte/Note eingetragen oder der Schüler als abwesend markiert wurde. Von /// DashboardViewModel (Karte "Offene Korrekturen") und ExamsOverviewViewModel (Klausuren- /// Hauptseite) gemeinsam genutzt, damit beide Stellen exakt dieselbe Zahl zeigen. public static class ExamCorrectionCounter { public static (int Expected, int Evaluated) Count(Exam exam, List groupMemberships, List examResults) { var expected = groupMemberships.Count(m => GroupMembershipService.IsActiveOn(m, exam.Date)); var evaluated = examResults.Count(r => r.Absent || !string.IsNullOrWhiteSpace(r.Grade) || r.Points.Count > 0); return (expected, Math.Min(evaluated, expected)); } } public enum ExamListStatus { Planned, AwaitingCorrection, CorrectionInProgress, CorrectionStuck, AwaitingReturn, Returned } public readonly record struct ExamPriorityInfo(ExamListStatus Status, double Score); /// Ordnet Klausuren für die Klausuren-Hauptseite nach einem unsichtbaren Prioritäts-Score statt /// nach Datum: unbearbeitete/überfällige Korrekturen oben, erledigte unten. "Korrektur läuft" /// wird bewusst nicht als eigener, manuell zu pflegender Status abgelegt (siehe /// `ExamCorrectionCounter`) — bleibt eine Klausur trotzdem lange in Bearbeitung hängen (typischer /// Grund: ein einzelner nie nachschreibender Schüler), fällt sie nach `StuckAfterDays` aus der /// dringenden Zone in eine ruhige "hängt fest"-Einstufung, statt dauerhaft oben zu kleben. public static class ExamPriorityService { public const int StuckAfterDays = 21; private const int CorrectionGraceDays = 3; public static ExamPriorityInfo Evaluate(Exam exam, int expected, int evaluated, DateOnly today) { if (exam.Status == ExamStatus.Returned) return new ExamPriorityInfo(ExamListStatus.Returned, -100); if (exam.Status == ExamStatus.Graded) return new ExamPriorityInfo(ExamListStatus.AwaitingReturn, 300); if (exam.Status == ExamStatus.Planned) { var daysUntil = exam.Date.DayNumber - today.DayNumber; return new ExamPriorityInfo(ExamListStatus.Planned, Math.Clamp(150 - daysUntil, 20, 150)); } // Status == Conducted: Korrekturfortschritt entscheidet über die genaue Einstufung. var daysSince = today.DayNumber - exam.Date.DayNumber; if (evaluated <= 0) { var score = daysSince <= CorrectionGraceDays ? 400 + daysSince * 10 : 600 + daysSince; return new ExamPriorityInfo(ExamListStatus.AwaitingCorrection, score); } if (expected > 0 && evaluated >= expected) return new ExamPriorityInfo(ExamListStatus.AwaitingReturn, 300); if (daysSince > StuckAfterDays) return new ExamPriorityInfo(ExamListStatus.CorrectionStuck, 90); return new ExamPriorityInfo(ExamListStatus.CorrectionInProgress, 400 + daysSince * 3); } }