feat: Untis-Hub-Kadenzkopplung, anpassbarer Statusvorschlag + KI-/MCP-Unterstuetzung beim Fehlzeitenabgleich (Nutzer-Feedback)
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
- UntisHubService.RecordRun: ein abgeschlossener Langzeit-Fehlzeitenabgleich schliesst die kurzfristige Kadenz derselben Gruppe automatisch mit ab (nicht umgekehrt). - Fehlzeitenabgleich-Dialog: neue "Uebernahme als"-ComboBox statt starrem Zielstatus, vorbelegt mit dem berechneten Vorschlag, aber frei aenderbar. - Neuer ai-backend-Endpunkt untis-status.php + AiPlanningService.RequestUntisStatusSuggestionsAsync: gebuendelter, anonymisierter KI-Statusvorschlag (nur Positions-Id + Rohsignale, nie Name/Klasse/ Datum), mit hartem Id-Mengen-Abgleich gegen Verwechslung. - Neue MCP-Tools (UntisComparisonTools): get_untis_hub_status, get_untis_absence_rows/ apply_untis_absence_status (anonymer Weg ueber ENr-Zuordnung) sowie get_named_untis_absence_pattern als bewusste, eng begrenzte Ausnahme (Name+Fehlzeiten fuer explizit angegebene Schueler-IDs, mit Bestaetigung ohne Sitzungsfreigabe - dafuer IMcpConfirmationService.ConfirmAsync um allowSessionTrust erweitert). - MapStatus/ENr-Zuordnung aus dem ViewModel in das neue, geteilte UntisLessonAbsenceHelper gezogen, damit Dialog und MCP-Tool nie unterschiedliche Statusvorschlaege berechnen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$config = require __DIR__ . '/config.php';
|
||||
$pdo = ai_backend_db($config);
|
||||
$user = ai_backend_authenticate($pdo);
|
||||
|
||||
if ((float) $user['balance_usd'] <= 0) {
|
||||
ai_backend_fail(402, 'Kein Guthaben mehr vorhanden.');
|
||||
}
|
||||
|
||||
$body = json_decode(file_get_contents('php://input'), true);
|
||||
if (!is_array($body) || !isset($body['rows']) || !is_array($body['rows']) || count($body['rows']) === 0) {
|
||||
ai_backend_fail(400, 'Ungültige Anfrage.');
|
||||
}
|
||||
|
||||
// Datensparsamkeit (Nutzer-Feedback zum Untis-Hub): dieser Endpunkt bekommt bewusst NIE Namen,
|
||||
// Klassen oder Datumsangaben - nur eine je Zeile rein technische, für die KI bedeutungslose "id"
|
||||
// (für die Rückzuordnung im Desktop-Client) sowie die für die Statuseinschätzung tatsächlich
|
||||
// relevanten Felder aus dem WebUntis-Fehlzeitendatensatz. Mehrere Zeilen eines Abgleichslaufs
|
||||
// werden in einer Anfrage gebündelt statt je Zeile eine eigene Anfrage zu stellen (Kosten/Latenz).
|
||||
$systemPrompt = <<<PROMPT
|
||||
Du hilfst einer Lehrkraft beim Abgleich von WebUntis-Fehlzeiten mit dem internen Anwesenheitsstatus
|
||||
ihrer Schulverwaltungs-App. Du bekommst eine Liste anonymisierter Fehlzeiten-Zeilen (KEINE Namen,
|
||||
KEINE Klassen, KEINE Daten) und sollst je Zeile den passendsten internen Status vorschlagen.
|
||||
|
||||
## Eingabeschema
|
||||
|
||||
{
|
||||
"rows": [
|
||||
{
|
||||
"id": "<rein technische Kennung ohne inhaltliche Bedeutung, exakt so zurückzugeben>",
|
||||
"reasonText": "<Freitext-Grund aus WebUntis, kann leer sein>",
|
||||
"absentMinutes": <Zahl, gemeldete Fehlminuten der Unterrichtsstunde>,
|
||||
"handledOn": <true, wenn der Eintrag laut WebUntis bereits vom Klassenlehrer bearbeitet wurde>,
|
||||
"externKeyInParentheses": <true/false/null - schulinterne Konvention: Klammerung der
|
||||
Entschuldigungsnummer bedeutet unentschuldigt, ohne Klammern entschuldigt; null, wenn keine
|
||||
Nummer hinterlegt ist>,
|
||||
"currentGuess": "<bereits regelbasiert ermittelter Status, siehe Schlüsselliste unten>"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
## Möglicher Zielstatus (exakt einer dieser Schlüssel je Zeile)
|
||||
|
||||
- "Present": eigentlich anwesend (z.B. wenn der Freitext klar nahelegt, dass die Fehlzeit ein Irrtum war)
|
||||
- "Late": Verspätung, kein vollständiges Fehlen der Stunde
|
||||
- "LeftDuringClass": während des Unterrichts nach Hause entlassen/gegangen
|
||||
- "ExcusePending": krank gemeldet, Entschuldigung noch offen/unbearbeitet
|
||||
- "Excused": krank, entschuldigt
|
||||
- "Unexcused": unentschuldigt
|
||||
|
||||
## Aufgabe
|
||||
|
||||
"currentGuess" kommt aus einer festen Regel über die strukturierten Felder und ist im Zweifel schon
|
||||
korrekt - weiche davon nur ab, wenn "reasonText" eindeutig eine andere Einordnung nahelegt (z.B. ein
|
||||
Text, der klar auf eine Klassenfahrt, einen Arzttermin mit bereits vorliegendem Attest, oder einen
|
||||
erkennbaren Fehleintrag hindeutet). Bei Unsicherheit "currentGuess" unverändert übernehmen, nicht raten.
|
||||
|
||||
## Antwortformat
|
||||
|
||||
Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Freitext davor/danach), mit GENAU einem Eintrag je
|
||||
Eingabe-"id" (dieselbe Anzahl, dieselben "id"-Werte unverändert, keine erfundenen oder ausgelassenen
|
||||
ids):
|
||||
{ "suggestions": [ { "id": "<unverändert aus der Eingabe>", "status": "<einer der Schlüssel oben>" } ] }
|
||||
PROMPT;
|
||||
|
||||
$userContent = json_encode($body);
|
||||
$result = ai_backend_call_and_charge($pdo, $config, $user, $systemPrompt, $userContent);
|
||||
|
||||
$parsed = ai_backend_decode_json_response($result['content']);
|
||||
if (!is_array($parsed) || !isset($parsed['suggestions']) || !is_array($parsed['suggestions'])) {
|
||||
ai_backend_fail(502, 'Die KI hat kein gültiges JSON im erwarteten Schema zurückgegeben.');
|
||||
}
|
||||
|
||||
echo json_encode($parsed);
|
||||
Reference in New Issue
Block a user