Mängel- & Ticket-Management mit Grundriss-Pin, KI-Klassifikation und SLA-Eskalation
§4.7 Mängel- & Ticket-Management — Grundriss-Pin, KI-Klassifikation, SLA-Eskalation
Abschnitt betitelt „§4.7 Mängel- & Ticket-Management — Grundriss-Pin, KI-Klassifikation, SLA-Eskalation“Kernaussage des Beraters: Ein Mangel ist kein Eintrag in einer Excel-Liste, sondern eine örtlich fixierte, fristgebundene, foto-dokumentierte Rechtsbehauptung. Die Mangelrüge nach §13 Abs. 5 VOB/B und die Abnahme nach §640 BGB hängen von drei Dingen ab: Wo (Raum + Punkt auf Grundriss), Was (Klassifikation + Foto) und Bis wann (SLA + Eskalation). Werkszeit liefert alle drei Achsen in einem Workflow — ohne Medienbruch, ohne Rechtstreitigkeiten über Zuordnung.
1. Ziel & Scope
Abschnitt betitelt „1. Ziel & Scope“1.1 Geschäftsziel
Abschnitt betitelt „1.1 Geschäftsziel“Mangelrüge, Mangelverfolgung und Mangelnachweis werden bei Bauvorhaben heute typischerweise mit drei bis fünf Werkzeugen parallel bearbeitet: WhatsApp-Foto an den Monteur, handschriftlicher Pin in einer ausgedruckten Grundriss-Kopie, Excel-Liste beim Bauleiter, E-Mail an den Sub, PDF-Abnahmeprotokoll am Ende. Die Folge sind systematische Zuordnungslücken (Raum verwechselt, Pin nicht übertragen), SLA-Verletzungen ohne Eskalation und Beweisprobleme bei der Abnahme.
Werkszeit konsolidiert diesen Workflow in ein fachliches Modell Mangel-Ticket, das (a) räumlich auf einem Grundriss-PDF verankert ist, (b) eine KI-gestützte Klassifikation als Vorschlag anbietet, (c) vor/nach-Fotopflicht erzwingt und (d) SLA-Fristen pro Mangelklasse automatisch eskaliert.
Kennzahlen-Versprechen (Zielwerte nach 6 Monaten Produktivbetrieb, Benchmark Gebr. Schmidt):
| KPI | Heute (ohne Werkszeit) | Ziel | Messquelle |
|---|---|---|---|
| Mangel-Erfassungszeit (Ort → Ticket offen) | 18 min | ≤ 3 min | maengel.created_at |
| Anteil Mängel mit Grundriss-Pin | 27 % | ≥ 95 % | maengel.floorplan_pin_id IS NOT NULL |
| Anteil “Behoben” mit Nachher-Foto | 41 % | 100 % | Enforced Gate, Reject unter 100 % |
| SLA-Verletzungen ohne Eskalation | 63 % | 0 % | Cron-Audit |
| Streitige Abnahmen (§12 VOB/B) | 1,8/Projekt | ≤ 0,3 | Projekt-Retro |
1.2 Fachlicher Scope
Abschnitt betitelt „1.2 Fachlicher Scope“In-Scope:
- Mangel-Ticket erfassen auf Grundriss-PDF (Pin via PDF.js + Canvas-Overlay), Pflichtfelder Raum, Titel, Kategorie, Verantwortlicher, Foto
- KI-Klassifikation (LLM-basiert) als Vorschlag für Mangel-Kategorie mit Transparenz-Hinweis nach KI-VO Art. 50
- SLA-Engine mit konfigurierbaren Fristen pro Mangelklasse (Reaktion, Zwischenstand, Behebung)
- Eskalation per Push/E-Mail an Bauleiter + Objektüberwachung bei Frist-Überschreitung
- Vorher-Foto bei Ticket-Öffnung, Nachher-Foto bei Statuswechsel auf “Behoben” (Enforced Gate)
- Mangelbericht-PDF für Abnahmeprotokoll (§12 VOB/B) mit Side-by-Side vorher/nachher
- Verknüpfung zu §4.1 Bautagebuch (Mangel-Eintrag pro Tag), §4.3 GAEB-LV (Mangel an LV-Position), §4.5 Foto-Doku
- Offline-Erfassung Mobile (PDF im Cache, Pin + Foto queued) mit Outbox-Sync
Out-of-Scope (bewusst):
- Gewährleistungs-Rückstellungen als Buchungssatz → §4 Finanzen (später)
- Mängel an Fremdgewerken mit eigener Rechnung → §4.13 Gutachter-Workflow (V3)
- 3D-BIM-Koordinaten → V4, Grundriss-PDF 2D ist V2
- Automatische Fristberechnung Verjährung §634a BGB → V3, Mahnwesen §4 Finanzen
1.3 Senior-Consultant-Note
Abschnitt betitelt „1.3 Senior-Consultant-Note“Die häufigste Fehlannahme in Bau-Apps ist, dass ein Mangel ein “Aufgabeneintrag” sei. Juristisch ist ein Mangel mit Mangelrüge nach §13 Abs. 5 VOB/B der Beginn der Gewährleistungsuhr. Wer Mängel ohne revisionssicheren Zeitstempel, ohne räumliche Verankerung und ohne Foto-Nachweis erfasst, verliert im Streitfall vor dem Landgericht. Deshalb ist das Ticket-Modell bei Werkszeit append-only (hashkettenbasiert) und jeder Status-Wechsel ist ein separater, nicht rückdatierbarer Audit-Eintrag — analog zum Buchungsjournal in §4 Finanzen.
2. Personas & User-Stories
Abschnitt betitelt „2. Personas & User-Stories“2.1 Personas
Abschnitt betitelt „2.1 Personas“- Thomas Schmidt, Bauleiter (Primär): 47 J., 18 Jahre Erfahrung, iPad auf Baustelle + Laptop im Büro, 4–6 parallele Projekte. Vergibt ca. 40 Mängel/Woche, erwartet: “Pin hin, Foto drauf, Sub anrufen automatisch.”
- Mehmet Yılmaz, Monteur SHK (Sekundär): Erhält Mangel-Push, arbeitet ab, macht Nachher-Foto, schließt Ticket. Nutzt nur Mobile, kein Desktop.
- Sandra Berger, Objektüberwachung (AG-Seite, tertiär): Kontrolliert aus AG-Perspektive, eigener schreibgeschützter Zugang, unterzeichnet am Ende das Abnahmeprotokoll.
- Polier Jürgen Kaya (sekundär): Gesamtkoordination Baustelle, ist Eskalations-Empfänger bei SLA-Überschreitung.
2.2 User-Stories
Abschnitt betitelt „2.2 User-Stories“| ID | Als | möchte ich | damit | Priorität |
|---|---|---|---|---|
| US-01 | Bauleiter Thomas | auf dem Grundriss-PDF einen Pin an die Mangelstelle setzen und ein Foto anhängen | der Mangel örtlich eindeutig und beweissicher dokumentiert ist | Must |
| US-02 | Bauleiter Thomas | eine KI-Klassifikations-Empfehlung für die Mangel-Kategorie bekommen, aber überschreiben können | ich Zeit spare, aber Verantwortung für die juristisch relevante Klassifikation behalte | Must |
| US-03 | Bauleiter Thomas | SLA-Regeln pro Mangelklasse konfigurieren (Wasserschaden = 2 h Reaktion, 24 h Behebung) | kritische Mängel priorisiert und fristgerecht behoben werden | Must |
| US-04 | Monteur Mehmet | eine Push-Benachrichtigung bekommen, wenn mir ein Mangel zugewiesen wird, mit direktem Absprungpunkt | ich nicht zwischen Apps wechseln muss | Must |
| US-05 | Monteur Mehmet | beim Statuswechsel auf “Behoben” gezwungen werden, ein Nachher-Foto zu machen | die Beweiskette lückenlos bleibt | Must |
| US-06 | System | Mängel automatisch eskalieren, wenn SLA-Frist überschritten wird | der Bauleiter/Polier nicht selbst nachhalten muss | Must |
| US-07 | Bauleiter Thomas | einen Mangelbericht-PDF für die Abnahme nach §12 VOB/B generieren, mit Vorher/Nachher-Fotos nebeneinander | ich im Abnahmetermin rechtssicher dokumentiert bin | Must |
| US-08 | Bauleiter Thomas | einen Mangel mit einer LV-Position aus §4.3 GAEB-LV verknüpfen | Kalkulations-Abweichungen bei Nacharbeit sichtbar werden | Should |
| US-09 | Objektüberwachung | als schreibgeschützter Gast die Mängelliste sehen und Kommentare hinzufügen | der AG parallel prüfen kann, ohne Schreibrechte am Bauherrenmodell | Should |
| US-10 | Monteur Mehmet | offline einen Mangel erfassen können (Baustellenkeller, kein Empfang), synchronisiert später | die Erfassungslücke verschwindet | Must |
| US-11 | Bauleiter Thomas | bei Kategorie-Wechsel durch Monteur informiert werden (KI-Vorschlag falsch) | ich die SLA-Klasse ggf. nachziehen muss | Should |
| US-12 | Polier Jürgen | bei Krankheit des Verantwortlichen automatisch als Ersatz-Empfänger einspringen (Vertretungsregel) | kein Mangel liegen bleibt | Should |
3. Funktionale Anforderungen
Abschnitt betitelt „3. Funktionale Anforderungen“3.1 Mobile (Flutter)
Abschnitt betitelt „3.1 Mobile (Flutter)“| ID | Anforderung |
|---|---|
| F-M-01 | Mangel-Erfassungs-Button global im Projekt-Kontext (FAB unten rechts, orange, 56 dp) |
| F-M-02 | Grundriss-PDF-Viewer mit Pan/Zoom (PDF.js-kompatibles Rendering, Flutter-Paket pdfx oder flutter_pdf_render), Pin-Setzen per Long-Press |
| F-M-03 | Pin-Koordinaten persistieren als {pdf_page: int, x_pct: float (0..1), y_pct: float (0..1)} → Auflösungs- und Zoom-unabhängig |
| F-M-04 | Pflichtfelder bei Ticket-Öffnung: Titel (max. 120 Zeichen), Kategorie (Pflicht, mit KI-Vorschlag), Verantwortlicher (aus Projektteam + Subs), mind. 1 Foto (Kamera oder Galerie) |
| F-M-05 | KI-Klassifikation: Post an /v1/ai/classify-defect mit Titel + Beschreibung + Foto-Thumbnail (base64, max. 256 KB), Response {category: 'elektrik', confidence: 0.87} → Chip mit Disclaimer “Vorschlag — bitte prüfen” (KI-VO Art. 50) |
| F-M-06 | Offline-Fallback: Wenn /v1/ai/classify-defect unerreichbar → keine KI-Kategorie, User wählt manuell, Hinweis “KI derzeit nicht verfügbar” |
| F-M-07 | Foto-Pflicht bei Statuswechsel auf “Behoben”: Dialog “Nachher-Foto erforderlich”, Button “Jetzt fotografieren”, Statuswechsel nur nach erfolgreichem Upload (bzw. Outbox-Queue bei Offline) |
| F-M-08 | Push-Benachrichtigung bei Zuweisung: Deeplink werkszeit://mangel/{id}, Aktionen “Öffnen” und “Angenommen” direkt im Push (Android Notification Action, iOS Notification Action) |
| F-M-09 | Offline-Modus: Grundriss-PDF wird bei Projekt-Öffnen heruntergeladen und lokal gecached (max. 50 MB pro PDF), Pins werden lokal gespeichert, Outbox-Sync bei Verbindung |
| F-M-10 | Mangel-Liste pro Projekt mit Filter: Status (offen/in Bearbeitung/behoben/abgelehnt), Kategorie, Verantwortlicher, “Nur meine Mängel” |
| F-M-11 | Kommentar-Funktion pro Ticket (Text + Foto), append-only, nicht editierbar — jeder Kommentar ist ein eigener Audit-Eintrag |
| F-M-12 | Sprachnotiz-Aufnahme (max. 2 min, AAC, 64 kbps) als alternative Beschreibungsquelle, wird serverseitig mit Whisper-kompatiblem STT transkribiert und als Text-Beschreibung vorbefüllt (Nutzer bestätigt) |
| F-M-13 | SLA-Countdown im Ticket-Detail sichtbar: “Reaktion erforderlich bis 14:30 Uhr”, rote Dringlichkeits-Pille bei < 30 min Restzeit |
3.2 Web (Tauri / Web)
Abschnitt betitelt „3.2 Web (Tauri / Web)“| ID | Anforderung |
|---|---|
| F-W-01 | Grundriss-Viewer mit PDF.js + HTML5-Canvas-Overlay; Pins als <svg><circle> mit Tenant-Farbe, Klick öffnet Ticket-Panel rechts |
| F-W-02 | Mängel-Kanban-Board: Spalten “Offen” / “In Bearbeitung” / “Warten auf Prüfung” / “Behoben” / “Abgelehnt”, Drag&Drop löst Statuswechsel mit Audit-Log |
| F-W-03 | Mängel-Listen-Ansicht mit Sortierung nach SLA-Restzeit, Bulk-Aktionen: Kategorie ändern, Verantwortlicher zuweisen, Status ändern (alle mit Audit-Begründung Pflicht) |
| F-W-04 | SLA-Konfigurator: Tabelle Mangelklasse × Frist (Reaktion/Behebung), editierbar nur durch Rolle projektleiter, Änderung greift nur für neue Mängel (Immutable History) |
| F-W-05 | Mangelbericht-PDF-Export: Auswahl “Offene Mängel” / “Alle Mängel” / “Abnahme-Bericht §12 VOB/B”, Format A4 quer, Seitenlayout: Pro Mangel eine Seite mit Pin-Screenshot, Vorher/Nachher-Foto, Metadaten, Unterschriftenfeld |
| F-W-06 | Eskalations-Dashboard: KPIs “SLA-Verletzungen heute/Woche/Monat”, Heatmap “Eskalationen pro Gewerk” |
| F-W-07 | Grundriss-Historie: Wenn ein neues Grundriss-PDF hochgeladen wird (Revision), bleiben bestehende Pins an alter Revision, neuer Pin zwingt User zu Revisionsauswahl — kein automatisches Remapping |
| F-W-08 | Rolle objektueberwachung (AG-Seite): Read-Only auf Tickets, schreibt nur Kommentare, kann signieren (§12 VOB/B Abnahmeprotokoll) |
3.3 KI-Klassifikation Backend
Abschnitt betitelt „3.3 KI-Klassifikation Backend“| ID | Anforderung |
|---|---|
| F-A-01 | LLM-basierte Klassifikation (Claude 3.5 Haiku oder GPT-4o-mini, konfigurierbar pro Tenant) mit System-Prompt „Du bist Mangel-Klassifikator. Kategorien: elektrik, sanitaer, heizung, lueftung, putz, maler, fliesen, trockenbau, bodenbelag, fenster, tueren, dach, estrich, sonstiges. Antwort in JSON mit {category, confidence, reasoning}.“ |
| F-A-02 | Input-Obergrenze: Title 120 Zeichen + Description 2000 Zeichen + 1 Foto-Thumbnail (base64, 512×512, JPEG Qualität 70, max. 256 KB). DSGVO: Foto wird NICHT persistiert im LLM-Provider (Anthropic/OpenAI “no training” API-Konfiguration, Auftragsverarbeitungsvertrag) |
| F-A-03 | Transparenz-Pflicht (KI-VO Art. 50): Jede KI-Kategorie wird im UI mit Badge “KI-Vorschlag · bitte prüfen” angezeigt, in maengel.category_source wird `‘ki-suggested’ |
| F-A-04 | Fallback: Bei LLM-Timeout (> 3 s) oder HTTP ≥ 500 → kein Vorschlag, User wählt manuell. Fehler wird im Audit-Log als ki.classify.failed geloggt, aber nicht blockierend |
| F-A-05 | Rate-Limit pro Tenant: 1000 Klassifikationen/Tag (skaliert mit Tarif), Überschreitung → 429 mit Retry-After, User wählt manuell |
| F-A-06 | Audit: ai_audit_log persistiert pro Request {tenant_id, user_id, input_hash (SHA-256), response_category, confidence, latency_ms} — kein Klartext, DSGVO-konform |
3.4 SLA-Engine
Abschnitt betitelt „3.4 SLA-Engine“| ID | Anforderung |
|---|---|
| F-S-01 | Konfigurierbare SLA-Regeln pro Mangelklasse × Projekt (Override) mit drei Zeitstempeln: reaction_deadline, intermediate_deadline (optional), resolution_deadline |
| F-S-02 | Beispiel-Defaults (gelten global, überschreibbar pro Projekt): Wasserschaden 2h/24h, Elektrik kritisch 4h/48h, Standard-Mangel 48h/14 Tage |
| F-S-03 | Deadlines berücksichtigen Werktage, Feiertage (Bundesland aus Projekt-Stammdaten), Arbeitszeit 07:00–17:00 — konfigurierbar in sla_calendar_rules |
| F-S-04 | Scheduler-Cron (jede 5 min, BullMQ) prüft überfällige Mängel → Statuswechsel auf eskaliert, Push+E-Mail an Bauleiter, Polier, Fallback-Empfänger |
| F-S-05 | Eskalations-Kette konfigurierbar: 1. Frist-Ablauf → Verantwortlicher+Bauleiter, 2. Frist+24h → Polier+Projektleiter, 3. Frist+48h → Geschäftsführung |
| F-S-06 | Vertretungsregel: Bei Abwesenheit (aus §4 Urlaubs-/Krankmeldungs-Modul) → Ersatz-Verantwortlicher automatisch. Wenn kein Vertreter gesetzt → Polier als Fallback |
| F-S-07 | SLA-Pausieren: Status warten_auf_materialanlieferung pausiert SLA (Grund Pflicht, Audit-Log); Resume bei Statuswechsel zurück |
3.5 Mangelbericht-PDF (Abnahme §12 VOB/B)
Abschnitt betitelt „3.5 Mangelbericht-PDF (Abnahme §12 VOB/B)“| ID | Anforderung |
|---|---|
| F-P-01 | PDF-Export A4 quer, Corporate-CI (Logo, Projekt-Kopfzeile, Seitenzahlen), Deckblatt mit Bauvorhaben, BV-Nr., Datum, Bauleiter, AG, Unterschriftenfeld |
| F-P-02 | Pro Mangel eine Seite: Oben Pin-Screenshot (Grundriss-Ausschnitt, 300×300 dpi), Mitte Side-by-Side Vorher/Nachher-Foto (je 800×600), unten Metadaten-Tabelle (Mangel-Nr., Kategorie, Raum, Erfasst am, Verantwortlicher, Status, Behoben am) |
| F-P-03 | Abnahme-Kontext §12 VOB/B: Deckblatt enthält Haken-Felder “Ohne Mängel abgenommen” / “Mit Vorbehalt abgenommen (siehe Mängelliste)” / “Abnahme verweigert (siehe §12 Abs. 3 VOB/B)” |
| F-P-04 | Fußzeile jeder Seite: “Erstellt mit Werkszeit am {datum}, {uhrzeit}, Hash: {sha256_first12}”, GoBD-konforme Hash-Kette aller enthaltenen Tickets |
| F-P-05 | Unterschriften-Seite (letzte Seite): Felder für AG, AN, Objektüberwachung; bei Signatur via qualifizierter elektronischer Signatur (eIDAS QES) → Hash im PDF-Metadatum DocumentSignature |
| F-P-06 | PDF/A-3 konform (Long-Term-Archivierung), eingebettete Metadaten XMP mit Projekt-UUID, Erstell-Hash, Tenant-ID |
4. UI/UX — ASCII-Wireframes
Abschnitt betitelt „4. UI/UX — ASCII-Wireframes“4.1 Mobile: Grundriss mit Pin setzen (Bauleiter)
Abschnitt betitelt „4.1 Mobile: Grundriss mit Pin setzen (Bauleiter)“┌────────────────────────────────────┐│ ← Projekt: Kita Bremen-Nord ││ ││ Grundriss · EG · Revision 3 ││ ┌──────────────────────────────┐ ││ │ │ ││ │ ░░░░░ │ ││ │ ┌─────┐ ┌──────┐ │ ││ │ │ 1.01│ │ 1.02 │ │ ││ │ │ Bad │ ● │ WC │ │ ││ │ └─────┘ ↑ └──────┘ │ ││ │ PIN hier │ ││ │ │ ││ │ ┌──────────────┐ │ ││ │ │ 1.03 Flur │ │ ││ │ └──────────────┘ │ ││ └──────────────────────────────┘ ││ [◎ Pin setzen · Long-Press] ││ ││ ──────────────────────────── ││ ●●● offener Pin, ●●● behoben ││ ││ [Mangel-Liste] [Neuer Mangel] │└────────────────────────────────────┘4.2 Mobile: Mangel-Ticket erfassen (nach Pin-Set)
Abschnitt betitelt „4.2 Mobile: Mangel-Ticket erfassen (nach Pin-Set)“┌────────────────────────────────────┐│ ← Neuer Mangel · Raum 1.01 Bad ││ ││ Titel * ││ ┌──────────────────────────────┐ ││ │ Steckdose lose, Wackelkontakt│ ││ └──────────────────────────────┘ ││ ││ Kategorie * ││ ┌──────────────────────────────┐ ││ │ ⚡ Elektrik [KI-Vorschlag]│ ││ │ » Vorschlag — bitte prüfen «│ ││ └──────────────────────────────┘ ││ [Andere Kategorie wählen ▼] ││ ││ Beschreibung ││ ┌──────────────────────────────┐ ││ │ Steckdose neben Waschbecken │ ││ │ sitzt nicht bündig, Abdeckung│ ││ │ wackelt, Kontakt unsicher. │ ││ └──────────────────────────────┘ ││ 🎙 Sprachnotiz 📷 Foto · 1/3 ││ ││ Verantwortlicher * ││ [Mehmet Yılmaz (Sub Elektro) ▼] ││ ││ Priorität ││ ( ) niedrig ( ) mittel (●) hoch ││ ││ SLA: Reaktion 4h, Behebung 48h ││ ││ [ Abbrechen ] [ Speichern ] │└────────────────────────────────────┘4.3 Mobile: Monteur erhält Push, öffnet Mangel
Abschnitt betitelt „4.3 Mobile: Monteur erhält Push, öffnet Mangel“┌────────────────────────────────────┐│ ← Mangel #2026-042-17 ││ ││ ┌─ Titel ───────────────────────┐ ││ │ Steckdose Bad lose │ ││ │ Wackelkontakt │ ││ └───────────────────────────────┘ ││ ││ ⚡ Elektrik · 🔴 Hoch · ⏱ 3h 42m ││ Projekt Kita Bremen-Nord ││ Raum 1.01 Bad ││ ││ [Grundriss zeigen ›] ││ ││ Vorher-Foto: ││ ┌──────────────────────────────┐ ││ │ [foto_vorher.jpg] │ ││ │ │ ││ └──────────────────────────────┘ ││ ││ Status: offen ││ Erfasst: 19.04.2026 08:15 · Thomas││ Frist: 19.04.2026 12:15 (4h) ││ Behebung: 21.04.2026 17:00 ││ ││ ───────────────────────────── ││ [Annehmen] [Behoben melden] ││ [Zurückweisen (Grund Pflicht)] │└────────────────────────────────────┘4.4 Mobile: Statuswechsel „Behoben“ — Nachher-Foto erzwungen
Abschnitt betitelt „4.4 Mobile: Statuswechsel „Behoben“ — Nachher-Foto erzwungen“┌────────────────────────────────────┐│ Behoben melden · #2026-042-17 ││ ││ ⚠ Nachher-Foto erforderlich ││ ││ Die Beweiskette für die ││ Mangelbehebung nach §13 VOB/B ││ verlangt ein Foto der ││ behobenen Stelle. ││ ││ Vorher-Foto (Referenz): ││ ┌──────────────────────────────┐ ││ │ [klein_vorher.jpg] │ ││ └──────────────────────────────┘ ││ ││ Nachher-Foto: ││ ┌──────────────────────────────┐ ││ │ │ ││ │ [ 📷 Jetzt fotografieren ]│ ││ │ │ ││ └──────────────────────────────┘ ││ ││ Kommentar (optional) ││ ┌──────────────────────────────┐ ││ │ Steckdose erneuert, Putz │ ││ │ ausgebessert. Ausgleich mit │ ││ │ Feinputz. │ ││ └──────────────────────────────┘ ││ ││ [ Abbrechen ] [ Fertig ] ││ (grau, bis Foto vorhanden) │└────────────────────────────────────┘4.5 Web: Mängel-Kanban mit Grundriss rechts
Abschnitt betitelt „4.5 Web: Mängel-Kanban mit Grundriss rechts“┌───────────────────────────────────────────────────────────────────────────┐│ Werkszeit > Projekt Kita Bremen-Nord > Mängel ││ 🔔 42 offen │├───────────────────────────────────────────────────────────────────────────┤│ [+ Neuer Mangel] [SLA-Config] [Bericht exportieren ▼] │├──────────────┬──────────────┬──────────────┬────────────────┬────────────┤│ OFFEN │ IN BEARBEITUNG│ ZUR PRÜFUNG │ BEHOBEN │ ABGELEHNT ││ (18) │ (12) │ (5) │ (7) │ (0) │├──────────────┼──────────────┼──────────────┼────────────────┼────────────┤│ #2026-042-17 │ #2026-042-14 │ #2026-042-11 │ #2026-042-08 │ ││ Steckdose… │ Rissbildung… │ Lüftung... │ Tür klemmt │ ││ ⚡ Elektrik │ Putz │ Lüftung │ ✓ Tueren │ ││ 🔴 4h / 48h │ ⏱ 12h │ ⏱ ok │ 16.04. ✓ │ ││ Mehmet │ Werner Y. │ Rolf K. │ Peter S. │ ││ │ │ │ │ ││ #2026-042-16 │ #2026-042-13 │ │ │ ││ Fliese ... │ Wasserfleck …│ │ │ ││ Fliesen │ 🔴 Sanitär │ │ │ ││ ⏱ 36h │ ESKALIERT! │ │ │ ││ Thorsten R. │ Uwe M. │ │ │ │├──────────────┴──────────────┴──────────────┴────────────────┴────────────┤│ ││ ┌─ Grundriss EG, Revision 3 ──────────────────────────────────────────┐ ││ │ │ ││ │ [SVG mit 42 Pins, farbcodiert nach Status] │ ││ │ │ ││ │ ● offen (18 rot) ● in Bearb. (12 orange) │ ││ │ ● zur Prüfung (5) ● behoben (7 grün) │ ││ └──────────────────────────────────────────────────────────────────────┘ │└───────────────────────────────────────────────────────────────────────────┘4.6 Web: Mangelbericht-PDF Vorschau (für §12 VOB/B Abnahme)
Abschnitt betitelt „4.6 Web: Mangelbericht-PDF Vorschau (für §12 VOB/B Abnahme)“┌───────────────────────────────────────────────────────────────────────────┐│ Mangelbericht-Generator · §12 VOB/B Abnahmeprotokoll │├───────────────────────────────────────────────────────────────────────────┤│ ││ Auswahl Mängel: ( ) Alle 42 (●) Nur behobene 7 ( ) Offene ││ Sortierung: (●) Nach Raum ( ) Nach Datum ( ) Nach Gewerk ││ Format: (●) PDF/A-3 ( ) PDF/1.7 ││ Unterschrift: [✓] Unterschriftenseite AG/AN/OU am Ende ││ Abnahme-Votum: (●) Mit Vorbehalt (Mängel siehe Liste) ││ ││ ─── Vorschau Seite 2 von 9 ─────────────────────────────────────────── ││ ││ Mangel #2026-042-08 · Tür klemmt ││ Projekt: Kita Bremen-Nord · Raum 1.12 Gruppenraum ││ ││ ┌───Grundriss-Ausschnitt────┐ ││ │ [Pin auf Tür markiert]│ ││ └────────────────────────────┘ ││ ││ VORHER (16.04.2026 09:22) NACHHER (16.04.2026 16:44) ││ ┌─────────────────┐ ┌─────────────────┐ ││ │ [Tür klemmt] │ │ [Tür o.B.] │ ││ │ │ │ │ ││ └─────────────────┘ └─────────────────┘ ││ ││ Kategorie: Türen · Priorität: mittel · Erfasst: Thomas Schmidt ││ Verantwortlich: Peter S. (Sub Trockenbau) ││ Behoben am: 16.04.2026 16:44 durch Peter S. ││ Kommentar: Scharniere nachgezogen, Tür gehobelt 3mm. ││ ││ SLA: Reaktion 4h (erfolgt 09:45, ok) · Behebung 48h (erfolgt 7h 22m, ok)││ ││ ────────────────────────────────────────────────────────────────────── ││ Erstellt mit Werkszeit · 19.04.2026 14:38 · Hash: a7f3b9… ││ ││ [Zurück] [Seite 1 ‹ 2 › 3 ... 9] [PDF generieren] │└───────────────────────────────────────────────────────────────────────────┘5. Datenmodell (Drizzle, Postgres 17)
Abschnitt betitelt „5. Datenmodell (Drizzle, Postgres 17)“5.1 Tabellen-Übersicht
Abschnitt betitelt „5.1 Tabellen-Übersicht“import { pgTable, uuid, text, varchar, timestamp, integer, real, boolean, jsonb, pgEnum, index, primaryKey } from 'drizzle-orm/pg-core';import { tenantsTable, projectsTable, usersTable } from './core';import { lvPositionsTable } from './gaeb';
// ──────────────────────────────────────────────────────────────// Enums// ──────────────────────────────────────────────────────────────export const mangelStatusEnum = pgEnum('mangel_status', [ 'offen', // neu erfasst 'angenommen', // von Verantwortlichem angenommen 'in_bearbeitung', 'warten_auf_material', // SLA pausiert 'zur_pruefung', // behoben, wartet auf Abnahme durch Bauleiter 'behoben', // abgenommen (Nachher-Foto vorhanden) 'abgelehnt', // vom Verantwortlichen abgelehnt (Grund Pflicht) 'eskaliert', // SLA überschritten, automatisch gesetzt]);
export const mangelKategorieEnum = pgEnum('mangel_kategorie', [ 'elektrik', 'sanitaer', 'heizung', 'lueftung', 'putz', 'maler', 'fliesen', 'trockenbau', 'bodenbelag', 'fenster', 'tueren', 'dach', 'estrich', 'metallbau', 'beton', 'sonstiges',]);
export const kategorieSourceEnum = pgEnum('kategorie_source', [ 'user-manual', // Nutzer hat ohne KI-Vorschlag gewählt 'ki-suggested', // KI-Vorschlag angenommen ohne Änderung 'user-confirmed', // KI-Vorschlag bestätigt (identisch) 'user-overridden', // Nutzer hat KI-Vorschlag überschrieben]);
// ──────────────────────────────────────────────────────────────// Hauptentität: Mangel-Ticket (append-only Status-History separat)// ──────────────────────────────────────────────────────────────export const maengelTable = pgTable('maengel', { id: uuid('id').defaultRandom().primaryKey(), tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id), projectId: uuid('project_id').notNull().references(() => projectsTable.id),
// Lokale Mangel-Nr. pro Projekt, menschenlesbar mangelNr: varchar('mangel_nr', { length: 32 }).notNull(), // z.B. 2026-042-17
titel: varchar('titel', { length: 120 }).notNull(), beschreibung: text('beschreibung'), sprachnotizUrl: text('sprachnotiz_url'), // S3-URL, AAC sprachnotizTranskript: text('sprachnotiz_transkript'),
kategorie: mangelKategorieEnum('kategorie').notNull(), kategorieSource: kategorieSourceEnum('kategorie_source').notNull(), kiConfidence: real('ki_confidence'), // 0..1, nur wenn kategorieSource = ki-* kiModel: varchar('ki_model', { length: 64 }), // z.B. 'claude-3-5-haiku-20241022'
prioritaet: varchar('prioritaet', { length: 16 }).notNull().default('mittel'), // niedrig/mittel/hoch/kritisch
// Grundriss-Pin (nullable, falls Raum ohne PDF) floorplanPdfId: uuid('floorplan_pdf_id'), // FK auf documents floorplanPage: integer('floorplan_page'), // 1-basiert pinXPct: real('pin_x_pct'), // 0..1 pinYPct: real('pin_y_pct'), // 0..1 raumBezeichnung: varchar('raum_bezeichnung', { length: 64 }), // z.B. "1.01 Bad"
// Verknüpfungen lvPositionId: uuid('lv_position_id').references(() => lvPositionsTable.id),
// Verantwortlichkeit verantwortlicherId: uuid('verantwortlicher_id').notNull() .references(() => usersTable.id), erfasstDurchId: uuid('erfasst_durch_id').notNull() .references(() => usersTable.id),
// SLA slaReaktionsFrist: timestamp('sla_reaktions_frist', { withTimezone: true }).notNull(), slaBehebungsFrist: timestamp('sla_behebungs_frist', { withTimezone: true }).notNull(), slaPausiertVon: timestamp('sla_pausiert_von', { withTimezone: true }), slaPausiertBis: timestamp('sla_pausiert_bis', { withTimezone: true }), slaPausierungsgrund: text('sla_pausierungsgrund'),
// Aktueller Status (denormalisiert, Source-of-Truth ist status_history) status: mangelStatusEnum('status').notNull().default('offen'),
// Zeitstempel createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), behobenAt: timestamp('behoben_at', { withTimezone: true }),
// Immutability / Audit hashPrev: varchar('hash_prev', { length: 64 }), hashSelf: varchar('hash_self', { length: 64 }).notNull(), // SHA-256 über alle Felder + hashPrev
}, (t) => ({ idxTenantProject: index('idx_maengel_tenant_project').on(t.tenantId, t.projectId), idxStatus: index('idx_maengel_status').on(t.tenantId, t.status), idxVerantwortlicher: index('idx_maengel_verantw').on(t.verantwortlicherId), idxSla: index('idx_maengel_sla').on(t.slaBehebungsFrist, t.status), uniqMangelNr: index('uniq_maengel_nr').on(t.projectId, t.mangelNr),}));
// ──────────────────────────────────────────────────────────────// Status-Historie (append-only, hash-chained)// ──────────────────────────────────────────────────────────────export const mangelStatusHistoryTable = pgTable('mangel_status_history', { id: uuid('id').defaultRandom().primaryKey(), tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id), mangelId: uuid('mangel_id').notNull().references(() => maengelTable.id), statusVon: mangelStatusEnum('status_von'), statusZu: mangelStatusEnum('status_zu').notNull(), begruendung: text('begruendung'), // Pflicht bei Ablehnung nachherFotoId: uuid('nachher_foto_id'), // FK documents, Pflicht bei Status 'behoben' aenderungDurchId: uuid('aenderung_durch_id').notNull().references(() => usersTable.id), aenderungZeitpunkt: timestamp('aenderung_zeitpunkt', { withTimezone: true }).notNull().defaultNow(), hashPrev: varchar('hash_prev', { length: 64 }), hashSelf: varchar('hash_self', { length: 64 }).notNull(),}, (t) => ({ idxMangel: index('idx_mangel_hist_mangel').on(t.mangelId, t.aenderungZeitpunkt),}));
// ──────────────────────────────────────────────────────────────// Kommentare (append-only)// ──────────────────────────────────────────────────────────────export const mangelKommentareTable = pgTable('mangel_kommentare', { id: uuid('id').defaultRandom().primaryKey(), tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id), mangelId: uuid('mangel_id').notNull().references(() => maengelTable.id), autorId: uuid('autor_id').notNull().references(() => usersTable.id), text: text('text').notNull(), fotoId: uuid('foto_id'), // FK documents createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), hashPrev: varchar('hash_prev', { length: 64 }), hashSelf: varchar('hash_self', { length: 64 }).notNull(),});
// ──────────────────────────────────────────────────────────────// Fotos (vorher + nachher, mit 1:n Verknüpfung zu Mangel)// ──────────────────────────────────────────────────────────────export const mangelFotosTable = pgTable('mangel_fotos', { id: uuid('id').defaultRandom().primaryKey(), tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id), mangelId: uuid('mangel_id').notNull().references(() => maengelTable.id), rolle: varchar('rolle', { length: 16 }).notNull(), // vorher | nachher | kommentar documentId: uuid('document_id').notNull(), // FK documents exifAufnahmeZeit: timestamp('exif_aufnahme_zeit', { withTimezone: true }), exifLatitude: real('exif_latitude'), exifLongitude: real('exif_longitude'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),});
// ──────────────────────────────────────────────────────────────// SLA-Regelwerk// ──────────────────────────────────────────────────────────────export const slaRulesTable = pgTable('sla_rules', { id: uuid('id').defaultRandom().primaryKey(), tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id), projectId: uuid('project_id').references(() => projectsTable.id), // null = tenant-default kategorie: mangelKategorieEnum('kategorie').notNull(), prioritaet: varchar('prioritaet', { length: 16 }).notNull(), reaktionsMinuten: integer('reaktions_minuten').notNull(), // 120 = 2h behebungsMinuten: integer('behebungs_minuten').notNull(), // 1440 = 24h zwischenstandMinuten: integer('zwischenstand_minuten'), eskalationsKette: jsonb('eskalations_kette').notNull(), // [{stufe:1, offset_min:0, empfaenger_rollen:['bauleiter']}] aktiv: boolean('aktiv').notNull().default(true), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),}, (t) => ({ uniqRule: index('uniq_sla_rule').on(t.tenantId, t.projectId, t.kategorie, t.prioritaet),}));
// ──────────────────────────────────────────────────────────────// Eskalations-Audit// ──────────────────────────────────────────────────────────────export const slaEskalationenTable = pgTable('sla_eskalationen', { id: uuid('id').defaultRandom().primaryKey(), tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id), mangelId: uuid('mangel_id').notNull().references(() => maengelTable.id), stufe: integer('stufe').notNull(), // 1, 2, 3 benachrichtigteUserIds: jsonb('benachrichtigte_user_ids').notNull(), // uuid[] ausgeloestAm: timestamp('ausgeloest_am', { withTimezone: true }).notNull().defaultNow(), quittungAm: timestamp('quittung_am', { withTimezone: true }), quittungUserId: uuid('quittung_user_id').references(() => usersTable.id),});
// ──────────────────────────────────────────────────────────────// KI-Audit (DSGVO-konform, nur Hashes)// ──────────────────────────────────────────────────────────────export const kiAuditLogTable = pgTable('ki_audit_log', { id: uuid('id').defaultRandom().primaryKey(), tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id), userId: uuid('user_id').notNull().references(() => usersTable.id), zweck: varchar('zweck', { length: 64 }).notNull(), // 'classify-defect' modell: varchar('modell', { length: 64 }).notNull(), inputHash: varchar('input_hash', { length: 64 }).notNull(), // SHA-256 responseKategorie: varchar('response_kategorie', { length: 32 }), confidence: real('confidence'), latenzMs: integer('latenz_ms').notNull(), erfolgreich: boolean('erfolgreich').notNull(), fehlerCode: varchar('fehler_code', { length: 32 }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),});5.2 Row-Level Security (RLS)
Abschnitt betitelt „5.2 Row-Level Security (RLS)“-- RLS enablealter table maengel enable row level security;alter table mangel_status_history enable row level security;alter table mangel_kommentare enable row level security;alter table mangel_fotos enable row level security;alter table sla_rules enable row level security;alter table sla_eskalationen enable row level security;alter table ki_audit_log enable row level security;
-- Tenant-Isolationcreate policy maengel_tenant_isolation on maengel using (tenant_id = current_setting('app.tenant_id')::uuid);
-- Scope: Monteur nur eigene + zugewiesene, Bauleiter alle im Projektcreate policy maengel_scope_select on maengel for select using ( tenant_id = current_setting('app.tenant_id')::uuid and ( current_setting('app.role') in ('admin','bauleiter','projektleiter','objektueberwachung') or verantwortlicher_id = current_setting('app.user_id')::uuid or erfasst_durch_id = current_setting('app.user_id')::uuid ) );
-- Append-only: KEIN UPDATE/DELETE auf Historiecreate policy mangel_history_readonly on mangel_status_history for update using (false);create policy mangel_history_nodel on mangel_status_history for delete using (false);
-- Objektüberwachung: Read-Only auf Tickets, Schreiben nur auf Kommentarecreate policy ou_no_mangel_write on maengel for insert with check (current_setting('app.role') <> 'objektueberwachung');create policy ou_no_mangel_update on maengel for update using (current_setting('app.role') <> 'objektueberwachung');5.3 Migrations-Reihenfolge
Abschnitt betitelt „5.3 Migrations-Reihenfolge“2026_04_19_100000_create_mangel_enums.sql2026_04_19_100100_create_maengel_table.sql2026_04_19_100200_create_mangel_history_fotos.sql2026_04_19_100300_create_sla_rules_eskalationen.sql2026_04_19_100400_create_ki_audit_log.sql2026_04_19_100500_enable_rls_maengel.sql2026_04_19_100600_seed_default_sla_rules.sql
6. API-Kontrakte (Hono, /v1)
Abschnitt betitelt „6. API-Kontrakte (Hono, /v1)“| Methode | Pfad | Zweck | Scope |
|---|---|---|---|
| POST | /v1/maengel |
Neuen Mangel anlegen | write |
| GET | /v1/maengel?projectId=…&status=… |
Liste mit Filter | read |
| GET | /v1/maengel/:id |
Einzelner Mangel mit voller Historie | read |
| PATCH | /v1/maengel/:id |
Bearbeiten (nur beschreibung, kategorie, verantwortlicher) | write |
| POST | /v1/maengel/:id/status |
Statuswechsel mit optional nachher_foto_id | write |
| POST | /v1/maengel/:id/kommentare |
Kommentar hinzufügen | write |
| POST | /v1/maengel/:id/fotos |
Foto uploaden (multipart) | write |
| POST | /v1/ai/classify-defect |
KI-Klassifikation anfordern | write |
| POST | /v1/sla/rules |
SLA-Regel anlegen/ändern | admin |
| GET | /v1/sla/overdue?projectId=… |
Überfällige Mängel (für Dashboard + Cron) | read |
| POST | /v1/sla/eskalieren/:mangelId |
Manuelle Eskalation auslösen | bauleiter+ |
| POST | /v1/maengel/:id/sla/pause |
SLA pausieren (Grund Pflicht) | bauleiter+ |
| POST | /v1/maengel/:id/sla/resume |
SLA fortsetzen | bauleiter+ |
| POST | /v1/reports/mangelbericht |
PDF-Bericht generieren (§12 VOB/B), async + SSE-Progress | bauleiter+ |
Beispiel-Schema POST /v1/maengel:
{ "projectId": "01924f3a-7b2e-7c01-a123-...", "titel": "Steckdose lose, Wackelkontakt", "beschreibung": "Steckdose neben Waschbecken sitzt nicht bündig.", "kategorie": "elektrik", "kategorieSource": "user-overridden", "kiConfidence": 0.87, "kiModel": "claude-3-5-haiku-20241022", "prioritaet": "hoch", "floorplanPdfId": "01924f3a-…", "floorplanPage": 1, "pinXPct": 0.342, "pinYPct": 0.617, "raumBezeichnung": "1.01 Bad", "verantwortlicherId": "01924f3a-…", "vorherFotoId": "01924f3a-…", "lvPositionId": null, "idempotencyKey": "01924f3a-7b2e-7c01-a123-pin-1713513300"}Beispiel-Response POST /v1/ai/classify-defect:
{ "category": "elektrik", "confidence": 0.87, "reasoning": "Schlüsselwörter 'Steckdose', 'Wackelkontakt' deuten stark auf Elektrik-Kategorie.", "model": "claude-3-5-haiku-20241022", "latencyMs": 842, "auditId": "01924f3a-…"}7. Gherkin — Happy Path + 3 Edge Cases
Abschnitt betitelt „7. Gherkin — Happy Path + 3 Edge Cases“7.1 Happy Path: Mangel erfassen → beheben → Bericht
Abschnitt betitelt „7.1 Happy Path: Mangel erfassen → beheben → Bericht“Feature: Mangel-Ticket mit Grundriss-Pin und SLA
Scenario: Bauleiter erfasst Mangel, Monteur behebt, Bericht wird erzeugt Given Thomas ist angemeldet als Bauleiter im Projekt "Kita Bremen-Nord" And Mehmet ist angemeldet als Monteur und im Projektteam And das Grundriss-PDF "EG_Revision_3.pdf" ist hochgeladen And die SLA-Regel "Elektrik/hoch" lautet "Reaktion 4h, Behebung 48h"
When Thomas öffnet den Grundriss, Long-Presst auf Raum 1.01 Bad, Pin (0.342, 0.617) And Thomas gibt ein: Titel "Steckdose lose, Wackelkontakt", Beschreibung "…" And Thomas erhält KI-Vorschlag "elektrik" mit confidence 0.87 And Thomas bestätigt "elektrik" unverändert And Thomas nimmt ein Foto der Steckdose auf, weist Mehmet zu, speichert
Then wird ein Ticket #2026-042-17 angelegt mit Status "offen" And SLA-Reaktions-Frist ist auf jetzt+4h (unter Berücksichtigung Feiertage 07-17 Uhr) gesetzt And Mehmet erhält eine Push-Benachrichtigung mit Deeplink
When Mehmet öffnet den Deeplink und klickt "Annehmen" Then Status wechselt auf "angenommen", Reaktions-SLA ist erfüllt, Audit-Eintrag
When Mehmet tauscht die Steckdose, klickt "Behoben melden" Then erscheint Dialog "Nachher-Foto erforderlich", Button "Jetzt fotografieren" And Mehmet nimmt Nachher-Foto auf, klickt "Fertig" Then wechselt Status auf "zur_pruefung", Thomas erhält Push
When Thomas akzeptiert die Behebung Then wechselt Status auf "behoben", behobenAt wird gesetzt
When Thomas exportiert Mangelbericht §12 VOB/B Then erhält er ein PDF/A-3 mit einem Abschnitt für #2026-042-17 And die Seite enthält Pin-Screenshot, Side-by-Side Vorher/Nachher, Metadaten, Hash-Fußzeile7.2 Edge Case 1 — Foto-Pin auf gelöschtem Grundriss
Abschnitt betitelt „7.2 Edge Case 1 — Foto-Pin auf gelöschtem Grundriss“ Scenario: Grundriss-PDF wurde durch Revision ersetzt, Pin verweist auf Vorversion Given Mangel #2026-042-17 zeigt auf floorplan_pdf_id = "EG_Rev3.pdf" And der Architekt lädt "EG_Rev4.pdf" hoch mit neuer floorplan_pdf_id And das System markiert Rev3 als "archiviert" (kein Hard-Delete)
When Thomas öffnet den Mangel #2026-042-17 im Web Then wird im Pin-Viewer ein gelber Info-Banner angezeigt: """ Dieser Mangel verweist auf Grundriss-Revision 3 (archiviert am 19.04.2026). Aktuelle Revision: 4. Pin wurde NICHT automatisch übertragen — räumliche Zuordnung würde verfälscht. """ And ein Button "Auf Revision 4 umpinnen" erlaubt manuelles Remapping mit Audit-Eintrag And der Bericht-Export zeigt beide Revisionen, wenn Mängel beider Revisionen enthalten sind
Scenario: Hard-Delete des Grundriss-PDF durch Admin Given das Grundriss-PDF wurde versehentlich hard-deleted (Admin-Override) When Thomas öffnet Mangel #2026-042-17 Then erscheint roter Error-Banner "Grundriss nicht mehr verfügbar — räumliche Zuordnung verloren. Mangel-Daten (Text, Foto, Raum-Bezeichnung) bleiben erhalten." And im Bericht-Export wird statt Pin-Screenshot der Text "Grundriss nicht mehr verfügbar" eingebettet7.3 Edge Case 2 — KI-Klassifikation falsch, Nutzer überschreibt
Abschnitt betitelt „7.3 Edge Case 2 — KI-Klassifikation falsch, Nutzer überschreibt“ Scenario: KI schlägt falsche Kategorie vor, Nutzer korrigiert, Audit greift Given Thomas erfasst Mangel "Boden wackelt beim Treten" And die KI schlägt Kategorie "bodenbelag" mit confidence 0.62 vor When Thomas öffnet das Kategorie-Dropdown und wählt "estrich" Then wird kategorieSource = "user-overridden" gesetzt And kiConfidence = 0.62 bleibt gespeichert And im Audit-Log ki_audit_log ist der Request dokumentiert mit responseKategorie="bodenbelag"
When Thomas speichert Then ist im Mangel-Detail ein kleiner Info-Hinweis sichtbar: """ KI-Vorschlag war "bodenbelag" (62 % Konfidenz) — überschrieben durch Thomas Schmidt am 19.04.2026 um 14:23. """
Scenario: Monteur wechselt Kategorie nachträglich (z.B. von 'putz' auf 'sanitaer') Given Mangel #2026-042-17 hat Kategorie "putz" mit SLA "Standard 48h/14d" When Mehmet öffnet den Mangel, erkennt Wasserschaden dahinter, wechselt Kategorie auf "sanitaer" Then wird ein neuer SLA "Sanitär 4h/48h" berechnet — aber NICHT rückwirkend And die alte Frist bleibt als historischer Datensatz in mangel_status_history And Thomas erhält Push "Kategorie geändert von 'putz' auf 'sanitaer', SLA neu berechnet"7.4 Edge Case 3 — SLA überschritten + Verantwortlicher krank
Abschnitt betitelt „7.4 Edge Case 3 — SLA überschritten + Verantwortlicher krank“ Scenario: Verantwortlicher ist krankgemeldet, Eskalation greift Vertretungsregel Given Mangel #2026-042-22 ist zugewiesen an Uwe M. (Sub Sanitär) And SLA-Behebungs-Frist ist 19.04.2026 12:00 Uhr And Uwe M. ist ab 18.04.2026 krankgemeldet (Status im §4 Abwesenheitsmodul) And Uwe M. hat Vertreter Heinz K. hinterlegt
When die Uhr 12:00 erreicht und der Cron-Job läuft 12:05 Then wechselt Status auf "eskaliert" And Stufe-1-Eskalation benachrichtigt: Heinz K. (Vertreter), Thomas (Bauleiter) And Push + E-Mail gehen raus mit Betreff "SLA-Überschreitung: Mangel #2026-042-22" And ein Eintrag in sla_eskalationen wird erzeugt
When 24h später die Frist+24h erreicht wird und kein Statuswechsel erfolgte Then Stufe-2-Eskalation: Jürgen K. (Polier), Projektleiter
When 48h später immer noch kein Statuswechsel Then Stufe-3-Eskalation: Geschäftsführung And im Kanban erscheint der Mangel in der roten "ESKALIERT"-Spalte mit Eskalations-Badge
Scenario: Vertreter nicht gesetzt, Fallback auf Polier Given Uwe M. ist krank, kein Vertreter gesetzt When SLA-Frist überschritten wird Then Eskalation geht an Polier Jürgen K. (Fallback-Konfiguration) And Thomas erhält zusätzliche Notification "Vertreter für Uwe M. fehlt — Fallback Polier"
Scenario: SLA-Berechnung berücksichtigt Feiertag Given Mangel erfasst am 30.04.2026 (Mittwoch) 16:00 Uhr, SLA "Reaktion 4h Arbeitszeit 07-17 Uhr" And 01.05.2026 ist Feiertag (Tag der Arbeit, NRW) When SLA-Frist berechnet wird Then ist sla_reaktions_frist = 04.05.2026 09:00 Uhr (1h am 30.04. genutzt von 16:00-17:00, Rest 3h am nächsten Werktag ab 07:00) And NICHT 30.04.2026 20:00 oder 01.05.2026 11:008. Edge Cases & Randbedingungen
Abschnitt betitelt „8. Edge Cases & Randbedingungen“| # | Fall |
|---|---|
| E1 | Pin wird offline gesetzt, Grundriss-PDF wurde inzwischen online durch Revision ersetzt → Konflikt-Ticket “Pin auf veralteter Revision”, Bauleiter entscheidet Remapping |
| E2 | Foto-Upload schlägt fehl wegen großer Dateigröße > 10 MB → Client komprimiert automatisch auf 1920×1440, Qualität 80, EXIF-erhaltend |
| E3 | KI-Provider liefert Kategorie, die nicht im Enum ist (“fenster-abdichtung”) → Mapping auf nächstliegende (“fenster”), im Audit dokumentiert |
| E4 | Mangel wird versehentlich doppelt erfasst durch zwei Personen gleichzeitig (z.B. Thomas + Sandra) → Idempotency-Key {user_id}-{project_id}-{pin_x}-{pin_y}-{date_hour} dedupliziert |
| E5 | SLA-Frist-Berechnung Zeitzone: Projekt in DE, Nutzer reist nach NYC, Gerät UTC-4 → Server rechnet immer in Projekt-Timezone (Europa/Berlin) |
| E6 | Verantwortlicher wird aus Projektteam entfernt, bevor Mangel behoben ist → Mangel bleibt zugewiesen (Audit), aber Push scheitert → Bauleiter bekommt “Verantwortlicher nicht mehr im Team”-Hinweis |
| E7 | Kommentar enthält DSGVO-relevanten Namen eines Dritten (“Nachbar Müller beschwert sich”) → Kein Auto-Redact, aber Manuell-Löschen via Kommentar-Überschreibung (neuer Kommentar “redacted”) |
| E8 | Grundriss-PDF ist 50 MB+ (Plotter-Export) → Server rendert serverseitig zu WebP-Tiles (Tile-Pyramid), Mobile lädt nur sichtbare Tiles |
| E9 | Nutzer fotografiert Vorher-Foto mit Kamera-Blitz nachts, Nachher-Foto bei Tag → kein Auto-Rejection, aber Bericht-Export enthält EXIF-Zeitstempel pro Foto |
| E10 | Push-Benachrichtigung kommt nicht an (z.B. Android-Battery-Saver) → Fallback E-Mail nach 30 min ohne Lese-Bestätigung der Push |
| E11 | Mangelbericht-PDF > 200 Seiten → Server-Render async mit SSE-Progress, Timeout 10 min, Fallback-Download per E-Mail-Link |
| E12 | Nutzer versucht Statuswechsel auf “behoben” ohne Nachher-Foto (Frontend-Bug) → Server-seitige Validierung blockiert mit 422 “Nachher-Foto fehlt” |
| E13 | Objektüberwachung versucht Mangel zu bearbeiten → RLS-Policy blockt mit 403 “Rolle objektueberwachung hat keine Schreibrechte auf Mängel” |
| E14 | Sprachnotiz-Transkription schlägt fehl (Hintergrundlärm) → Transkript leer, User bearbeitet manuell |
| E15 | Bauleiter löscht Mangel “versehentlich erfasst” → Soft-Delete mit Grund-Pflicht, Audit, aber KEIN physisches Löschen (GoBD) |
9. Compliance-Mapping
Abschnitt betitelt „9. Compliance-Mapping“9.1 GoBD (AO §147, §238-§239 HGB)
Abschnitt betitelt „9.1 GoBD (AO §147, §238-§239 HGB)“| Anforderung | Umsetzung |
|---|---|
| Unveränderbarkeit | Tabelle mangel_status_history append-only, RLS-Policy blockt UPDATE/DELETE |
| Nachvollziehbarkeit | Hash-Kette hashPrev/hashSelf (SHA-256) über alle Status-Änderungen |
| Vollständigkeit | Jeder Statuswechsel dokumentiert, auch „Abgelehnt“ mit Begründung |
| Zeitgerechte Erfassung | createdAt = Server-Zeit (UTC), Mobile-Offline-Erfassung preserves client_created_at mit sync_offset_seconds |
| Aufbewahrungsfrist 10 Jahre | S3 Object Lock Compliance-Mode (legal-hold), AWS Backup → Glacier Deep Archive nach 1 Jahr |
9.2 DSGVO
Abschnitt betitelt „9.2 DSGVO“| Artikel | Umsetzung |
|---|---|
| Art. 5 (Zweckbindung) | Mängeldaten nur für Baudokumentation/VOB-Abnahme verwendet, keine Zweckfremdnutzung |
| Art. 6 (Rechtsgrundlage) | Vertrag (AG-AN), berechtigtes Interesse (Gewährleistung) |
| Art. 9 (besondere Kat.) | Mängelfotos dürfen Personen zeigen (z.B. Monteur in Aktion) → Foto-Policy: keine Gesichter auf Bericht-Fotos, Auto-Blur optional |
| Art. 17 (Löschung) | Nach Ablauf Gewährleistung (5 J. §634a BGB) + GoBD-Frist (10 J.) → Soft-Delete + Anonymisierung (Nutzer-Bezug entfernen) |
| Art. 20 (Portabilität) | Export Mängel als JSON + ZIP-Fotos pro Projekt |
| Art. 30 (VVT) | Eintrag “Mängel-Verwaltung” im Verzeichnis, Empfänger: Subunternehmer (Auftragsverarbeitung) |
| Art. 35 (DSFA) | DSFA erforderlich für KI-Klassifikation → siehe §14 |
9.3 KI-VO (EU AI Act) Art. 50
Abschnitt betitelt „9.3 KI-VO (EU AI Act) Art. 50“| Anforderung | Umsetzung |
|---|---|
| Transparenz-Pflicht gegenüber Nutzer | Badge “KI-Vorschlag — bitte prüfen” immer sichtbar, kategorieSource im Datenmodell |
| Kennzeichnung in Ausgabe | Mangelbericht-PDF kennzeichnet Kategorie mit “(KI-Vorschlag akzeptiert)” / “(manuell gewählt)” in Fußnote |
| Nutzerentscheidung dokumentiert | kategorieSource enum persistiert Entscheidungsweg |
| Logging (Art. 12) | ki_audit_log dokumentiert jeden Request (nur Hashes, DSGVO-konform) |
9.4 BGB §634 (Mängelrechte) / §640 (Abnahme) / VOB/B §13-§14
Abschnitt betitelt „9.4 BGB §634 (Mängelrechte) / §640 (Abnahme) / VOB/B §13-§14“| Norm | Umsetzung |
|---|---|
| §634 BGB | Mangel als Anspruchsgrundlage für Nacherfüllung, Rücktritt, Minderung, SE → Mangel-Ticket ist Beweismittel |
| §640 BGB Abnahme | Mangelbericht-PDF kann als Grundlage für Abnahmeprotokoll dienen (Votum “Mit Vorbehalt”) |
| §13 Abs. 5 VOB/B | Mangelrüge durch Status “offen” mit Zeitstempel = Beginn Gewährleistungsuhr, Audit-Log revisionssicher |
| §14 VOB/B | Abrechnung (später in §4 Finanzen) kann Mängel-Rückstellungen berücksichtigen via lvPositionId-Verknüpfung |
| §12 VOB/B Abnahme | Mangelbericht-PDF als Anhang zum Abnahmeprotokoll, Unterschriftenseite enthält Abnahme-Votum-Checkboxen |
9.5 BaustellV / HSSE
Abschnitt betitelt „9.5 BaustellV / HSSE“| Anforderung | Umsetzung |
|---|---|
| Sicherheitsrelevante Mängel | Kategorie “elektrik” + Priorität “kritisch” → automatischer Stop-Work-Workflow in §4.5 HSSE |
| Dokumentation SiGeKo-relevant | Export in SiGeKo-PDF möglich (separat in §4.5) |
10. Test-Matrix
Abschnitt betitelt „10. Test-Matrix“10.1 Unit-Tests (Vitest/Dart-Test)
Abschnitt betitelt „10.1 Unit-Tests (Vitest/Dart-Test)“| Code-Bereich | Test-Szenario |
|---|---|
slaEngine.calculateDeadline |
Feiertag NRW 01.05., Arbeitszeit 07-17 Uhr, Frist 4h startend 30.04.2026 16:00 → 04.05.2026 09:00 Uhr |
hashChain.verifyStatusChain |
Sequenz von 5 Status-Änderungen, Manipulation von Eintrag 3 erkannt |
pinCoordinateTransform |
PDF-Page-Space 0.342/0.617 → SVG-Pixel bei Viewport 1920×1080, zoom 1.5 |
kiFallback |
LLM-Timeout 3s → kein Vorschlag, manueller Workflow, Audit-Log enthält fehlerCode='timeout' |
mangelNrGenerator |
Format YYYY-{projectShortId}-{seq} pro Projekt, Race-Condition-frei (DB-Sequence + Locking) |
10.2 Integration (Playwright/Patrol)
Abschnitt betitelt „10.2 Integration (Playwright/Patrol)“| Szenario |
|---|
| Mobile: PDF laden → Pin setzen → Ticket anlegen → offline gehen → Status ändern → online gehen → Sync erfolgt |
| Web: Kanban-Drag&Drop von “offen” nach “behoben” löst Nachher-Foto-Modal aus, Ablehnung → Status unverändert |
| E2E: Bauleiter erstellt 20 Mängel, alle mit KI-Vorschlag, davon 5 überschrieben → DB-Zählung kategorie_source = “user-overridden” = 5 |
| Eskalations-Test: SLA-Frist manuell auf vergangenen Zeitpunkt gesetzt, Cron läuft → Eskalation Stufe 1 + Push + E-Mail erfolgt |
| Bericht-Export: 50 Mängel, davon 30 behoben → PDF/A-3 hat 30 Seiten + Deckblatt + Unterschriftenseite = 32 Seiten, alle mit vorher/nachher |
10.3 Performance-SLA
Abschnitt betitelt „10.3 Performance-SLA“| Metrik | Zielwert |
|---|---|
| Mangel-Anlage (POST) p95 | ≤ 400 ms (ohne Foto-Upload) |
| KI-Klassifikation p95 | ≤ 2.5 s (Timeout 3 s, Fallback manuell) |
| Grundriss-PDF-Load im Mobile Cache | ≤ 1 s (5 MB PDF, lokal) |
| Mangelbericht-PDF-Render (100 Mängel) | ≤ 45 s async mit SSE-Progress |
| SLA-Cron pro 10.000 aktive Mängel | ≤ 8 s |
| Kanban-Board-Load im Web (500 Mängel) | ≤ 1.5 s p95 |
10.4 DoR/DoD-Gates (aus DOR-DOD.md)
Abschnitt betitelt „10.4 DoR/DoD-Gates (aus DOR-DOD.md)“- DoR §1.1: Akzeptanzkriterien aus US-01 bis US-12 Gherkin-verifiziert
- DoR §1.2: Compliance-Matrix vollständig (§9) — GoBD, DSGVO, KI-VO, VOB/B
- DoR §1.3: UI-Wireframes + Design-Token-Mapping (wz-pill, wz-btn, wz-card)
- DoD §2.1: Unit-Test-Coverage ≥ 80 % auf Modul
maengel - DoD §2.4: RLS-Policies getestet (5 Rollen × 4 Operationen = 20 Fälle)
- DoD §2.7: DSFA-Dokument zu KI-Klassifikation abgeschlossen (siehe §14)
11. Non-Goals & bewusste Auslassungen
Abschnitt betitelt „11. Non-Goals & bewusste Auslassungen“- BIM-/3D-Koordinaten: V4+. 2D-Grundriss-PDF ist V2, ausreichend für 95 % der Baustellen.
- Automatische Mangelbehebungs-Kalkulation: Keine automatische Rückstellungs-Buchung in §4 Finanzen. Manuelle Ableitung durch Buchhaltung aus offenen Mängeln.
- Gutachter-Workflow: Externe Gutachter-Einbindung (mit eigener Rechnung) → V3, §4.13.
- OCR auf Plänen: Raumerkennung aus PDF-Plan per OCR → V3. In V2 manuelle Raum-Eingabe oder Auswahl aus Raumbuch.
- Integration Bau-Software (iTWO, RIB, Nevaris): V3, über Standard-API oder IFC-Export.
- Mängel-Statistik-Benchmark Inter-Tenant: Nicht geplant (DSGVO-Bedenken).
- Automatische Verjährungs-Berechnung §634a BGB: V3, erfordert fundiertes Werkleistungs-Modell.
- Unterstützung fremder Grundriss-Formate (DWG, DXF): V3, erfordert CAD-Parser.
12. Risiken & Gegenmaßnahmen
Abschnitt betitelt „12. Risiken & Gegenmaßnahmen“| # | Risiko | Eintr.-Wahrscheinlichkeit | Impact | Gegenmaßnahme |
|---|---|---|---|---|
| R1 | KI-Klassifikation halluziniert Kategorie, Nutzer übernimmt blind → falscher SLA | Mittel | Hoch | Confidence-Threshold 0.7, unter 0.7 keine Vorauswahl, Transparenz-Hinweis, Audit |
| R2 | Grundriss-PDF-Revision ohne Pin-Remapping → Mangel räumlich nicht mehr zuordenbar | Mittel | Mittel | Banner + manuelles Remapping + Bericht zeigt beide Revisionen |
| R3 | Push-Benachrichtigung kommt nicht an (iOS Background-Policy) → SLA verletzt, Mangel unbehandelt | Mittel | Hoch | Fallback E-Mail nach 30 min, SMS bei Kritikalität=kritisch (optional) |
| R4 | LLM-Provider-Ausfall (Anthropic, OpenAI) → KI-Klassifikation unverfügbar, UX-Degradation | Niedrig | Mittel | Zwei Provider konfigurierbar (Fallback), bei beiden Ausfall: manuell mit Info-Banner |
| R5 | DSGVO-Verstoß: Mitarbeiter-Foto auf Nachher-Foto, im Bericht exportiert | Mittel | Hoch | Foto-Policy, Auto-Blur-Option (Cloudflare AI Face-Blur), Schulung |
| R6 | Server-side PDF-Render OOM bei 500+ Mängel | Niedrig | Mittel | Streaming-Render (pdfkit-stream), Chunk pro 50 Mängel, Timeout + Retry |
| R7 | Offline-Outbox: Pins werden 5 Tage offline gehortet, dann Massen-Sync bricht wegen Rate-Limit | Niedrig | Mittel | Rate-Limit pro Client-ID progressiv, Retry mit Backoff, Bauleiter-Benachrichtigung bei > 100 queued |
| R8 | Hash-Chain-Bruch durch Datenbank-Restore von Backup vor Manipulation | Sehr niedrig | Hoch | Zweiter Hash-Anker in S3 Object Lock (Hash pro Tag), dreifach abgesichert |
| R9 | Rechtsstreit AG vs. AN: Mangel vorhanden, aber Zeitstempel strittig (“Rückdatiert?”) | Niedrig | Sehr hoch | Hash-Kette + S3 Object Lock + optionale qualifizierte Zeitstempel via DATEV-TSA |
| R10 | KI-VO-Verstoß: Transparenz-Hinweis nicht sichtbar genug → Bußgeld bis 15 Mio € / 3 % Umsatz | Sehr niedrig | Sehr hoch | DSFA + UX-Test mit Anwälten (Kanzlei Waldeck), Screenshot-Archiv als Beweis |
13. Abhängigkeiten & Cross-Links
Abschnitt betitelt „13. Abhängigkeiten & Cross-Links“- §4.1 Bautagebuch: Mangel-Eintrag wird automatisch als Tages-Notiz in Tagesbericht aufgenommen (Bidirektional-Link).
- §4.3 GAEB-LV: Mangel kann an LV-Position verknüpft werden → Kalkulations-Abweichung bei Nacharbeit.
- §4.5 Foto-Doku/HSSE: Mängel-Fotos werden im Foto-Archiv mit Tag
mangel-#2026-042-17gespeichert. - §4.6 Mobiles Lager: Bei Mangel “Material defekt/fehlerhaft” kann Rück-Bestellung via OCI-Punchout ausgelöst werden.
- §4.8 Subunternehmer: Verantwortlicher-Zuweisung greift auf Sub-User-Liste zu.
- Stammdaten/04 Ansprechpartner: Verantwortlicher kann auch externer Ansprechpartner (ohne Login) sein — dann nur E-Mail-Notification, kein Push.
14. DSFA (Datenschutz-Folgenabschätzung) — Kurzfassung
Abschnitt betitelt „14. DSFA (Datenschutz-Folgenabschätzung) — Kurzfassung“Verarbeitung: Mangelfotos, Grundrisse, KI-Klassifikation (LLM-Cloud).
Risiko-Klassifikation: Mittel (Art. 35 DSGVO).
Betroffene Personen: Mitarbeiter (Monteure auf Fotos), AG-Vertreter, Dritte (z.B. Nachbarn in Kommentaren).
Schutzmaßnahmen:
- Fotos werden in S3 eu-central-1 gespeichert, verschlüsselt SSE-S3
- LLM-Anfragen: Anthropic Claude API mit “no-training”-Flag, AVV vorliegend
- KI-Audit-Log speichert nur SHA-256-Hashes des Inputs, kein Klartext
- RLS blockt unautorisierten Zugriff
- Löschkonzept: 10 Jahre GoBD, danach Anonymisierung
- Auto-Blur-Option für Gesichter in Export-Fotos (optional, Default an)
Restrisiko: Akzeptabel. Vollständige DSFA siehe docs/werkszeit/dsfa/07-maengel-tickets-dsfa.md (separates Dokument).
15. Referenz-Kunde
Abschnitt betitelt „15. Referenz-Kunde“Bauleitung Gebrüder Schmidt GmbH, Bremen — 42 Mitarbeiter, 4-6 parallele Bauvorhaben (Wohnbau, Kita, Gewerbe) — Pilot ab Q3/2026 geplant, 3 Projekte als Referenz.
Nutzungs-Benchmark (Baseline):
- 42 Mängel/Woche pro Bauleiter
- 1100 m² BGF ⌀ Projektgröße
- 7 Subunternehmer ⌀
- Derzeitige Tool-Landschaft: WhatsApp, Bautagebuch-Papier, Excel, Dropbox-Fotos
Erwarteter Business-Case (ROI):
- 8h/Woche Ersparnis pro Bauleiter × 42 €/h × 46 Wochen = 15.456 € pro Bauleiter/Jahr
- Reduktion streitiger Abnahmen 1,8 → 0,3 = 1,5 Rechtsstreitigkeiten weniger/Jahr × 12.000 € ⌀ Kanzlei/Streit = 18.000 €/Jahr
- Gesamt: ~33.000 €/Jahr pro Bauleiter, ROI <6 Monate bei Werkszeit-Lizenz 49 €/User/Monat
16. Migration & Rollout
Abschnitt betitelt „16. Migration & Rollout“16.1 Migrations-Strategie
Abschnitt betitelt „16.1 Migrations-Strategie“- Greenfield in V2, keine Bestandsdaten-Migration
- Alt-App hatte Mangel-Excel-Import → optional CSV-Import-Endpoint (V2.1)
- Import-Format:
projekt_nr;mangel_nr;titel;beschreibung;kategorie;raum;status;erfasst_am;erfasst_durch
16.2 Rollout-Plan
Abschnitt betitelt „16.2 Rollout-Plan“- Q2/2026 (Alpha): Internes Test-Projekt, 2 Bauleiter, 1 Sub
- Q3/2026 (Beta): Referenz-Kunde Gebr. Schmidt, 3 Projekte, 20 User
- Q4/2026 (GA): Öffentliche Freigabe nach KI-VO-Compliance-Review
- Q1/2027 (V2.1): CSV-Import, SLA-Kalender mit regionalen Feiertagen, SMS-Fallback
17. Senior-Consultant-Empfehlung
Abschnitt betitelt „17. Senior-Consultant-Empfehlung“Unsere Empfehlung für die Umsetzungsreihenfolge (in 12 Wochen bis GA):
Sprint 1-2 (Wochen 1-3): Datenmodell + RLS
- Drizzle-Schema + Migrations + RLS-Policies
- Hash-Chain mit Unit-Tests
- SLA-Engine Core (ohne UI)
Sprint 3-4 (Wochen 4-6): Mobile Mangel-Erfassung
- Grundriss-Viewer mit Pin-Setzen
- Foto-Pflicht-Workflow (vorher/nachher)
- Offline-Cache + Outbox-Sync
Sprint 5-6 (Wochen 7-9): Web-Kanban + SLA-Config
- Kanban-Board mit Drag&Drop
- Grundriss-Viewer mit PDF.js + Canvas
- SLA-Konfigurator
- Eskalations-Cron
Sprint 7 (Woche 10): KI-Klassifikation
- LLM-Integration mit Fallback
- KI-Audit-Log
- DSFA abschließen
Sprint 8-9 (Wochen 11-12): Mangelbericht-PDF + Abnahme
- PDF-Render mit vorher/nachher
- §12 VOB/B Abnahme-Checkboxen
- Unterschriftenseite
- E2E-Tests mit Referenz-Kunde
Kritische Pfade: PDF.js + Flutter-PDF-Interop (Wochen 4-5), SLA-Feiertagsberechnung (Woche 7), KI-VO-Compliance-Review (Wochen 10-11).
Nicht-verhandelbar: (1) Nachher-Foto-Pflicht als Server-Side-Validation, (2) Transparenz-Hinweis bei KI-Vorschlag in allen Views, (3) Hash-Kette ab Tag 1.
18. Offene Fragen / TODO für Review
Abschnitt betitelt „18. Offene Fragen / TODO für Review“- Q: Soll das Grundriss-PDF serverseitig auf Tile-Pyramid vorgerendert werden (für Plotter-Exports > 50 MB)? — Antwort vorgeschlagen: Ja, ab V2.1.
- Q: Welches LLM-Modell ist Default (Claude 3.5 Haiku vs. GPT-4o-mini)? — Antwort vorgeschlagen: Claude 3.5 Haiku primär (DSGVO-AVV vorhanden, EU-Rechenzentrum über AWS Bedrock eu-central-1 verfügbar).
- Q: Ist die Kategorie-Enum-Liste final oder tenant-erweiterbar? — Antwort vorgeschlagen: Tenant-erweiterbar via
mangel_kategorienLookup-Table (V2.1), Enum in V2.0. - Q: Wie restriktiv ist der Nachher-Foto-Enforcement bei Ablehnung? — Antwort vorgeschlagen: Bei Status “abgelehnt” KEIN Foto-Zwang, bei “behoben” Pflicht.
- Q: Soll SMS-Fallback bei kritischen Mängeln in V2 enthalten sein (Twilio-Kosten, AVV)? — Offen, Business-Entscheidung.
- Q: Wie werden Mängel bei Projektabschluss archiviert — aktiv verschiebbar oder automatisch nach Abnahme? — Antwort vorgeschlagen: Automatisch 30 Tage nach finaler Abnahme, dann Read-Only.
19. R1-Implementation Addendum (Migration 0032)
Abschnitt betitelt „19. R1-Implementation Addendum (Migration 0032)“Die R1-Scheibe liefert PDF-Mängelprotokoll (§4.7.6) und SLA-Eskalations-Timer
(§4.7.7) in Migration 0032_handwerk_07_maengel_sla.sql. Die Tabellen-Namen
weichen pragmatisch vom ursprünglichen Entwurf (§11) ab — funktional
identisch, aber kürzer und konsistent mit dem Feature-Präfix maengel_*:
| Feinkonzept §11 | R1-Migration 0032 | Zweck |
|---|---|---|
sla_rules |
sla_konfig |
Pro tenant × prioritaet SLA-Defaults |
sla_eskalationen |
maengel_eskalationen |
Append-only Audit mit Hash-Chain |
| — | maengel.reaktion_faellig_bis |
ALTER maengel (Trigger-berechnet) |
| — | maengel.loesung_faellig_bis |
ALTER maengel (Trigger-berechnet) |
| — | maengel.eskalation_faellig_bis |
ALTER maengel (Trigger-berechnet) |
19.1 SLA-Seed-Defaults
Abschnitt betitelt „19.1 SLA-Seed-Defaults“Die Seed-Zeilen pro Tenant (via SELECT id FROM tenants beim
INSERT ... ON CONFLICT DO NOTHING):
| Priorität | Reaktion | Lösung | Eskalation |
|---|---|---|---|
| kritisch | 1 h | 4 h | 2 h |
| hoch | 4 h | 24 h | 12 h |
| mittel | 24 h | 72 h | 48 h |
| niedrig | 72 h | 168 h | 120 h |
19.2 Idempotenz des SLA-Checks
Abschnitt betitelt „19.2 Idempotenz des SLA-Checks“UNIQUE-Index (mangel_id, eskalationsstufe, ueberfaellig_typ) auf
maengel_eskalationen verhindert doppelte Einträge beim Cron-Rerun.
Der Insert läuft in einem SAVEPOINT (SAVEPOINT me_insert); Konflikt
auf der UNIQUE-Constraint (errcode 23505) → ROLLBACK TO SAVEPOINT
und stilles Überspringen. Damit ist der Cron beliebig oft aufrufbar.
19.3 PDF-Streaming-Contract
Abschnitt betitelt „19.3 PDF-Streaming-Contract“- Einzel-PDF:
GET /v1/handwerk/maengel/{id}/protokoll.pdf - Batch-PDF:
POST /v1/handwerk/maengel/batch-protokoll.pdf(1..200 IDs) - Content-Type:
application/pdf - Content-Disposition:
attachment; filename="mangelprotokoll-{id}.pdf" - Transport:
Readable.toWeb()→ Honoc.body(ReadableStream)(pdfkit flusht Seiten-weise, RSS bleibt konstant auch bei 200er-Batch).
19.4 Out-of-Scope R1 (weiter R2+)
Abschnitt betitelt „19.4 Out-of-Scope R1 (weiter R2+)“Erhalten bleiben: KI-Klassifikation, Grundriss-Pin, sla_kalender (Arbeitszeit-aware Fristen), Web-Kanban, Sprachnotiz. Die SLA-Engine R1 rechnet mit kalendarischen Stunden — die Feiertagsberechnung (§17 Sprint 5-6) wandert mit sla_kalender in R2.
Ende des Feinkonzepts §4.7 — Mängel- & Ticket-Management.
20. Admin-Konfiguration
Abschnitt betitelt „20. Admin-Konfiguration“Die zugehörigen Web-Admin-Screens sind in §3.4 (SLA-Engine), §4.5 (Mängel-Kanban) und §4.6 (Mangelbericht-PDF) als Anforderungen bzw. Wireframes ausgearbeitet. Diese Sektion fasst die F-A-* Feature-IDs konsolidiert zusammen — Admin-Modul-Stub-URL: /admin/handwerk/maengel-tickets/.
- F-A-01 — Mangel-Kategorien-Katalog. Hierarchische Kategorisierung (Gewerk → Untergewerk → Schweregrad), Default-SLA pro Knoten, Default-Verantwortlicher (Rolle oder konkreter User), Sichtbarkeit pro Projekt-Typ.
- F-A-02 — SLA-Regeln. Antwortzeit (Acknowledge), Lösungszeit (Resolve), Eskalations-Pfad (n Stufen, je Stufe Empfänger + Auslöse-Schwelle). Werktage vs. Kalendertage konfigurierbar, Feiertagskalender pro Bundesland.
- F-A-03 — KI-Klassifikator-Schwelle. Confidence-Threshold ab dem die Klassifikation auto-übernommen wird, untere Schwelle ab der Mensch entscheiden muss. Override-Pflicht-Modus für Phase-1-Rollout (immer Mensch dran). Klassifikator-Modell-Version anzeigen + manuelles Re-Training auslösen.
- F-A-04 — Mangelbericht-PDF-Vorlage. Logo-Upload, Briefkopf-Felder, Pflicht-Felder (BV-Nummer, Bauleiter-Unterschrift, §12-VOB/B-Hinweise), Sprachen (DE/EN/PL), Vorschau-Button mit Test-Daten.
- F-A-05 — Push-Empfänger-Routing. Gewerke-Routing (welche Kategorie an welchen Polier/Subunternehmer), Bereitschafts-Plan (Wochentag/Uhrzeit), Wochenend-/Urlaubsvertretung mit automatischer Re-Routing-Logik.
- F-A-06 — Foto-Pflicht-Konfiguration. Pro Status-Übergang konfigurierbar (Erfassung: 1 Foto Pflicht; Behoben: Nachher-Foto Pflicht; Abgenommen: Bauleiter-Foto optional). Mindest-Auflösung in Pixel, EXIF-Pflicht (Geo + Zeit) zur Beweissicherung.
- F-A-07 — Aufbewahrungs-Policy. Lösch-Frist nach Abnahme + Garantie-Ablauf konfigurierbar pro Projekt-Typ (Default 5 Jahre VOB/B-Gewährleistung), GoBD-Pflichtfristen für rechnungs-relevante Tickets (10 Jahre), DSGVO-Personenbezug-Anonymisierung nach Frist X.
- F-A-08 — Status-Workflow-Editor. Definition zusätzlicher Status-Übergänge (z.B. „Wartet auf Material“, „Wartet auf Subunternehmer“) und deren SLA-Pause-Verhalten (zählt SLA weiter ja/nein).
- F-A-09 — Kanban-Spalten-Konfiguration. Welche Statuse als eigene Spalte, Reihenfolge, Farbe, WIP-Limit pro Spalte (Lean-Ansatz für Polier-Übersicht).
Für Entwickler — API-Endpoints12
| Methode | Pfad | Auth | Zweck |
|---|---|---|---|
| GET | /v1/handwerk/maengel | bearerAuth | Mängel-Liste (Cursor-Pagination) |
| POST | /v1/handwerk/maengel | bearerAuth | Mangel-Ticket anlegen |
| PUT | /v1/handwerk/maengel-sla-konfig/{prioritaet} | bearerAuth | Mängel-SLA-Konfiguration einer Priorität setzen (admin) |
| GET | /v1/handwerk/maengel/{id} | bearerAuth | Mangel-Ticket Detail |
| POST | /v1/handwerk/maengel/{id}/comments | bearerAuth | Kommentar hinzufügen (append-only) |
| POST | /v1/handwerk/maengel/{id}/photos | bearerAuth | Foto-Metadaten registrieren |
| GET | /v1/handwerk/maengel/{id}/protokoll.pdf | bearerAuth | Mangel-Protokoll als PDF |
| PATCH | /v1/handwerk/maengel/{id}/status | bearerAuth | Status-Übergang auslösen |
| POST | /v1/handwerk/maengel/batch-protokoll.pdf | bearerAuth | Sammel-Mangel-Protokoll als PDF |
| POST | /v1/handwerk/maengel/sla-check | bearerAuth | SLA-Check ausführen (Eskalationen erzeugen) |
| GET | /v1/handwerk/maengel/sla-konfig | bearerAuth | Mängel-SLA-Konfiguration |
| GET | /v1/handwerk/maengel/ueberfaellig | bearerAuth | Überfällige Mängel (SLA-Verletzungen) |