Zum Inhalt springen

Mobiles Aufmaß nach REB 23.003 (DA11 / DA12) mit Formel-Interpreter, Offline, Foto-Pin und Signatur

Beta — in Erprobung

Modul: Handwerk · Referenz: FUNKTIONSUMFANG.md §4.4 Cross-Links: 03-gaeb-lv.md (referenziert OZ aus LV-Version) · 05-bau-abrechnung.md (Aufmaß-Mengen → Schlussrechnung nach VOB/B §14) Status: Feinkonzept v0.1 · 2026-04-19


feature_id: handwerk/04-mobile-aufmass
title: Mobiles Aufmaß nach REB 23.003 (DA11 / DA12) mit Formel-Interpreter, Offline, Foto-Pin und Signatur
funktionsumfang_ref: §4.4
roadmap_horizont: V1
plattformen:
mobile: vollständig # Hauptarbeitsplatz: Baustelle, Tablet, Handschuhe
web: mit-Bulk # Review und Freigabe im Büro
desktop: ab V1.5 bei Bedarf
owner_rolle: Bauleitung # erfassen + Bauherrn signieren lassen
modul_gate_flag: module.handwerk.aufmass
compliance_flags:
gobd: true # Mengen-Nachweis Grundlage für Rechnung
arbzg: false
vob: true # VOB/C (DIN 18299 ff.) Aufmaß-Regeln pro Gewerk
dsgvo: true # Signatur = personenbezogen
bfsg: false
betrvg: false
stvg: false
weitere: [REB 23.003 (BMV-Bund, elektronische Bauabrechnung), VOB/C DIN 18299 ff., §147 AO, StGB §267 (Urkundenfälschung bei Signatur-Manipulation)]
referenzkunde:
status: TBD
name: "zu klären mit Sales — Kandidat: shk-gebruder-schmidt oder Hochbau-Partner mit öffentlichen AG"
quelle: Interview-Protokoll 2026-02-20, Bauleiter bemängelt Excel-Aufmaß-Chaos
estimate_eng_tage: 62 # Formel-Interpreter 10 · Mobile-UI 14 · PDF-Pin + Signatur 10 · Sync/Konflikt 10 · REB-Export 8 · Tests 10
abhängigkeiten:
- handwerk/03-gaeb-lv # LV-Version als Quelle der OZ
- kern/11-rollen-rls
- kern/12-audit-log
- kern/offline-outbox # Idempotency-Key-Infrastruktur

REB 23.003 („Regelungen für die Elektronische Bauabrechnung“, herausgegeben vom Bundesministerium für Verkehr und digitale Infrastruktur, Stand 1979 mit Updates bis 2009/2022) ist der verbindliche Austausch-Standard zwischen AG (Bauamt, DB Netz, Kommune) und AN (Bauunternehmen) für Aufmaß- und Abrechnungs-Daten. Zwei Datenarten sind zentral:

  • REB DA11 — fertige Aufmaßzeilen (OZ, Menge, Formel-Ergebnis, optional Textbeschreibung). Wird vom AN an den AG geliefert.
  • REB DA12 — Mengenberechnung mit expliziten Formeln, Variablen und Kommentaren. Der AG kann jede Aufmaßzeile gegen die Formel nachrechnen — ohne dass der AN eine Excel-Datei mitschickt.

REB 23.003 ist im Bundesstraßenbau (Bundesfernstraßen, Bundeswasserstraßen), bei der DB AG und in vielen Landesbauverwaltungen vertraglich erzwungen. Öffentliche AG lehnen reine PDF-Aufmaße für Abschlagsrechnungen ab; nur DA11/DA12 ist prüfbar. Kommunale AG akzeptieren teilweise noch Papier, verlangen aber zunehmend DA-Format.

Der Monteur oder Bauleiter misst heute:

  1. Zollstock + Notizblock → Block voll mit L × B – Abzug Tür × ..., Skizze nebenan.
  2. Abends ins Büro → Excel-Tabelle, Formel reingetippt, Kollege prüft am nächsten Tag.
  3. Büro ruft an: „Was ist hier OZ 01.02.030, wurde das wirklich so gemessen?“ → Diskussion, niemand weiß es sicher, kein Foto vom Messort, keine Skizze.
  4. Bauherr-Streitfall später: „Das stand nicht so im Auftrag“ → ohne Signatur des Bauherrn vor Ort ist das Aufmaß rechtlich schwach (VOB/B §14 Abs. 2 — Feststellung mit AG).

Alternativen heute:

  • Papier-Aufmaßblatt (DIN A4-Vordrucke von Feldhaus, Rößler) — klassisch, dokumentarisch ok, aber nicht REB-11/12-kompatibel.
  • RIB iTWO Aufmaß mobil — funktional, aber Lizenz 4-stellig/Jahr.
  • Elektro-Aufmaß (AutoCAD Tools, Revu Bluebeam) — sehr spezialisiert, viel zu teuer fürs Gewerk.
  • Eigen-Excel — am häufigsten. Ohne REB-Export, ohne Signatur, ohne OZ-Bindung, ohne Foto-Referenz. Grenzwertig GoBD (leicht manipulierbar).

In zeitapp gab es keinen Aufmaß-Screen. Der Versuch, Mengen frei in den Zeiteintrag zu klemmen („Tätigkeit: 128 m² Putz abreißen“), ist bei VOB/B §14-Abrechnung vor Gericht bedeutungslos.

LESSONS-LEARNED §5: „Business-Substanz fehlt im Code — GAEB, REB-23.003-Aufmaß, VOB/B Teil-/Schlussrechnung mit Kumulation: damit fehlt die Zahlungsbereitschaft der lautstärksten Zielgruppe (Bau-Betriebe).“

LESSONS-LEARNED §4: „Offline-First war nur halb offline — Konfliktauflösung ist im Kern Last-Write-Wins ohne Diff-UI, keine Idempotenz-Keys.“ → Für Aufmaß inakzeptabel: Wenn Monteur A und B parallel offline messen und dieselbe OZ betreffen, darf der letzte Sync nicht den ersten überschreiben.

Metrik Ist Ziel mit Werkszeit
Zeit für Aufmaß-Erfassung (100 Zeilen) 2–3 h (Messen + abends Excel) ≤ 45 min (mobil in einem Rutsch)
Aufmaß-Streit mit Bauherrn (pro Projekt) 20–30 % der Abschlagsrechnungen < 5 % (Vor-Ort-Signatur + Foto-Pin)
Zeit bis REB-DA11/12-Abgabe 3–5 Tage (Büro-Übersetzung) sofort (Export-Button)
Formel-Fehler in Aufmaß-Zeile 3–5 % (manuelle Excel) < 0,1 % (Parser validiert)

Rolle Aktion Scope Plattform
Bauleitung Aufmaß erfassen, Formel tippen, Foto-Pin setzen, Bauherr signieren lassen team 📱 (primär) + 🌐 (Review)
Monteur Eigene Aufmaßzeilen erfassen (ohne Bauherr-Signatur — Signatur ist Bauleitungs-Aufgabe) own 📱
Manager / Bauleiter Büro Aufmaß-Review, Freigabe, Änderungs-Protokoll einsehen, DA11/DA12 exportieren team 🌐
Kalkulator Aufmaß-Abweichungen zum kalkulierten Mengenansatz sehen (Soll-/Ist-Vergleich) team 🌐
Buchhaltung Aufmaß-Summen je OZ als Grundlage für Rechnungsposition (§4.5) all 🌐
Bauherr / AG Signatur per Touch, optional: DA12 per Portal-Link nachvollziehen — (extern) 📱 (Touch) + 🌐 (Portal-Link)

Persona-Skizzen:

  • Hannes K. (Monteur, 38, SHK) — arbeitet mit Handschuhen, Tablet fällt regelmäßig auf Baustellen-Boden, hat kein LTE im Heizungskeller, möchte „möglichst nur Zollstock lesen und tippen“.
  • Sabine M. (Bauleiterin, 45, Hochbau) — 3 Baustellen parallel, erledigt Aufmaß auf den Baustellen direkt. Braucht am Abend den Sync-Konflikt-Dialog, wenn Monteur und sie dieselbe OZ gemessen haben.
  • Herr Özdemir (Bauherr, 63, privat) — misstrauisch gegenüber Tablets, will aber nicht wochenlang warten. Akzeptiert Signatur, wenn Bauleiter die Zahlen vorliest und er den Messort auf dem Foto sieht.

