3.1.3: Aspekt-Typen Scale3/Binary/Points in Raster und Dialog unterstützt

Neue Klasse ParticipationRatingScale (Core) ist die einzige Quelle für
Rohwerte je Aspekt-Typ. Scale3/Binary liegen bewusst direkt auf derselben
-2..+2-Achse wie Scale5 (nur mit weniger Zwischenschritten), damit die
bestehende Gewichtung/Mittelwertbildung zur Mitarbeitsnote unverändert
kompatibel bleibt. Points ist grundverschieden (echter Zählwert 0..MaxPoints,
neues Feld auf ParticipationAspect) und wird nur zur Aggregation linear auf
dieselbe Achse normiert.

Ohne diese Normierung hätte ein Punkte-Aspekt die Mitarbeitsnote verfälscht:
drei Stellen summierten bisher den Rohwert direkt (ParticipationGradeDialog-
ViewModel.Recompute, ParticipationWizardViewModels.WeightedRating und
.ComputeSuggestion) - alle drei sind jetzt auf die Normierung umgestellt.

Raster: Punkte-Aspekte zeigen ein NumericUpDown statt fester Stufen-Buttons.
Schnelleingabe-Dialog: Zifferntasten/+/- sind jetzt typabhängig, Legende
zeigt live die für den aktuellen Aspekt gültigen Tasten. Aspekt-Verwaltung
um "Max. Punkte"-Feld ergänzt (nur bei Typ "Punkte" sichtbar).

Bewusst nicht angefasst: die Trendlinien-Visualisierung im
Mitarbeits-Assistenten bleibt fest auf die drei Standardaspekte
zugeschnitten - eigener, größerer Umbau.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 00:12:16 +02:00
co-authored by Claude Sonnet 5
parent f8dd567ef8
commit 16ff4875d8
12 changed files with 502 additions and 97 deletions
+42
View File
@@ -96,6 +96,9 @@ public class ParticipationAspect
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; }
@@ -113,3 +116,42 @@ public static class DefaultParticipationAspects
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,
};
}