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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
12243f177d
commit
f7ccd9a908
+232
@@ -19,6 +19,8 @@
|
||||
* Lehrgang mit Warteliste, Arbeitsdienst, Jahreshauptversammlung, ein
|
||||
* abgesagter Termin und eine abgehakte Anwesenheitsliste. Bewusst OHNE
|
||||
* Zustellkanäle: niemand wird ungefragt benachrichtigt, auch nicht im Seed.
|
||||
* Ab Phase 8 dazu: vier Wiki-Seiten mit eigenem Text — Prüffristen,
|
||||
* Atemschutz-Merkblatt, Schlauchpflege und Alarmierungsordnung.
|
||||
*
|
||||
* Das Skript läuft über DIRECT_DATABASE_URL, also mit dem Eigentümer-Zugang,
|
||||
* der Row Level Security umgehen darf. Anders ginge es nicht: der Seed legt
|
||||
@@ -62,6 +64,7 @@ import {
|
||||
ObjektKategorie,
|
||||
Serienmuster,
|
||||
StandortArt,
|
||||
WikiKategorie,
|
||||
Teilnahmestand,
|
||||
TerminArt,
|
||||
TruppStatus,
|
||||
@@ -2999,6 +3002,232 @@ async function termineAnlegen(organisationId: string): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Phase 8 — Wissen
|
||||
//
|
||||
// Vier Seiten, wie eine Wehr sie wirklich hätte: eine Zusammenfassung der
|
||||
// Prüffristen, ein Merkblatt zum Atemschutz, eine Anleitung zur Schlauchpflege
|
||||
// und die Alarmierungsordnung.
|
||||
//
|
||||
// Der Inhalt ist bewusst **eigener Text** und keine abgeschriebene Vorschrift.
|
||||
// Eine Dienstvorschrift ist urheberrechtlich geschützt; was hier steht, ist
|
||||
// eine Zusammenfassung mit Fundstellenangabe — genau so, wie eine Wehr ihre
|
||||
// eigenen Merkblätter schreibt.
|
||||
//
|
||||
// Ohne Sprachmodell bleiben die Seiten uneingebettet. Das ist kein Mangel: sie
|
||||
// sind über die Wortsuche auffindbar, und die Seitenansicht sagt, dass der
|
||||
// Suchstand fehlt.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
async function wikiAnlegen(organisationId: string): Promise<void> {
|
||||
const vorhanden = await prisma.wikiseite.count({ where: { organisationId } });
|
||||
if (vorhanden > 0) {
|
||||
log("Wiki bereits vorhanden — übersprungen.");
|
||||
return;
|
||||
}
|
||||
|
||||
const autor = await prisma.person.findFirst({
|
||||
where: {
|
||||
organisationId,
|
||||
rollen: { some: { rolle: { schluessel: RolleSchluessel.KOMMANDANT } } },
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
const SEITEN: Array<{
|
||||
slug: string;
|
||||
titel: string;
|
||||
art: WikiKategorie;
|
||||
quelle: string;
|
||||
schlagworte: string[];
|
||||
inhalt: string;
|
||||
}> = [
|
||||
{
|
||||
slug: "pruefristen-im-ueberblick",
|
||||
titel: "Prüffristen im Überblick",
|
||||
art: WikiKategorie.GERAETE,
|
||||
quelle: "Zusammenfassung nach DGUV Vorschrift 49 und Herstellerangaben",
|
||||
schlagworte: ["prüffrist", "dguv", "gerätewart", "wartung"],
|
||||
inhalt: [
|
||||
"Diese Seite fasst zusammen, was bei uns in welchem Abstand geprüft wird.",
|
||||
"Verbindlich sind die Vorschrift und die Herstellerangabe — im Zweifel gilt",
|
||||
"die kürzere Frist.",
|
||||
"",
|
||||
"## Jährlich",
|
||||
"",
|
||||
"- Pressluftatmer: Sicht- und Dichtprüfung",
|
||||
"- Atemschutzmasken: Sichtprüfung, Dichtprüfung",
|
||||
"- Tragbare Leitern: Sichtprüfung nach DGUV",
|
||||
"- Rettungsgeräte und Sprungpolster",
|
||||
"- Handscheinwerfer und Handfunkgeräte: Funktionsprüfung",
|
||||
"",
|
||||
"## Alle zwei Jahre",
|
||||
"",
|
||||
"- Feuerwehrschläuche: Druckprüfung",
|
||||
"- Kupplungen und Armaturen: Dichtheit",
|
||||
"",
|
||||
"## Alle drei Jahre",
|
||||
"",
|
||||
"- Feuerlöscher: Wartung durch einen Sachkundigen",
|
||||
"",
|
||||
"## Alle sechs Jahre",
|
||||
"",
|
||||
"- Pressluftatmer: Grundüberholung durch die Werkstatt",
|
||||
"- Atemluftflaschen: wiederkehrende Prüfung nach Druckgeräterichtlinie",
|
||||
"",
|
||||
"## Was passiert, wenn eine Frist abläuft",
|
||||
"",
|
||||
"Das Gerät wechselt in FLORIAN von selbst auf **Prüfung fällig**. Benutzen",
|
||||
"darf man es weiter — es ist nicht gesperrt. Gesperrt wird nur von Hand und",
|
||||
"immer mit Begründung.",
|
||||
"",
|
||||
"Wer eine Frist nicht halten kann, meldet das dem Gerätewart. Ein",
|
||||
"stillschweigend abgelaufener Nachweis ist im Schadensfall das Problem des",
|
||||
"Kommandanten, nicht des Geräts.",
|
||||
].join("\n"),
|
||||
},
|
||||
{
|
||||
slug: "atemschutz-merkblatt",
|
||||
titel: "Atemschutz — was vor jedem Einsatz gilt",
|
||||
art: WikiKategorie.DIENSTVORSCHRIFT,
|
||||
quelle: "Zusammenfassung nach FwDV 7, Ausgabe 2002 mit Änderung 2005",
|
||||
schlagworte: ["atemschutz", "fwdv 7", "überwachung", "rückzugsdruck"],
|
||||
inhalt: [
|
||||
"## Wer darf unter Atemschutz",
|
||||
"",
|
||||
"Drei Nachweise müssen gleichzeitig gültig sein:",
|
||||
"",
|
||||
"1. **G 26.3** — arbeitsmedizinische Vorsorge, in der Regel drei Jahre gültig,",
|
||||
" ab 50 Jahren jährlich.",
|
||||
"2. **Belastungsübung** — jährlich, in einer Atemschutzübungsanlage.",
|
||||
"3. **Einsatz oder Übung unter Atemschutz** — jährlich.",
|
||||
"",
|
||||
"Fehlt einer davon, ist der Einsatz unter Atemschutz nicht zulässig. FLORIAN",
|
||||
"zeigt das an, sperrt aber nicht: die Entscheidung trifft der",
|
||||
"Atemschutzgerätewart, nicht die Software.",
|
||||
"",
|
||||
"## Rückzugsdruck",
|
||||
"",
|
||||
"Der Rückzugsdruck ist der Druck, bei dem der Trupp den Rückweg antreten",
|
||||
"muss. Er wird aus dem **doppelten Anmarschverbrauch** plus einem",
|
||||
"Sicherheitszuschlag gerechnet:",
|
||||
"",
|
||||
"```",
|
||||
"Rückzugsdruck = Eingangsdruck - 2 x (Eingangsdruck - Druck am Ziel) + Zuschlag",
|
||||
"```",
|
||||
"",
|
||||
"Ist der Anmarsch noch nicht gemessen, gilt die Zwei-Drittel-Regel: bei zwei",
|
||||
"Dritteln des Eingangsdrucks wird zurückgegangen.",
|
||||
"",
|
||||
"## Überwachung",
|
||||
"",
|
||||
"Dokumentiert werden: Truppzusammensetzung, Einsatzbeginn, Druckwerte in",
|
||||
"Abständen von höchstens zehn Minuten, Rückzugsdruck und Ende.",
|
||||
"",
|
||||
"> Die analoge Überwachung bleibt führend, solange die Wehr die digitale",
|
||||
"> nicht ausdrücklich freigegeben hat. Das ist keine Formalie — eine",
|
||||
"> Software, die im Einsatz ausfällt, darf keine Lücke im Nachweis",
|
||||
"> hinterlassen.",
|
||||
].join("\n"),
|
||||
},
|
||||
{
|
||||
slug: "schlaeuche-pflegen",
|
||||
titel: "Schläuche pflegen und prüfen",
|
||||
art: WikiKategorie.GERAETE,
|
||||
quelle: "Merkblatt der Wehr, beschlossen in der Kommandantensitzung",
|
||||
schlagworte: ["schlauch", "pflege", "druckprüfung", "ablegereife"],
|
||||
inhalt: [
|
||||
"## Nach jedem Einsatz",
|
||||
"",
|
||||
"1. Schläuche vom Fahrzeug nehmen und durch neue ersetzen — nie einen",
|
||||
" benutzten Schlauch wieder aufs Fahrzeug legen.",
|
||||
"2. Grob reinigen, bei Verunreinigung mit Öl oder Chemikalien gesondert",
|
||||
" behandeln und dem Gerätewart melden.",
|
||||
"3. Trocknen, bevor sie gerollt werden. Ein feucht gerollter Schlauch",
|
||||
" schimmelt von innen.",
|
||||
"",
|
||||
"## Ablegereife",
|
||||
"",
|
||||
"Ein Schlauch wird ausgesondert, wenn:",
|
||||
"",
|
||||
"- das Gewebe durchgescheuert oder eingerissen ist,",
|
||||
"- die Einbindung sich gelöst hat,",
|
||||
"- er die Druckprüfung nicht besteht,",
|
||||
"- die Innenbeschichtung sich ablöst.",
|
||||
"",
|
||||
"Ein ausgesonderter Schlauch wird gekennzeichnet und **sofort** aus dem",
|
||||
"Fahrzeug genommen. Ein abgelegter Schlauch, der noch im Regal liegt, kommt",
|
||||
"irgendwann wieder aufs Fahrzeug.",
|
||||
"",
|
||||
"## Druckprüfung",
|
||||
"",
|
||||
"Alle zwei Jahre. Prüfdruck nach Herstellerangabe, in der Regel:",
|
||||
"",
|
||||
"| Größe | Prüfdruck |",
|
||||
"|---|---|",
|
||||
"| C 42 / C 52 | 12 bar |",
|
||||
"| B 75 | 12 bar |",
|
||||
"| A 110 | 8 bar |",
|
||||
"",
|
||||
"Das Ergebnis wird in FLORIAN am Schlauch eingetragen, nicht auf einem",
|
||||
"Zettel im Schlauchturm.",
|
||||
].join("\n"),
|
||||
},
|
||||
{
|
||||
slug: "alarmierungsordnung",
|
||||
titel: "Alarmierungsordnung",
|
||||
art: WikiKategorie.ORGANISATION,
|
||||
quelle: "Beschluss der Kommandantschaft",
|
||||
schlagworte: ["alarm", "ausrücken", "einsatzleitung"],
|
||||
inhalt: [
|
||||
"## Wer rückt aus",
|
||||
"",
|
||||
"Alarmiert wird über Meldeempfänger und Sirene. Wer alarmiert wird, kommt",
|
||||
"zum Gerätehaus — die Entscheidung, welches Fahrzeug besetzt wird, trifft",
|
||||
"der Einsatzleiter vor Ort.",
|
||||
"",
|
||||
"Die Mindestbesetzung ist die **Staffel** (1/5). Kommt sie in fünf Minuten",
|
||||
"nicht zusammen, wird mit dem nachrückenden Personal aufgefüllt und",
|
||||
"ausgerückt.",
|
||||
"",
|
||||
"## Einsatzleitung",
|
||||
"",
|
||||
"Bis zum Eintreffen des Kommandanten führt der ranghöchste Anwesende. Die",
|
||||
"Übergabe wird ausdrücklich ausgesprochen und in der Lagemeldung vermerkt —",
|
||||
"ein stillschweigender Wechsel der Einsatzleitung ist im Nachhinein nicht",
|
||||
"rekonstruierbar.",
|
||||
"",
|
||||
"## Was im Bericht stehen muss",
|
||||
"",
|
||||
"- Lage beim Eintreffen",
|
||||
"- Getroffene Maßnahmen, in der Reihenfolge",
|
||||
"- Übergabe: an wen, wann",
|
||||
"",
|
||||
"Nicht in den Bericht gehören Vermutungen über die Brandursache und",
|
||||
"Bewertungen des Verhaltens Beteiligter. Beides ist Sache der Polizei.",
|
||||
].join("\n"),
|
||||
},
|
||||
];
|
||||
|
||||
for (const seite of SEITEN) {
|
||||
await prisma.wikiseite.create({
|
||||
data: {
|
||||
organisationId,
|
||||
...seite,
|
||||
veroeffentlicht: true,
|
||||
autorId: autor?.id ?? null,
|
||||
// Bewusst kein eingebettetAm: ohne Sprachmodell gibt es keine
|
||||
// Einbettungen. Die Seitenansicht sagt das, und die Wortsuche
|
||||
// funktioniert trotzdem.
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
log(
|
||||
`Wissen: ${SEITEN.length} Seiten angelegt (noch nicht eingebettet — dafür braucht es ein Sprachmodell)`,
|
||||
);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Ablauf
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -3042,6 +3271,9 @@ async function main(): Promise<void> {
|
||||
// Phase 7 — Benachrichtigungen und Termine
|
||||
await termineAnlegen(organisation.id);
|
||||
|
||||
// Phase 8 — Lokale KI
|
||||
await wikiAnlegen(organisation.id);
|
||||
|
||||
log("Fertig.");
|
||||
log("");
|
||||
log("Anmeldung für den Erststart:");
|
||||
|
||||
Reference in New Issue
Block a user