Files
LehrerApp/LehrerApp.Core/Models/Participation.cs
T
adminandClaude Sonnet 5 1215d4ac22
CI / build-and-test (push) Canceled after 0s
Tagesflagge im Sitzplan, Leistungsüberblick mit Zielnoten-Rechner
- Mitarbeit: Tagesflagge (👑 Spitzentag / 😴 Schlaftag /  Schlechter Tag)
  je Schüler und Sitzung. Primär im Sitzplatz-Dialog (⇧1/2/3, ⇧X) mit
  Badge auf der Sitzplatz-Kachel; Fallback in der Schnelleingabe für
  Lerngruppen ohne Sitzplan.
- Noten: neuer Dialog "Überblick" in der Notenübersicht zeigt Klausuren,
  Mitarbeit- und sonstige Noten eines Schülers samt berechneter
  Zeugnisnote. Zielnoten-Rechner (ReportGradeTargetCalculator) beantwortet
  "was brauche ich noch für Note X", ein Was-wäre-wenn-Rechner simuliert
  eine zusätzliche Klausurnote. Bewerter-/Schülermodus per Umschalter im
  selben Fenster — Bewertermodus zeigt zusätzlich Kursdurchschnitt je
  Klausur und erlaubt das Bearbeiten von Mitarbeit-/Sonstige-Noten.
  Klausurverlauf als Balken-Sparkline wie die bestehende Notenentwicklung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 11:43:47 +02:00

204 lines
8.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string> 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<AspectRating> Ratings { get; set; } = [];
public List<CompetencyRating> 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; }
/// <summary>
/// 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.
/// </summary>
public DayHighlightKind? DayHighlight { get; set; }
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
public enum DayHighlightKind { Standout, Sleepy, Rough }
/// <summary>
/// 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.
/// </summary>
public enum HomeworkStatus
{
Completed,
MissingOpen,
MissingOverdue,
SubmittedLate,
// Hinten angefügt, damit bestehende LiteDB-Werte numerisch stabil bleiben.
PartiallyCompleted,
PartialSubmittedLate,
PartialMissingOverdue,
}
/// <summary>
/// Anwesenheitsstatus einer Sitzung; null bedeutet, dass die Anwesenheit für diesen Termin
/// noch nicht kontrolliert wurde. <see cref="Present"/> bestätigt die Anwesenheit ausdrücklich.
/// <see cref="ExcusePending"/> ist ein bewusster Zwischenzustand, da die Entschuldigung meist
/// erst später eintrifft. <see cref="Truant"/> bezeichnet gesichertes Schwänzen;
/// <see cref="OtherSchoolEvent"/> eine schulisch veranlasste Abwesenheit.
/// </summary>
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,
}
/// <summary>
/// Ein Bewertungsabschnitt einer Lerngruppe (z.B. alle 47 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.
/// </summary>
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<ParticipationAspect> 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 },
];
}
/// <summary>
/// Definiert je <see cref="AspectValueType"/>, welche Rohwerte eine Bewertung annehmen kann und
/// wie sie sich auf die gemeinsame Qualitätsachse (-2..+2, wie <see cref="AspectValueType.Scale5"/>)
/// 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 (<see cref="ParticipationAspect.MaxPoints"/>)
/// und wird für die Aggregation linear auf dieselbe Achse normiert (0 → -2, MaxPoints → +2).
/// </summary>
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,
};
}
/// <summary>
/// Nutzer-Feedback: die Strichliste im Sitzplan (<see cref="ParticipationEntry.RaisedHandCount"/>)
/// 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 <see cref="AspectValueType.Scale5"/> (der
/// Standardtyp des "quantity"-Aspekts) — für individuell umkonfigurierte Aspekttypen gäbe es keine
/// sinnvolle, unmissverständliche Abbildung.
/// </summary>
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,
};
}
}