feat: Planungsideen-Feld je Stunde + persistierter Materialerstellungs-Prompt
CI / build-and-test (push) Canceled after 0s

Lesson bekommt ein neues Freitextfeld PlanningIdeas fuer grobe Ideen vor der
Feinplanung, sichtbar im LessonDialog und als Kontext an die KI-Planung
(Backend + MCP) durchgereicht. LessonPhaseStep.MaterialPrompt speichert den
beim Uebernehmen einer KI-Stunde erzeugten Materialerstellungs-Prompt dauerhaft,
statt ihn nur einmalig im AiAssistDialog anzuzeigen - im Verlaufsplan-Editor
ueber einen neuen Kopieren-Button je Phase erneut nutzbar, auch ueber MCP
lesbar/schreibbar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 00:41:13 +02:00
co-authored by Claude Sonnet 5
parent 3675b70004
commit 7b660c7152
12 changed files with 141 additions and 15 deletions
@@ -76,6 +76,9 @@ public class AiLesson
public List<AiPhaseStep> Phases { get; set; } = [];
public string? Homework { get; set; }
public string? Reflection { get; set; }
/// Rohentwurf/erste Ideen der Lehrkraft vor der Feinplanung (siehe Lesson.PlanningIdeas) — reiner
/// Kontext für die KI wie Homework/Reflection, wird symmetrisch übernommen/zurückgegeben.
public string? PlanningIdeas { get; set; }
}
public class AiPhaseStep
+15
View File
@@ -54,6 +54,13 @@ public class Lesson : IHasAttachments
/// Optionaler Stundenbeginn — solange kein Stundenplan (Kapitel 4.3) existiert, manuell
/// gepflegt. Dient nur der abgeleiteten Uhrzeit-Anzeige je Phase in <see cref="Phases"/>.
public TimeOnly? StartTime { get; set; }
/// Grobe Ideen/erster Entwurf, festgehalten lange bevor der Verlaufsplan (<see cref="Phases"/>)
/// feingeplant wird (Nutzer-Feedback: die eigentliche Ideenfindung liegt zeitlich oft weit vor
/// der Feinplanung) — bewusst ein eigenes Feld statt Zweckentfremdung von <see cref="Reflection"/>
/// (die ist nach der Stunde) oder <see cref="Unit.Notes"/> (die ist auf Einheitenebene, nicht je
/// Stunde). Wird auch der KI-Planungsunterstützung (4.5.9) als Kontext mitgegeben, damit erste
/// eigene Ideen bei der KI-gestützten Weiterplanung nicht erneut abgetippt werden müssen.
public string? PlanningIdeas { get; set; }
public List<LessonPhaseStep> Phases { get; set; } = [];
public TeachingTimelineState? TeachingTimeline { get; set; }
public string? Homework { get; set; }
@@ -95,6 +102,14 @@ public class LessonPhaseStep
public string Activity { get; set; } = "";
public string Material { get; set; } = "";
public string Shorthand { get; set; } = "";
/// Der über <see cref="AiPlanning.AiPlanningService.BuildMaterialPrompt"/> erzeugte, vollständige
/// Prompt für diese Phase (4.5.20), sofern die KI beim letzten "Übernehmen" einen Medienvorschlag
/// gemacht hatte — anders als der reine Vorschlagstext (<c>AiPhaseStep.MaterialSuggestion</c>,
/// nur transient während der Review) bewusst hier persistiert (Nutzer-Feedback: der Prompt soll
/// auch nach dem Übernehmen noch abrufbar/erneut kopierbar bleiben, statt nur einmalig im
/// Review-Dialog verfügbar zu sein). Rein informativ, kein Datenmodell-Bezug zu einem separaten
/// "Material"-Konzept, das es weiterhin nicht gibt — siehe TODO.md 4.5.26.
public string? MaterialPrompt { get; set; }
/// <summary>
/// null = Hauptweg. Sonst Verweis auf einen benannten <see cref="AlternativeLessonPath"/> aus
/// dem Katalog, über den Phasen alternativer Unterrichtsverläufe zusammengehören —
@@ -188,6 +188,7 @@ public class AiPlanningService(HttpClient http, ILessonRepository lessons,
StartTime = l.StartTime,
Homework = l.Homework,
Reflection = l.Reflection,
PlanningIdeas = l.PlanningIdeas,
Phases = ToAiPhases(l.Phases, pathNames),
})
.ToList();
@@ -270,6 +271,9 @@ public class AiPlanningService(HttpClient http, ILessonRepository lessons,
if (!string.Equals(existing.Reflection, proposed.Reflection, StringComparison.Ordinal))
diffs.Add("Reflexion geändert");
if (!string.Equals(existing.PlanningIdeas, proposed.PlanningIdeas, StringComparison.Ordinal))
diffs.Add("Planungsideen geändert");
var pathNames = altPaths.GetAll().ToDictionary(p => p.Id, p => p.Name);
var existingPhases = ToAiPhases(existing.Phases, pathNames);
if (!PhasesEqual(existingPhases, proposed.Phases))
@@ -586,6 +590,7 @@ public class AiPlanningService(HttpClient http, ILessonRepository lessons,
StartTime = ai.StartTime,
Homework = ai.Homework,
Reflection = ai.Reflection,
PlanningIdeas = ai.PlanningIdeas,
// Status bleibt bei einer Änderung erhalten — sonst würde eine bereits
// durchgeführte Stunde durch eine KI-Anpassung stillschweigend auf "Geplant"
// zurückgesetzt (die KI kennt/liefert diesen Status gar nicht).
@@ -597,6 +602,10 @@ public class AiPlanningService(HttpClient http, ILessonRepository lessons,
Activity = p.Activity,
Material = p.Material,
Shorthand = p.Shorthand,
// Persistiert den Prompt (4.5.20/4.5.36), damit er nach dem Übernehmen weiterhin
// im Stundeneditor kopierbar bleibt statt nur einmalig im Review-Dialog.
MaterialPrompt = string.IsNullOrWhiteSpace(p.MaterialSuggestion)
? null : BuildMaterialPrompt(unit, ai, p),
AlternativePathId = p.AlternativePathName is { } name && pathIdsByName.TryGetValue(name, out var pathId)
? pathId : null,
}).ToList(),
+9 -3
View File
@@ -24,14 +24,20 @@ public record TimeEntryDto(
Guid Id, Guid? TaskId, string Category, Guid? GroupId, DateOnly Date,
TimeOnly? StartTime, TimeOnly? EndTime, int DurationMinutes, string? Description);
public record LessonPhaseDto(Guid Id, string Name, int DurationMinutes, string Activity, string Material, string Shorthand);
/// <summary><see cref="MaterialPrompt"/> ist der beim letzten KI-"Übernehmen" gespeicherte
/// Materialerstellungs-Prompt (4.5.20/4.5.36) — nur gesetzt, wenn die KI für diese Phase einen
/// Medienvorschlag gemacht hatte. Ein MCP-Client kann ihn direkt zur Materialerzeugung nutzen, ohne
/// erst den Desktop-Dialog öffnen zu müssen.</summary>
public record LessonPhaseDto(
Guid Id, string Name, int DurationMinutes, string Activity, string Material, string Shorthand,
string? MaterialPrompt);
public record LessonAttachmentDto(string StorageId, string FileName, long SizeBytes);
public record LessonDto(
Guid Id, Guid UnitId, Guid GroupId, DateOnly Date, int? LessonNumber, string Topic,
string? Homework, LessonStatus Status, List<string> Competencies, List<LessonPhaseDto> Phases,
List<LessonAttachmentDto> Attachments);
string? Homework, string? PlanningIdeas, LessonStatus Status, List<string> Competencies,
List<LessonPhaseDto> Phases, List<LessonAttachmentDto> Attachments);
/// <summary>Ergebnis von "download_lesson_attachment": Inhalt Base64-kodiert, weil MCP-Tool-Antworten
/// als JSON/Text übertragen werden. Bewusst kein Ressourcen-URI-Mechanismus (siehe Planungsdokument) -
@@ -188,11 +188,12 @@ public class LessonPlanTools(
return new WriteResultDto(true, lesson.Id, "Einzelstunde gespeichert.");
}
[Description("Ändert Metadaten einer bestehenden Einzelstunde (Thema, Hausaufgabe, Status, Beginn, Stundennummer) — der Verlaufsplan (Phasen) bleibt unverändert. Nur angegebene Felder werden geändert.")]
[Description("Ändert Metadaten einer bestehenden Einzelstunde (Thema, Hausaufgabe, Planungsideen, Status, Beginn, Stundennummer) — der Verlaufsplan (Phasen) bleibt unverändert. Nur angegebene Felder werden geändert.")]
public async Task<WriteResultDto> UpdateLesson(
[Description("ID der Einzelstunde.")] Guid lessonId,
[Description("Neues Thema. Unverändert lassen: weglassen.")] string? topic = null,
[Description("Neue Hausaufgabe. Unverändert lassen: weglassen.")] string? homework = null,
[Description("Neue Planungsideen (grober Entwurf vor der Feinplanung). Unverändert lassen: weglassen.")] string? planningIdeas = null,
[Description("Neuer Status: Planned, Conducted, Draft, Ready oder Cancelled (ausgefallen, z.B. Exkursion/Feiertag - Alternative zu delete_lesson, wenn die Stunde als Ereignis dokumentiert bleiben soll). Unverändert lassen: weglassen.")] LessonStatus? status = null,
[Description("Neuer Stundenbeginn, Format HH:mm. Unverändert lassen: weglassen.")] TimeOnly? startTime = null,
[Description("Neue Stundennummer. Unverändert lassen: weglassen.")] int? lessonNumber = null,
@@ -204,6 +205,7 @@ public class LessonPlanTools(
var changes = new StringBuilder();
if (topic is not null && topic != lesson.Topic) { changes.AppendLine($"Thema: „{lesson.Topic}“ → „{topic}“"); lesson.Topic = topic; }
if (homework is not null && homework != lesson.Homework) { changes.AppendLine($"Hausaufgabe: „{lesson.Homework}“ → „{homework}“"); lesson.Homework = homework; }
if (planningIdeas is not null && planningIdeas != lesson.PlanningIdeas) { changes.AppendLine($"Planungsideen: „{lesson.PlanningIdeas}“ → „{planningIdeas}“"); lesson.PlanningIdeas = planningIdeas; }
if (status is not null && status != lesson.Status) { changes.AppendLine($"Status: {lesson.Status} → {status}"); lesson.Status = status.Value; }
if (startTime is not null && startTime != lesson.StartTime) { changes.AppendLine($"Beginn: {lesson.StartTime:HH\\:mm} → {startTime:HH\\:mm}"); lesson.StartTime = startTime; }
if (lessonNumber is not null && lessonNumber != lesson.LessonNumber) { changes.AppendLine($"Nr.: {lesson.LessonNumber} → {lessonNumber}"); lesson.LessonNumber = lessonNumber; }
@@ -228,6 +230,7 @@ public class LessonPlanTools(
[Description("Tätigkeit/Sozialform.")] string activity = "",
[Description("Material.")] string material = "",
[Description("Kurzsymbol, z.B. \"AB001->S\".")] string shorthand = "",
[Description("Optionaler, vollständiger Prompt zur Materialerstellung für diese Phase (siehe get_lesson_plans, LessonPhaseDto.MaterialPrompt) - z.B. wenn eine externe KI-Sitzung ihn selbst formuliert hat und er zur Wiederverwendung gespeichert werden soll.")] string? materialPrompt = null,
CancellationToken ct = default)
{
var lesson = lessons.GetById(lessonId);
@@ -240,7 +243,7 @@ public class LessonPlanTools(
var phase = new LessonPhaseStep
{
Name = name, DurationMinutes = durationMinutes, Activity = activity,
Material = material, Shorthand = shorthand,
Material = material, Shorthand = shorthand, MaterialPrompt = materialPrompt,
};
lesson.Phases.Add(phase);
lessons.Save(lesson);
@@ -256,6 +259,7 @@ public class LessonPlanTools(
[Description("Neue Tätigkeit. Unverändert lassen: weglassen.")] string? activity = null,
[Description("Neues Material. Unverändert lassen: weglassen.")] string? material = null,
[Description("Neues Kurzsymbol. Unverändert lassen: weglassen.")] string? shorthand = null,
[Description("Neuer Prompt zur Materialerstellung (siehe get_lesson_plans, LessonPhaseDto.MaterialPrompt). Unverändert lassen: weglassen.")] string? materialPrompt = null,
CancellationToken ct = default)
{
var lesson = lessons.GetById(lessonId);
@@ -269,6 +273,7 @@ public class LessonPlanTools(
if (activity is not null && activity != phase.Activity) { changes.AppendLine($"Tätigkeit: „{phase.Activity}“ → „{activity}“"); phase.Activity = activity; }
if (material is not null && material != phase.Material) { changes.AppendLine($"Material: „{phase.Material}“ → „{material}“"); phase.Material = material; }
if (shorthand is not null && shorthand != phase.Shorthand) { changes.AppendLine($"Kürzel: „{phase.Shorthand}“ → „{shorthand}“"); phase.Shorthand = shorthand; }
if (materialPrompt is not null && materialPrompt != phase.MaterialPrompt) { changes.AppendLine("Materialerstellungs-Prompt geändert."); phase.MaterialPrompt = materialPrompt; }
if (changes.Length == 0)
return new WriteResultDto(true, phase.Id, "Keine Änderung nötig.");
@@ -409,7 +414,7 @@ public class LessonPlanTools(
}
private static LessonDto ToDto(Lesson l) => new(
l.Id, l.UnitId, l.GroupId, l.Date, l.LessonNumber, l.Topic, l.Homework, l.Status, l.Competencies,
l.Phases.Select(p => new LessonPhaseDto(p.Id, p.Name, p.DurationMinutes, p.Activity, p.Material, p.Shorthand)).ToList(),
l.Id, l.UnitId, l.GroupId, l.Date, l.LessonNumber, l.Topic, l.Homework, l.PlanningIdeas, l.Status, l.Competencies,
l.Phases.Select(p => new LessonPhaseDto(p.Id, p.Name, p.DurationMinutes, p.Activity, p.Material, p.Shorthand, p.MaterialPrompt)).ToList(),
l.Attachments.Select(a => new LessonAttachmentDto(a.StorageId, a.FileName, a.SizeBytes)).ToList());
}
@@ -737,6 +737,7 @@ public partial class LessonDialogViewModel : ObservableObject
[ObservableProperty] private int? _lessonNumber;
[ObservableProperty] private string _topic = "";
[ObservableProperty] private string _startTimeText = "";
[ObservableProperty] private string _planningIdeas = "";
[ObservableProperty] private string _homework = "";
[ObservableProperty] private bool _homeworkChecked;
[ObservableProperty] private bool _homeworkCheckDismissed;
@@ -823,6 +824,7 @@ public partial class LessonDialogViewModel : ObservableObject
LessonNumber = editingLesson.LessonNumber;
Topic = editingLesson.Topic;
StartTimeText = editingLesson.StartTime?.ToString("HH:mm") ?? "";
PlanningIdeas = editingLesson.PlanningIdeas ?? "";
Homework = editingLesson.Homework ?? "";
HomeworkChecked = editingLesson.HomeworkChecked;
HomeworkCheckDismissed = editingLesson.HomeworkCheckDismissed;
@@ -888,6 +890,7 @@ public partial class LessonDialogViewModel : ObservableObject
Activity = source?.Activity ?? "",
Material = source?.Material ?? "",
Shorthand = source?.Shorthand ?? "",
MaterialPrompt = source?.MaterialPrompt,
};
item.OnChanged = RecomputeTimes;
item.OnRemove = RemovePhase;
@@ -1075,6 +1078,7 @@ public partial class LessonDialogViewModel : ObservableObject
Result.LessonNumber = LessonNumber;
Result.Topic = Topic.Trim();
Result.StartTime = startTime;
Result.PlanningIdeas = string.IsNullOrWhiteSpace(PlanningIdeas) ? null : PlanningIdeas.Trim();
Result.Phases = Phases.Select(p => p.ToModel()).ToList();
Result.Homework = string.IsNullOrWhiteSpace(Homework) ? null : Homework.Trim();
Result.HomeworkChecked = HomeworkChecked;
@@ -1103,6 +1107,13 @@ public partial class PhaseStepEditItem : ObservableObject
[ObservableProperty] private string _material = "";
[ObservableProperty] private string _shorthand = "";
[ObservableProperty] private string _computedTimeDisplay = "";
/// Gespeicherter Materialerstellungs-Prompt (4.5.20/4.5.36) — nur gesetzt, wenn die KI beim
/// letzten "Übernehmen" einen Medienvorschlag für diese Phase gemacht hatte. Steuert die
/// Sichtbarkeit des Kopieren-Buttons im Verlaufsplan-Editor.
[ObservableProperty] private string? _materialPrompt;
public bool HasMaterialPrompt => !string.IsNullOrWhiteSpace(MaterialPrompt);
partial void OnMaterialPromptChanged(string? value) => OnPropertyChanged(nameof(HasMaterialPrompt));
/// Checkbox-Zustand im Editor: unchecked→checked öffnet den Zuweisen-Dialog
/// (<see cref="OnAssignAlternativePath"/>); checked→unchecked entfernt die Zuordnung.
@@ -1162,6 +1173,7 @@ public partial class PhaseStepEditItem : ObservableObject
Activity = Activity.Trim(),
Material = Material.Trim(),
Shorthand = Shorthand.Trim(),
MaterialPrompt = MaterialPrompt,
AlternativePathId = AlternativePathId,
};
}
@@ -49,6 +49,13 @@
IsVisible="{Binding TopicError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Planungsideen (Rohentwurf)" FontSize="12" Opacity="0.7"
ToolTip.Tip="Erste, noch grobe Ideen — lange bevor der Verlaufsplan unten feingeplant wird. Fließt auch als Kontext in die KI-Unterstützung (Backend und MCP) ein."/>
<TextBox Text="{Binding PlanningIdeas}" AcceptsReturn="True" Height="64" TextWrapping="Wrap"
PlaceholderText="Grobe Ideen, mögliche Aufhänger, erste Materialgedanken ..."/>
</StackPanel>
<Separator Margin="0,4"/>
<Grid ColumnDefinitions="*,Auto">
@@ -66,7 +73,7 @@
</Grid>
<!-- Tabellenkopf -->
<Grid ColumnDefinitions="130,95,120,*,100,95,26,26,26" Margin="4,0,0,0">
<Grid ColumnDefinitions="130,95,120,*,100,95,26,26,26,26" Margin="4,0,0,0">
<TextBlock Grid.Column="0" Text="Phase" FontSize="11" FontWeight="SemiBold" Opacity="0.6"/>
<TextBlock Grid.Column="1" Text="Pfad" FontSize="11" FontWeight="SemiBold" Opacity="0.6"
ToolTip.Tip="Ankreuzen, wenn diese Phase zu einem alternativen Ablauf gehört (z.B. Kurzversion bei Zeitnot) — öffnet die Zuweisung. Phasen mit demselben Ablauf werden im Verlaufsplan-Viewer gruppiert."/>
@@ -83,7 +90,7 @@
<DataTemplate x:DataType="vm:PhaseStepEditItem">
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,0,0,1" Padding="0,8">
<Grid ColumnDefinitions="130,95,120,*,100,95,26,26,26">
<Grid ColumnDefinitions="130,95,120,*,100,95,26,26,26,26">
<TextBox Grid.Column="0" Text="{Binding Name}" PlaceholderText="z.B. Erarbeitung"
VerticalAlignment="Top" Margin="0,0,6,0"/>
<CheckBox Grid.Column="1" IsChecked="{Binding HasAlternativePath}"
@@ -110,11 +117,14 @@
FilterMode="Contains" MinimumPrefixLength="0" VerticalAlignment="Top"
Margin="0,0,6,0" PlaceholderText="z.B. AB001-&gt;S, Plenum, LDE"
ToolTip.Tip="Freitext für den schnellen Überblick — mal ein Materialfluss-Pfeil (AB001-&gt;S), mal nur eine Sozialform (Plenum, LDE)."/>
<Button Grid.Column="6" Content="" Command="{Binding MoveUpCommand}" Padding="4,2"
<Button Grid.Column="6" Content="📋" Padding="4,2" VerticalAlignment="Top" Margin="0,0,2,0"
IsVisible="{Binding HasMaterialPrompt}" Click="OnCopyMaterialPrompt"
ToolTip.Tip="Gespeicherten Prompt zur Materialerstellung erneut in die Zwischenablage kopieren."/>
<Button Grid.Column="7" Content="↑" Command="{Binding MoveUpCommand}" Padding="4,2"
VerticalAlignment="Top" ToolTip.Tip="Nach oben"/>
<Button Grid.Column="7" Content="↓" Command="{Binding MoveDownCommand}" Padding="4,2"
<Button Grid.Column="8" Content="↓" Command="{Binding MoveDownCommand}" Padding="4,2"
VerticalAlignment="Top" Margin="2,0,0,0" ToolTip.Tip="Nach unten"/>
<Button Grid.Column="8" Content="✕" Command="{Binding RemoveCommand}" Padding="4,2"
<Button Grid.Column="9" Content="✕" Command="{Binding RemoveCommand}" Padding="4,2"
VerticalAlignment="Top" Margin="2,0,0,0" ToolTip.Tip="Entfernen"/>
</Grid>
</Border>
@@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using LehrerApp.Core.Interfaces;
@@ -48,6 +49,20 @@ public partial class LessonDialog : Window
Close(false);
}
/// Kopiert den beim letzten KI-"Übernehmen" gespeicherten Materialerstellungs-Prompt (4.5.36,
/// Nachtrag zu 4.5.20) erneut in die Zwischenablage — derselbe Mechanismus wie im AiAssistDialog,
/// hier aber für einen bereits gespeicherten, nicht mehr nur transienten Vorschlag.
private async void OnCopyMaterialPrompt(object? sender, RoutedEventArgs e)
{
if (sender is not Button { DataContext: PhaseStepEditItem item } || string.IsNullOrWhiteSpace(item.MaterialPrompt))
return;
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard is null) return;
await clipboard.SetTextAsync(item.MaterialPrompt);
App.Services.GetRequiredService<NotificationService>().ShowSuccess("Prompt in die Zwischenablage kopiert.");
}
private async void OnAddAttachment(object? sender, RoutedEventArgs e)
{
if (DataContext is not LessonDialogViewModel vm) return;
+34
View File
@@ -2916,6 +2916,40 @@ folgenden Punkte gehören direkt in `LehrerApp.Desktop`:
- Kein zusätzliches Feature wie Windows-Autostart für LehrerApp.Desktop selbst oder ein
Tray-Icon (auf Nachfrage bewusst nicht Teil dieser Änderung) — die Bridge löst das Problem
rein auf Protokollebene, unabhängig davon, wann der Nutzer LehrerApp tatsächlich startet.
- [x] **4.5.35** Freitextfeld "Planungsideen" je Stunde (2026-09-14, Nutzer-Feedback): erste, noch
grobe Ideen zu einer Stunde entstehen erfahrungsgemäß lange bevor der Verlaufsplan (4.2.2)
feingeplant wird — dafür gab es bisher kein Feld, nur `Reflection` (nach der Stunde) und
`Unit.Notes` (auf Einheiten- statt Stundenebene). Neues `Lesson.PlanningIdeas` (`string?`),
im [LessonDialog](LehrerApp.Desktop/Views/Groups/LessonDialog.axaml) direkt unter "Thema" als
mehrzeiliges Freitextfeld, noch vor dem Verlaufsplan — bewusste Platzierung, damit die Idee
zuerst und die Feinplanung erst danach kommt. Keine Migration nötig (LiteDB, neues Feld).
Auch als Kontext für die KI-Planungsunterstützung (4.5.9) durchgereicht, wie von der
Lehrkraft gewünscht ("auch nützlich für die KI-gestützte Weiterplanung"): `AiLesson`/
`AiUnitContext` (Core/AiPlanning) bekommen ein neues, zu `Homework`/`Reflection` symmetrisches
Feld `planningIdeas` (gelesen in `AiPlanningService.BuildContext`, übernommen in
`ApplyResponse`, im Planungsdiff berücksichtigt in `DescribeChanges`); `ai-backend/plan.php`
bekommt das Feld in Ein- und Ausgabeschema samt Hinweis an die KI, es als starken Kontext zu
nutzen und unverändert zurückzugeben, außer die Anweisung verlangt ausdrücklich eine
Überarbeitung. Für die MCP-gestützte Weiterplanung (4.5.28) zusätzlich in `LessonDto`
(Services/Mcp/Tools/Dto.cs) exponiert und über einen neuen optionalen `planningIdeas`-
Parameter von `update_lesson` (LessonPlanTools.cs) schreibbar — bewusst kein neues, eigenes
MCP-Tool nur für dieses eine Feld, passt in das bestehende kleinteilige Muster.
- [x] **4.5.36** Materialerstellungs-Prompt bleibt nach dem Übernehmen erhalten (2026-09-14,
Nachtrag zu 4.5.20, Nutzer-Feedback): der in 4.5.20 gebaute Prompt war bisher nur im
`AiAssistDialog` (Review vor dem Speichern) kopierbar — einmal übernommen, war er weg, obwohl
die Lehrkraft ihn oft erst später in einer externen KI-Sitzung tatsächlich einlöst. Neues
`LessonPhaseStep.MaterialPrompt` (`string?`, keine Migration nötig): `AiPlanningService.
ApplyResponse` befüllt es automatisch mit dem von `BuildMaterialPrompt` erzeugten Text, sobald
die Phase einen `MaterialSuggestion`-Vorschlag hatte — bewusst weiterhin kein separates
"Material"-Domänenmodell (siehe 4.5.26), nur ein zusätzliches Feld an der bestehenden Zeile.
Im [LessonDialog](LehrerApp.Desktop/Views/Groups/LessonDialog.axaml) erscheint dafür in der
Verlaufsplan-Tabelle ein neuer Button "📋" je Phasenzeile, nur sichtbar wenn ein Prompt
gespeichert ist (`PhaseStepEditItem.HasMaterialPrompt`) — derselbe Zwischenablage-Mechanismus
wie im `AiAssistDialog` (`LessonDialog.axaml.cs:OnCopyMaterialPrompt`). Für die MCP-gestützte
Weiterplanung zusätzlich in `LessonPhaseDto.MaterialPrompt` gelesen und über einen neuen
optionalen Parameter an `add_lesson_phase`/`update_lesson_phase` (LessonPlanTools.cs)
schreibbar, damit auch ein extern (z.B. in Claude Desktop) formulierter Prompt zur
Wiederverwendung gespeichert werden kann, nicht nur ein vom eigenen KI-Backend erzeugter.
**Wichtige Abweichung von der ursprünglichen Planung (5.2):** Vor der Umsetzung zeigte sich,
dass 5.2 wie ursprünglich beschrieben eine zweite, parallele Fehlzeiten-Erfassung neben dem
+2 -1
View File
@@ -39,7 +39,8 @@ Unterricht einsetzen kann, auch wenn sie nicht ihre eigene Idee war.
"phases": [
{ "name": "<Phasenname>", "durationMinutes": <Zahl>, "activity": "<Tätigkeit>", "material": "<Material>", "shorthand": "<Kurzsymbol>" }
],
"homework": "<Hausaufgabe oder null>", "reflection": "<Reflexion oder null>"
"homework": "<Hausaufgabe oder null>", "reflection": "<Reflexion oder null>",
"planningIdeas": "<grobe Ideen der Lehrkraft vor der Feinplanung oder null, nur Lesekontext>"
}
}
+9 -2
View File
@@ -65,7 +65,8 @@ Die Eingabe hat exakt diese Struktur:
}
],
"homework": "<Hausaufgabe oder null>",
"reflection": "<Reflexion oder null>"
"reflection": "<Reflexion oder null>",
"planningIdeas": "<Grobe Ideen/Rohentwurf der Lehrkraft vor der Feinplanung oder null>"
}
]
}
@@ -111,6 +112,11 @@ Stunde zur Prüfung, alles andere würde ihr gar nicht angezeigt.
"AB" Arbeitsblatt) — kein Fließtext.
- "durationMinutes" je Phase sollte in Summe zur für die Stunde realistischen Zeit passen
(i.d.R. rund 45 Minuten je Einzelstunde, abzüglich organisatorischer Zeit).
- "planningIdeas" einer Stunde enthält oft schon vor der Feinplanung festgehaltene grobe Ideen der
Lehrkraft (Stichpunkte, erste Gedanken, mögliche Aufhänger) — nutze das, wo vorhanden, als starken
Hinweis für Thema/Verlaufsplan dieser Stunde, ohne jedes Stichwort wörtlich übernehmen zu müssen.
Gib das Feld in deiner Antwort unverändert zurück, außer die Anweisung der Lehrkraft verlangt
ausdrücklich eine Überarbeitung dieser Ideen selbst.
- "materialSuggestion" je Phase (nur in deiner Antwort, nicht in der Eingabe): ein kurzer Vorschlag
(1-2 Sätze), WAS ein zu dieser Phase passendes Medium/Material konkret zeigen oder enthalten
sollte (z.B. Aufbau eines Tafelbilds, Inhalt eines Arbeitsblatts) — nicht nur "ein Tafelbild
@@ -142,7 +148,8 @@ Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Freitext davor/danach) in gena
}
],
"homework": "<Hausaufgabe oder null>",
"reflection": "<Reflexion oder null>"
"reflection": "<Reflexion oder null>",
"planningIdeas": "<unverändert aus der Eingabe übernommen, siehe oben, oder null>"
}
],
"summary": "<kurze menschenlesbare Zusammenfassung, was du getan hast>"
+9
View File
@@ -129,6 +129,15 @@ statt der typisierten `Lessons`-Collection — nach jeder Modelländerung kennt
`Lesson`-Klasse die alten Feldnamen nicht mehr, ein Zugriff darüber hätte sie beim Deserialisieren
bereits verworfen, bevor sie gelesen werden können.
`LessonPhaseStep.MaterialPrompt` (4.5.36, Nachtrag zu 4.5.20) ist kein von Hand gepflegtes Feld:
`AiPlanningService.ApplyResponse` befüllt es automatisch mit dem vollständigen, lokal erzeugten
Prompt (`BuildMaterialPrompt`), sobald die KI beim letzten "Übernehmen" für diese Phase einen
Medienvorschlag gemacht hatte — ursprünglich (4.5.20) bewusst NICHT persistiert, weil der Vorschlag
nur für die Review-Anzeige gedacht war; Nutzer-Feedback nach erstem Einsatz war, dass der Prompt
auch nach dem Übernehmen noch abrufbar bleiben soll. Kein neues "Material"-Domänenmodell dafür (das
gibt es weiterhin nicht, siehe 4.5.26) — der Prompt hängt einfach als zusätzliches Freitextfeld an
der bereits bestehenden `LessonPhaseStep`-Zeile.
## Eindeutige Schlüssel
Die Datenbank schützt folgende Kombinationen mit eindeutigen Indizes: