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>
81 lines
3.6 KiB
PHP
81 lines
3.6 KiB
PHP
<?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);
|