Klassenlehrer: Verspätungen nicht mehr als fehlende Entschuldigung, echter Ferienkalender für Fehlquote
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
Verspätungen sind i.d.R. nicht entschuldigungsfähig - "Offene Entschuldigungen" erinnert dafür nicht mehr, stattdessen eskaliert die Mustererkennung ab 5 Verspätungen im Schuljahr zu einem Elterngespräch/Brief-Hinweis mit Wiedervorlage-Option. Die Jahresfehlquote nutzte bislang den 1. August als fest verdrahteten Schuljahresbeginn und zählte damit noch laufende Sommerferien als Schultage mit, was die Quote kurz nach Schuljahresbeginn stark verfälschte (~57% statt ~100% bei durchgehend fehlenden Schülern). Nutzt jetzt den echten WebUntis-Ferienkalender (GetHolidaysAsync, gecacht in WebUntisSettingsService). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -264,6 +264,32 @@ public sealed class ClassTeacherViewModelsTests
|
||||
Assert.Equal(row.StatusText, row.StatusTextWithGlyph);
|
||||
}
|
||||
|
||||
// ── Nenner der Jahresfehlquote: echter WebUntis-Ferienkalender statt grober Werktagszählung ──
|
||||
|
||||
[Fact]
|
||||
public void CountSchoolWeekdays_SchliesstFerientageAus()
|
||||
{
|
||||
// 1.-31.08.2026 hat 21 Werktage; die (fiktiven) Sommerferien bis 11.08. nehmen davon 7
|
||||
// Werktage weg (Mo 03. - Fr 07., Mo 10. - Di 11.) - Nutzer-Feedback: Schüler, die seit
|
||||
// Unterrichtsbeginn jeden Tag fehlten, kamen wegen dieser mitgezählten Ferienzeit nur auf
|
||||
// ~57 % statt ~100 % Fehlquote.
|
||||
var holidays = new[] { new CachedUntisHoliday("Sommerferien", new DateOnly(2026, 7, 1), new DateOnly(2026, 8, 11)) };
|
||||
|
||||
var schoolDays = ClassTeacherOverviewViewModel.CountSchoolWeekdays(
|
||||
new DateOnly(2026, 8, 1), new DateOnly(2026, 8, 31), holidays);
|
||||
|
||||
Assert.Equal(14, schoolDays);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CountSchoolWeekdays_OhneFerienVerhaeltSichWieReineWerktagszaehlung()
|
||||
{
|
||||
var schoolDays = ClassTeacherOverviewViewModel.CountSchoolWeekdays(
|
||||
new DateOnly(2026, 8, 1), new DateOnly(2026, 8, 31), []);
|
||||
|
||||
Assert.Equal(21, schoolDays);
|
||||
}
|
||||
|
||||
// ── Offene Entschuldigungen (Feature-Idee 2) ─────────────────────────────
|
||||
|
||||
[Fact]
|
||||
@@ -290,6 +316,87 @@ public sealed class ClassTeacherViewModelsTests
|
||||
Assert.False(rows[1].IsOverdue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenExcuseBuild_IgnoriertUnentschuldigteVerspaetungen()
|
||||
{
|
||||
// Verspätungen sind i.d.R. nicht entschuldigungsfähig - "unentschuldigt" darf hier keine
|
||||
// Erinnerung an eine fehlende Entschuldigung auslösen (Nutzer-Feedback).
|
||||
var absences = new[]
|
||||
{
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 25), "Neu Ben", 1002, 0, 15,
|
||||
["Deu"], [1], ["nicht entsch."], ["Verspätung"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Fehlt Cem", 1003, 1, 45,
|
||||
["Deu"], [1], ["nicht entsch."], ["Absent"], null, null, false),
|
||||
};
|
||||
|
||||
var rows = ClassTeacherOpenExcuseRow.Build(absences, new DateOnly(2026, 8, 26));
|
||||
|
||||
Assert.Equal(["Cem Fehlt"], rows.Select(r => r.StudentName));
|
||||
}
|
||||
|
||||
// ── Verspätungsmuster: Elterngespräch-Eskalation (Nutzer-Feedback) ───────
|
||||
|
||||
[Fact]
|
||||
public void DetectLatePatterns_EskaliertBeiHoherJahressummeMitWiedervorlageOption()
|
||||
{
|
||||
var absences = Enumerable.Range(0, 5)
|
||||
.Select(i => new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 3 + i), "Spaet Timo", 1001, 0, 10,
|
||||
["Deu"], [1], ["entsch."], ["Verspätung"], null, null, false))
|
||||
.ToArray();
|
||||
var names = new Dictionary<string, string> { [UntisNameMatching.NameKey("Spaet Timo")] = "Timo Spät" };
|
||||
|
||||
var notices = ClassTeacherOverviewViewModel.DetectLatePatterns(
|
||||
absences, names, sevenDayStart: new DateOnly(2026, 8, 31));
|
||||
|
||||
var notice = Assert.Single(notices);
|
||||
Assert.Equal("Timo Spät", notice.StudentName);
|
||||
Assert.Contains("5 Verspätungen seit Schuljahresbeginn", notice.Message);
|
||||
Assert.Equal(ClassTeacherStatusKind.Danger, notice.Kind);
|
||||
Assert.True(notice.CanCreateReminder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetectLatePatterns_KurzfristigesClusterBleibtHinweisOhneAktion()
|
||||
{
|
||||
var absences = new[]
|
||||
{
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Spaet Timo", 1001, 0, 10,
|
||||
["Deu"], [1], ["entsch."], ["Verspätung"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 25), "Spaet Timo", 1001, 0, 10,
|
||||
["Deu"], [1], ["entsch."], ["Verspätung"], null, null, false),
|
||||
};
|
||||
var names = new Dictionary<string, string> { [UntisNameMatching.NameKey("Spaet Timo")] = "Timo Spät" };
|
||||
|
||||
var notices = ClassTeacherOverviewViewModel.DetectLatePatterns(
|
||||
absences, names, sevenDayStart: new DateOnly(2026, 8, 20));
|
||||
|
||||
var notice = Assert.Single(notices);
|
||||
Assert.Equal("2-mal verspätet in 7 Tagen", notice.Message);
|
||||
Assert.Equal(ClassTeacherStatusKind.Warning, notice.Kind);
|
||||
Assert.False(notice.CanCreateReminder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetectLatePatterns_UnentschuldigteFehltageGehenVorVerspaetungseskalation()
|
||||
{
|
||||
var absences = new[]
|
||||
{
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Fehlt Cem", 1001, 1, 45,
|
||||
["Deu"], [1], ["nicht entsch."], ["Absent"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 25), "Fehlt Cem", 1001, 1, 45,
|
||||
["Deu"], [1], ["nicht entsch."], ["Absent"], null, null, false),
|
||||
};
|
||||
var names = new Dictionary<string, string> { [UntisNameMatching.NameKey("Fehlt Cem")] = "Cem Fehlt" };
|
||||
|
||||
var notices = ClassTeacherOverviewViewModel.DetectLatePatterns(
|
||||
absences, names, sevenDayStart: new DateOnly(2026, 8, 20));
|
||||
|
||||
var notice = Assert.Single(notices);
|
||||
Assert.Contains("unentschuldigte Fehltage", notice.Message);
|
||||
Assert.Equal(ClassTeacherStatusKind.Danger, notice.Kind);
|
||||
Assert.False(notice.CanCreateReminder);
|
||||
}
|
||||
|
||||
// ── Aggregierte Klassenbuch-Kategorien (Feature-Idee 5) ──────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user