Neuer Endpunkt ai-backend/explain.php mit eigenem statischen Systemprompt (Begründung des Phasenaufbaus, mögliche Stolpersteine, Differenzierungsideen). Bewusst als separater Endpunkt statt Zusatzfeld in jeder plan.php-Antwort, damit die Erklärung nur bei tatsächlicher Nutzung abgerechnet wird statt bei jeder Planungsanfrage mitgeneriert zu werden. Die Guthaben-Abrechnung (SELECT-FOR-UPDATE, Transaktions-Insert) wurde aus plan.php nach ai_backend_call_and_charge in db.php ausgelagert, damit sie nicht an zwei Stellen gepflegt werden muss. Kein neues DB-Schema nötig. Im AiAssistDialog erscheint je Stunde ein Button "Didaktischen Hintergrund erklären", der nach dem Laden durch den Text ersetzt wird. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
116 lines
4.5 KiB
C#
116 lines
4.5 KiB
C#
namespace LehrerApp.Core.AiPlanning;
|
|
|
|
/// <summary>
|
|
/// Wire-Vertrag mit dem externen KI-Backend (siehe ai-backend/, TODO 4.5.9). Bewusst getrennt von
|
|
/// den internen Domänenmodellen in Core/Models: dieser Vertrag muss unabhängig von internen
|
|
/// Domänen-Refactors abwärtskompatibel zum deployten PHP-Backend bleiben.
|
|
/// </summary>
|
|
public class AiPlanningRequest
|
|
{
|
|
public string Instruction { get; set; } = "";
|
|
public AiUnitContext Unit { get; set; } = new();
|
|
// Steuert, ob die KI bestehende Lessons inhaltlich ändern darf, oder nur neue Lessons
|
|
// vorschlagen soll (Einheit umplanen ohne vs. mit Ändern bestehender Stunden). Wird sowohl im
|
|
// Systemprompt des Backends durchgesetzt als auch client-seitig in ApplyResponse defensiv
|
|
// geprüft — die KI-Antwort wird dafür nicht blind vertraut.
|
|
public bool AllowModifyingExistingLessons { get; set; } = true;
|
|
}
|
|
|
|
public class AiUnitContext
|
|
{
|
|
public Guid Id { get; set; }
|
|
public string Title { get; set; } = "";
|
|
public DateOnly? StartDate { get; set; }
|
|
public DateOnly? EndDate { get; set; }
|
|
public List<string> Competencies { get; set; } = [];
|
|
public string? Notes { get; set; }
|
|
|
|
// Nur Kontext für die KI, wird nicht zurückerwartet.
|
|
public string SubjectName { get; set; } = "";
|
|
public int GradeLevel { get; set; }
|
|
public string GroupName { get; set; } = "";
|
|
public List<AiCompetencyDomain> CompetencyCatalog { get; set; } = [];
|
|
public List<AiAlternativePath> AlternativePathCatalog { get; set; } = [];
|
|
|
|
public List<AiLesson> Lessons { get; set; } = [];
|
|
}
|
|
|
|
public class AiCompetencyDomain
|
|
{
|
|
public string Name { get; set; } = "";
|
|
public List<AiCompetencyItem> Items { get; set; } = [];
|
|
}
|
|
|
|
public class AiCompetencyItem
|
|
{
|
|
public string Code { get; set; } = "";
|
|
public string Description { get; set; } = "";
|
|
}
|
|
|
|
public class AiAlternativePath
|
|
{
|
|
public Guid Id { get; set; }
|
|
public string Name { get; set; } = "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="Id"/> ist der einzige Unterscheidungsmechanismus neu/geändert: gesetzt und einer
|
|
/// tatsächlich im Request gesendeten Lesson zugehörig = Änderung; null oder eine unbekannte Id =
|
|
/// neue Lesson. Eine unbekannte Id wird beim Import NIE als Update einer bestehenden Lesson
|
|
/// interpretiert (siehe AiPlanningService.ApplyResponse) — sonst könnte eine halluzinierte Id im
|
|
/// schlimmsten Fall eine fremde Lesson überschreiben.
|
|
/// </summary>
|
|
public class AiLesson
|
|
{
|
|
public Guid? Id { get; set; }
|
|
public DateOnly? Date { get; set; }
|
|
public int? LessonNumber { get; set; }
|
|
public string Topic { get; set; } = "";
|
|
public TimeOnly? StartTime { get; set; }
|
|
public List<AiPhaseStep> Phases { get; set; } = [];
|
|
public string? Homework { get; set; }
|
|
public string? Reflection { get; set; }
|
|
}
|
|
|
|
public class AiPhaseStep
|
|
{
|
|
public string Name { get; set; } = "";
|
|
public int DurationMinutes { get; set; }
|
|
public string Activity { get; set; } = "";
|
|
public string Material { get; set; } = "";
|
|
public string Shorthand { get; set; } = "";
|
|
// Name statt Guid: LLMs erfinden/verändern Guids unzuverlässig, ein Name ist beim Import
|
|
// gegen den Katalog abgleichbar (kein Treffer -> null = Hauptweg, kein Fehler).
|
|
public string? AlternativePathName { get; set; }
|
|
// Kurzer Vorschlag für ein konkretes Medium/Material zu dieser Phase (z.B. Tafelbild,
|
|
// Arbeitsblatt), nur gesetzt, wenn eines wirklich sinnvoll wäre — nicht bei jeder Phase.
|
|
// Wird NICHT in Lesson.Phases übernommen (nur für die Review-Anzeige/Prompt-Erzeugung
|
|
// relevant, siehe AiPlanningService.BuildMaterialPrompt), daher kein Feld auf
|
|
// LessonPhaseStep nötig.
|
|
public string? MaterialSuggestion { get; set; }
|
|
}
|
|
|
|
public class AiPlanningResponse
|
|
{
|
|
public List<AiLesson> Lessons { get; set; } = [];
|
|
public string? Summary { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wire-Vertrag für den "Schattenfeld"-Endpunkt (4.5.21, ai-backend/explain.php): fragt nur auf
|
|
/// Nachfrage einen didaktischen Hintergrund zu einer bereits vorgeschlagenen/geplanten Lesson ab
|
|
/// (Warum dieser Aufbau, mögliche Stolpersteine, Differenzierung) — ändert nichts an der Planung,
|
|
/// eigener Endpunkt statt Zusatzfeld in jeder plan.php-Antwort, damit nur bei tatsächlicher
|
|
/// Nutzung abgerechnet wird.
|
|
/// </summary>
|
|
public class AiExplainRequest
|
|
{
|
|
public AiUnitContext Unit { get; set; } = new();
|
|
public AiLesson Lesson { get; set; } = new();
|
|
}
|
|
|
|
public class AiExplainResponse
|
|
{
|
|
public string Explanation { get; set; } = "";
|
|
}
|