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:
@@ -35,6 +35,9 @@ public partial class ParticipationGradeDialogViewModel : ObservableObject
|
||||
|
||||
public ObservableCollection<ParticipationGradeRow> Rows { get; } = [];
|
||||
public ObservableCollection<AspectWeightItem> AspectWeights { get; } = [];
|
||||
// Für die Normierung von Punkte-Aspekten (3.1.3) auf die gemeinsame -2..+2-Achse vor der
|
||||
// Gewichtung — AspectWeightItem kennt selbst nur Key/Label/Weight, nicht ValueType/MaxPoints.
|
||||
private readonly Dictionary<string, ParticipationAspect> _aspectsByKey = [];
|
||||
|
||||
public ParticipationGradeDialogViewModel(
|
||||
IParticipationSessionRepository sessions, IParticipationRepository entries,
|
||||
@@ -63,6 +66,7 @@ public partial class ParticipationGradeDialogViewModel : ObservableObject
|
||||
|
||||
foreach (var a in all)
|
||||
{
|
||||
_aspectsByKey[a.Key] = a;
|
||||
var item = new AspectWeightItem(a, _aspects);
|
||||
item.OnChanged = Recompute;
|
||||
AspectWeights.Add(item);
|
||||
@@ -105,7 +109,10 @@ public partial class ParticipationGradeDialogViewModel : ObservableObject
|
||||
{
|
||||
var w = aspectWeights.TryGetValue(r.Key, out var aw) ? aw : 1.0;
|
||||
if (w <= 0) continue;
|
||||
valueSum += r.Value * w;
|
||||
var normalized = _aspectsByKey.TryGetValue(r.Key, out var aspect)
|
||||
? ParticipationRatingScale.Normalize(aspect.ValueType, r.Value, aspect.MaxPoints)
|
||||
: r.Value;
|
||||
valueSum += normalized * w;
|
||||
weightSum += w;
|
||||
}
|
||||
if (weightSum > 0) points.Add((session.Date, valueSum / weightSum));
|
||||
|
||||
@@ -383,7 +383,7 @@ public partial class ParticipationStudentRow : ObservableObject
|
||||
foreach (var a in aspects)
|
||||
{
|
||||
var existing = entry.Ratings.FirstOrDefault(r => r.Key == a.Key);
|
||||
var cell = new RatingCell(id, a.Key, existing?.Value);
|
||||
var cell = new RatingCell(id, a.Key, existing?.Value, a.ValueType, a.MaxPoints);
|
||||
cell.OnChanged = (sid, key, val) => OnRatingChanged?.Invoke(sid, key, val);
|
||||
Cells.Add(cell);
|
||||
}
|
||||
@@ -564,53 +564,64 @@ public partial class RatingCell : ObservableObject
|
||||
{
|
||||
public Guid StudentId { get; }
|
||||
public string AspectKey { get; }
|
||||
public AspectValueType Type { get; }
|
||||
public int MaxPoints { get; }
|
||||
|
||||
[ObservableProperty] private int? _value;
|
||||
[ObservableProperty] private string _displayLabel = "";
|
||||
|
||||
public Action<Guid, string, int?>? OnChanged { get; set; }
|
||||
|
||||
public RatingCell(Guid studentId, string key, int? value)
|
||||
public RatingCell(Guid studentId, string key, int? value,
|
||||
AspectValueType type = AspectValueType.Scale5, int maxPoints = 5)
|
||||
{
|
||||
StudentId = studentId;
|
||||
AspectKey = key;
|
||||
_value = value;
|
||||
Type = type;
|
||||
MaxPoints = maxPoints;
|
||||
_value = type == AspectValueType.Points && value is { } v ? Math.Clamp(v, 0, maxPoints) : value;
|
||||
UpdateLabel();
|
||||
}
|
||||
|
||||
public void SetValue(int? value)
|
||||
{
|
||||
Value = value;
|
||||
Value = Type == AspectValueType.Points && value is { } v ? Math.Clamp(v, 0, MaxPoints) : value;
|
||||
UpdateLabel();
|
||||
OnChanged?.Invoke(StudentId, AspectKey, value);
|
||||
OnChanged?.Invoke(StudentId, AspectKey, Value);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CycleUp()
|
||||
{
|
||||
var next = Value is null ? -2 : Math.Min(2, Value.Value + 1);
|
||||
SetValue(next);
|
||||
if (Type == AspectValueType.Points)
|
||||
{
|
||||
SetValue(Value is null ? 0 : Math.Min(MaxPoints, Value.Value + 1));
|
||||
return;
|
||||
}
|
||||
var steps = ParticipationRatingScale.Steps(Type).Select(s => s.Value).ToList();
|
||||
if (steps.Count == 0) return;
|
||||
var idx = Value is null ? -1 : steps.IndexOf(Value.Value);
|
||||
SetValue(steps[Math.Clamp(idx + 1, 0, steps.Count - 1)]);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CycleDown()
|
||||
{
|
||||
var next = Value is null ? 2 : Math.Max(-2, Value.Value - 1);
|
||||
SetValue(next);
|
||||
if (Type == AspectValueType.Points)
|
||||
{
|
||||
SetValue(Value is null ? MaxPoints : Math.Max(0, Value.Value - 1));
|
||||
return;
|
||||
}
|
||||
var steps = ParticipationRatingScale.Steps(Type).Select(s => s.Value).ToList();
|
||||
if (steps.Count == 0) return;
|
||||
var idx = Value is null ? steps.Count : steps.IndexOf(Value.Value);
|
||||
SetValue(steps[Math.Clamp(idx - 1, 0, steps.Count - 1)]);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Clear() => SetValue(null);
|
||||
|
||||
private void UpdateLabel() => DisplayLabel = Value switch
|
||||
{
|
||||
2 => "++",
|
||||
1 => "+",
|
||||
0 => "~",
|
||||
-1 => "−",
|
||||
-2 => "−−",
|
||||
_ => "",
|
||||
};
|
||||
private void UpdateLabel() => DisplayLabel = ParticipationRatingScale.DisplayLabel(Type, Value);
|
||||
}
|
||||
|
||||
// ── Kompetenz-Tags ────────────────────────────────────────────────────────────
|
||||
@@ -650,7 +661,12 @@ public class AspectColumnDef
|
||||
{
|
||||
public string Key { get; }
|
||||
public string Label { get; }
|
||||
public AspectColumnDef(ParticipationAspect a) { Key = a.Key; Label = a.Label; }
|
||||
public AspectValueType ValueType { get; }
|
||||
public int MaxPoints { get; }
|
||||
public AspectColumnDef(ParticipationAspect a)
|
||||
{
|
||||
Key = a.Key; Label = a.Label; ValueType = a.ValueType; MaxPoints = a.MaxPoints;
|
||||
}
|
||||
}
|
||||
|
||||
public class ParticipationSessionItem
|
||||
@@ -712,6 +728,26 @@ public partial class QuickInputViewModel : ObservableObject
|
||||
|
||||
public ObservableCollection<QuickAspectRow> AspectRows { get; } = [];
|
||||
|
||||
/// Wechselt je nach Typ des aktuell gewählten Aspekts (3.1.3) — Scale3/Binary haben andere
|
||||
/// gültige Zifferntasten als Scale5, Points nutzt 0-9 als direkte Punkteingabe statt fester
|
||||
/// Stufen. Ändert sich mit AspectIndex, siehe OnAspectIndexChanged.
|
||||
public string HotkeyLegend
|
||||
{
|
||||
get
|
||||
{
|
||||
var ratingHint = CurrentAspectType() switch
|
||||
{
|
||||
AspectValueType.Scale5 => "1–5 bewerten",
|
||||
AspectValueType.Scale3 => "1–3 bewerten",
|
||||
AspectValueType.Binary => "1 Nein / 2 Ja",
|
||||
AspectValueType.Points => $"0–9 Punkte eingeben (bis {CurrentAspectMaxPoints()})",
|
||||
_ => "1–5 bewerten",
|
||||
};
|
||||
return $"{ratingHint} · Q/W/E/R/T Aspekt wählen · Leertaste nächster Aspekt · " +
|
||||
"Enter nächster Schüler · Backspace vorheriger · +/− anpassen · Esc schließen";
|
||||
}
|
||||
}
|
||||
|
||||
public QuickInputViewModel(List<ParticipationStudentRow> rows, List<AspectColumnDef> aspects)
|
||||
{
|
||||
_rows = rows;
|
||||
@@ -719,6 +755,13 @@ public partial class QuickInputViewModel : ObservableObject
|
||||
if (rows.Any()) ShowStudent(0);
|
||||
}
|
||||
|
||||
partial void OnAspectIndexChanged(int value) => OnPropertyChanged(nameof(HotkeyLegend));
|
||||
|
||||
private AspectValueType CurrentAspectType() =>
|
||||
_aspects.Count == 0 ? AspectValueType.Scale5 : _aspects[Math.Clamp(AspectIndex, 0, _aspects.Count - 1)].ValueType;
|
||||
private int CurrentAspectMaxPoints() =>
|
||||
_aspects.Count == 0 ? 5 : _aspects[Math.Clamp(AspectIndex, 0, _aspects.Count - 1)].MaxPoints;
|
||||
|
||||
private void ShowStudent(int index)
|
||||
{
|
||||
if (index < 0 || index >= _rows.Count) return;
|
||||
@@ -731,7 +774,7 @@ public partial class QuickInputViewModel : ObservableObject
|
||||
foreach (var (a, i) in _aspects.Select((a, i) => (a, i)))
|
||||
{
|
||||
var val = row.GetRating(a.Key);
|
||||
AspectRows.Add(new QuickAspectRow(i, a.Label, val, i == AspectIndex));
|
||||
AspectRows.Add(new QuickAspectRow(i, a.Label, val, i == AspectIndex, a.ValueType));
|
||||
}
|
||||
UpdateCurrentAspect();
|
||||
}
|
||||
@@ -748,26 +791,48 @@ public partial class QuickInputViewModel : ObservableObject
|
||||
|
||||
public void SetRatingByNumber(int num)
|
||||
{
|
||||
// 1=−−, 2=−, 3=~, 4=+, 5=++
|
||||
var val = num switch { 1 => -2, 2 => -1, 3 => 0, 4 => 1, 5 => 2, _ => (int?)null };
|
||||
if (val is null) return;
|
||||
ApplyRating(val.Value);
|
||||
var type = CurrentAspectType();
|
||||
if (type == AspectValueType.Points)
|
||||
{
|
||||
ApplyRating(Math.Clamp(num, 0, CurrentAspectMaxPoints()));
|
||||
return;
|
||||
}
|
||||
var steps = ParticipationRatingScale.Steps(type);
|
||||
if (num < 1 || num > steps.Count) return;
|
||||
ApplyRating(steps[num - 1].Value);
|
||||
}
|
||||
|
||||
public void IncrementRating()
|
||||
{
|
||||
var cell = GetCurrentCell();
|
||||
if (cell is null) return;
|
||||
var next = cell.Value is null ? -2 : Math.Min(2, cell.Value.Value + 1);
|
||||
ApplyRating(next);
|
||||
var type = CurrentAspectType();
|
||||
if (type == AspectValueType.Points)
|
||||
{
|
||||
var max = CurrentAspectMaxPoints();
|
||||
ApplyRating(cell.Value is null ? 0 : Math.Min(max, cell.Value.Value + 1));
|
||||
return;
|
||||
}
|
||||
var steps = ParticipationRatingScale.Steps(type).Select(s => s.Value).ToList();
|
||||
if (steps.Count == 0) return;
|
||||
var idx = cell.Value is null ? -1 : steps.IndexOf(cell.Value.Value);
|
||||
ApplyRating(steps[Math.Clamp(idx + 1, 0, steps.Count - 1)]);
|
||||
}
|
||||
|
||||
public void DecrementRating()
|
||||
{
|
||||
var cell = GetCurrentCell();
|
||||
if (cell is null) return;
|
||||
var next = cell.Value is null ? 2 : Math.Max(-2, cell.Value.Value - 1);
|
||||
ApplyRating(next);
|
||||
var type = CurrentAspectType();
|
||||
if (type == AspectValueType.Points)
|
||||
{
|
||||
ApplyRating(cell.Value is null ? CurrentAspectMaxPoints() : Math.Max(0, cell.Value.Value - 1));
|
||||
return;
|
||||
}
|
||||
var steps = ParticipationRatingScale.Steps(type).Select(s => s.Value).ToList();
|
||||
if (steps.Count == 0) return;
|
||||
var idx = cell.Value is null ? steps.Count : steps.IndexOf(cell.Value.Value);
|
||||
ApplyRating(steps[Math.Clamp(idx - 1, 0, steps.Count - 1)]);
|
||||
}
|
||||
|
||||
private void ApplyRating(int val)
|
||||
@@ -781,7 +846,7 @@ public partial class QuickInputViewModel : ObservableObject
|
||||
row.Value = val;
|
||||
row.UpdateLabel();
|
||||
}
|
||||
CurrentValueLabel = RatingLabel(val);
|
||||
CurrentValueLabel = ParticipationRatingScale.DisplayLabel(CurrentAspectType(), val);
|
||||
}
|
||||
|
||||
public void SelectAspect(int index)
|
||||
@@ -819,34 +884,28 @@ public partial class QuickInputViewModel : ObservableObject
|
||||
var key = _aspects[Math.Clamp(AspectIndex, 0, _aspects.Count - 1)].Key;
|
||||
return _rows[StudentIndex].Cells.FirstOrDefault(c => c.AspectKey == key);
|
||||
}
|
||||
|
||||
private static string RatingLabel(int? v) => v switch
|
||||
{
|
||||
2 => "++", 1 => "+", 0 => "~", -1 => "−", -2 => "−−", _ => "",
|
||||
};
|
||||
}
|
||||
|
||||
public partial class QuickAspectRow : ObservableObject
|
||||
{
|
||||
public int Index { get; }
|
||||
public string Label { get; }
|
||||
public AspectValueType Type { get; }
|
||||
[ObservableProperty] private bool _isActive;
|
||||
[ObservableProperty] private string _displayLabel = "";
|
||||
public int? Value { get; set; }
|
||||
|
||||
public QuickAspectRow(int index, string label, int? value, bool isActive)
|
||||
public QuickAspectRow(int index, string label, int? value, bool isActive, AspectValueType type = AspectValueType.Scale5)
|
||||
{
|
||||
Index = index;
|
||||
Label = label;
|
||||
Value = value;
|
||||
IsActive = isActive;
|
||||
Type = type;
|
||||
UpdateLabel();
|
||||
}
|
||||
|
||||
public void UpdateLabel() => DisplayLabel = Value switch
|
||||
{
|
||||
2 => "++", 1 => "+", 0 => "~", -1 => "−", -2 => "−−", _ => "·",
|
||||
};
|
||||
public void UpdateLabel() => DisplayLabel = ParticipationRatingScale.DisplayLabel(Type, Value);
|
||||
}
|
||||
|
||||
// ── Schnelleingabe Anwesenheit / Hausaufgaben ────────────────────────────────
|
||||
@@ -935,11 +994,15 @@ public partial class AspectEditItem : ObservableObject
|
||||
|
||||
[ObservableProperty] private string _label;
|
||||
[ObservableProperty] private string _valueTypeName;
|
||||
[ObservableProperty] private int _maxPoints;
|
||||
[ObservableProperty] private double _weight;
|
||||
[ObservableProperty] private bool _isActive;
|
||||
[ObservableProperty] private string _errorMessage = "";
|
||||
|
||||
public string[] ValueTypeOptions => AspectValueTypeDisplay.Options;
|
||||
// Nur bei ValueType "Punkte" relevant (3.1.3) — MaxPoints-Feld nur dann in der Verwaltung
|
||||
// anzeigen, siehe ParticipationAspectsDialog.axaml.
|
||||
public bool IsPointsType => AspectValueTypeDisplay.FromName(ValueTypeName) == AspectValueType.Points;
|
||||
public Action<AspectEditItem>? OnDelete { get; set; }
|
||||
public IRelayCommand DeleteCommand { get; }
|
||||
public IRelayCommand MoveUpCommand { get; }
|
||||
@@ -951,6 +1014,7 @@ public partial class AspectEditItem : ObservableObject
|
||||
_aspect = aspect; _repo = repo;
|
||||
_label = aspect.Label;
|
||||
_valueTypeName = AspectValueTypeDisplay.ToName(aspect.ValueType);
|
||||
_maxPoints = aspect.MaxPoints;
|
||||
_weight = aspect.Weight;
|
||||
_isActive = aspect.IsActive;
|
||||
|
||||
@@ -979,7 +1043,7 @@ public partial class AspectEditItem : ObservableObject
|
||||
private void TrySave(Action apply)
|
||||
{
|
||||
ErrorMessage = "";
|
||||
var before = (_aspect.Label, _aspect.ValueType, _aspect.Weight, _aspect.IsActive);
|
||||
var before = (_aspect.Label, _aspect.ValueType, _aspect.MaxPoints, _aspect.Weight, _aspect.IsActive);
|
||||
apply();
|
||||
try { _repo.Save(_aspect); }
|
||||
catch (Exception ex) when (ex is ArgumentException or InvalidOperationException)
|
||||
@@ -988,18 +1052,23 @@ public partial class AspectEditItem : ObservableObject
|
||||
// die Backing-Felder direkt gesetzt statt der generierten Properties — über die
|
||||
// Properties würde erneut On{X}Changed feuern und damit wieder TrySave aufrufen.
|
||||
#pragma warning disable MVVMTK0034
|
||||
(_aspect.Label, _aspect.ValueType, _aspect.Weight, _aspect.IsActive) = before;
|
||||
(_aspect.Label, _aspect.ValueType, _aspect.MaxPoints, _aspect.Weight, _aspect.IsActive) = before;
|
||||
_label = _aspect.Label; _valueTypeName = AspectValueTypeDisplay.ToName(_aspect.ValueType);
|
||||
_weight = _aspect.Weight; _isActive = _aspect.IsActive;
|
||||
_maxPoints = _aspect.MaxPoints; _weight = _aspect.Weight; _isActive = _aspect.IsActive;
|
||||
#pragma warning restore MVVMTK0034
|
||||
OnPropertyChanged(nameof(Label)); OnPropertyChanged(nameof(ValueTypeName));
|
||||
OnPropertyChanged(nameof(Weight)); OnPropertyChanged(nameof(IsActive));
|
||||
OnPropertyChanged(nameof(MaxPoints)); OnPropertyChanged(nameof(Weight)); OnPropertyChanged(nameof(IsActive));
|
||||
ErrorMessage = ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnLabelChanged(string value) => TrySave(() => _aspect.Label = value);
|
||||
partial void OnValueTypeNameChanged(string value) => TrySave(() => _aspect.ValueType = AspectValueTypeDisplay.FromName(value));
|
||||
partial void OnValueTypeNameChanged(string value)
|
||||
{
|
||||
TrySave(() => _aspect.ValueType = AspectValueTypeDisplay.FromName(value));
|
||||
OnPropertyChanged(nameof(IsPointsType));
|
||||
}
|
||||
partial void OnMaxPointsChanged(int value) => TrySave(() => _aspect.MaxPoints = value);
|
||||
partial void OnWeightChanged(double value) => TrySave(() => _aspect.Weight = value);
|
||||
partial void OnIsActiveChanged(bool value) => TrySave(() => _aspect.IsActive = value);
|
||||
}
|
||||
@@ -1017,9 +1086,11 @@ public partial class ParticipationAspectsDialogViewModel : ObservableObject
|
||||
[ObservableProperty] private string _newKey = "";
|
||||
[ObservableProperty] private string _newLabel = "";
|
||||
[ObservableProperty] private string _newValueTypeName = AspectValueTypeDisplay.Options[0];
|
||||
[ObservableProperty] private int _newMaxPoints = 5;
|
||||
[ObservableProperty] private string _newAspectError = "";
|
||||
|
||||
public string[] ValueTypeOptions => AspectValueTypeDisplay.Options;
|
||||
public bool IsNewPointsType => AspectValueTypeDisplay.FromName(NewValueTypeName) == AspectValueType.Points;
|
||||
public ObservableCollection<AspectEditItem> Aspects { get; } = [];
|
||||
public Func<AspectEditItem, Task<bool>>? OnConfirmDelete { get; set; }
|
||||
|
||||
@@ -1029,6 +1100,8 @@ public partial class ParticipationAspectsDialogViewModel : ObservableObject
|
||||
Load();
|
||||
}
|
||||
|
||||
partial void OnNewValueTypeNameChanged(string value) => OnPropertyChanged(nameof(IsNewPointsType));
|
||||
|
||||
private void Load()
|
||||
{
|
||||
Aspects.Clear();
|
||||
@@ -1069,6 +1142,7 @@ public partial class ParticipationAspectsDialogViewModel : ObservableObject
|
||||
Key = NewKey.Trim(),
|
||||
Label = NewLabel.Trim(),
|
||||
ValueType = AspectValueTypeDisplay.FromName(NewValueTypeName),
|
||||
MaxPoints = NewMaxPoints,
|
||||
SortOrder = Aspects.Count,
|
||||
};
|
||||
try { _repo.Save(aspect); }
|
||||
@@ -1080,7 +1154,7 @@ public partial class ParticipationAspectsDialogViewModel : ObservableObject
|
||||
|
||||
Aspects.Add(CreateItem(aspect));
|
||||
RefreshMoveState();
|
||||
NewKey = ""; NewLabel = ""; NewValueTypeName = AspectValueTypeDisplay.Options[0];
|
||||
NewKey = ""; NewLabel = ""; NewValueTypeName = AspectValueTypeDisplay.Options[0]; NewMaxPoints = 5;
|
||||
}
|
||||
|
||||
private async void DeleteAspectAsync(AspectEditItem item)
|
||||
|
||||
@@ -30,6 +30,9 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
|
||||
private readonly List<ParticipationSession> _allSessions;
|
||||
private readonly List<ParticipationSection> _sectionList;
|
||||
private readonly Dictionary<string, double> _aspectWeights;
|
||||
// Für die Normierung von Punkte-Aspekten (3.1.3) auf die gemeinsame -2..+2-Achse vor der
|
||||
// Gewichtung, analog zu ParticipationGradeDialogViewModel.
|
||||
private readonly Dictionary<string, ParticipationAspect> _aspectsByKey;
|
||||
|
||||
public string GroupLabel { get; }
|
||||
|
||||
@@ -81,10 +84,10 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
|
||||
GroupLabel = groupLabel;
|
||||
_membershipsByStudent = memberships.GetByGroup(groupId).ToDictionary(m => m.StudentId);
|
||||
|
||||
_aspectWeights = aspects.GetDefaults()
|
||||
.Concat(aspects.GetByGroup(groupId))
|
||||
.GroupBy(a => a.Key)
|
||||
.ToDictionary(g => g.Key, g => g.Last().Weight);
|
||||
var applicableAspects = aspects.GetDefaults().Concat(aspects.GetByGroup(groupId))
|
||||
.GroupBy(a => a.Key).Select(g => g.Last()).ToList();
|
||||
_aspectWeights = applicableAspects.ToDictionary(a => a.Key, a => a.Weight);
|
||||
_aspectsByKey = applicableAspects.ToDictionary(a => a.Key);
|
||||
var schoolYearRange = GroupMembershipService.SchoolYearPeriod(schoolYear, SchoolYearPeriodKind.FullYear);
|
||||
_students = students.GetByGroup(groupId)
|
||||
.Where(s => !_membershipsByStudent.TryGetValue(s.Id, out var membership)
|
||||
@@ -221,11 +224,15 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
|
||||
{
|
||||
var w = _aspectWeights.TryGetValue(r.Key, out var aw) ? aw : 1.0;
|
||||
if (w <= 0) continue;
|
||||
valueSum += r.Value * w; weightSum += w;
|
||||
valueSum += NormalizedValue(r) * w; weightSum += w;
|
||||
}
|
||||
return weightSum <= 0 ? null : valueSum / weightSum;
|
||||
}
|
||||
|
||||
private double NormalizedValue(AspectRating r) => _aspectsByKey.TryGetValue(r.Key, out var aspect)
|
||||
? ParticipationRatingScale.Normalize(aspect.ValueType, r.Value, aspect.MaxPoints)
|
||||
: r.Value;
|
||||
|
||||
private static string RatingLabel(int v) => v switch
|
||||
{
|
||||
>= 2 => "++", 1 => "+", 0 => "~", -1 => "−", _ => "−−",
|
||||
@@ -306,7 +313,7 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
|
||||
{
|
||||
var w = _aspectWeights.TryGetValue(r.Key, out var aw) ? aw : 1.0;
|
||||
if (w <= 0) continue;
|
||||
valueSum += r.Value * w; weightSum += w;
|
||||
valueSum += NormalizedValue(r) * w; weightSum += w;
|
||||
}
|
||||
if (weightSum > 0) points.Add(valueSum / weightSum);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user