Files
FireAdministration/tests/unit/ki-anweisungen.test.ts
T
Florian WildeandClaude Opus 5 f7ccd9a908 Phase 8: Lokale KI
Wiki mit semantischer Suche ueber pgvector, Diktat ueber faster-whisper,
Berichtsassistent und Belegerfassung — alles gegen selbst betriebene
Modelle. Dazu zwei Protokollansichten: was die Maschine getan hat und was
Menschen getan haben.

Nichts, was ein Modell erzeugt, gelangt ungeprueft in einen Nachweis. Jede
Ausgabe ist zuerst ein eigener Datensatz, gekennzeichnet, mit zwei gleich
grossen Knoepfen davor. Die Datenbank haelt gegen: Eingabe, Ausgabe und
Modell sind unveraenderlich, geloescht wird nicht, entschieden wird einmal.

Kein Rueckfall auf einen Anbieter: fehlen die Container, fehlt die Funktion.
Dafuer bleibt alles andere ohne Modell vollstaendig benutzbar — geprueft von
vierzehn E2E-Tests, die ohne laufendes Modell durchlaufen.

Postgres bringt jetzt PostGIS und pgvector mit (eigenes Image), Ollama und
faster-whisper stehen im Compose-Profil "ki".

73 neue Unit-Tests, 14 neue E2E-Tests, Version 0.9.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 11:06:03 +02:00

