Phase 5: Atemschutzüberwachung nach FwDV 7 #6

Merged
florian.wilde merged 1 commits from feat/phase-5-atemschutz into main 2026-09-03 21:42:21 +00:00
Owner

Das sicherheitsrelevanteste Modul des Projekts. Über jeder Ansicht steht
dauerhaft:

Ergänzendes System — die analoge Atemschutzüberwachung bleibt führend.

Abschalten lässt er sich nur zusammen mit einer Fundstelle
(ATEMSCHUTZ_FREIGABE). Fehlt sie, bleibt der Hinweis stehen — eine Freigabe
ohne Beschluss ist keine. Ist er abgeschaltet, steht dort und später im
Protokoll, worauf sich die Freigabe stützt.

Version 0.6.0.

Die eine Entscheidung, aus der alles folgt

Es gibt keinen Timer. In der Datenbank stehen Zeitstempel; wie lange ein
Trupp unter Atemschutz ist, rechnet jede Ansicht bei jedem Bild neu aus dem
Einstiegszeitpunkt und der aktuellen Uhr. Server und Browser benutzen dieselben
Funktionen.

Damit ist die wichtigste Anforderung aus Phase-5.md keine Funktion, die
gelingen muss, sondern eine Eigenschaft, die sich nicht verlieren lässt:

Was passiert Was die Anzeige macht
Browser neu laden zeigt dieselbe Zeit weiter
Zweiter Bildschirm daneben zeigt dieselbe Zeit, ohne Abgleich
Verbindung bricht ab Uhr läuft weiter, der Abriss steht sichtbar da
Server neu gestartet nach der ersten Antwort ist alles wieder da

Was drin ist

Datenmodell — Atemschutzeinsatz, Atemschutztrupp, Truppmitglied,
Druckmessung, AtemschutzEreignis, G26Untersuchung, Belastungsuebung.
Alle mit organisationId und RLS.

Rückzugsdruck nach dem doppelten Anmarschverbrauch — 2 × (Eingangsdruck − Zieldruck) + Warndruck + Zuschlag. Bis der Trupp das Einsatzziel meldet,
greift die Zwei-Drittel-Regel; sie liegt höher und ist damit die vorsichtigere
Annahme. Maßgeblich ist immer der ungünstigste Wert im Trupp.

Überwachungsschirm — Trupp anlegen mit Eingangsdruck je Person,
Druckabfrage für den ganzen Trupp in einer Zeile, Erinnerung im
konfigurierbaren Intervall, Rückzug anordnen, Mayday (aufzuheben, nicht
wegzuklicken), Notizen. Beenden geht erst, wenn alle Trupps als draußen
gemeldet sind.

Tauglichkeit — G26.3, Belastungsübung und Einsatzübung müssen gleichzeitig
gelten. Ampel mit Klartext, was fehlt. Gesperrt wird niemand: im
Einsatz entscheidet der Einheitsführer, und ein Programm, das ihm eine Person
verweigert, wird umgangen — dann steht am Ende gar nichts im Protokoll.

Protokoll — lückenloses PDF, Ereignisse und Druckmessungen in einer
Zeitachse. druckmessung und atemschutz_ereignis sind gegen UPDATE und
DELETE gesperrt, doppelt: entzogene Rechte und ein Trigger.

Abweichung von der Vorgabe

Server-Sent Events statt Socket.IO (nach Rücksprache). Es geht nur in eine
Richtung; ein Route Handler genügt, EventSource verbindet von selbst neu, und
das Deployment bleibt bei next start statt einen eigenen Node-Server zu
brauchen.

Kein RFID-Scan beim Truppanlegen. Der Kartenleser hängt am Terminal
(Phase 4). Am Überwachungsschirm wählt der Überwacher zwei Namen aus einer
Liste, auf der ohnehin die Tauglichkeit steht — nachrüstbar, wenn der Scan im
Betrieb vermisst wird.

Zwei Fehler, die die Tests gefunden haben

  1. Ein abgerissener Ereignisstrom meldete sich nicht. EventSource
    schweigt bei einem Abriss, ohne einen Fehler auszulösen — die Anzeige hätte
    weiter „Verbunden" behauptet, während der Draht tot war. Das ist der
    gefährlichste Zustand, den diese Ansicht haben kann: ein alter Stand, der
    wie ein aktueller aussieht. Jetzt gilt Stille von acht Sekunden als Abriss.
  2. Nebenbei kam heraus, dass context.setOffline() einen offenen SSE-Strom
    in Chromium gar nicht kappt — der ursprüngliche Test prüfte etwas anderes,
    als er behauptete. Er schneidet den Strom jetzt über eine abgewiesene Route
    ab.

Zwei Altlasten mitgeräumt

  • Der Navigationseintrag „Protokoll" führte auf eine Seite, die es nicht
    gibt. Die Ansicht des Audit-Logs gehört zum Löschkonzept in Phase 8; bis
    dahin steht der Eintrag ausgegraut da.
  • Die Startseite stand noch auf „Phase 1 ist abgeschlossen … als Nächstes
    kommt das Prüfmanagement".

Testlauf: zwei Arbeiter statt vier

Bei vier gleichzeitigen Arbeitern fielen in jedem Gesamtlauf zwei bis fünf
Tests ins Zeitlimit — jedes Mal andere, jeder einzeln grün. Mit zwei Arbeitern:
132 Tests grün, und der Lauf ist mit 17,5 Minuten sogar kürzer als vorher
mit 22.

Zum Testen

git checkout feat/phase-5-atemschutz
docker compose up -d && npm install
npm run db:migrate && npm run db:seed && npm run dev
  • /atemschutz — die Beispielüberwachung „Zimmerbrand Hauptstraße 12" aus
    dem Seed ist vollständig durchgespielt; darunter die Tauglichkeitsliste mit
    allen drei Ampelzuständen (einer hat eine abgelaufene G26, einem fehlt die
    Belastungsübung).
  • Eine eigene eröffnen, Trupp anlegen (300 und 290 bar), Einstieg. Dann:
    • „Einsatzziel erreicht" mit 260/250 — der Rückzugsdruck springt von
      vorläufig 210 auf 145 bar.
    • Seite neu laden — die Uhr läuft weiter, als wäre nichts gewesen.
    • WLAN in den Entwicklerwerkzeugen kappen — oben steht „Verbindung
      unterbrochen", die Uhr läuft trotzdem.
    • Mayday — die Karte wird rot, es piept.
  • Beenden und das Protokoll als PDF ansehen.

Nachweise

  • npx tsc --noEmit ohne Ausgabe
  • npm run test — 306 Tests grün, davon 36 gegen eine echte Datenbank
  • npx playwright test — 132 Tests grün, ohne Wackler
  • npm run build — 38 Routen

Zum Dashboard

Beim dritten Phasenabschluss dasselbe: die E2E-Suite schreibt in die
Demowehr
. Diesmal lagen 34 Überwachungen und 10 Einsätze aus den Testläufen
in der Datenbank — sie standen in den Bildschirmfotos und auf dem
Hallenmonitor. Einmalig abgeräumt, die Ursache bleibt. Ein eigener Testmandant,
den die Suite anlegt und hinterher abräumt, wäre die Lösung.

Weiterhin offen: npm run lint läuft nicht (keine ESLint-Konfiguration im
Repo).

🤖 Generated with Claude Code

