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
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.
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).
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)
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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Das sicherheitsrelevanteste Modul des Projekts. Über jeder Ansicht steht
dauerhaft:
Abschalten lässt er sich nur zusammen mit einer Fundstelle
(
ATEMSCHUTZ_FREIGABE). Fehlt sie, bleibt der Hinweis stehen — eine Freigabeohne 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.mdkeine Funktion, diegelingen muss, sondern eine Eigenschaft, die sich nicht verlieren lässt:
Was drin ist
Datenmodell —
Atemschutzeinsatz,Atemschutztrupp,Truppmitglied,Druckmessung,AtemschutzEreignis,G26Untersuchung,Belastungsuebung.Alle mit
organisationIdund 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.
druckmessungundatemschutz_ereignissind gegenUPDATEundDELETEgesperrt, 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,
EventSourceverbindet von selbst neu, unddas Deployment bleibt bei
next startstatt einen eigenen Node-Server zubrauchen.
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
EventSourceschweigt 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.
context.setOffline()einen offenen SSE-Stromin 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
gibt. Die Ansicht des Audit-Logs gehört zum Löschkonzept in Phase 8; bis
dahin steht der Eintrag ausgegraut da.
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
/atemschutz— die Beispielüberwachung „Zimmerbrand Hauptstraße 12" ausdem Seed ist vollständig durchgespielt; darunter die Tauglichkeitsliste mit
allen drei Ampelzuständen (einer hat eine abgelaufene G26, einem fehlt die
Belastungsübung).
vorläufig 210 auf 145 bar.
unterbrochen", die Uhr läuft trotzdem.
Nachweise
npx tsc --noEmitohne Ausgabenpm run test— 306 Tests grün, davon 36 gegen eine echte Datenbanknpx playwright test— 132 Tests grün, ohne Wacklernpm run build— 38 RoutenZum 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 lintläuft nicht (keine ESLint-Konfiguration imRepo).
🤖 Generated with Claude Code