US-01 [V1] Als Monteur möchte ich offline im Heizungskeller eine Aufmaßzeile mit der
Formel "4,80*2,60-1,20*2,10" erfassen, damit ich auch ohne LTE arbeiten
kann und die Formel später für den AG nachvollziehbar bleibt.
US-02 [V1] Als Bauleitung möchte ich zu jeder Aufmaßzeile ein Foto vom Messort machen
(optional Pin auf dem Grundriss-PDF), damit bei späterer Rückfrage klar
ist, wo gemessen wurde.
US-03 [V1] Als Bauleitung möchte ich den Bauherrn vor Ort auf dem Tablet signieren
lassen, damit das Aufmaß nach VOB/B §14 Abs. 2 als einvernehmlich
festgestellt gilt.
US-04 [V1] Als Bauleitung möchte ich eine Aufmaßzeile als Negativ-Menge erfassen
(z. B. "minus Türöffnung"), damit der Netto-Flächen-Abzug korrekt in die
Position eingeht, ohne eine separate OZ dafür zu benötigen.
US-05 [V1] Als Büro-Bauleiter möchte ich beim Sync erkennen, wenn zwei Geräte
parallel offline zur selben OZ Aufmaß erfasst haben, damit ich keine
stille Überschreibung habe und explizit entscheide, welche Zeilen behalten
werden.
US-06 [V1] Als Kalkulator möchte ich auf einen Blick sehen, wenn die Aufmaß-Summe
einer OZ vom LV-Soll abweicht (über ±5 %), damit ich rechtzeitig einen
Nachtrag anstoßen kann.
US-07 [V1] Als Buchhaltung möchte ich den Aufmaß-Stand einer OZ als REB-DA11 und
DA12 exportieren, damit ich die Datei mit der Abschlagsrechnung an den
öffentlichen AG schicken kann.
US-08 [V1.5] Als Admin möchte ich Aufmaß-Vorlagen definieren (z. B. "Raum ausmessen"
erzeugt automatisch Länge, Breite, Höhe mit Variablen-Namen), damit
Monteure nicht immer wieder dieselben Formeln tippen.

  • F-M-01 — OZ-Picker aus beauftragter LV-Version. Aufmaß ist nur möglich, wenn Projekt ein LV mit Status beauftragt hat. Picker zeigt Baum mit OZ + Kurztext + verbleibende Menge (Soll minus bisher aufgemessen).
  • F-M-02 — Aufmaßzeile erfassen. Felder: Raum/Position (Freitext), ME (aus LV übernommen, read-only), Formel (REB-Syntax), Ergebnis (auto berechnet + editierbar), Notiz (optional). Pflichtfelder: OZ + Formel oder direktes Ergebnis + Messdatum.
  • F-M-03 — Formel-Interpreter inline. Siehe §5.6 unten. Live-Evaluation während der Eingabe (< 50 ms), rote Unterstreichung bei Syntaxfehler.
  • F-M-04 — Variablen-Kontext. Innerhalb derselben Aufmaßzeile können Variablen definiert werden: $L=4,80 ; $B=2,60 ; $L*$B. Variablen sind zeilen-lokal, nicht global (gem. REB-Semantik).
  • F-M-05 — Negativ-Menge. Toggle oder Präfix - vor der Formel macht das Ergebnis negativ. UI zeigt Zeile mit (Abzug)-Badge.
  • F-M-06 — Foto-Pin auf Grundriss. Für eine OZ kann der Bauleiter den zuvor hochgeladenen Grundriss-PDF öffnen (siehe F-W-02) und per Tap einen Pin setzen. Foto-Anhang direkt aus Kamera (EXIF-GPS + Zeitstempel Pflicht). Bis 10 Fotos je Zeile.
  • F-M-07 — Touch-Signatur Bauherr. Nach Abschluss eines Aufmaß-Blocks (eine oder mehrere OZ zusammen) → Signatur-Pad, Name des Unterschreibenden, Rolle („Bauherr“ / „Bauleitung AG“), optional Handy-Nr. für DSGVO-Rückruf. Signatur als PNG + SVG-Pfade (beide, für Druck und für UI). Signatur-Hash wird in Audit-Log aufgenommen.
  • F-M-08 — Offline-Vollständig. Aufmaß ist voll-offline nach Profil DOR §1.2 (9): alle Mutationen gehen in Outbox mit Idempotency-Key (UUID v7). Foto-Upload per S3-Multipart mit presigned URL, SQLite hält Thumbnail + Referenz.
  • F-M-09 — Sync-Konflikt-Diff. Beim Sync: Wenn Server-Version eine Zeile mit derselben (lv_version_id, oz, source_device ≠ eigenes) erhalten hat, UI zeigt Diff-View Seite-an-Seite (meine Zeile vs. Server-Zeile). User muss manuell auflösen: Beide behalten, Nur meine, Nur Server, Merge-Dialog. Kein Last-Write-Wins.
  • F-M-10 — Geo-Tag. Optional (konfigurierbar Admin + BetrVG-Hinweis): Aufmaßzeile bekommt GPS-Stempel bei Erstellung.
  • F-M-11 — Werkzeug-Messen per Bluetooth Laser (V1.5). Anbindung an Leica Disto / Bosch GLM — Maß direkt in Formel-Variable übernommen.
  • F-W-01 — Aufmaß-Review-Tabelle. Sortier-/filterbare Tabelle: OZ, Zeile-Nr, Formel, Ergebnis, ME, Raum, Erfasst-von, Signatur-Status, Foto-Anzahl, Konflikt-Status. Bulk-Freigabe möglich für Zeilen ohne Konflikte.
  • F-W-02 — Grundriss-PDF-Upload pro Projekt. Admin lädt PDF (mehrseitig) hoch. System erzeugt PNG-Kacheln für Mobile-Anzeige (DPI ≥ 150). Pins können nachträglich am Web-Tablet verschoben werden.
  • F-W-03 — REB-Export DA11 + DA12. Job-basiert: User wählt OZ-Auswahl oder „alle seit letzter Abrechnung“ + Phase (Teilaufmaß / Schlussaufmaß). Lambda erzeugt beide Dateien (ASCII fixed-width, CP850 per default, UTF-8 optional), Download als ZIP.
  • F-W-04 — Soll-/Ist-Vergleich. Dashboard pro Projekt: Säulendiagramm OZ — Soll (aus LV) vs. Ist (aus Aufmaß). Abweichung > 5 % wird hervorgehoben → CTA „Nachtrag anstoßen“.
  • F-W-05 — Manuelle Nachbearbeitung einer Aufmaßzeile. Nur möglich, wenn Zeile nicht signiert und nicht exportiert. Signierte Zeilen sind append-only: Korrektur = neue Zeile mit Verweis auf alte + Begründung.
  • F-W-06 — Bauherr-Portal-Link. Freigabe-E-Mail an Bauherrn mit Token-URL: Bauherr sieht Aufmaß-Zeilen + Fotos + Unterschrift, kann als PDF herunterladen (DA12-menschlich-lesbar-transformiert).
  • F-W-07 — Bulk-Export für Buchhaltung. Aufmaß-Summen je OZ als CSV/JSON für §4.5-Rechnungs-Import (GET /v1/handwerk/aufmass/summary?lvVersionId=...).
  • F-X-01 — Formel-Interpreter in Dart (packages/reb_formula/) — ausführbar auf Mobile, Web und Server. Deterministisch, bit-identische Ergebnisse auf allen Plattformen. Property-Tests auf beiden Seiten.
  • F-X-02 — OZ-Resolver (Shared mit §4.3) — konsumiert packages/oz-core.
  • F-X-03 — Signatur-Rendering — SVG-Path-Rendering identisch Mobile/Web/PDF-Generator.
  • F-A-01 — Konflikt-Auflösungs-Profil. Pro Tenant: manuell-Pflicht (default) / server-priorität-wenn-später-erfasst / erstes-Gerät-wins (nur mit BetrVG-Zustimmung, da Leistungsvergleich-Risiko).
  • F-A-02 — Foto-Pflicht-Profil. Pro Gewerk: 0/1/N Fotos Pflicht. Default SHK: 1 Foto pro Zeile; Hochbau: 0 (nicht blockierend).
  • F-A-03 — REB-Export-Profil. Zeichensatz (CP850 default / ISO-8859-15 / UTF-8), Zeilenende (CR-LF / LF), DA12-Kommentar-Präfix.
  • F-A-04 — Aufmaß-Vorlagen (V1.5). Benannte Formel-Templates mit Variablen-Defaults.
  • F-A-05 — Geo-Tag aktivieren — BetrVG-relevant (nicht automatisch aktiv, braucht BR-Freischaltung + Tenant-Toggle).
