Atemschutzueberwachung nach FwDV 7 ergaenzen

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>
This commit is contained in:
Florian Wilde
2026-09-03 23:19:05 +02:00
co-authored by Claude Opus 5
parent b2b31ed00a
commit 4e2daaafd4
107 changed files with 7214 additions and 40 deletions
+169 -10
View File
@@ -1,10 +1,9 @@
# Anleitung
Bedienung von FLORIAN, Stand Phase 4 (Version 0.5.0). Die Bilder zeigen die
Bedienung von FLORIAN, Stand Phase 5 (Version 0.6.0). Die Bilder zeigen die
Beispielwehr aus dem Seed und werden mit `npm run screenshots` neu erzeugt.
> Atemschutz und Karten kommen in den folgenden Phasen dazu. Siehe
> [ROADMAP.md](ROADMAP.md).
> Die Kartenmodule kommen in Phase 6 dazu. Siehe [ROADMAP.md](ROADMAP.md).
---
@@ -603,7 +602,7 @@ Bericht — wird später nachgetragen. Der Einsatz bekommt sofort eine
fortlaufende Nummer im Format `JJJJ-NNNN`.
**Koordinaten** lassen sich über „Koordinaten suchen" ermitteln, wenn die Wehr
einen eigenen Nominatim betreibt (Kapitel 30). Ohne den bleibt das Feld leer —
einen eigenen Nominatim betreibt (Kapitel 34). Ohne den bleibt das Feld leer —
das ist in Ordnung, die Karte kommt in Phase 6.
---
@@ -945,7 +944,163 @@ nützt eine Minute später nichts mehr.
---
## 30. Adressen zu Koordinaten (Nominatim)
## 30. Atemschutz — was das Modul ist und was nicht
![Atemschutzübersicht](screenshots/31-atemschutz-light.png)
> **Über jeder Ansicht dieses Moduls steht:**
> *Ergänzendes System — die analoge Atemschutzüberwachung bleibt führend.*
Das ist kein Kleingedrucktes. Die Überwachung, die im Ernstfall zählt, hängt am
Fahrzeug und wird mit Stift geführt. FLORIAN schreibt mit, rechnet mit und
druckt am Ende ein Protokoll — aber wenn dieses Tablet ausfällt, läuft der
Einsatz unverändert weiter.
Der Hinweis lässt sich abschalten, aber nur zusammen mit einem Beleg:
```bash
# .env
ATEMSCHUTZ_HINWEIS=false
ATEMSCHUTZ_FREIGABE=Kommandantenbeschluss vom 12.03.2026
```
Fehlt die Fundstelle, bleibt der Hinweis stehen — egal was in
`ATEMSCHUTZ_HINWEIS` steht. Eine Freigabe ohne Beschluss ist keine. Ist er
abgeschaltet, verschwindet er nicht spurlos: dann steht dort, worauf sich die
Freigabe stützt, und dasselbe steht später im Protokoll.
Die Startseite zeigt die Zahl, nach der im Einsatz als Erstes gefragt wird:
**wie viele Atemschutzgeräteträger heute einsetzbar sind** — und wie viele
davon knapp davor stehen, es nicht mehr zu sein.
---
## 31. Eine Überwachung führen
![Überwachungsschirm](screenshots/33-atemschutz-ueberwachung-light.png)
### Eröffnen
Auf `/atemschutz` genügt eine Bezeichnung — „Zimmerbrand Hauptstraße 12". Die
Zuordnung zu einem Einsatz lässt sich nachtragen; im Ernstfall wird zuerst
überwacht und später sortiert.
### Trupp anlegen
Zwei oder drei Personen, für jede der Eingangsdruck vom Manometer, eine davon
Truppführer. Neben jedem Namen steht, ob die Person nach FwDV 7 einsetzbar ist
und was gegebenenfalls fehlt.
> **Verweigert wird nichts.** Im Einsatz entscheidet der Einheitsführer. Ein
> Programm, das ihm im entscheidenden Moment eine Person sperrt, wird umgangen —
> und dann steht am Ende gar nichts im Protokoll. Der Hinweis nützt, die Sperre
> schadet.
### Was auf der Karte steht
| Anzeige | Bedeutung |
|---|---|
| **unter Atemschutz** | Zeit seit dem Einstieg — läuft, solange der Trupp drin ist |
| **niedrigster Druck** | Der ungünstigste Wert im Trupp. Der Trupp geht gemeinsam zurück |
| **Rückzug bei** | Der errechnete Rückzugsdruck |
Die Farbe der Karte ist die Ampel, und daneben steht immer der Text dazu:
„im Rahmen", „Rückzugsdruck bald erreicht", „Rückzugsdruck erreicht — Trupp
zurückziehen". Gelb kommt schon zehn Prozent vor der Grenze — der Trupp soll
losgehen, bevor der Wert erreicht ist.
### Der Rückzugsdruck
Gerechnet wird nach dem **doppelten Anmarschverbrauch**:
```
Rückzug = 2 × (Eingangsdruck − Druck am Einsatzziel) + Warndruck + Zuschlag
```
Dafür meldet der Trupp einmal, wenn er das Einsatzziel erreicht hat — dafür ist
der Knopf **„Einsatzziel erreicht"** da. Bis dahin steht ein vorläufiger Wert
nach der Zwei-Drittel-Regel; er liegt höher und ist damit die vorsichtigere
Annahme. Die Karte sagt das auch dazu.
Beispiel: 300 bar rein, 260 bar am Ziel — 40 bar für den Hinweg, also 80 für
hin und zurück, plus 55 bar Warndruck und 10 bar Zuschlag: **Rückzug bei
145 bar.**
### Druckabfrage
Alle Werte des Trupps in einer Zeile, ein Knopf. Bleibt eine Meldung aus, steht
nach `ATEMSCHUTZ_ABFRAGE_INTERVALL` Sekunden (Vorgabe zehn Minuten) ein
deutlicher Hinweis auf der Karte — gerechnet ab der letzten Meldung, nicht ab
dem Einstieg.
### Mayday
Ein Knopf je Trupp. Er ändert nichts an der Lage: er hält fest, wann es losging,
macht es auf jedem Bildschirm sichtbar und lässt einen Ton hören. Aufgehoben
wird er **ausdrücklich**, nicht durch Wegklicken — und beides steht im
Protokoll.
### Beenden
Geht erst, wenn alle Trupps als draußen gemeldet sind. Das ist keine Schikane:
eine beendete Überwachung mit einem Trupp, der noch drin steht, wäre die
gefährlichste Zeile im ganzen Protokoll.
---
## 32. Was passiert, wenn die Technik streikt
Die wichtigste Eigenschaft dieser Ansicht ist, dass man ihr nichts anhaben
kann. Der Grund dafür ist einfach: **es gibt keinen Timer.** In der Datenbank
steht, wann der Trupp eingestiegen ist; wie lange er drin ist, rechnet der
Bildschirm jede Sekunde neu aus.
| Was passiert | Was Sie sehen |
|---|---|
| Seite neu laden | Dieselbe Zeit läuft weiter |
| Zweites Tablet daneben | Dieselbe Zeit, ohne Abgleich |
| WLAN bricht ab | Die Uhr läuft weiter; oben steht **Verbindung unterbrochen** |
| Server neu gestartet | Nach der ersten Antwort ist alles wieder da |
Oben rechts steht dauerhaft, ob die Verbindung steht. Das ist Absicht: wer
nicht sieht, dass der Draht weg ist, hält einen alten Stand für den aktuellen.
Meldet sich der Server acht Sekunden lang nicht, springt die Anzeige auf
„unterbrochen" — auch dann, wenn der Browser selbst keinen Fehler meldet.
Ist die Verbindung weg, bleibt die Ansicht bedienbar; sie fragt weiter im Takt
nach und holt auf, sobald es wieder geht.
---
## 33. Nachweise und Tauglichkeit
![Nachweise](screenshots/32-atemschutz-nachweise-light.png)
Drei Nachweise müssen **gleichzeitig** gelten, sonst ist jemand nicht
einsetzbar:
| Nachweis | Frist | Wo erfasst |
|---|---|---|
| **G26.3** | drei Jahre, ab 50 Jahren eines | `/atemschutz/nachweise` |
| **Belastungsübung** | jährlich | dort, für die ganze Gruppe auf einmal |
| **Einsatzübung unter Atemschutz** | jährlich | dort — ein echter Einsatz zählt ebenso |
Dazu die Qualifikation als Atemschutzgeräteträger aus Kapitel 9.
Das Ablaufdatum der G26 wird nach dem Alter vorgeschlagen; was auf der
Bescheinigung steht, gilt und lässt sich überschreiben. Eine Untersuchung mit
dem Ergebnis „Nachuntersuchung" oder „nicht tauglich" zählt nicht als Nachweis.
In der Übersicht steht neben jedem Namen **im Klartext**, was fehlt — „G26.3 ist
abgelaufen", „Belastungsübung in diesem Jahr fehlt". Eine rote Ampel ohne
Begründung hilft am Fahrzeug niemandem.
Drei Monate vor Ablauf der G26 wechselt die Ampel auf Gelb: die Person ist
weiter einsetzbar, aber es wird Zeit für einen Termin.
---
## 34. Adressen zu Koordinaten (Nominatim)
FLORIAN löst Einsatzadressen nur auf, wenn die Wehr einen **eigenen** Nominatim
betreibt:
@@ -965,7 +1120,7 @@ GEOCODING_LAENDER=de,at,ch
Ohne Konfiguration bleibt die Suche leer. Koordinaten lassen sich dann von Hand
eintragen oder weglassen.
## 31. Rollen und was sie dürfen
## 35. Rollen und was sie dürfen
Neun Rollen, jede mit einem festen Satz Rechte. Die Zuordnung steht zentral in
`src/lib/authz.ts` und wird beim Seed in die Datenbank gespiegelt — eine Wehr
@@ -994,7 +1149,7 @@ Das Terminal selbst ist nur die Tür.
---
## 32. Anmeldung über authentik einrichten
## 36. Anmeldung über authentik einrichten
authentik läuft **getrennt** von FLORIAN und ist nicht Teil dieses Stacks.
@@ -1047,7 +1202,7 @@ ganze Wehr aus.
---
## 33. PIN und Ausweiskarte verwalten
## 37. PIN und Ausweiskarte verwalten
Wie sich am Terminal angemeldet wird, steht in Kapitel 24. Hier geht es darum,
was dahinter liegt.
@@ -1070,7 +1225,7 @@ Huber), PIN `471203`.
---
## 34. Was protokolliert wird
## 38. Was protokolliert wird
Jede schreibende Änderung erzeugt einen Eintrag mit Zeitpunkt, Person,
betroffenem Datensatz und den geänderten Feldern im Vorher-Nachher-Vergleich.
@@ -1095,7 +1250,7 @@ jeweiligen Detailseite sichtbar.
---
## 35. Wenn etwas nicht geht
## 39. Wenn etwas nicht geht
| Beobachtung | Wahrscheinliche Ursache | Was hilft |
|---|---|---|
@@ -1115,6 +1270,10 @@ jeweiligen Detailseite sichtbar.
| Die Leiste zeigt dauerhaft wartende Änderungen | Server erreichbar, aber der Vorgang wird abgelehnt | Über „anzeigen" den Grund lesen; landet er als Konflikt, steht er unter `/terminal` |
| „Der Code wurde nicht erkannt" beim QR-Login | Marke älter als 60 Sekunden | Auf dem Handy die Seite Anmeldemarke offen halten, sie erneuert den Code selbst |
| Der Monitor zeigt keine Wetterwarnung | `WETTER_ENABLED` aus oder `DWD_WARNCELL` leer | Beides setzen; ohne Warnzelle wird nichts abgerufen |
| Der Sicherheitshinweis im Atemschutz bleibt trotz `ATEMSCHUTZ_HINWEIS=false` | `ATEMSCHUTZ_FREIGABE` ist leer | Fundstelle der Freigabe eintragen — ohne sie bleibt der Hinweis absichtlich stehen |
| Die Überwachung lässt sich nicht beenden | Ein Trupp ist noch nicht als draußen gemeldet | Trupp abmelden, dann beenden. Die Meldung nennt die Anzahl |
| Oben steht „Verbindung unterbrochen", obwohl das Netz da ist | Der Ereignisstrom wurde von einem Proxy gepuffert oder gekappt | Die Ansicht arbeitet weiter; bei einem Reverse Proxy `X-Accel-Buffering: no` durchreichen |
| Das Atemschutzprotokoll fehlt | Die Überwachung ist noch nicht beendet | Ein Protokoll entsteht erst mit dem Ende — vorher wäre es unvollständig und sähe vollständig aus |
Zustand aller Randsysteme auf einen Blick:
+28 -1
View File
@@ -249,7 +249,7 @@ soll, sind es nicht.
## Phase 5 — Atemschutz nach FwDV 7
**Vergleichsaufwand: 14–18 PT**
**Vergleichsaufwand: 14–18 PT** · **Tatsächlich: 2:40 h Sitzungszeit**
| Baustein | PT |
|---|---:|
@@ -268,6 +268,33 @@ Timer, der einen Reload überlebt, ist Pflicht — deshalb muss die Zeit
serverseitig geführt werden und der Browser nur anzeigen. Diese Phase bekommt
den höchsten Testanteil im Projekt.
**Rückblick.** Der Satz stimmt, aber die Schlussfolgerung war die falsche. Die
Aufstellung veranschlagt 3,5 PT für „serverseitig autoritative Timer" — als
wäre ein Timer zu bauen. Gebraucht wurde das Gegenteil: **kein** Timer. Wer die
Zeit als Zeitstempel in die Datenbank schreibt und jede Ansicht selbst rechnen
lässt, hat nichts, was einen Reload nicht überlebt. Der teuerste Posten der
Schätzung war der billigste der Umsetzung, weil die richtige Entscheidung ihn
auflöst statt ihn zu lösen.
Auch Socket.IO fiel weg. Es geht nur in eine Richtung — der Server meldet eine
Änderung, der Browser holt den Stand über den Weg, den es ohnehin gibt. Ein
Route Handler mit Server-Sent Events ist siebzig Zeilen und braucht kein
verändertes Deployment.
Teurer war das, was in keiner Zeile der Aufstellung steht: **die Frage, was
passiert, wenn nichts passiert.** Ein abgerissener Ereignisstrom meldet sich
nicht — `EventSource` schweigt einfach. Eine Anzeige, die deshalb weiter
„Verbunden" behauptet, ist gefährlicher als eine, die gar nichts anzeigt: sie
lässt einen alten Stand wie einen aktuellen aussehen. Aufgefallen ist das nur,
weil der E2E-Test das Netz gekappt und danach nachgesehen hat — die 2,0 PT für
„Tests: Reload und Verbindungsabbruch überstehen" waren wieder der
bestangelegte Posten der Phase.
Billiger als gedacht war die Tauglichkeit. Nicht das Rechnen — die
Entscheidung, **nicht** zu sperren. Damit entfielen alle Sonderfälle, die eine
Sperre nach sich zieht: Übersteuern, Begründungspflicht, Freigabe durch wen.
Was bleibt, ist ein Satz neben dem Namen, und der ist im Einsatz mehr wert.
## Phase 6 — Kartenmodule
**Vergleichsaufwand: 14–18 PT**
+81 -1
View File
@@ -16,7 +16,87 @@ Die aufgewendete Zeit wandert nach jedem Abschnitt in die Spalte
| Phase 2 — Wartung und Prüfung | 2:25 h | 1 | 03.09.2026 |
| Phase 3 — Einsätze und Übungen | 2:10 h | 1 | 03.09.2026 |
| Phase 4 — Kiosk-PWA im Gerätehaus | 3:00 h | 1 | 03.09.2026 |
| **Summe** | **10:40 h** | **6** | |
| Phase 5 — Atemschutz nach FwDV 7 | 2:40 h | 1 | 03.09.2026 |
| **Summe** | **13:20 h** | **7** | |
---
## 2026-09-03 · Phase 5 — Atemschutz nach FwDV 7
**19:40 – 22:20 · 2:40 h · Version 0.6.0**
### Was entstanden ist
Die Atemschutzüberwachung. Sieben Tabellen, ein Ereignisstrom, ein
lückenloses Protokoll — und der Hinweis über allem, dass die analoge
Überwachung führend bleibt.
| Bereich | Dateien |
|---|---|
| Rechnen | `lib/atemschutz/berechnung.ts`, `tauglichkeit.ts`, `verfuegbarkeit.ts` |
| Server | `server/routers/atemschutz/ueberwachung.ts`, `traeger.ts` |
| Strom | `app/api/atemschutz/strom/route.ts`, `components/atemschutz/stand.ts` |
| Protokoll | `lib/atemschutz/protokoll.ts`, `protokoll-erzeugen.ts`, `api/atemschutz/protokoll` |
| Oberfläche | `app/(app)/atemschutz/`, `components/atemschutz/` |
| Tests | 51 Unit, 17 Datenbank, 8 E2E je Browser |
### Entscheidungen
**Server-Sent Events statt Socket.IO.** Phase-5.md nennt Socket.IO; das
verlangt in Next.js einen eigenen Node-Server und damit ein anderes
Deployment. Gebraucht wird aber nur eine Richtung: der Server meldet „es hat
sich etwas geändert", der Browser holt den Stand über tRPC. Ein Route Handler
genügt, `EventSource` verbindet nach einem Abbruch von selbst neu, und
`next start` bleibt wie es ist. Nach Rücksprache so entschieden.
**Es gibt keinen Timer.** Kein Zähler, kein Intervall, das Zeit führt. Jede
Dauer entsteht aus zwei Zeitstempeln — einem aus der Datenbank, einem von der
Uhr. Damit ist die Anforderung „übersteht Reload und Verbindungsabbruch" keine
Funktion, die man testen muss, sondern eine Eigenschaft, die sich nicht
verlieren lässt. Getestet wird sie trotzdem.
**Der Rückzugsdruck nach dem doppelten Anmarschverbrauch.** Nach Rücksprache:
die Wehr rechnet mit dem tatsächlichen Verbrauch auf dem Hinweg. Solange keine
Zielmeldung vorliegt, greift die Zwei-Drittel-Regel — sie liegt höher, ist also
die vorsichtigere Annahme, und das steht auch so auf der Karte.
**Die Tauglichkeit sperrt niemanden.** Neben jedem Namen steht, ob die Person
nach FwDV 7 einsetzbar wäre und was fehlt. Verweigert wird nichts. Ein
Programm, das im Einsatz eine Person blockiert, wird umgangen — und dann steht
am Ende gar nichts im Protokoll. Der Hinweis nützt, die Sperre schadet.
**Das Protokoll ist doppelt gesichert.** Entzogene Rechte **und** ein Trigger
auf `druckmessung` und `atemschutz_ereignis`. Eine Sperre hätte gereicht; bei
einer Aufzeichnung, an der Menschenleben hängen, ist die zweite ihren Preis
wert.
### Wo es gehakt hat
| Problem | Ursache | Lösung |
|---|---|---|
| Ein gekapptes Netz ließ die Anzeige „Verbunden" melden | `context.setOffline()` trennt einen **offenen** SSE-Strom in Chromium nicht — er liefert weiter. Der Test prüfte damit etwas anderes als gedacht. Schlimmer: bei einem echten Abriss schweigt `EventSource`, ohne einen Fehler zu melden | Der Server schickt alle zwei Sekunden ein Lebenszeichen; bleibt es acht Sekunden aus, gilt die Verbindung als tot. Der Test schneidet den Strom jetzt über eine abgewiesene Route ab, statt das Netz zu kappen |
| Prisma meldete den Teilindex nicht beim Namen | Ein `UNIQUE INDEX … WHERE` erscheint als verletzte Eindeutigkeit auf der Spalte | Zusicherung im Test daran angepasst, mit Verweis auf den Index |
| Die Bildschirmfotos zeigten Testleichen | 34 Überwachungen aus den E2E-Läufen standen über der einen echten aus dem Seed | Rückstände in der Entwicklungsdatenbank abgeräumt, Auswahl im Skript auf Nicht-Testdaten eingegrenzt. Die Ursache bleibt: die E2E-Suite schreibt in die Demowehr |
### Abweichung von der Vorgabe
**Kein Socket.IO.** Siehe oben — nach Rücksprache durch Server-Sent Events
ersetzt. Die Roadmap ist entsprechend berichtigt.
**Kein RFID-Scan beim Truppanlegen.** Phase-5.md nennt „Truppanlage per
RFID-Scan oder Auswahl". Umgesetzt ist die Auswahl. Der Kartenleser hängt am
Terminal im Gerätehaus (Phase 4) und meldet dort Personen an; am
Überwachungsschirm steht der Überwacher mit einem Tablet und wählt zwei Namen
aus einer Liste, auf der ohnehin die Tauglichkeit steht. Der Scan wäre dort
ein zweiter Weg zum selben Ziel — nachrüstbar, wenn er im Betrieb vermisst
wird.
### Nachweise
- `npx tsc --noEmit` — ohne Ausgabe
- `npm run test` — 306 Tests grün, davon 36 gegen eine echte Datenbank
- `npx playwright test tests/e2e/atemschutz.spec.ts` — 16 grün (Desktop und Tablet)
- `npm run db:seed` — Nachweise und eine abgeschlossene Überwachung angelegt
---
+6 -4
View File
@@ -14,7 +14,7 @@ Phase 1 ████████████████████ fertig
Phase 2 ████████████████████ fertig 0.3.0
Phase 3 ████████████████████ fertig 0.4.0
Phase 4 ████████████████████ fertig 0.5.0
Phase 5 ░░░░░░░░░░░░░░░░░░░░ offen 0.6.0
Phase 5 ████████████████████ fertig 0.6.0
Phase 6 ░░░░░░░░░░░░░░░░░░░░ offen 0.7.0
Phase 7 ░░░░░░░░░░░░░░░░░░░░ offen 0.8.0
Phase 8 ░░░░░░░░░░░░░░░░░░░░ offen 0.9.0
@@ -116,14 +116,16 @@ Für die Wandtablets in der Fahrzeughalle.
**Danach kann die Wehr:** einen Mangel in unter 30 Sekunden melden, ohne den
Rechner im Büro einzuschalten — auch wenn das Netz gerade weg ist.
## Phase 5 — Atemschutz nach FwDV 7 · als Nächstes
## Phase 5 — Atemschutz nach FwDV 7 · fertig
Sicherheitsrelevant. Jede Ansicht zeigt dauerhaft den Hinweis, dass die
analoge Überwachung führend bleibt — abschaltbar erst über eine Option, die
eine dokumentierte Freigabe durch den Kommandanten voraussetzt.
- Truppanlage per RFID, Eingangsdruck erfassen
- Serverseitig autoritative Timer, per Socket.IO verteilt
- Serverseitig autoritative Zeitstempel; verteilt über Server-Sent Events
statt Socket.IO — es geht nur in eine Richtung, und so bleibt es bei
`next start` ohne eigenen Node-Server
- Rückzugsdruck-Berechnung mit Ampel und akustischem Alarm
- Erinnerung zur Druckabfrage im konfigurierbaren Intervall
- Mayday-Knopf je Trupp
@@ -133,7 +135,7 @@ eine dokumentierte Freigabe durch den Kommandanten voraussetzt.
**Danach kann die Wehr:** die Überwachung digital mitführen — parallel zur
analogen, bis die Wehr ausdrücklich anders entscheidet.
## Phase 6 — Kartenmodule
## Phase 6 — Kartenmodule · als Nächstes
- MapLibre GL mit selbst gehosteten Kacheln fürs Ausrückegebiet
- Hydranten mit Typ, Nennweite, Durchfluss, letzter Prüfung; Import aus
Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 733 KiB

After

Width:  |  Height:  |  Size: 733 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 KiB

After

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 KiB

After

Width:  |  Height:  |  Size: 969 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1013 KiB

After

Width:  |  Height:  |  Size: 1014 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 KiB

After

Width:  |  Height:  |  Size: 1005 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 KiB

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 KiB

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 KiB

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 KiB

After

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

After

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 432 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB