namespace LehrerApp.Core.Models; public class ParticipationSession { public Guid Id { get; set; } = Guid.NewGuid(); public Guid GroupId { get; set; } public DateOnly Date { get; set; } = DateOnly.FromDateTime(DateTime.Today); public Guid? LessonId { get; set; } public string? Comment { get; set; } public List CompetencyCodes { get; set; } = []; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; } public class ParticipationEntry { public Guid Id { get; set; } = Guid.NewGuid(); public Guid SessionId { get; set; } public Guid StudentId { get; set; } public List Ratings { get; set; } = []; public List CompetencyRatings { get; set; } = []; public string? Note { get; set; } public HomeworkStatus? Homework { get; set; } // Bleibt für bereits gespeicherte Daten erhalten. Neue Schreibvorgänge setzen beide Felder. public bool HomeworkMissing { get; set; } public AttendanceStatus? Attendance { get; set; } // Strichliste im Sitzplan (Nutzer-Feedback): RaisedHandCount zählt jede beobachtete Meldung, // CalledOnCount die Teilmenge davon, bei der der Schüler auch drangekommen ist — // "drangekommen" ist immer auch eine Meldung und erhöht daher immer beide Zähler zugleich, // nie CalledOnCount allein. Ergibt nebenbei eine Aufrufgerechtigkeits-Quote // (CalledOnCount / RaisedHandCount) und schlägt die Quantitätsbewertung vor, siehe // ParticipationCountSuggestion. public int RaisedHandCount { get; set; } public int CalledOnCount { get; set; } /// /// Tagesflagge (Nutzer-Feedback): markiert eine session-bezogene Bewertung als besonders /// herausragend oder besonders schwach, unabhängig von der Richtung ("egal in welche /// Richtung, herausragend festhalten"). Rein deskriptiv — fließt nirgends in eine Berechnung /// (Mitarbeitsnote 3.2, Aufrufgerechtigkeit) ein, nur zur Erinnerung/Dokumentation. /// public DayHighlightKind? DayHighlight { get; set; } public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; } public enum DayHighlightKind { Standout, Sleepy, Rough } /// /// Hausaufgabenstatus einer Sitzung; null bedeutet, dass für diesen Termin keine /// Hausaufgabe erfasst wurde. MissingOpen kann später in MissingOverdue oder SubmittedLate /// überführt werden. /// public enum HomeworkStatus { Completed, MissingOpen, MissingOverdue, SubmittedLate, // Hinten angefügt, damit bestehende LiteDB-Werte numerisch stabil bleiben. PartiallyCompleted, PartialSubmittedLate, PartialMissingOverdue, } /// /// Anwesenheitsstatus einer Sitzung; null bedeutet, dass die Anwesenheit für diesen Termin /// noch nicht kontrolliert wurde. bestätigt die Anwesenheit ausdrücklich. /// ist ein bewusster Zwischenzustand, da die Entschuldigung meist /// erst später eintrifft. bezeichnet gesichertes Schwänzen; /// eine schulisch veranlasste Abwesenheit. /// public enum AttendanceStatus { ExcusePending, Excused, Unexcused, Truant, OtherSchoolEvent, // Hinten angefügt, damit die numerischen Werte bereits gespeicherter LiteDB-Einträge stabil bleiben. Present, Late, SignificantlyLate, LeftDuringClass, LearningIsland, Suspended, } /// /// Ein Bewertungsabschnitt einer Lerngruppe (z.B. alle 4–7 Wochen), an dessen Ende eine /// Abschnittsnote Mitarbeit vergeben wird. Nur abgeschlossene Abschnitte werden gespeichert; /// der aktuell laufende Zeitraum ergibt sich aus dem Ende des letzten Abschnitts bis heute. /// public class ParticipationSection { public Guid Id { get; set; } = Guid.NewGuid(); public Guid GroupId { get; set; } public string Label { get; set; } = ""; public DateOnly StartDate { get; set; } public DateOnly EndDate { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; } public class CompetencyRating { public string Code { get; set; } = ""; public int Value { get; set; } } public class AspectRating { public string Key { get; set; } = ""; public int Value { get; set; } } public class ParticipationAspect { public Guid Id { get; set; } = Guid.NewGuid(); public Guid? GroupId { get; set; } public string Key { get; set; } = ""; public string Label { get; set; } = ""; public AspectValueType ValueType { get; set; } = AspectValueType.Scale5; // Nur bei ValueType.Points relevant: die Rohbewertung ist dann ein echter Punktwert // 0..MaxPoints (nicht auf der -2..+2-Achse), siehe ParticipationRatingScale. public int MaxPoints { get; set; } = 5; public double Weight { get; set; } = 1.0; public bool IsActive { get; set; } = true; public int SortOrder { get; set; } public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; } public enum AspectValueType { Scale5, Scale3, Binary, Points } public static class DefaultParticipationAspects { public static readonly IReadOnlyList All = [ new() { Key = "quality", Label = "Qualität", ValueType = AspectValueType.Scale5, SortOrder = 0 }, new() { Key = "quantity", Label = "Quantität", ValueType = AspectValueType.Scale5, SortOrder = 1 }, new() { Key = "workphase", Label = "Arbeitsphase", ValueType = AspectValueType.Scale5, SortOrder = 2 }, ]; } /// /// Definiert je , welche Rohwerte eine Bewertung annehmen kann und /// wie sie sich auf die gemeinsame Qualitätsachse (-2..+2, wie ) /// abbilden, auf der die Gewichtung/Mittelwertbildung zur Mitarbeitsnote (3.2) rechnet. /// Scale3/Binary liegen bewusst direkt AUF dieser Achse (nur mit weniger Zwischenschritten) /// statt einer eigenen Wertemenge zu bekommen — dadurch bleibt die bestehende Aggregation /// unverändert kompatibel, ohne pro Bewertung erst umrechnen zu müssen. Nur "Points" hat eine /// eigene, von der Lehrkraft festgelegte Obergrenze () /// und wird für die Aggregation linear auf dieselbe Achse normiert (0 → -2, MaxPoints → +2). /// public static class ParticipationRatingScale { /// Feste Rohwert/Label-Paare für Typen mit einer kleinen, festen Stufenzahl — Grundlage für /// Raster-Buttons und Schnelleingabe-Zifferntasten. Leer für Points (freie Zahleneingabe). public static IReadOnlyList<(int Value, string Label)> Steps(AspectValueType type) => type switch { AspectValueType.Scale5 => [(-2, "−−"), (-1, "−"), (0, "∼"), (1, "+"), (2, "++")], AspectValueType.Scale3 => [(-2, "−"), (0, "∼"), (2, "+")], AspectValueType.Binary => [(-2, "Nein"), (2, "Ja")], _ => [], }; public static string DisplayLabel(AspectValueType type, int? value) { if (value is null) return "·"; if (type == AspectValueType.Points) return value.Value.ToString(); var match = Steps(type).FirstOrDefault(s => s.Value == value.Value); return match.Label ?? "·"; } /// Bildet einen Rohwert auf die gemeinsame -2..+2-Achse ab, für die Mittelwertbildung in 3.2. public static double Normalize(AspectValueType type, int value, int maxPoints) => type switch { AspectValueType.Points when maxPoints > 0 => Math.Clamp(value, 0, maxPoints) / (double)maxPoints * 4.0 - 2.0, AspectValueType.Points => 0, // keine sinnvolle Obergrenze konfiguriert -> neutral werten _ => value, }; } /// /// Nutzer-Feedback: die Strichliste im Sitzplan () /// schlägt direkt eine Quantitätsbewertung vor — wie oft sich jemand meldet, ist ja bereits die /// Quantität. Nur ein Richtwert, kein fester Automatismus: die Lehrkraft übernimmt den Vorschlag /// per Klick oder ignoriert ihn. Bewusst nur für (der /// Standardtyp des "quantity"-Aspekts) — für individuell umkonfigurierte Aspekttypen gäbe es keine /// sinnvolle, unmissverständliche Abbildung. /// public static class ParticipationCountSuggestion { public static int? SuggestQuantity(AspectValueType type, int raisedHandCount) { if (type != AspectValueType.Scale5) return null; return raisedHandCount switch { 0 => -2, 1 => -1, 2 or 3 => 0, 4 or 5 => 1, _ => 2, }; } }