ID MVP V1 V1.5 V2
F-M-01 bis F-M-09 (Kern-Aufmaß-Flow)
F-M-10 Geo-Tag (opt.)
F-M-11 Laser-Disto-Bluetooth
F-W-01 Review-Tabelle
F-W-02 Grundriss-PDF-Upload
F-W-03 REB-Export DA11+12
F-W-04 Soll-/Ist-Dashboard
F-W-05 Nachbearbeitung
F-W-06 Bauherr-Portal
F-W-07 Bulk-Export Buchhaltung
F-X-01 Formel-Interpreter
F-A-01 Konflikt-Profil
F-A-02 Foto-Pflicht
F-A-04 Aufmaß-Vorlagen

Grammatik (EBNF-nah):

formula = assignment ( ";" assignment )* ";" expression
| expression ;
assignment = variable "=" expression ;
variable = "$" ( letter | digit )+ ; /* $L, $B, $1, $h2 */
expression = term ( ("+" | "-") term )* ;
term = factor ( ("*" | "/") factor )* ;
factor = number | variable | func | "(" expression ")" | "-" factor ;
func = ident "(" arglist? ")" ;
ident = "sqrt" | "pi" | "abs" | "pow" | "round" | "floor" | "ceil" ;
arglist = expression ( "," expression )* ;
number = digit+ ( ("," | ".") digit+ )? ; /* de-DE Komma + en-US Punkt beide akzeptiert */

Semantik (REB 23.003, §5 Datenart 12):

  • Dezimaltrennzeichen: , (de-DE) oder . — intern immer als decimal.Decimal (arbiträre Präzision) mit 6 Nachkomma, Ausgabe auf 4 Nachkomma gerundet HALF_EVEN (REB-Standard: Rundung auf die in der LV-Position festgelegte ME-Genauigkeit, default 4 Nachkommastellen für Fläche, 2 für Länge).
  • Operatoren: + - * / (binär, linksassoziativ), - unär.
  • Klammern: (, ) beliebige Verschachtelung.
  • Funktionen: sqrt(x), pi() (= 3.14159265358979), abs(x), pow(x, y), round(x, n), floor(x), ceil(x).
  • Variablen: $<ident>, zeilenlokal. $1, $2, ...$99 = numerisch indexiert, gem. REB-Tradition oft $1=Länge, $2=Breite.
  • Keine Kontrollstrukturen, keine Schleifen, keine arbitrary code execution — reiner Ausdrucks-Interpreter.
  • Ausführungs-Limit: max 500 Tokens, 10 Assignments, 50 Operatoren pro Zeile. Verhindert ReDoS-ähnliche Angriffe.

Referenz-Implementierung: Dart-Package packages/reb_formula auf Basis petitparser (gem. TECH-STACK.md §2.93). Gleicher Code Mobile/Web/Server (auch TS-Port für Server-Validierung — Cross-Check der Ergebnisse).

Akzeptierte Beispiele:

  • 4,80*2,60-1,20*2,10 → 9,96 m² (Wand minus Tür)
  • $L=4,80; $B=2,60; $L*$B → 12,48 m²
  • (2,40+3,10)*2,60 → 14,30 m²
  • sqrt(3,5*3,5 + 2,5*2,5) → 4,3012 m (Diagonale)
  • pi()*pow(0,5, 2) → 0,7854 m² (Kreis r=0,5 m)
  • -1,20*2,10 → −2,52 m² (Abzug Türöffnung)

Abgelehnt:

  • rm -rf / → Parser-Error
  • import os; os.system('x') → Parser-Error
  • while(1){} → Parser-Error
  • 0/0 → Laufzeit-Fehler mit Meldung „Division durch 0“

HTML-Hero-Mockup: 04-mobile-aufmass.html — Mobile dominant, Web als schmale Review-Sicht (Aufmaß ist Feldarbeit).

┌────────────────────────────────┐
│ ← Aufmaß · Schule A ✓ ⋯ │
├────────────────────────────────┤
│ OZ: 01.02.030 │
│ Putz-Innenwand abreißen · m² │
│ LV-Soll: 128,50 m² │
│ Bisher: 45,20 m² (35,2 %) │
│ ────────────────────────────── │
│ Raum: ▼ Klassenzimmer 1.04 │
│ │
│ 🟢 Formel │
│ ┌──────────────────────────┐ │
│ │ 4,80*2,60-1,20*2,10 │ │ │
│ └──────────────────────────┘ │
│ = 9,96 m² │
│ │
│ [ ] Negativ-Menge (Abzug) │
│ │
│ Notiz (optional): │
│ ┌──────────────────────────┐ │
│ │ Nordwand, ohne Heizung │ │
│ └──────────────────────────┘ │
│ │
│ Fotos · 2/10 │
│ [📷 IMG_0412] [📷 IMG_0413] │
│ [📷 + Pin auf Grundriss] │
│ │
│ 📍 48.1371°N 11.5754°E ±4 m │
│ │
│ [ Speichern ] [+ Neue Zeile] │
└────────────────────────────────┘
┌────────────────────────────────┐
│ ← Aufmaß-Signatur │
├────────────────────────────────┤
│ Aufmaß-Block · 6 Zeilen │
│ Gesamt: 64,20 m² für 01.02.030│
│ Gesamt: 4 Stk für 02.01.010 │
│ │
│ Anwesend: │
│ Bauleitung · Sabine Maier ✓ │
│ Bauherr · ▢ Name Pflicht │
│ │
│ Name Bauherr: │
│ ┌──────────────────────────┐ │
│ │ Herbert Özdemir │ │
│ └──────────────────────────┘ │
│ Rolle: ( ) AG (•) Bauherr │
│ │
│ ✍️ Unterschrift: │
│ ┌──────────────────────────┐ │
│ │ │ │
│ │ ╱╲ ╱╲ │ │
│ │ ╱ ╲ ╱ ╲_ _ │ │
│ │ ╱ ╲╱ ╲╱ ╲╱ │ │
│ │ │ │
│ └──────────────────────────┘ │
│ [Löschen] [Signatur-Hash 4a..] │
│ │
│ ⚠ Mit der Unterschrift bestä- │
│ tigt der Bauherr die Maße │
│ gem. VOB/B §14 Abs. 2. │
│ │
│ [ Signieren & Abschließen ] │
└────────────────────────────────┘
┌────────────────────────────────┐
│ ⚠ Sync-Konflikt · 2 Zeilen │
├────────────────────────────────┤
│ OZ 01.02.030 · Raum 1.04 │
│ │
│ ── Meine Zeile (Hannes) │
│ Formel: 4,80*2,60 │
│ = 12,48 m² │
│ erfasst 19.04. 09:12 │
│ 📷 1 Foto · nicht signiert │
│ │
│ ── Server-Zeile (Sabine) │
│ Formel: 4,80*2,60-1,20*2,10 │
│ = 9,96 m² │
│ erfasst 19.04. 09:08 │
│ 📷 2 Fotos · nicht signiert │
│ │
│ Auflösung: │
│ ( ) Beide behalten (als A/B) │
│ (•) Nur meine behalten │
│ ( ) Nur Server-Zeile behalten │
│ ( ) Merge (manuell bearbeiten) │
│ │
│ Begründung 🔴 (für Audit-Log): │
│ ┌──────────────────────────┐ │
│ │ Türöffnung bereits in │ │
│ │ separater Zeile erfasst │ │
│ └──────────────────────────┘ │
│ │
│ [Weiter zur nächsten] [Lösen]│
└────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ Werkszeit · shk-gebruder-schmidt Sabine M. · Bauleitung [Profil ▾]│
├────────────────────────────────────────────────────────────────────────────────┤
│ Sidebar │ Aufmaß · Schule A · LV v3 beauftragt │
│ ───────── │ [⤓ DA11] [⤓ DA12] [Bulk-Freigabe ▾] [+ PDF-Grundriss] │
│ Zeit │ ┌──────────────────────────────────────────────────────────────┐ │
│ LV │ │ OZ │ Zeile │ Formel │ Menge │ Status │ │
│ ▶ Aufmaß │ ├─────────────┼───────┼──────────────────┼─────────┼──────────┤ │
│ Nachtr. │ │ 01.02.030 │ 001 │ 4,80*2,60-1,20*2,10 │ 9,96 m²│ 🟢 sig. │ │
│ Rechn. │ │ 01.02.030 │ 002 │ 5,20*2,60 │ 13,52 m²│ 🟢 sig. │ │
│ Doku │ │ 01.02.030 │ 003 │ -1,20*2,10 │ -2,52 m²│ 🟢 sig. │ │
│ │ │ 01.02.030 │ 004A │ 4,80*2,60 │ 12,48 m²│ 🟡 Konf.│ │
│ │ │ 01.02.030 │ 004B │ 4,80*2,60-1,20*2,10 │ 9,96 m²│ 🟡 Konf.│ │
│ │ │ 02.01.010 │ 001 │ $1=2,5; pi()*$1*$1 │19,63 Stk│ 🔴 offen│ │
│ │ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Soll/Ist · 01.02.030 │
│ │ ▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐ 64,20 m² Ist (50,0 %) │
│ │ ▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐ 128,50 m² Soll (LV) │
│ │ ⚠ Abweichung −16,3% → Nachtrag prüfen? [Nachtrag anstoßen] │
└────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ Grundriss · Schule A · EG.pdf · Seite 1/3 [📐 Pin setzen] │
├────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┬────────────┬────────────┐ │
│ │ │ │ │ │
│ │ Klassen- │ Flur │ Klassen- │ │
│ │ zimmer 1.04│ │ zimmer 1.05│ │
│ │ 📍1 │ │ 📍3 │ │
│ │ │ │ │ │
│ ├────────────┴────────────┴────────────┤ │
│ │ │ │
│ │ Aula 📍4│ │
│ │ │ │
│ └───────────────────────────────────────┘ │
│ │
│ Pins: 1: OZ 01.02.030 · Zeile 001 2: OZ 01.02.030 · Zeile 002 │
│ 3: OZ 01.02.030 · Zeile 003 4: OZ 01.02.040 · Zeile 001 │
│ │
│ [Pin verschieben] [Pin löschen] [Alle Pins als PDF-Overlay] │
└────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ REB-Export · Aufmaß Schule A │
├────────────────────────────────────────────────────────────────┤
│ Abrechnungs-Phase: (•) Teilaufmaß ( ) Schlussaufmaß │
│ Auswahl: │
│ (•) Alle seit letzter Abrechnung (62 Zeilen) │
│ ( ) OZ-Auswahl: ______________ │
│ ( ) Zeitraum: 19.04.2026 − 19.04.2026 │
│ │
│ Format: │
│ [✓] DA11 (Aufmaßzeilen) │
│ [✓] DA12 (Mengenberechnung mit Formeln) │
│ │
│ Zeichensatz: ( ) UTF-8 (•) CP850 ( ) ISO-8859-15 │
│ Zeilenende: (•) CR-LF ( ) LF │
│ │
│ Profil: ▼ Standard (BMV-Bund) │
│ │
│ Unsignierte Zeilen (7): │
│ ⚠ 7 Zeilen sind noch nicht vom Bauherrn signiert. │
│ Export trotzdem? (üblich bei Teilaufmaß) │
│ [✓] Ja, unsignierte einschließen │
│ │
│ [Abbrechen] [Export starten] │
└────────────────────────────────────────────────────────────────┘