147 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from "vitest";
import {
BELEG_ANWEISUNG,
BERICHT_ANWEISUNG,
DIKTAT_ANWEISUNG,
WIKI_ANWEISUNG,
einsatzAlsEingabe,
wikiAlsEingabe,
} from "@/lib/ki/anweisungen";
/**
* Die Anweisungen an das Modell.
*
* Sie lassen sich nicht darauf prüfen, ob ein Modell sie befolgt — das hängt
* am Modell. Prüfbar ist, ob die Regeln überhaupt drinstehen, und genau das
* ist der Zweck dieser Tests: sie schlagen an, wenn jemand beim Umformulieren
* die Zeile über das Nichterfinden herausnimmt.
*
* Dazu die Aufbereitung der Eingaben, und die ist echte Logik: was das Modell
* **nicht** bekommt, ist ebenso wichtig wie das, was es bekommt.
*/
describe("Die Anweisungen enthalten ihre Regeln", () => {
it("verbietet dem Berichtsassistenten das Erfinden", () => {
expect(BERICHT_ANWEISUNG).toContain("Erfinde nichts");
expect(BERICHT_ANWEISUNG).toContain("Bewerte nicht");
});
/**
* Die Zeitstempel stehen bereits als Felder im Bericht. Stünden sie noch
* einmal im Fließtext, hätte man sie zweimal — und beim zweiten Mal
* womöglich falsch.
*/
it("hält den Berichtsassistenten von den Zeitstempeln fern", () => {
expect(BERICHT_ANWEISUNG).toContain("Nicht wiederholen");
expect(BERICHT_ANWEISUNG).toContain("Alarmzeit");
});
it("verlangt von der Wiki-Antwort, Nichtwissen zuzugeben", () => {
expect(WIKI_ANWEISUNG).toContain("AUSSCHLIESSLICH");
expect(WIKI_ANWEISUNG).toContain("nicht darin");
expect(WIKI_ANWEISUNG).toContain("Fundstellen");
});
it("erlaubt der Belegerfassung ausdrücklich leere Felder", () => {
expect(BELEG_ANWEISUNG).toContain("null");
expect(BELEG_ANWEISUNG).toContain("Rate nicht");
expect(BELEG_ANWEISUNG).toContain("JJJJ-MM-TT");
});
/**
* Ein Modell, das „Hohlstrahlrohr" für einen Hörfehler hält und zu
* „Hohlstrahler" verbessert, macht aus einem brauchbaren Transkript einen
* falschen Bericht.
*/
it("verbietet dem Diktat das Verbessern von Fachbegriffen", () => {
expect(DIKTAT_ANWEISUNG).toContain("Fachbegriffe");
expect(DIKTAT_ANWEISUNG).toContain("Nicht erlaubt");
});
it("ist durchgängig auf Deutsch verfasst", () => {
for (const anweisung of [
BERICHT_ANWEISUNG,
WIKI_ANWEISUNG,
BELEG_ANWEISUNG,
DIKTAT_ANWEISUNG,
]) {
expect(anweisung.length).toBeGreaterThan(200);
// Keine englischen Anweisungswörter — das Modell soll auf Deutsch denken.
expect(anweisung).not.toMatch(/\bYou are\b/i);
}
});
});
describe("einsatzAlsEingabe", () => {
it("führt die vorhandenen Angaben auf", () => {
const eingabe = einsatzAlsEingabe({
stichwort: "B 3 Wohnhausbrand",
kategorie: "BRAND",
ort: "Schulstraße 4, Musterhausen",
lage: "Verrauchtes Treppenhaus",
stichpunkte: "Angriffstrupp unter PA ins EG",
fahrzeuge: ["Florian Musterhausen 40/1", "Florian Musterhausen 30/1"],
kraefte: 14,
dauerMinuten: 95,
});
expect(eingabe).toContain("B 3 Wohnhausbrand");
expect(eingabe).toContain("Schulstraße 4");
expect(eingabe).toContain("Florian Musterhausen 40/1, Florian Musterhausen 30/1");
expect(eingabe).toContain("14");
expect(eingabe).toContain("95 Minuten");
expect(eingabe).toContain("Angriffstrupp unter PA");
});
/**
* Fehlende Angaben werden weggelassen, nicht als „unbekannt" mitgeschickt.
* Ein Modell, das „Einsatzort: unbekannt" liest, schreibt gern einen Satz
* darüber, dass der Ort unbekannt sei — und das steht dann im Bericht.
*/
it("lässt fehlende Angaben ganz weg", () => {
const eingabe = einsatzAlsEingabe({
stichwort: "H 1 Ölspur",
stichpunkte: "Bindemittel aufgebracht",
});
expect(eingabe).toContain("H 1 Ölspur");
expect(eingabe).not.toContain("Einsatzort");
expect(eingabe).not.toContain("Fahrzeuge");
expect(eingabe).not.toContain("unbekannt");
expect(eingabe).not.toContain("null");
});
it("stellt die Stichpunkte ans Ende", () => {
const eingabe = einsatzAlsEingabe({
stichwort: "Test",
stichpunkte: "Der letzte Absatz",
});
expect(eingabe.trimEnd().endsWith("Der letzte Absatz")).toBe(true);
});
});
describe("wikiAlsEingabe", () => {
it("nummeriert die Auszüge, damit das Modell darauf verweisen kann", () => {
const eingabe = wikiAlsEingabe("Wie oft wird geprüft?", [
{ fundstelle: "Prüffristen › Jährlich", text: "Einmal im Jahr." },
{ fundstelle: "Prüffristen › Alle zwei Jahre", text: "Alle zwei Jahre." },
]);
expect(eingabe).toContain("[1] Prüffristen › Jährlich");
expect(eingabe).toContain("[2] Prüffristen › Alle zwei Jahre");
});
it("stellt die Frage ans Ende", () => {
const eingabe = wikiAlsEingabe("Wie oft?", [{ fundstelle: "A", text: "B" }]);
expect(eingabe.trimEnd().endsWith("Frage: Wie oft?")).toBe(true);
});
it("kommt ohne Auszüge zurecht", () => {
// Kommt in der Anwendung nicht vor — dort wird ohne Auszüge gar nicht
// gefragt —, darf aber nicht abstürzen.
expect(() => wikiAlsEingabe("Frage", [])).not.toThrow();
});
});