Klassenlehrer: Fehlquote nutzt echten WebUntis-Ferienkalender + Sommerferien-Näherung, Diagnose-Logging
CI / build-and-test (push) Canceled after 0s

Nutzer-Verifikation deckte auf, dass WebUntis' getHolidays für dieses Konto nie einen
Sommerferien-Eintrag liefert (11 Jahre Kontohistorie geprüft, kein einziger Juli-/August-Zeitraum
dabei) - vermutlich weil die Sommerferien WebUntis-intern zwischen zwei Schuljahres-Datensätzen
liegen statt in einem. Die reine Ferienkalender-Abfrage aus dem letzten Commit kann diese Lücke
also nicht schließen: der frühere heuristische Ansatz (frühester Fehlzeiten-Eintrag der Klasse als
Näherung für den ersten Unterrichtstag) ist deshalb zusätzlich wieder da, kombiniert mit dem echten
Ferienkalender für den Rest des Jahres.

Herkunft des Nenners jetzt im Tooltip sichtbar (angenommener Unterrichtsbeginn, abgezogene
Ferientage) statt nur intern verrechnet, plus Logging jedes Ferienabrufs (Erfolg oder Fehler) -
sonst wäre der falsche Wert wieder unbemerkt geblieben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-31 13:21:16 +02:00
co-authored by Claude Sonnet 5
parent cdbed69d8b
commit 95e3f677e4
3 changed files with 160 additions and 50 deletions
+39 -24
View File
@@ -1704,30 +1704,45 @@ eigenen Unterricht abfragt und deshalb mit den regulären Lehrkraft-Rechten funk
tatsächlichen Sommerferien enden je nach Bundesland/Jahr aber erst Wochen später, und kurz nach
Schuljahresbeginn macht diese Restferienzeit einen großen Teil des bis dahin "verstrichenen"
Zeitraums aus (rechnerisch: absolut korrekte Fehlzeiten-Zähler, aber ein um mehrere Wochen zu
großer Nenner). Erster Fix-Versuch (`EstimateTermStart`, frühester Fehlzeiten-Eintrag der Klasse
als Näherung für den ersten Unterrichtstag) kam ohne zusätzlichen WebUntis-Abruf aus, war aber
nur eine Näherung. Nutzer-Hinweis: WebUntis kennt den echten Ferienkalender bereits
(`getHolidays`-Bericht, in `LehrerApp.WebUntis/WebUntisClient.GetHolidaysAsync` schon
implementiert, aber bis dahin nirgends im Desktop verdrahtet) — `EstimateTermStart` durch den
echten Kalender ersetzt:
- `WebUntisIntegrationService.GetHolidaysAsync` (neuer `UntisHolidayDto`) als dünner Wrapper,
gleiches Muster wie `GetSchoolYearsAsync`.
- Cache dafür bewusst nicht über die LiteDB-Tabellen von `UntisReportCacheService` (deren
heißes/kaltes Fenster ist auf sich laufend ändernde Fehlzeiten/Klassenbucheinträge
zugeschnitten) — Ferien ändern sich innerhalb eines Schuljahrs praktisch nie. Stattdessen
einfacher tagesgenauer Cache direkt in `WebUntisSettingsService`
(`CachedUntisHoliday`-Liste + `HolidaysFetchedAt`, unverschlüsselt, kein Geheimnis anders als
iCal-URL/API-Zugangsdaten in derselben Datei). Schlägt der Live-Abruf fehl, wird der zuletzt
bekannte Stand weiterverwendet statt die ganze Übersicht mit einem Fehler zu blockieren.
- `ClassTeacherOverviewViewModel.CountSchoolWeekdays` (ersetzt `CountWeekdays`) zählt Werktage
zwischen Schuljahresbeginn und heute abzüglich aller Ferienzeiträume — behebt damit nicht nur
die große Verzerrung kurz nach Schuljahresbeginn, sondern nebenbei auch die bislang akzeptierte
kleinere Ungenauigkeit durch Herbst-/Weihnachts-/Osterferien im weiteren Jahresverlauf (oben
unter "Trend & Fehlquote" noch als bewusste Vereinfachung dokumentiert — damit erledigt).
Reine, ohne ViewModel-Zustand testbare Kernlogik, gleiches Muster wie
`DetectWeekdayPatterns`/`DetectLatePatterns`.
Neue Abhängigkeit `WebUntisIntegrationService` in `ClassTeacherOverviewViewModel` (bereits als
Singleton registriert, nur Konstruktor-Injection ergänzt).
großer Nenner). Mehrstufig gelöst, jede Stufe durch Nutzer-Feedback ausgelöst:
1. Erster Versuch: `EstimateTermStart` (frühester Fehlzeiten-Eintrag der ganzen Klasse als
Näherung für den tatsächlichen ersten Unterrichtstag) — ohne zusätzlichen WebUntis-Abruf, aber
nur eine Näherung.
2. Nutzer-Hinweis: WebUntis kennt den echten Ferienkalender bereits (`getHolidays`-Bericht, in
`LehrerApp.WebUntis/WebUntisClient.GetHolidaysAsync` schon implementiert, aber bis dahin
nirgends im Desktop verdrahtet). Verdrahtet über `WebUntisIntegrationService.GetHolidaysAsync`
(neuer `UntisHolidayDto`, gleiches Muster wie `GetSchoolYearsAsync`), gecacht (nicht über die
LiteDB-Tabellen von `UntisReportCacheService` deren heißes/kaltes Fenster ist auf sich
laufend ändernde Fehlzeiten zugeschnitten, Ferien ändern sich dagegen innerhalb eines
Schuljahrs praktisch nie — sondern einfacher tagesgenauer Cache direkt in
`WebUntisSettingsService`: `CachedUntisHoliday`-Liste + `HolidaysFetchedAt`, unverschlüsselt,
kein Geheimnis anders als iCal-URL/API-Zugangsdaten in derselben Datei). Neue
`ClassTeacherOverviewViewModel.CountSchoolWeekdays` zieht Ferienzeiträume von der
Werktagszählung ab.
3. **Nutzer-Verifikation deckte auf: `getHolidays` liefert für dieses Konto nie einen
Sommerferien-Eintrag** — geprüft anhand der tatsächlich gecachten Antwort (179 Einträge,
11 Jahre Historie ab 2015): Herbst-/Weihnachts-/Osterferien und einzelne bewegliche
Ferientage sind lückenlos dabei, aber kein einziger Juli-/August-Zeitraum, in keinem der
11 Jahre. Vermutlich weil die Sommerferien WebUntis-intern zwischen zwei
Schuljahres-Datensätzen liegen (die bei 1.8./31.7. enden) statt "in" einem davon — WebUntis
scheint sie deshalb keinem Schuljahr zuzuordnen. `CountSchoolWeekdays` (Ferien innerhalb eines
Zeitraums abziehen) kann diese Lücke also grundsätzlich nicht schließen, unabhängig von
Caching oder Implementierung. `EstimateTermStart` bleibt deshalb zusätzlich bestehen (behebt
die Sommerferien-Lücke am Startpunkt), `CountSchoolWeekdays` läuft ab diesem geschätzten
Starttag (behebt Herbst-/Weihnachts-/Osterferien & bewegliche Ferientage innerhalb des
restlichen Jahres — oben unter "Trend & Fehlquote" noch als bewusste Vereinfachung
dokumentiert, für den Teil jetzt erledigt). Beide Kombinationen als Regressionstest
festgehalten (`CountSchoolWeekdays_AbEchtemTerminstartOhneSommerferienEintragStimmtMitBeobachtungUeberein`)
mit den vom Nutzer nachgezählten echten Werten (13 Schultage seit 13.08.2026).
- Herkunft des Nenners jetzt direkt im Tooltip nachvollziehbar statt nur intern verrechnet:
`ClassTeacherRosterRow.TermStart` ("seit dd.MM.") und `HolidayWeekdaysExcluded`
("X Ferientage abgezogen") — Lehre aus diesem Vorfall, bei dem der falsche Wert sonst erneut
unbemerkt geblieben wäre.
- `AppLogger` (optional, DI) protokolliert jeden `GetHolidaysAsync`-Aufruf: bei Erfolg Name +
Zeitraum jedes geladenen Ferieneintrags, bei Fehlschlag die Exception — nächster Diagnoseschritt
wäre sonst wieder nur stilles Rätselraten gewesen.
Neue Abhängigkeiten `WebUntisIntegrationService`, `AppLogger?` in `ClassTeacherOverviewViewModel`
(beide bereits als Singleton registriert, nur Konstruktor-Injection ergänzt).
**Nicht behoben, weil mit den vorhandenen Daten nicht erkennbar:** Kolleg*innen, die die
Anwesenheitsliste nur sporadisch führen, drücken die Quote auf dieselbe Weise (fehlende
Fehlzeiten-Einträge an Tagen mit tatsächlichem Unterricht) — von echter Anwesenheit ist das aus