Symbol-Konvention: 🟢 signiert / OK, 🟡 Sync-Konflikt, 🔴 offen / Pflichtfeld, 📷 Foto, ✍️ Signatur, 📍 Grundriss-Pin, 📐 Messen.


apps/api/src/db/schema/aufmass.ts
export const aufmassBlockTable = pgTable('aufmass_block', {
id: uuid('id').primaryKey().defaultRandom(), // UUID v7
tenantId: uuid('tenant_id').notNull(),
projectId: uuid('project_id').notNull().references(() => projectsTable.id),
lvVersionId: uuid('lv_version_id').notNull(), // muss 'beauftragt' sein (FK-Check)
bezeichnung: text('bezeichnung'), // "Klassenzimmer-Abriss W1"
phase: text('phase').notNull(), // 'teilaufmass'|'schlussaufmass'
status: text('status').notNull(), // 'entwurf'|'signiert'|'exportiert'|'abgerechnet'
signaturPng: text('signatur_png'), // base64 (oder S3-Ref)
signaturSvg: text('signatur_svg'), // SVG-Path für Druck/PDF
signaturName: text('signatur_name'),
signaturRolle: text('signatur_rolle'), // 'bauherr'|'ag'|'bauleitung_ag'
signaturHash: bytea('signatur_hash'), // SHA-256 über (png+name+rolle+timestamp)
signaturAt: timestamp('signatur_at', { withTimezone: true }),
hashPrev: bytea('hash_prev'),
hashSelf: bytea('hash_self').notNull(),
immutableAt: timestamp('immutable_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
createdBy: uuid('created_by').notNull(),
}, (t) => ({
tenantProjIdx: index('aufmass_block_tenant_proj_idx').on(t.tenantId, t.projectId),
lvVerIdx: index('aufmass_block_lv_ver_idx').on(t.lvVersionId),
}));
export const aufmassZeileTable = pgTable('aufmass_zeile', {
id: uuid('id').primaryKey().defaultRandom(), // UUID v7 == Idempotency-Key
tenantId: uuid('tenant_id').notNull(),
blockId: uuid('block_id').references(() => aufmassBlockTable.id), // nullable: Zeile kann lose existieren
projectId: uuid('project_id').notNull(),
lvVersionId: uuid('lv_version_id').notNull(),
oz: text('oz').notNull(),
zeileNr: text('zeile_nr').notNull(), // '001', '001A', '002.neg'
raum: text('raum'), // 'Klassenzimmer 1.04'
mengeneinheit: text('mengeneinheit').notNull(), // 'm2', aus lv_knoten
formel: text('formel'), // Original-Formel-Text
variablen: jsonb('variablen'), // { "$L": "4,80", "$B": "2,60" }
ergebnis: numeric('ergebnis', { precision: 14, scale: 4 }).notNull(), // auto + manuell override
ergebnisCalc: numeric('ergebnis_calc', { precision: 14, scale: 6 }), // aus Interpreter (6 Nachkomma)
negativ: boolean('negativ').notNull().default(false),
notiz: text('notiz'),
fotos: jsonb('fotos'), // [{s3Key, sha256, exifLat, exifLon, exifTs}]
grundrissPin: jsonb('grundriss_pin'), // { pdfId, page, x, y } (normalized 0..1)
geoLat: numeric('geo_lat', { precision: 9, scale: 6 }),
geoLon: numeric('geo_lon', { precision: 9, scale: 6 }),
geoAccuracyM: integer('geo_accuracy_m'),
sourceDevice: text('source_device'), // 'device:uuid' (FPR-Fingerprint)
sourceAppVersion: text('source_app_version'),
messzeitpunkt: timestamp('messzeitpunkt', { withTimezone: true }).notNull(),
// Append-only / Korrektur-Kette
replacesZeileId: uuid('replaces_zeile_id').references((): any => aufmassZeileTable.id),
replacedReason: text('replaced_reason'),
// GoBD
hashPrev: bytea('hash_prev'),
hashSelf: bytea('hash_self').notNull(),
immutableAt: timestamp('immutable_at', { withTimezone: true }),
// Sync
clientCreatedAt: timestamp('client_created_at', { withTimezone: true }),
syncedAt: timestamp('synced_at', { withTimezone: true }),
conflictWith: uuid('conflict_with').references((): any => aufmassZeileTable.id),
conflictResolution: text('conflict_resolution'), // 'both_kept'|'mine'|'theirs'|'merged'
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
createdBy: uuid('created_by').notNull(),
}, (t) => ({
lvOzIdx: index('aufmass_zeile_lv_oz_idx').on(t.lvVersionId, t.oz),
blockIdx: index('aufmass_zeile_block_idx').on(t.blockId),
replacesIdx: index('aufmass_zeile_replaces_idx').on(t.replacesZeileId),
}));
// PDF-Grundrisse pro Projekt
export const grundrissPdfTable = pgTable('grundriss_pdf', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
projectId: uuid('project_id').notNull(),
name: text('name').notNull(), // "Schule A · EG"
s3Key: text('s3_key').notNull(),
seitenCount: integer('seiten_count').notNull(),
tilesS3Prefix: text('tiles_s3_prefix'), // PNG-Kacheln für Mobile
});
// Aufmaß-Vorlage (V1.5)
export const aufmassVorlageTable = pgTable('aufmass_vorlage', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
name: text('name').notNull(), // "Raum ausmessen"
formelTemplate: text('formel_template').notNull(), // "$L=?; $B=?; $L*$B"
beschreibung: text('beschreibung'),
});
ALTER TABLE aufmass_block ENABLE ROW LEVEL SECURITY;
ALTER TABLE aufmass_zeile ENABLE ROW LEVEL SECURITY;
ALTER TABLE grundriss_pdf ENABLE ROW LEVEL SECURITY;
-- Tenant-Isolation
CREATE POLICY aufmass_zeile_tenant_iso ON aufmass_zeile
USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- Scope: own = nur eigene erfasste, team = Projekt-Mitglieder, all = Admin
CREATE POLICY aufmass_zeile_scope ON aufmass_zeile
FOR SELECT
USING (
tenant_id = current_setting('app.tenant_id')::uuid
AND (
current_setting('app.scope') = 'all'
OR (current_setting('app.scope') = 'team'
AND project_id IN (SELECT project_id FROM project_member WHERE user_id = current_setting('app.user_id')::uuid))
OR (current_setting('app.scope') = 'own'
AND created_by = current_setting('app.user_id')::uuid)
)
);
-- GoBD-Sperre: nach Signatur / Export keine UPDATEs auf Zeile
CREATE POLICY aufmass_zeile_no_update_after_immutable ON aufmass_zeile
FOR UPDATE
USING (immutable_at IS NULL);
-- Korrektur nur über INSERT neue Zeile mit replaces_zeile_id
CREATE POLICY aufmass_zeile_no_delete ON aufmass_zeile
FOR DELETE USING (false); -- nie löschen
-- aufmass_block: nur Bauleitung + Manager dürfen signieren
CREATE POLICY aufmass_block_sign ON aufmass_block
FOR UPDATE
USING (
current_setting('app.role') IN ('bauleitung', 'manager', 'admin')
AND tenant_id = current_setting('app.tenant_id')::uuid
);
  • aufmass_zeile.lv_version_idlv_version.id (muss phase='auftrag' und immutable_at IS NOT NULL)
  • aufmass_zeile.oz + lv_version_id → logisch eine Position aus lv_knoten
  • aufmass_block.signatur_hash fließt in globale Audit-Hash-Chain
  • aufmass_zeile.fotos[].s3Key → S3 mit Object Lock (Compliance, 10 Jahre)

