469 lines
23 KiB
Markdown
469 lines
23 KiB
Markdown
# Aufwandsschätzung
|
||
|
||
Schätzung je Phase, daneben der tatsächliche Aufwand aus dem
|
||
[Devlog](DEVLOG.md). Stand: 4. September 2026.
|
||
|
||
## Wie hier gerechnet wird
|
||
|
||
Es stehen bewusst **zwei** Größen nebeneinander, weil sie Verschiedenes
|
||
aussagen:
|
||
|
||
| Größe | Bedeutung |
|
||
|---|---|
|
||
| **Sitzungszeit** | Wie lange Claude tatsächlich gearbeitet hat. Das ist die Zahl, die im Devlog steht, und die Grundlage für die Planung der nächsten Sitzung. |
|
||
| **Vergleichsaufwand** | Was eine einzelne Person mit diesem Stack für dasselbe Ergebnis gebraucht hätte, in Personentagen zu 8 Stunden. Das ist die Zahl, die gegenüber der Wehr etwas bedeutet — sie beantwortet „was wäre das sonst wert gewesen". |
|
||
|
||
Der Vergleichsaufwand ist eine **Schätzung**, keine Messung. Er unterstellt
|
||
jemanden, der den Stack kennt, die Feuerwehr-Fachlichkeit aber erst klären
|
||
muss — also Recherche zu DGUV-Fristen, FwDV 3 und FwDV 7 einschließt.
|
||
|
||
Faktoren, die in die Schätzung eingehen:
|
||
|
||
- **Fachlichkeit** — wie viel muss geklärt werden, bevor gebaut werden kann?
|
||
- **Sicherheitsbezug** — Atemschutz und Prüfnachweise brauchen mehr Tests und
|
||
mehr Sorgfalt als eine Adressliste.
|
||
- **Fremdsysteme** — Gotenberg, Nominatim, tileserver-gl, Ollama, RFID-Leser.
|
||
Jedes ist eine eigene Fehlerquelle.
|
||
- **Offline und Echtzeit** — die teuersten Anforderungen im ganzen Projekt.
|
||
|
||
## Übersicht
|
||
|
||
| Phase | Inhalt | Vergleichsaufwand | Sitzungszeit geschätzt | Sitzungszeit tatsächlich |
|
||
|---|---|---:|---:|---:|
|
||
| 0 | Fundament | 12–16 PT | — | **1:00 h** |
|
||
| 1 | Stammdaten | 10–14 PT | 2,5–3,5 h | **2:05 h** |
|
||
| 2 | Wartung und Prüfung | 18–24 PT | 4,5–6 h | **2:25 h** |
|
||
| 3 | Einsätze und Übungen | 12–16 PT | 3–4 h | **2:10 h** |
|
||
| 4 | Kiosk-PWA mit Offline | 16–22 PT | 4–5,5 h | **3:00 h** |
|
||
| 5 | Atemschutz (FwDV 7) | 14–18 PT | 3,5–4,5 h | **2:40 h** |
|
||
| 6 | Kartenmodule | 14–18 PT | 3,5–4,5 h | **2:50 h** |
|
||
| 7 | Benachrichtigungen und Termine | 10–13 PT | 2,5–3,5 h | **3:10 h** |
|
||
| 8 | Lokale KI | 9–12 PT | 2,5–3 h | **3:40 h** |
|
||
| 9 | Kassenverwaltung | 8–11 PT | 2–3 h | **4:10 h** |
|
||
| 10 | Mandantenfähigkeit | 4–6 PT | 1–1,5 h | **4:20 h** |
|
||
| 11 | Freigabereife | 5–7 PT | 1,5–2 h | **2:40 h** |
|
||
| | **Summe** | **132–177 PT** | **30,5–40,5 h** | **35:30 h** |
|
||
|
||
123–164 Personentage entsprechen etwa **6 bis 8 Monaten Vollzeitarbeit** einer
|
||
einzelnen Person. Zum Vergleich: eine kommerzielle Feuerwehrverwaltung kostet
|
||
je nach Umfang 500 bis 2.500 Euro im Jahr, dauerhaft, mit den Daten beim
|
||
Anbieter.
|
||
|
||
---
|
||
|
||
## Phase 0 — Fundament
|
||
|
||
**Vergleichsaufwand: 12–16 PT** · **Tatsächlich: 1:00 h Sitzungszeit**
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| Monorepo, Werkzeuge, TypeScript, Tailwind, shadcn | 1,5 |
|
||
| Docker Compose, Dockerfile, Traefik, Healthchecks | 2,5 |
|
||
| Prisma-Schema der Basismodelle | 2,0 |
|
||
| Row Level Security samt Rollen- und Rechtekonzept in Postgres | 2,5 |
|
||
| Auth.js mit OIDC, lokalem Login und Terminal-Anmeldung | 3,0 |
|
||
| storage, audit, warteschlange, Integrationsschnittstellen | 2,0 |
|
||
| Basis-Layout, Anmeldeseite, Übersicht, Dark Mode | 1,5 |
|
||
| Seed, Vitest, Playwright, Dokumentation | 1,5 |
|
||
|
||
Das Teuerste war nicht der sichtbare Teil, sondern die Mandantentrennung. RLS
|
||
so aufzusetzen, dass Migrationen, Seed und Anmeldung funktionieren und die
|
||
Anwendung trotzdem nichts Fremdes sieht, verlangt zwei getrennte
|
||
Datenbankzugänge und ein sauberes Verständnis davon, wann welcher greift. Wer
|
||
das nachträglich einbaut, zahlt ein Vielfaches.
|
||
|
||
## Phase 1 — Stammdaten
|
||
|
||
**Vergleichsaufwand: 10–14 PT** · **Tatsächlich: 2:05 h Sitzungszeit** (1:15 h Hauptteil, 0:50 h Nachtrag)
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| Modelle: Standort, Geraeteraum, Geraetetyp, Qualifikation | 1,5 |
|
||
| Fahrzeug und Geraet um DIN-Typisierung und Lebensdauer erweitern | 1,5 |
|
||
| Tabellen mit Sortierung, Filter, Volltextsuche, Spaltenauswahl | 3,0 |
|
||
| QR-Code je Gerät, Etikettenbogen als PDF (65 Etiketten, DIN A4) | 1,5 |
|
||
| QR-Scan im Browser über die Kamera | 1,0 |
|
||
| CSV-/Excel-Import mit Spaltenzuordnung und Vorschau | 2,5 |
|
||
| Gerätehistorie aus dem Audit-Log | 1,0 |
|
||
| Massenbearbeitung und Tastaturbedienung | 1,0 |
|
||
|
||
Der Import ist der Posten, der erfahrungsgemäß aus dem Ruder läuft. Jede Wehr
|
||
bringt eine andere Excel-Datei mit, und die Vorschau vor dem Übernehmen ist
|
||
kein Komfort, sondern die Bedingung dafür, dass die Wehr dem Ergebnis traut.
|
||
|
||
**Rückblick.** Die Schätzung hat gut getroffen — mit einer Verschiebung: Der
|
||
Import war teurer als angesetzt (deutsche Datumsformate, Semikolon-CSV,
|
||
Windows-1252, Excel-Seriennummern, Dubletten in der Datei), die Tabellen
|
||
dagegen billiger, weil Sortierung und Blättern serverseitig laufen und keine
|
||
Tabellenbibliothek nötig war. Nicht eingeplant war die Zeitzonen-Falle in der
|
||
Fristenrechnung; sie hätte in Phase 2 deutlich mehr gekostet, wenn sie erst
|
||
dort aufgefallen wäre.
|
||
|
||
Das zunächst offen gebliebene Formular zum einzelnen Anlegen von Geräten wurde
|
||
als Nachtrag ergänzt — geschätzt waren 0,5 PT, tatsächlich 0:50 h Sitzungszeit.
|
||
Die Hälfte davon ging für zwei Dinge drauf, die nichts mit dem Formular zu tun
|
||
hatten: eine Server/Client-Grenze, die stillschweigend `undefined` liefert, und
|
||
Datumsfelder, die sich in Chromium nicht automatisiert füllen lassen.
|
||
|
||
## Phase 2 — Wartung und Prüfung
|
||
|
||
**Vergleichsaufwand: 18–24 PT** · **Tatsächlich: 2:25 h Sitzungszeit** — die aufwendigste Phase
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| Modelle: Vorlage, Checklistenpunkt, Termin, Prüfung, Ergebnis, Mangel, Rückruf | 3,0 |
|
||
| Fristen-Dashboard mit Gruppierungen | 2,5 |
|
||
| Nächtlicher BullMQ-Job: Termine erzeugen, Status setzen, erinnern | 2,5 |
|
||
| Prüfungs-Wizard mit Fotoupload je Checklistenpunkt | 3,5 |
|
||
| Unterschrift auf Canvas, Touch und Maus | 1,5 |
|
||
| PDF-Protokoll über Gotenberg mit Fotos und Signatur | 3,0 |
|
||
| Unveränderlichkeit und Korrekturprotokolle mit Verweis | 1,5 |
|
||
| Lebensdauerüberwachung und Aussonderungsvorschlag | 1,5 |
|
||
| Rückrufe, externe Wartung, Mängel-Workflow | 2,5 |
|
||
|
||
Hier entsteht der eigentliche Wert: die Software wird zum Nachweis gegenüber
|
||
der Unfallkasse. Das heißt aber auch, dass jeder Fehler teuer ist. Die
|
||
Unveränderlichkeit abgeschlossener Protokolle ist der Kern — und der Grund,
|
||
warum der Audit-Log schon in Phase 0 append-only ist.
|
||
|
||
**Rückblick.** Deutlich unter der geschätzten Sitzungszeit, und das aus einem
|
||
Grund, der sich benennen lässt: die Fristenrechnung aus Phase 1 war schon da
|
||
und musste nur exportiert werden. Wäre sie hier entstanden — samt der
|
||
Zeitzonen-Falle, über die Phase 1 gestolpert ist —, sähe die Zahl anders aus.
|
||
|
||
Teurer als gedacht war das, was zwischen den Bausteinen liegt. Drei Beispiele:
|
||
|
||
- Die Reihenfolge beim Abschluss einer Prüfung ist nicht beliebig.
|
||
Unterschriften und Mängel müssen vor dem Statuswechsel entstehen, denn danach
|
||
lässt die Datenbank am Protokoll nichts mehr zu. Derselbe Stolperstein traf
|
||
auch den Seed, der die Beispielprüfung zunächst fertig anlegen wollte.
|
||
- Die Protokollnummer muss unter einer Sperre vergeben werden. Aufgefallen ist
|
||
das erst, weil die E2E-Tests Desktop und Tablet gleichzeitig prüfen ließen —
|
||
zwei Prüfer, die im selben Moment abschließen, sind im Gerätehaus kein
|
||
konstruierter Fall.
|
||
- Das PDF entsteht zwischen zwei Datenbanktransaktionen, nicht in einer. Fotos
|
||
aus MinIO holen und Gotenberg rendern lassen dauert länger, als eine offene
|
||
Transaktion warten darf.
|
||
|
||
Billiger als angesetzt war das Unterschriftenfeld: Pointer-Events decken Finger,
|
||
Stift und Maus in einem ab, und mehr als Skalierung auf die Pixeldichte und
|
||
`touch-action: none` braucht es nicht.
|
||
|
||
## Phase 3 — Einsätze und Übungen
|
||
|
||
**Vergleichsaufwand: 12–16 PT** · **Tatsächlich: 2:10 h Sitzungszeit**
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| Modelle: Einsatz, Einsatzfahrzeug, Einsatzkraft, Material, Lagemeldung, Übung | 2,0 |
|
||
| Schnellerfassung mit Nachtragen | 1,5 |
|
||
| Geocoding über self-hosted Nominatim mit Rückfallweg | 1,5 |
|
||
| Personalauswahl nach Verfügbarkeit und Qualifikation | 2,0 |
|
||
| Stärkeberechnung nach FwDV 3 | 1,0 |
|
||
| Einsatzbericht als PDF, Jahresexport für die Gemeinde | 2,0 |
|
||
| Statistik: Kategorien, Eintreffzeiten, Beteiligung, Auslastung | 2,5 |
|
||
| Externes Portal mit signiertem QR-Link, ohne Login, rate-limited | 2,0 |
|
||
|
||
Das externe Portal für Fremdwehren ist sicherheitsrelevant: ein Zugang ohne
|
||
Anmeldung, der zeitlich begrenzt und mengenbegrenzt sein muss. Der Aufwand
|
||
steckt weniger in der Funktion als in der Absicherung.
|
||
|
||
**Rückblick.** Die Schätzung lag richtig darin, wo der Aufwand steckt — nur
|
||
nicht in welcher Reihenfolge.
|
||
|
||
Billiger als gedacht war das Portal. Der Grund liegt in Phase 0 und 2: die
|
||
Mandantentrennung, das Hashen von Geheimnissen und die Ablage von Dateien gab
|
||
es schon, und der Zugang war am Ende ein Datensatz mit Hash, ein Zeitfenster
|
||
und ein Zähler in Redis. Was blieb, war die Frage, was das Portal **nicht**
|
||
zeigen darf — und die ist fachlicher Natur, nicht technischer.
|
||
|
||
Teurer war die Statistik. Nicht das Rechnen, sondern die Entscheidung, welche
|
||
Zahl gilt: der Durchschnitt der Eintreffzeiten sagt nichts, weil ein einzelner
|
||
Einsatz auf der Autobahn ihn verschiebt. Es zählt der 80-Prozent-Wert, weil die
|
||
Gemeinde danach fragt — und das steht in keinem Pflichtenheft, das muss man
|
||
wissen.
|
||
|
||
Zwei Dinge fielen erst beim Tun auf:
|
||
|
||
- Koordinaten müssen doppelt liegen. Prisma kann den PostGIS-Typ nicht lesen;
|
||
ohne die Dezimalgrade daneben wäre jede Anzeige eine Rohabfrage geworden. Ein
|
||
Trigger hält beides zusammen.
|
||
- Der Nummernkreis war schon zweimal da (Prüfprotokolle, jetzt Einsätze und
|
||
Übungen). Beim dritten Mal wurde daraus ein eigener Baustein — die passende
|
||
Gelegenheit war genau diese, keine früher.
|
||
|
||
## Phase 4 — Kiosk-PWA
|
||
|
||
**Vergleichsaufwand: 16–22 PT** · **Tatsächlich: 3:00 h Sitzungszeit**
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| Eigenes Kiosk-Layout, 64px-Ziele, hoher Kontrast, kein Hover-Zwang | 2,0 |
|
||
| Anmeldung per RFID (WebHID), PIN und QR vom Handy | 2,5 |
|
||
| Wizards: Wartung, Fahrtenbuch, Mangel, Inventur, Wäsche, Anwesenheit | 4,0 |
|
||
| Hallen-Monitor mit DWD-Wetterwarnung | 2,0 |
|
||
| Service Worker, App-Shell und Stammdaten offline | 3,0 |
|
||
| IndexedDB-Outbox mit Idempotenzschlüssel und Synchronisation | 4,0 |
|
||
| Konfliktbehandlung und Konfliktliste für den Administrator | 2,0 |
|
||
| Playwright-Tests für den Offline-Fall | 1,5 |
|
||
|
||
Offline-Fähigkeit mit schreibenden Aktionen ist die technisch schwierigste
|
||
Anforderung des ganzen Projekts. Eine Outbox ist schnell gebaut; dass sie
|
||
unter Verbindungsabbruch, Doppelklick und parallelem Zugriff von zwei
|
||
Terminals nichts verliert und nichts doppelt schreibt, ist die eigentliche
|
||
Arbeit. Die Idempotenzschlüssel sind dafür der Schlüssel, nicht die Synchronisation.
|
||
|
||
**Rückblick.** Der Satz oben hat sich bestätigt, nur anders als gedacht. Die
|
||
Outbox selbst war in einer halben Stunde geschrieben und mit zwölf Unit-Tests
|
||
belegt. Zwei Stellen kosteten die Zeit, und keine davon steht in der
|
||
Aufstellung:
|
||
|
||
- **Der Zeitgeber, der nie ablief.** Die Warteschlange hatte einen
|
||
Wiederholungstakt von 20 Sekunden — und der wurde bei jedem Rendern neu
|
||
aufgezogen, weil das Mutationsobjekt von React Query in den Abhängigkeiten
|
||
stand. Alles sah richtig aus, alle Unit-Tests waren grün, und der liegen
|
||
gebliebene Vorgang wäre im Gerätehaus trotzdem liegen geblieben. Gefunden hat
|
||
das nur der Playwright-Test, der `context.setOffline(true)` setzt und dann
|
||
wartet. Die 1,5 PT für „Playwright-Tests für den Offline-Fall" waren der
|
||
bestangelegte Posten der Phase.
|
||
- **Die Frage, wann ein Vorgang aus der Warteschlange darf.** Nicht „wenn
|
||
erfolgreich", sondern „wenn er beim Server angekommen ist" — dazu gehört auch
|
||
„bereits verarbeitet" und „in der Konfliktliste". Diese Unterscheidung ist die
|
||
ganze Idempotenz; ohne sie kreist ein Vorgang für immer.
|
||
|
||
Billiger als angesetzt war die Anmeldung per RFID: die Aufstellung nennt WebHID,
|
||
gebraucht wurde es nicht. Handelsübliche Leser geben die Kartenkennung als
|
||
Tastatureingabe aus. Ein Zuhörer auf dem Fenster, der am Anschlagstempo
|
||
erkennt, dass da keine Hand tippt, ersetzt eine ganze Geräteschnittstelle.
|
||
|
||
Billiger war auch der Service Worker — 80 Zeilen von Hand, ohne Workbox. Die
|
||
Regeln sind einfach, solange man sie sich traut aufzuschreiben: Seiten aus dem
|
||
Netz mit Ausweichseite, lesende Abfragen aus dem Zwischenspeicher mit
|
||
Nachladen, schreibende **überhaupt nicht** anfassen. Eine Bibliothek hätte hier
|
||
mehr Fragen aufgeworfen als beantwortet.
|
||
|
||
Teurer war die Terminaloberfläche selbst. Nicht die Kacheln, sondern die
|
||
Zustände dahinter: Wer schon eingecheckt ist, sieht „Gehen" statt „Kommen". Wer
|
||
mit einem Fahrzeug unterwegs ist, sieht „Fahrt beenden". Wer keine Rechte für
|
||
die Inventur hat, sieht die Kachel nicht. Jede dieser Verzweigungen ist
|
||
trivial — sechs davon in einem Schirm, der ohne Nachdenken bedienbar bleiben
|
||
soll, sind es nicht.
|
||
|
||
## Phase 5 — Atemschutz nach FwDV 7
|
||
|
||
**Vergleichsaufwand: 14–18 PT** · **Tatsächlich: 2:40 h Sitzungszeit**
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| Modelle: Atemschutzeinsatz, Trupp, Druckmessung, G26, Belastungsübung | 2,0 |
|
||
| Truppanlage per RFID, Eingangsdruck | 1,5 |
|
||
| Serverseitig autoritative Timer, Socket.IO-Verteilung | 3,5 |
|
||
| Rückzugsdruck-Berechnung, Ampel, akustischer Alarm | 2,0 |
|
||
| Erinnerung zur Druckabfrage im Intervall | 1,0 |
|
||
| Mayday-Knopf mit Protokolleintrag | 1,0 |
|
||
| Lückenloses PDF-Protokoll am Einsatz | 1,5 |
|
||
| G26.3-Fristen, Belastungsübungen, Tauglichkeitsampel | 2,0 |
|
||
| Tests: Reload und Verbindungsabbruch überstehen | 2,0 |
|
||
|
||
Der Aufwand liegt nicht in der Funktion, sondern im Beweis, dass sie hält. Ein
|
||
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** · **Tatsächlich: 2:50 h Sitzungszeit**
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| MapLibre GL, tileserver-gl, MBTiles fürs Ausrückegebiet | 2,5 |
|
||
| Hydranten mit PostGIS, Cluster, Filter, Umkreissuche | 3,0 |
|
||
| Import aus GeoJSON und Stadtwerke-CSV | 1,5 |
|
||
| Objekte und Feuerwehrpläne nach DIN 14095, Volltextsuche | 3,0 |
|
||
| Lagekarte mit taktischen Zeichen nach FwDV 102 als SVG-Set | 3,5 |
|
||
| Echtzeit-Sync der Lagekarte zwischen Geräten | 2,0 |
|
||
| Wasserförderung: Höhenprofil, Druckverlust, Verstärkerpumpen | 2,5 |
|
||
|
||
Das SVG-Set für die taktischen Zeichen ist Fleißarbeit, aber unvermeidbar —
|
||
fertige Sets sind selten frei verwendbar. Die Wasserförderungsberechnung ist
|
||
Physik mit klaren Formeln und deshalb gut testbar.
|
||
|
||
**Rückblick.** Beide Sätze stimmen. Das Zeichenset war Fleißarbeit — 22 Zeichen
|
||
als SVG-Pfade, keine davon schwierig, alle zusammen eine Stunde. Und die
|
||
Wasserförderung war tatsächlich der am besten testbare Teil des ganzen
|
||
Projekts: 21 Tests, die alle etwas über die Physik aussagen statt über den Code.
|
||
|
||
Dabei fiel auf, dass die Beiwerte im ersten Wurf um den Faktor zehn danebenlagen
|
||
— aufgefallen ist es nur, weil die Tests gegen die **Tabellenwerte der
|
||
Ausbildung** prüfen und nicht gegen das, was die Funktion gerade ausrechnet.
|
||
Ein Test, der die eigene Implementierung bestätigt, hätte hier fröhlich grün
|
||
gemeldet.
|
||
|
||
Billiger als angesetzt war die Umkreissuche. 3,0 PT für „Hydranten mit PostGIS,
|
||
Cluster, Filter, Umkreissuche" — davon entfiel der Löwenanteil auf eine einzige
|
||
SQL-Abfrage mit `ST_DWithin` und einen GiST-Index. PostGIS war seit Phase 0 im
|
||
Container; genutzt wurde es erst jetzt.
|
||
|
||
Teurer war die Frage, was passiert, wenn es **keine Karte** gibt. Eine MBTiles-
|
||
Datei des Ausrückegebiets ist hunderte Megabyte groß, gehört niemandem außer
|
||
der Wehr und kann nicht im Repository liegen. Das heißt: der Normalfall beim
|
||
ersten Start ist eine Karte ohne Hintergrund — und sie muss trotzdem etwas
|
||
taugen. Aus dieser Anforderung kam der leere Stil mit Hinweis, die Erkennung
|
||
eines nicht erreichbaren Servers und die Zusicherung im E2E-Test, dass genau
|
||
das ehrlich angezeigt wird. In der Aufstellung steht davon keine Zeile.
|
||
|
||
## Phase 7 — Benachrichtigungen und Termine
|
||
|
||
**Vergleichsaufwand: 10–13 PT** · **Tatsächlich: 3:10 h Sitzungszeit**
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| Kanalabstraktion: E-Mail, Telegram, ntfy, Web-Push | 2,5 |
|
||
| Vorlagen mit Platzhaltern, je Ereignis und Rolle | 2,0 |
|
||
| Abonnements je Person | 1,0 |
|
||
| Kalender mit Serienterminen und Selbstanmeldung | 3,0 |
|
||
| ICS-Feed je Person mit Token | 1,5 |
|
||
| Digitaler Dienstausweis mit signiertem QR und Prüfseite | 2,0 |
|
||
|
||
Das Grundgerüst der Kanalabstraktion stand bereits aus Phase 0
|
||
(`lib/integrations/`), das hat etwa einen Personentag gespart.
|
||
|
||
Die einzige Phase bisher, die über der geschätzten Sitzungszeit liegt — und
|
||
zwar an einer Stelle, die in der Aufstellung nicht vorkommt. „Kalender mit
|
||
Serienterminen" klingt nach einer Datumsrechnung; teuer war die Entscheidung
|
||
**gegen** eine Regel und **für** ausgeschriebene Termine, und dann die
|
||
Kalenderarithmetik dahinter: `setMonth(+1)` auf dem 31. Januar ergibt den
|
||
3. März, der 29. Februar existiert in vier von fünf Jahren nicht, und ein
|
||
Übungsabend muss über die ganze Serie auf demselben Wochentag bleiben. Das sind
|
||
drei Tests, die man erst schreibt, wenn man den Fall kennt.
|
||
|
||
Die 1,5 PT für den Feed waren dagegen zu hoch angesetzt, weil sie CalDAV
|
||
unterstellen. ICS ist ein Textformat; der ganze Erzeuger sind hundertsechzig
|
||
Zeilen samt Maskierung und Zeilenumbruch nach 75 Oktetten.
|
||
|
||
## Phase 8 — Lokale KI
|
||
|
||
**Vergleichsaufwand: 9–12 PT** · **Tatsächlich: 3:40 h Sitzungszeit**
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| `lib/ai/` mit OpenAI-kompatiblem Client, abschaltbar | 1,0 |
|
||
| Diktat: Aufnahme im Browser, faster-whisper, Transkript | 2,5 |
|
||
| Berichtsassistent mit Übernehmen/Verwerfen | 2,0 |
|
||
| Wiki mit pgvector, semantische Suche, RAG mit Quellenangabe | 3,5 |
|
||
| Belegerfassung über ein Vision-Modell, immer bestätigen | 2,0 |
|
||
|
||
Der Client aus Phase 0 (`lib/integrations/llm.ts`) war bereits vorhanden — er
|
||
konnte allerdings nur Text und musste um Einbettungen und Bilder erweitert
|
||
werden.
|
||
|
||
Die Kennzeichnungspflicht war mit „kostet wenig Zeit" zu günstig eingeschätzt.
|
||
Sie kostet wenig, wenn man sie als Abzeichen an einer Ansicht versteht. Als
|
||
Zusicherung — eigener Datensatz je Ausgabe, unveränderlich, genau eine
|
||
Entscheidung, Trigger und REVOKE dahinter, zwei Protokollansichten — ist sie
|
||
ein guter halber Personentag, der in der Aufstellung gar nicht vorkommt.
|
||
|
||
Zwei weitere Posten fehlen in der Schätzung:
|
||
|
||
**Das eigene Postgres-Image.** pgvector und PostGIS gibt es nicht zusammen; es
|
||
gibt kein offizielles Image mit beidem. Ein Dockerfile, ein Neubau, ein Neustart
|
||
des Dienstes — eine halbe Stunde, aber sie stand nirgends.
|
||
|
||
**Der Betrieb ohne Modell.** Der größte Einzelposten dieser Phase, und in der
|
||
Aufstellung steht davon keine Zeile. Eine Wehr setzt FLORIAN auf, lange bevor
|
||
sie über eine Grafikkarte nachdenkt — also muss jede Ansicht ohne Modell
|
||
vollständig benutzbar bleiben und ehrlich sagen, was fehlt. Das sind vier
|
||
verschiedene Hinweise an vier Stellen, eine zweite Suche, die immer
|
||
funktioniert, und vierzehn E2E-Tests, die genau diesen Zustand absichern.
|
||
|
||
## Phase 9 — Kassenverwaltung
|
||
|
||
**Vergleichsaufwand: 8–11 PT** — am 03.09.2026 ergänzt
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| Modelle: Konto, Kategorie, Buchung, Beitragssatz, Beitrag, Rechnung, Abschluss | 2,0 |
|
||
| Buchungsmaske mit Kategorievorschlag, Beleg als Datei | 1,5 |
|
||
| Beitragslauf, Mahnstufen, Zahlungsabgleich aus CAMT.053 oder MT940 | 2,5 |
|
||
| Rechnungen mit Nummernkreis und PDF | 1,0 |
|
||
| Kassenabschluss mit Zeitraumsperre und Prüfbericht | 1,5 |
|
||
| Auswertungen und Exporte | 1,5 |
|
||
|
||
Der Zahlungsabgleich ist der unsichere Posten: CAMT.053 ist gut spezifiziert,
|
||
MT940 dagegen je nach Bank unterschiedlich ausgelegt. Falls die Hausbank nur
|
||
MT940 liefert, kommt ein halber bis ganzer Tag für die Eigenheiten dazu.
|
||
|
||
Die Unveränderlichkeit festgeschriebener Buchungen kostet hier fast nichts —
|
||
das Muster steht seit Phase 0 mit dem Audit-Log-Trigger und lässt sich
|
||
übernehmen.
|
||
|
||
**Tatsächlich: 4:10 h** — die längste Phase bisher. Beide Schätzungen oben
|
||
haben sich bestätigt und zugleich verschoben: MT940 kostete weniger als
|
||
gedacht, weil beide Formate ohne Fremdbibliothek auskommen. Dafür war die
|
||
Unveränderlichkeit nicht das Übernehmen eines Musters, sondern drei
|
||
Trigger mit drei genau begründeten Ausnahmen — und die Ausnahmen sind der
|
||
Teil, der Zeit gekostet hat. Zwei davon sind erst im Test aufgefallen:
|
||
der Prüfbericht, der nach dem Festschreiben entsteht, und die
|
||
DSGVO-Löschung einer Wehr, die an ihren eigenen festgeschriebenen Buchungen
|
||
scheiterte.
|
||
|
||
## Phase 10 — Mandantenfähigkeit
|
||
|
||
**Vergleichsaufwand: 4–6 PT** — aus der Kandidatenliste vorgezogen, am
|
||
05.09.2026 umgesetzt.
|
||
|
||
| Baustein | PT |
|
||
|---|---:|
|
||
| `Benutzer` und `Mitgliedschaft`, datenerhaltende Umstellung | 1,5 |
|
||
| Anmeldung: Konto statt Akte, Wehrwechsel im Sitzungstoken | 1,5 |
|
||
| Kreisebene mit reinen Leserechten | 1,0 |
|
||
| Wehrwähler und Verwaltung der Installation | 1,5 |
|
||
|
||
Die Schätzung ging von „technisch bereits möglich, Oberfläche fehlt" aus —
|
||
und das war zur Hälfte falsch. Datenmodell und RLS trugen tatsächlich mehrere
|
||
Wehren, aber die Anmeldung nicht: Passwort und OIDC-Subject hingen an der
|
||
Personenakte, und die gehört genau einer Wehr. Das war kein Oberflächenthema,
|
||
sondern ein Umbau am Fundament.
|
||
|
||
**Tatsächlich: 4:20 h.** Getragen hat eine Entscheidung ganz am Anfang: die
|
||
Sitzung behält ihre Form und entsteht nur anders. Ohne sie wären 61 Dateien
|
||
mit 414 Fundstellen zu ändern gewesen; so waren es acht. Die Zeit ging
|
||
stattdessen in die Absicherung — zwei Trigger, vier CHECKs und elf
|
||
Datenbanktests — und das ist die richtige Verteilung für eine Phase, deren
|
||
einziger Zweck es ist, dass eine Wehr die andere nicht sieht.
|
||
|
||
---
|
||
|
||
## Was hier nicht drin ist
|
||
|
||
**Betrieb und Einführung.** Datenübernahme aus der Altsoftware, Schulung der
|
||
Wehr, Sicherungskonzept, Monitoring: nach Erfahrung noch einmal 15 bis 25
|
||
Prozent des Entwicklungsaufwands.
|