Das sicherheitsrelevanteste Modul des Projekts. Über jeder Ansicht steht dauerhaft: > **Ergänzendes System — die analoge Atemschutzüberwachung bleibt führend.** Abschalten lässt er sich nur zusammen mit einer Fundstelle (`ATEMSCHUTZ_FREIGABE`). Fehlt sie, bleibt der Hinweis stehen — eine Freigabe ohne Beschluss ist keine. Ist er abgeschaltet, steht dort und später im Protokoll, worauf sich die Freigabe stützt. Version 0.6.0. ## Die eine Entscheidung, aus der alles folgt **Es gibt keinen Timer.** In der Datenbank stehen Zeitstempel; wie lange ein Trupp unter Atemschutz ist, rechnet jede Ansicht bei jedem Bild neu aus dem Einstiegszeitpunkt und der aktuellen Uhr. Server und Browser benutzen dieselben Funktionen. Damit ist die wichtigste Anforderung aus `Phase-5.md` keine Funktion, die gelingen muss, sondern eine Eigenschaft, die sich nicht verlieren lässt: | Was passiert | Was die Anzeige macht | |---|---| | Browser neu laden | zeigt dieselbe Zeit weiter | | Zweiter Bildschirm daneben | zeigt dieselbe Zeit, ohne Abgleich | | Verbindung bricht ab | Uhr läuft weiter, der Abriss steht sichtbar da | | Server neu gestartet | nach der ersten Antwort ist alles wieder da | ## Was drin ist **Datenmodell** — `Atemschutzeinsatz`, `Atemschutztrupp`, `Truppmitglied`, `Druckmessung`, `AtemschutzEreignis`, `G26Untersuchung`, `Belastungsuebung`. Alle mit `organisationId` und RLS. **Rückzugsdruck nach dem doppelten Anmarschverbrauch** — `2 × (Eingangsdruck − Zieldruck) + Warndruck + Zuschlag`. Bis der Trupp das Einsatzziel meldet, greift die Zwei-Drittel-Regel; sie liegt höher und ist damit die vorsichtigere Annahme. Maßgeblich ist immer der ungünstigste Wert im Trupp. **Überwachungsschirm** — Trupp anlegen mit Eingangsdruck je Person, Druckabfrage für den ganzen Trupp in einer Zeile, Erinnerung im konfigurierbaren Intervall, Rückzug anordnen, Mayday (aufzuheben, nicht wegzuklicken), Notizen. Beenden geht erst, wenn alle Trupps als draußen gemeldet sind. **Tauglichkeit** — G26.3, Belastungsübung und Einsatzübung müssen gleichzeitig gelten. Ampel mit **Klartext**, was fehlt. **Gesperrt wird niemand:** im Einsatz entscheidet der Einheitsführer, und ein Programm, das ihm eine Person verweigert, wird umgangen — dann steht am Ende gar nichts im Protokoll. **Protokoll** — lückenloses PDF, Ereignisse und Druckmessungen in einer Zeitachse. `druckmessung` und `atemschutz_ereignis` sind gegen `UPDATE` und `DELETE` gesperrt, doppelt: entzogene Rechte **und** ein Trigger. ## Abweichung von der Vorgabe **Server-Sent Events statt Socket.IO** (nach Rücksprache). Es geht nur in eine Richtung; ein Route Handler genügt, `EventSource` verbindet von selbst neu, und das Deployment bleibt bei `next start` statt einen eigenen Node-Server zu brauchen. **Kein RFID-Scan beim Truppanlegen.** Der Kartenleser hängt am Terminal (Phase 4). Am Überwachungsschirm wählt der Überwacher zwei Namen aus einer Liste, auf der ohnehin die Tauglichkeit steht — nachrüstbar, wenn der Scan im Betrieb vermisst wird. ## Zwei Fehler, die die Tests gefunden haben 1. **Ein abgerissener Ereignisstrom meldete sich nicht.** `EventSource` schweigt bei einem Abriss, ohne einen Fehler auszulösen — die Anzeige hätte weiter „Verbunden" behauptet, während der Draht tot war. Das ist der gefährlichste Zustand, den diese Ansicht haben kann: ein alter Stand, der wie ein aktueller aussieht. Jetzt gilt Stille von acht Sekunden als Abriss. 2. Nebenbei kam heraus, dass `context.setOffline()` einen **offenen** SSE-Strom in Chromium gar nicht kappt — der ursprüngliche Test prüfte etwas anderes, als er behauptete. Er schneidet den Strom jetzt über eine abgewiesene Route ab. ## Zwei Altlasten mitgeräumt - Der Navigationseintrag **„Protokoll"** führte auf eine Seite, die es nicht gibt. Die Ansicht des Audit-Logs gehört zum Löschkonzept in Phase 8; bis dahin steht der Eintrag ausgegraut da. - Die **Startseite** stand noch auf „Phase 1 ist abgeschlossen … als Nächstes kommt das Prüfmanagement". ## Testlauf: zwei Arbeiter statt vier Bei vier gleichzeitigen Arbeitern fielen in jedem Gesamtlauf zwei bis fünf Tests ins Zeitlimit — jedes Mal andere, jeder einzeln grün. Mit zwei Arbeitern: **132 Tests grün**, und der Lauf ist mit 17,5 Minuten sogar kürzer als vorher mit 22. ## Zum Testen ```bash git checkout feat/phase-5-atemschutz docker compose up -d && npm install npm run db:migrate && npm run db:seed && npm run dev ``` - **`/atemschutz`** — die Beispielüberwachung „Zimmerbrand Hauptstraße 12" aus dem Seed ist vollständig durchgespielt; darunter die Tauglichkeitsliste mit allen drei Ampelzuständen (einer hat eine abgelaufene G26, einem fehlt die Belastungsübung). - **Eine eigene eröffnen**, Trupp anlegen (300 und 290 bar), Einstieg. Dann: - **„Einsatzziel erreicht"** mit 260/250 — der Rückzugsdruck springt von vorläufig 210 auf 145 bar. - **Seite neu laden** — die Uhr läuft weiter, als wäre nichts gewesen. - **WLAN in den Entwicklerwerkzeugen kappen** — oben steht „Verbindung unterbrochen", die Uhr läuft trotzdem. - **Mayday** — die Karte wird rot, es piept. - **Beenden** und das **Protokoll** als PDF ansehen. ## Nachweise - `npx tsc --noEmit` ohne Ausgabe - `npm run test` — 306 Tests grün, davon 36 gegen eine echte Datenbank - `npx playwright test` — **132 Tests grün**, ohne Wackler - `npm run build` — 38 Routen ## Zum Dashboard Beim dritten Phasenabschluss dasselbe: die **E2E-Suite schreibt in die Demowehr**. Diesmal lagen 34 Überwachungen und 10 Einsätze aus den Testläufen in der Datenbank — sie standen in den Bildschirmfotos und auf dem Hallenmonitor. Einmalig abgeräumt, die Ursache bleibt. Ein eigener Testmandant, den die Suite anlegt und hinterher abräumt, wäre die Lösung. Weiterhin offen: **`npm run lint` läuft nicht** (keine ESLint-Konfiguration im Repo). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
florian.wilde added 1 commit 2026-09-03 21:40:45 +00:00
Phase 5: das sicherheitsrelevanteste Modul des Projekts. Ueber jeder Ansicht
steht dauerhaft, dass die analoge Atemschutzueberwachung fuehrend bleibt —
abschaltbar nur zusammen mit der Fundstelle einer Freigabe des Kommandanten.

Es gibt keinen Timer. In der Datenbank stehen Zeitstempel; wie lange ein
Trupp unter Atemschutz ist, rechnet jede Ansicht bei jedem Bild neu aus.
Damit ist die Anforderung "uebersteht Neuladen und Verbindungsabbruch" keine
Funktion, die gelingen muss, sondern eine Eigenschaft, die sich nicht
verlieren laesst. Server und Browser rechnen mit denselben Funktionen.

Der Rueckzugsdruck folgt dem doppelten Anmarschverbrauch: was der Weg hinein
gekostet hat, kostet der Weg zurueck noch einmal. Bis der Trupp das
Einsatzziel meldet, greift die Zwei-Drittel-Regel — sie liegt hoeher und ist
damit die vorsichtigere Annahme.

Das Protokoll ist doppelt gesichert: entzogene Rechte und ein Trigger auf
druckmessung und atemschutz_ereignis. Ein Fehler wird durch einen neuen
Eintrag richtiggestellt, nicht durch Aendern.

Verteilt wird ueber Server-Sent Events statt Socket.IO — es geht nur in eine
Richtung, und so bleibt das Deployment bei "next start".

Behoben: ein abgerissener Ereignisstrom meldete sich nicht. Die Anzeige haette
weiter "Verbunden" behauptet, waehrend der Draht tot war. Jetzt gilt Stille
von acht Sekunden als Abriss. Dazu zwei Altlasten: der Navigationseintrag
"Protokoll" fuehrte ins Leere, und die Startseite stand noch auf Phase 1.

68 neue Tests (51 Unit, 17 gegen eine echte Datenbank, 8 E2E je Browser).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
florian.wilde merged commit e1480c0c3e into main 2026-09-03 21:42:21 +00:00
florian.wilde deleted branch feat/phase-5-atemschutz 2026-09-03 21:42:21 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: florian.wilde/FireAdministration#6