Pfad-Konvention: /v1/handwerk/aufmass/…, Gate module.handwerk.aufmass.

Methode Pfad Auth-Scope Rate-Limit Idempotenz Beschreibung
POST /v1/handwerk/aufmass/zeilen aufmass:write Standard Idem-Key Pflicht Aufmaßzeile anlegen (ID = Idem-Key)
GET /v1/handwerk/aufmass/zeilen aufmass:read:<scope> Standard Liste (Filter OZ, Projekt, Zeitraum)
GET /v1/handwerk/aufmass/zeilen/{id} aufmass:read:<scope> Standard Detail
POST /v1/handwerk/aufmass/zeilen/{id}/replace aufmass:write Standard Idem-Key Korrektur-Zeile anlegen (Begründung Pflicht)
POST /v1/handwerk/aufmass/zeilen/{id}/conflict aufmass:write Standard Idem-Key Konflikt auflösen (both/mine/theirs/merged)
POST /v1/handwerk/aufmass/bloecke aufmass:write Standard Idem-Key Block anlegen, Zeilen zuordnen
POST /v1/handwerk/aufmass/bloecke/{id}/sign aufmass:sign Privileged Idem-Key Signatur hinterlegen
POST /v1/handwerk/aufmass/validate-formula aufmass:read Standard Formel trocken auswerten
GET /v1/handwerk/aufmass/summary aufmass:read:<scope> Standard Aggregation pro OZ für Rechnung (§4.5)
POST /v1/handwerk/aufmass/export aufmass:export Privileged Idem-Key DA11/DA12-Export starten
GET /v1/handwerk/aufmass/export/jobs/{jobId} aufmass:export Standard Job-Status
POST /v1/handwerk/aufmass/grundriss admin:write Admin Idem-Key Grundriss-PDF hochladen
POST /v1/handwerk/aufmass/fotos/presigned-url aufmass:write Standard S3-Multipart-URL (Foto-Upload)
paths:
/v1/handwerk/aufmass/validate-formula:
post:
operationId: validateRebFormula
x-werkszeit-scope: aufmass:read
x-werkszeit-rate-limit: standard
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [formel, mengeneinheit]
properties:
formel: { type: string, example: "4,80*2,60-1,20*2,10" }
variablen: { type: object, additionalProperties: { type: string } }
mengeneinheit: { type: string, enum: [m, m2, m3, Stk, h, kg, t, psch, l] }
rundung: { type: integer, default: 4, minimum: 0, maximum: 6 }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
ok: { type: boolean }
ergebnis: { type: number, format: double, example: 9.96 }
ergebnisStr: { type: string, example: "9,9600" }
tokenCount: { type: integer }
warnungen: { type: array, items: { type: string } }
'422':
content:
application/json:
schema:
type: object
properties:
fehlerCode: { type: string, example: "PARSE_ERR" }
position: { type: integer, example: 12 }
meldung: { type: string }
  • aufmass.zeile.created
  • aufmass.zeile.replaced (Korrektur-Zeile)
  • aufmass.zeile.conflict (Detection)
  • aufmass.zeile.conflict.resolved
  • aufmass.block.signed
  • aufmass.export.done (Payload: jobId, format, s3Key)

Profil Begründung Mechanik
Voll-offline (Mobile Aufmaß-Erfassung) Baustellen haben regelmäßig kein LTE; Arbeitsfluss muss komplett ohne Netz laufen SQLite lokal; Outbox + Idempotency-Key (UUID v7 = Zeile-ID); Foto-Multipart-Upload queued; Formel-Interpreter in Dart lokal; Konflikt-Resolution bei Sync (Diff-UI)
Online-Review + Export (Web) Admin-Review benötigt Server-Konsistenz REB-Export läuft als Lambda-Job, UI blockt Export bei connectivity=none

Expliziter Nicht-LWW-Algorithmus:

  1. Jede Zeile hat (lv_version_id, oz, source_device, zeile_nr). zeile_nr wird lokal pro Device vergeben, source_device aus Flutter-Fingerprint.
  2. Beim Sync sendet der Client: POST /zeilen mit Idempotency-Key: <zeile.id> (UUID v7).
  3. Server prüft:
    • Existiert id bereits? → 200 mit bestehender Zeile (reine Idempotenz).
    • Existiert andere Zeile mit gleicher (lv_version_id, oz, raum, sourceDevice != input.sourceDevice, messzeitpunkt ± 15 min)? → Conflict-Detection, Server markiert beide Zeilen mit conflict_with=<id> und antwortet 201 mit Conflict-Flag (keine Fehlermeldung, Zeile wird gespeichert — beide existieren).
    • Wenn nur eine existiert: Insert, 201.
  4. UI zeigt Konflikt-Diff-Screen (§6.3). User wählt Auflösung.
  5. POST /zeilen/{id}/conflict mit Resolution both_kept|mine|theirs|merged + Begründung → Audit-Log.
  6. Bei mine/theirs: jeweils andere Zeile bekommt replacedReason='konflikt_aufloesung' und immutableAt=NOW() (GoBD-append-only — nicht gelöscht, nur markiert).
  7. Bei merged: User bearbeitet Felder manuell, neue Zeile mit replaces_zeile_id auf beide Vorgänger.

Warum nicht LWW? Weil zwei Monteure, die parallel offline zur selben Wand messen, wahrscheinlich unterschiedliche Wand-Abschnitte meinten — beide Zeilen sind gültig, nur der Raum-Bezug war uneindeutig. LWW würde stillschweigend einen Messwert vernichten. (LESSONS-LEARNED §4.)

  • Foto wird in SQLite als BLOB-Thumbnail (≤ 20 KB) + S3-Key-Platzhalter angelegt
  • Outbox-Queue: Multipart-Upload gegen presigned URL (Retry mit exponential backoff, max 5)
  • Nach Upload: fotos[i].sha256 gesetzt, Thumbnail bleibt als Cache
  • Bei Upload-Fehler (> 5×): User bekommt „Foto-Upload hängt“-Banner, kann manuell retriggern

  • Append-only aufmass_zeile: Signierte oder exportierte Zeilen sind unveränderlich (immutable_at gesetzt, RLS-Policy blockt UPDATE). Korrektur erzeugt neue Zeile mit replaces_zeile_id + replaced_reason Pflichtfeld.
  • Hash-Chain: Pro Block und pro Zeile hash_self = SHA-256(canonicalJSON). Kette an globalen Audit-Hash.
  • S3 Object Lock (Compliance-Mode): Fotos, Signatur-PNG, PDF-Grundrisse 10 Jahre ab immutable_at. Schlüssel s3://werkszeit-gobd/{tenantId}/aufmass/{projectId}/{blockId}/....
  • Verfahrensdokumentation: gobd-verfahrensdoku.md Abschnitt „Aufmaß-Erfassung mobil“.

Nicht zutreffend (keine Arbeitszeit-Erfassung im Aufmaß-Flow). Randnotiz: Wenn Monteur 90 min lang Aufmaß macht, sollte in der regulären Zeiterfassung parallel gestempelt sein (Verantwortung §3.1).

  • VOB/C (DIN 18299 ff.): Gewerke-spezifische Aufmaß-Regeln (z.B. DIN 18350 Putz-Arbeiten: „Putzflächen werden ohne Aussparungen bis 2,5 m² abgerechnet“ — das ist Geschäftslogik des Kalkulators, nicht Werkszeit-Feature). Werkszeit liefert die Roh-Zeilen, die Anwendung der Regeln erfolgt in der Abrechnung (§4.5) oder im Kalkulations-Regelwerk (§4.3).
  • VOB/B §14 Abs. 1 (prüfbare Schlussrechnung): Aufmaß-Summen pro OZ müssen aus DA11/DA12 rückrechenbar sein — das stellt unser REB-Export sicher.
  • VOB/B §14 Abs. 2 (gemeinsames Aufmaß): Signatur durch Bauherr/AG vor Ort ist die digitale Umsetzung des „gemeinsamen Aufmaßes“. UI-Disclaimer gem. §6.2 weist auf rechtliche Wirkung hin.
  • Art. 5 Datenminimierung: Signatur-Name + Rolle ist ausreichend; keine E-Mail / Geburtsdatum des Bauherrn im Standard-Flow.
  • Art. 6 Rechtsgrundlage: Art. 6 Abs. 1 lit. b (Vertrag mit AG) + Abs. 1 lit. f (berechtigtes Interesse Dokumentation).
  • Art. 17 Löschung: Signatur-Name kann nach Ablauf Aufbewahrungsfrist maskiert werden (signatur_name='[gelöscht]'), Signatur-Hash bleibt.
  • Art. 20 Export: Bauherr kann per Portal seine signierten Zeilen + Fotos als PDF/ZIP herunterladen.
  • DSFA: Prüfung nötig, wenn Geo-Tag aktiviert ist (Bewegungsprofil-Risiko Mitarbeiter); Default deaktiviert (siehe F-A-05).

Nicht zwingend Pflicht (kein ESS-Flow, kein öffentlicher Zugang im Kern). Aber: Mobile-App-A11y (TalkBack, VoiceOver, Kontrast) ist für den Feld-Monteur praktisch — Semantics-Labels auf Stempel-/Signatur-Buttons Pflicht.

Relevant bei aktivierter Geo-Tag-Funktion (F-A-05): Betriebsrat-Freischaltungs-Workflow mit dokumentierter BR-Zustimmung, Default deaktiviert. Audit-Eintrag bei Aktivierung mit BR-Beschluss-Nummer.

  • REB 23.003 (BMV-Bund): DA11- und DA12-Export gegen amtliche Muster-Dateien validieren (siehe C-04). Zeichensatz CP850 default (historische Kompatibilität), UTF-8 opt-in.
  • §147 AO: 10-Jahre-Aufbewahrung Aufmaß-Dateien als Buchungsgrundlage.
  • StGB §267 (Urkundenfälschung): Signatur-Manipulation wäre strafbar. Werkszeit’s Maßnahmen: Signatur-Hash in Audit-Log, Mobile-Gerät-Fingerprint, Zeitstempel + Geo-Tag (wenn aktiviert). Dokumentation im Verfahrensdoku-Textbaustein.

# Szenario Erwartetes Verhalten
EC-01 Formel mit rm -rf / Parser-Error PARSE_ERR, Zeile wird nicht gespeichert, UI zeigt rote Inline-Markierung
EC-02 Division durch 0 (10/0 in Formel) Laufzeit-Fehler „Division durch 0“, Zeile nicht speicherbar
EC-03 Offline 4 h, 60 Zeilen erfasst, dann Sync Outbox-Queue arbeitet Batch-weise; Konflikte pro Zeile einzeln gemeldet; Fotos hintergrund-synchronisiert
EC-04 Sync-Konflikt: dieselbe OZ, beide Geräte, ± 2 min Zeitunterschied Conflict-Detection-Heuristik (15 min + Device-Fingerprint); UI-Dialog §6.3; Begründung Pflicht
EC-05 Multi-Tenant-Bleed: User versucht Aufmaß-Zeile aus Tenant B zu lesen 404, kein Audit-Log in B
EC-06 Bauherr verweigert Signatur Block bleibt im Status entwurf, UI-Notiz „Signatur optional nachholen“. Teilaufmaß-Export trotzdem möglich (mit Warnung im PDF)
EC-07 Signatur-Hash Bruch (manuelle DB-Änderung am Signatur-PNG) Compliance-Test C-01: Re-Hash-Check schlägt Alarm, Block wird als verdacht markiert, Tech-Lead-Eskalation
EC-08 Formel mit 2.000 Zeichen (Copy-Paste-Angriff) Token-Limit 500, Parser bricht mit Meldung „Formel zu komplex (max 500 Tokens)“ ab
EC-09 Foto-Upload bricht permanent (kein Netz 3 Tage) UI-Banner „1 Foto wartet auf Upload“, User kann manuell retriggern; Zeile bleibt nutzbar mit Thumbnail-only
EC-10 LV-Version wird nach Aufmaß-Erfassung geändert (z.B. durch Nachtrag, neue Version v4) Aufmaß-Zeilen bleiben an ursprünglicher v3 gebunden; Rechnungs-Engine muss entscheiden, wohin sie zählt
EC-11 Grundriss-Pin außerhalb der PDF-Seite Validierung: Pin-Koordinaten normalisiert 0..1 pro Seite; ungültige Koords werden auf Rand geklemmt
EC-12 EXIF-GPS der Foto-Datei weicht > 500 m von geo_lat/lon der Zeile ab Audit-Warnung (nicht blockierend), Badge „GPS-Abweichung“ in Review-Tabelle
EC-13 REB-DA12-Export mit Zeilen, die Kommentar-Zeichen # im Raum-Feld enthalten Escape bei Serialisierung, Round-Trip-Test
EC-14 Plan-Limit Starter: Kunde hat GAEB (V1) nicht gekauft → LV-Version fehlt → Aufmaß-Zugriff Modul-Gate module.handwerk.aufmass hat harte Abhängigkeit auf module.handwerk.gaeb; UI blockiert Aufmaß ohne LV, zeigt Upgrade-Modal

Funktionalität: Mobiles Aufmaß nach REB 23.003
Hintergrund:
Angenommen ein Tenant "shk-gebruder-schmidt" mit aktivem Modul-Gate "module.handwerk.aufmass"
Und ein Projekt "Sanierung Schule A" mit beauftragter LV-Version v3
Und die OZ "01.02.030" ("Putz-Innenwand abreißen", ME=m², Menge=128,50)
Und ein Bauleiter "Sabine" mit Rolle "Bauleitung" und Scope "team"
Szenario: Happy Path — Offline-Erfassung mit Formel und Signatur
Angenommen Sabines Tablet ist offline
Wenn sie die Aufmaß-App öffnet und OZ "01.02.030" wählt
Und eine neue Zeile für Raum "Klassenzimmer 1.04" anlegt
Und die Formel "4,80*2,60-1,20*2,10" eingibt
Dann wird das Ergebnis mit 9,96 m² (ROUND_HALF_EVEN, 4 NK) live angezeigt
Und ein Foto wird lokal an die Zeile gehängt
Und die Zeile wird in SQLite gespeichert mit source_device="device:UUID"
Wenn Sabine 5 weitere Zeilen erfasst (gesamt 64,20 m²)
Und den Bauherrn "Herbert Özdemir" signieren lässt
Dann wird ein aufmass_block mit status="signiert" lokal angelegt
Und signatur_hash ist SHA-256 über png+name+rolle+timestamp
Wenn das Tablet wieder Netz hat
Dann synchronisieren 6 Zeilen + 1 Block mit Idempotency-Key ohne Duplikate
Und die Fotos werden per S3-Multipart hochgeladen
Und ein Audit-Log-Eintrag "aufmass.block.signed" wird erzeugt
Szenario: Grenzfall — Formel-Validator-Fehler
Wenn Sabine die Formel "4,80*(2,60-" eingibt
Dann zeigt der Interpreter den Fehler "PARSE_ERR: unerwartetes Dateiende nach '('"
Und das Eingabefeld wird rot markiert
Und die Zeile kann nicht gespeichert werden
Szenario: Grenzfall — Sync-Konflikt zwei Geräte parallel offline
Angenommen Hannes (Monteur) hat offline für OZ "01.02.030" eine Zeile "001" mit 12,48 m² erfasst
Und Sabine (Bauleitung) hat offline für dieselbe OZ eine Zeile "001" mit 9,96 m² erfasst
Beide messzeitpunkt innerhalb 4 min
Wenn beide Geräte synchronisieren
Dann werden beide Zeilen als "conflict_with" verknüpft gespeichert
Und beide Clients zeigen den Konflikt-Diff-Screen
Und Sabine wählt "Beide behalten (als A/B)" mit Begründung "getrennte Wandabschnitte"
Dann werden die Zeile-Nummern automatisch auf "001A" und "001B" umnummeriert
Und ein Audit-Log-Eintrag "aufmass.zeile.conflict.resolved" wird mit der Begründung erzeugt
Szenario: Grenzfall — Versions-Migration LV ändert sich nach Aufmaß-Erfassung
Angenommen Aufmaß-Zeile für OZ "01.02.030" (LV v3) existiert und ist signiert
Wenn der Kalkulator einen Nachtrag auf OZ "01.02.030.N1" in LV v4 anlegt
Dann bleibt die bestehende Aufmaß-Zeile an v3 gebunden
Und die Summary-API "/v1/handwerk/aufmass/summary?lvVersionId=v3" zeigt sie weiterhin
Und die Rechnungs-Engine kann beide Versionen korrekt zuordnen
Szenario: Grenzfall — Multi-Tenant-Isolation
Angenommen ein zweiter Tenant "musterbetrieb-maler"
Wenn Sabine (Tenant "shk-gebruder-schmidt") per API eine Aufmaß-Zeile-ID aus "musterbetrieb-maler" anfragt
Dann antwortet die API mit 404
Und es entsteht KEIN Audit-Log-Eintrag in "musterbetrieb-maler"
Szenario: Grenzfall — REB-DA12-Export gegen amtliches Muster
Angenommen 62 Aufmaß-Zeilen sind signiert und exportbereit
Wenn Sabine den Export als "DA12, CP850, CR-LF" startet
Dann erzeugt die Lambda eine Datei mit dem REB-23.003-Header und genau 62 Datensätzen
Und die Compliance-Test-Suite (C-04) validiert diese Datei gegen das amtliche Muster mit 0 Abweichungen
Und jeder Datensatz enthält OZ + Formel + Ergebnis + Raum
Und ein Audit-Log-Eintrag "aufmass.export.done" wird erzeugt

  • U-01 — Formel-Parser: 50 Referenz-Formeln aus REB-Muster-Dokumentation, beide Sprachen liefern identisches Ergebnis (Property-Test, ±0 in 6. Nachkommastelle)
  • U-02 — Formel-Injektion: 30 Angriffs-Strings (eval, system, import, SQL-Injection-Patterns) — alle PARSE_ERR
  • U-03 — Rundungs-Property-Test: 100.000 Zufalls-Tripel (l, b, abz) — 4 NK HALF_EVEN identisch zwischen Dart/TS/Postgres
  • U-04 — OZ-Scope-Filter: lv_knoten filter für lv_version.phase=='auftrag' AND immutable_at IS NOT NULL liefert keine Entwürfe
  • U-05 — Hash-Chain: Mutation an aufmass_zeile.ergebnis nach Setzen von immutable_at bricht hash_self-Kette — Detector erkennt
  • W-01 — Formel-Input: Eingabe erzeugt Live-Ergebnis innerhalb 50 ms (Stopwatch-Assertion)
  • W-02 — Signatur-Pad: Touch-Events werden zu SVG-Path konvertiert, Hash stabil
  • W-03 — Foto-Pin auf Grundriss: Tap auf PDF-Tile erzeugt normalisierte Koords 0..1
  • W-04 — Golden-Tests: Aufmaß-Detail (Light/Dark/de-DE), Konflikt-Diff-Screen, Signatur-Pad
  • W-05 — Keyboard-Input auf Numeric-Pad: Komma und Punkt beide akzeptiert
  • I-01 — Multi-Tenant-Isolation (Pflicht): Tenant A legt Zeile an, Tenant B bekommt 404 auf allen Pfaden (GET, PATCH, POST-replace, POST-conflict, DELETE, export)
  • I-02 — RLS-Scope own: Monteur sieht nur eigene Zeilen, Bauleiter (team) sieht Team-Zeilen
  • I-03 — Idempotenz: POST /zeilen mit identischem Idem-Key → 2. Request liefert bestehende Zeile, kein 2. DB-Insert
  • I-04 — Konflikt-Detection: Zwei POSTs mit gleicher (lv_version_id, oz, raum) ±15 min von verschiedenen Devices → conflict_with auf beiden
  • I-05 — Append-Only: UPDATE auf aufmass_zeile.ergebnis nach Signatur → 409, kein DB-Write
  • I-06 — LV-Gate: POST /zeilen auf LV-Version mit phase!='auftrag' → 422 LV_NOT_AUTHORIZED
  • E-01 — Patrol iOS+Android + Playwright Web: Happy Path §12, Offline-Switch simuliert (Patrol network off → on)
  • E-02 — Konflikt-Flow: 2 Emulatoren gleichzeitig, gleiche OZ, Sync, Diff-Screen-Flow
  • E-03 — Foto-Upload bei wiederkehrender Netzstörung (Patrol-Net-Throttle)
  • E-04 — Visual-Regression: 8 Golden-Screens (Light/Dark × 4 Haupt-Flows)
  • E-05 — A11y: axe-core-Lauf auf Web-Review-Tabelle; Flutter-Semantics-Tests auf Mobile-Erfassung
  • C-01 — Hash-Chain-Integrität (Blöcke + Zeilen)
  • C-02 — Signatur-Hash-Integrität: Manipulation Signatur-PNG → Detection
  • C-03 — GoBD-Append-Only: automatische Prüfung, dass keine UPDATE-Statements auf aufmass_zeile mit immutable_at IS NOT NULL laufen können (INFORMATION_SCHEMA + Permission-Tests)
  • C-04REB-23.003-Referenz-Muster: Import+Export-Round-Trip mit amtlichen BMV-Bund-Mustern (3 Muster aus REB-Spezifikations-Anhang) — bit-identisch (modulo CR-LF-Canonical)
  • C-05 — Formel-Evaluations-Property-Test (Cross-Sprache Dart/TS)
  • C-06 — Foto-EXIF-Stripping (bevor S3-Upload entfernt: GPS-EXIF wird in separate Spalte extrahiert und aus Datei entfernt, damit DSGVO-Datenminimierung greift)
  • Lokaler 20× Re-Run der neuen E2E-Tests grün
  • Konflikt-Szenario E-02 läuft deterministisch (kontrollierte Zeit-Stubs)

Nicht-Ziel Begründung
CAD-Import (DWG, DXF) mit Auto-Vermessung Viel zu teuer (Autodesk-Library-Lizenz), nicht die Zielgruppe. PDF-Grundriss mit manuellen Pins genügt.
Automatisches Raum-Erkennen aus Foto / AR-Vermessung Reife nicht ausreichend für verbindliche Abrechnung. Ggf. V3 nach Benchmarking.
Komplexe Gewerke-spezifische Regeln (DIN 18350 ff.) automatisiert anwenden Das ist Aufgabe des Kalkulators / der Abrechnung — Werkszeit liefert Roh-Zeilen, nicht die Regel-Engine.
Aufmaß-Historie mehrerer LV-Versionen kombinieren Komplex und fehlerträchtig; in V1 bleibt Aufmaß-Zeile an einer LV-Version. Rechnungs-Engine aggregiert.
Kollaboratives Live-Editing (Google Docs-artig) Overkill für 1–3 Bauleiter pro Baustelle. Sync-Konflikt-Auflösung ist der gewählte Pfad.
Integration externer REB-Aufmaß-Tools (MENG, iTWO Messen) V2 bei Bedarf. Zielgruppe nutzt diese Tools nicht primär.
Blockchain für Signatur-Nachweis Buzzword, kein rechtlicher Mehrwert — SHA-256-Hash-Chain + Object Lock ist GoBD-konform und ausreichend.
Video-Aufzeichnung Messung Speicher-Overhead, DSGVO-riskant; Fotos genügen.

Risiko / Annahme Impact Wahrscheinlichkeit Gegenmaßnahme
Formel-Interpreter-Divergenz Dart ↔ TS (Floating-Point-Verhalten) hoch mittel decimal.Decimal (arbitrage Präzision) statt IEEE-754; Property-Tests als CI-Gate; Server ist Source of Truth bei Divergenz
Signatur-rechtliche Anerkennung (EES/AES-Frage) mittel niedrig Bauherr-Signatur ist EES (einfache elektronische Signatur) gem. eIDAS — für VOB/B §14 Abs. 2 mit gleichzeitigem Foto + Zeitstempel + Geo-Tag ausreichend. Rechtsgutachten vor V1 eingeplant.
Offline-Konflikt-Diff-UX ist für Monteure unverständlich mittel hoch User-Testing mit 3 Monteuren vor V1-Freeze; Fallback: Bauleitung löst Konflikte im Web, Monteur sieht nur Hinweis
PDF-Grundriss-Upload (größere Pläne > 100 MB) mittel mittel Chunk-Upload + serverseitige Tile-Generation via Lambda; Limits dokumentiert
BMV-REB-Muster-Dateien kostenpflichtig / NDA niedrig niedrig Kostenlos beim BMV beziehbar; Klärung vor Sprint-Start
Kompatibilität mit AG-System (iTWO Validate): unser DA12-Export wird abgelehnt hoch mittel Muster-Export gegen RIB iTWO Import im Monat 3 testen, Schematron-Profil anpassen
Bluetooth-Laser-Disto Bluetooth-Pairing-Stabilität niedrig mittel Feature V1.5, Prototyping-Phase mit 2 Geräten (Leica Disto D2, Bosch GLM 50-27C)

  • Vorbedingung:
    • handwerk/03-gaeb-lv — LV-Version (beauftragt) als Datenquelle
    • kern/11-rollen-rls — Rolle „Bauleitung“, Scope-Modell
    • kern/12-audit-log — Hash-Chain-Infrastruktur
    • kern/offline-outbox — Idempotency-Key, Outbox-Queue
  • Schnittstelle zu:
    • handwerk/05-bau-abrechnung.md — Aufmaß-Summary liefert Mengen für Rechnungspositionen
    • handwerk/04.2-nachtrag — OZ-Bezug + Aufmaß-Mengen → Nachtrags-Anker
    • Bautagebuch (§4.1) — Foto-Referenz auf OZ-Ebene
  • Wird konsumiert von:
    • Schlussrechnung (§4.5) — prüfbare Menge je OZ
    • Soll/Ist-Dashboard Kalkulator (§4.3)
    • Nachtrags-Modul

Status: TBD — zu klären mit Sales.

DOR §1.1.1 verlangt: „Mindestens ein zahlender Design-Partner ist namentlich dokumentiert, der das Feature konkret fordert.“

Kandidaten-Profile:

  • 20–100-MA-Bauunternehmen (Hochbau, SHK, Trockenbau) mit öffentlichen AG (Stadt, DB, Landesbau)
  • Betrieb mit bereits vorhandener mobiler IT (iPads/Android-Tablets im Feld)
  • Klarer Pain-Punkt: „Zollstock + Notizblock + Excel“ oder „iTWO-Aufmaß zu teuer“

Validierungs-Fragen:

  1. Wie viele Aufmaß-Zeilen erfasst Ihr Betrieb pro Monat? Wie lange dauert die Büro-Nachbereitung?
  2. Welche AG verlangen DA11/DA12-Format? Gab es 2024/2025 Rechnungs-Ablehnungen wegen fehlender REB-Daten?
  3. Wie oft kommt es zum Aufmaß-Streit mit Bauherrn? Was würde eine Vor-Ort-Signatur wert sein?
  4. Bereit für 30-Tage-Beta mit 1 Bauleiter auf 1 Baustelle und einem Probe-Export an einen echten AG?

  1. Datenmodell + RLS + Multi-Tenant-Test (wie immer zuerst). Inkl. Hash-Chain-Tabellen.
  2. Formel-Interpreter (packages/reb_formula in Dart + TS-Port) — mit Property-Tests bevor UI gebaut wird. Das ist die kritischste Single-Piece-of-Logic.
  3. Backend-API + OpenAPI → Dart-Client-Generierung.
  4. Mobile-UI Kern-Flow: OZ wählen · Zeile anlegen · Formel tippen · Foto · Speichern · Block + Signatur · Sync.
  5. Offline-Outbox-Integration (Idempotency-Key, Foto-Multipart, Konflikt-Detection-Client-Side).
  6. Web-Review-Tabelle + REB-Export-Lambda (DA11 + DA12, CP850-Serializer, C-04-Muster-Tests).
  7. Grundriss-PDF-Upload + Tile-Generation + Pin-Flow.
  8. Konflikt-Diff-UI Mobile + Web (die UX-Risiko-Komponente, nimmt mindestens 3 Iterationen).
  9. Soll-/Ist-Dashboard als Zusatz, nach dem Kern-Flow.
  10. Compliance-Tests C-01…C-06 vollständig, Gate vor V1-Freeze.
  11. E2E-Suite + Doku-PR mit auto-generierten Screenshots.
  • Formel-Interpreter + Multi-Tenant + Hash-Chain + Konflikt-Auflösung = nicht verhandelbar.
  • Zuerst einsparbar: Bluetooth-Laser (V1.5), Aufmaß-Vorlagen (V1.5), Bauherr-Portal (V1.5), Geo-Tag (opt.), Soll-/Ist-Dashboard-Visualisierung (Tabelle allein reicht anfangs).
  • Multi-Tenant-Bleed in irgendeinem Test.
  • Hash-Chain-Bruch in C-01 / C-02.
  • Formel-Interpreter-Divergenz Dart↔TS > 1 ppm.
  • REB-DA12-Muster-Round-Trip scheitert für ein amtliches Muster.
  • Signatur-Hash ist nicht reproduzierbar nach Re-Serialisierung.
  • packages/reb_formula als kleines, stabiles, property-getestes Paket: Der nächste Anwendungsfall (Kalkulations-Formeln in §4.3, ggf. Nachtrags-Automatik) konsumiert es ohne Neuaufbau.
  • Konflikt-First statt LWW: Wir werden nicht in 6 Monaten panisch rückwärts-migrieren, wenn der erste Kunde merkt, dass Messungen still gelöscht wurden.
  • Signatur-Hash-Chain: spart vor Gericht Tage an Nachweis-Arbeit, falls ein Bauherr behauptet, er habe nie unterschrieben.
  • Voll-offline von Tag 1: Jeder Feature-Nachrüstungs-Versuch für Offline-Zwang ist teurer als der Initialaufwand. Die Alt-App hat das genau umgekehrt gelernt (LESSONS-LEARNED §4).

Letzte Aktualisierung: 2026-04-19. Änderungen erfordern PR-Review durch Tech-Lead + PO.

Für Entwickler — API-Endpoints14
MethodePfadAuthZweck
POST/v1/handwerk/aufmass/bloeckebearerAuthAufmaß-Block anlegen
POST/v1/handwerk/aufmass/bloecke/{id}/signbearerAuthBauherr-Signatur hinterlegen (VOB/B §14 Abs. 2)
POST/v1/handwerk/aufmass/exportbearerAuthREB-Export DA11/DA12 starten [out-of-scope v1 → 501]
GET/v1/handwerk/aufmass/export/jobs/{jobId}bearerAuthExport-Job-Status [out-of-scope v1 → 501]
POST/v1/handwerk/aufmass/fotos/presigned-urlbearerAuthS3-Foto-Presigned-URL [out-of-scope v1 → 501]
POST/v1/handwerk/aufmass/grundrissbearerAuthGrundriss-PDF hochladen [out-of-scope v1 → 501]
POST/v1/handwerk/aufmass/reb-validatebearerAuthREB-Formel validieren (Variante reb-validate).
GET/v1/handwerk/aufmass/summarybearerAuthAufmaß-Aggregation pro OZ (Rechnungs-Import §4.5)
POST/v1/handwerk/aufmass/validate-formulabearerAuthREB-Formel validieren und auswerten (§5.6 Spec)
GET/v1/handwerk/aufmass/zeilenbearerAuthAufmaßzeilen abrufen (gefiltert)
POST/v1/handwerk/aufmass/zeilenbearerAuthAufmaßzeile anlegen (Offline-First, Idempotency-Key = Zeile-UUID v7)
GET/v1/handwerk/aufmass/zeilen/{id}bearerAuthAufmaßzeile (Detail)
POST/v1/handwerk/aufmass/zeilen/{id}/conflictbearerAuthSync-Konflikt auflösen (both_kept|mine|theirs|merged)
POST/v1/handwerk/aufmass/zeilen/{id}/replacebearerAuthKorrektur-Zeile anlegen (GoBD append-only)