diff --git a/mandates/pek/datenmodell/PEK Datenmodell.md b/mandates/pek/datenmodell/PEK Datenmodell.md
new file mode 100644
index 0000000..61c2e67
--- /dev/null
+++ b/mandates/pek/datenmodell/PEK Datenmodell.md
@@ -0,0 +1,558 @@
+# Datenmodell Architektur-Planungs-App
+
+## Übersicht
+
+Dieses Datenmodell bildet die Grundlage für eine Schweizer Architektur-Planungs-App zur Verwaltung von Bauprojekten, Parzellen, Dokumenten und regulatorischen Kontextinformationen.
+
+## Datenfluss-Diagramm
+
+```mermaid
+---
+title: Hauptflüsse - Architektur-Planungs-App
+---
+flowchart TD
+ Start([Datenmodell Start])
+
+ subgraph Admin[Administrative Ebene]
+ Land[LAND
Schweiz]
+ Kanton[KANTON
z.B. Zürich]
+ Gemeinde[GEMEINDE
z.B. Zürich Stadt]
+ Land --> Kanton
+ Kanton --> Gemeinde
+ end
+
+ subgraph Geo[Geografische Daten]
+ Parzelle[PARZELLE
Grundstück mit
Bauparametern]
+ GeoPunkt[GEO_PUNKT
Koordinaten]
+ Gemeinde --> Parzelle
+ Parzelle --> GeoPunkt
+ end
+
+ subgraph Core[Kern-Business-Logik]
+ Projekt[PROJEKT
Bauprojekt]
+ Dokument[DOKUMENT
Dateien & URLs]
+ Projekt -.Perimeter.-> Parzelle
+ Projekt -.Dokumente.-> Dokument
+ Parzelle -.Dokumente.-> Dokument
+ end
+
+ subgraph Support[Unterstützende Daten]
+ Kontext[KONTEXT
Zusatzinfos]
+ Projekt --> Kontext
+ Parzelle --> Kontext
+ end
+
+ Start --> Admin
+
+ style Land fill:#50C878,stroke:#2D7A4A,stroke-width:2px,color:#fff
+ style Kanton fill:#50C878,stroke:#2D7A4A,stroke-width:2px,color:#fff
+ style Gemeinde fill:#50C878,stroke:#2D7A4A,stroke-width:2px,color:#fff
+ style Parzelle fill:#4A90E2,stroke:#2E5C8A,stroke-width:3px,color:#fff
+ style Projekt fill:#4A90E2,stroke:#2E5C8A,stroke-width:3px,color:#fff
+ style Dokument fill:#4A90E2,stroke:#2E5C8A,stroke-width:3px,color:#fff
+ style GeoPunkt fill:#F5A623,stroke:#C17D11,stroke-width:2px,color:#fff
+ style Kontext fill:#F5A623,stroke:#C17D11,stroke-width:2px,color:#fff
+```
+
+---
+
+## Alle Datenobjekte als Tabellen
+
+### Übersichtstabelle
+
+| Objekt | Typ | Beschreibung | Hauptfelder |
+|--------|-----|--------------|-------------|
+| **Projekt** | Hauptentität | Bauprojekt mit Status und Perimeter | id, label, statusProzess |
+| **Parzelle** | Hauptentität | Grundstück mit Bauparametern | id, label, bauzone, AZ, BZ |
+| **Dokument** | Hauptentität | Dateien und URLs mit Versionierung | id, label, typ, format |
+| **Land** | Admin | Nationale Ebene | id, label |
+| **Kanton** | Admin | Kantonale Ebene mit Baurecht | id, label, Baureglement |
+| **Gemeinde** | Admin | Gemeinde-Ebene mit BZO | id, label, plz, BZO |
+| **GeoPunkt** | Hilfsobjekt | 3D-Koordinate im LV95 | x, y, z, referenzen |
+| **Kontext** | Hilfsobjekt | Flexible Zusatzinformationen | id, thema, inhalt |
+| **Tag** | Enum | Dokumentkategorien | - |
+| **GeoTag** | Enum | Geopunkt-Kategorien | - |
+| **JaNein** | Enum | Drei-wertiger Status | "", "Ja", "Nein" |
+| **StatusProzess** | Enum | Projektstatus | 7 Werte |
+
+---
+
+## Zentrale Entitäten
+
+### 1. Projekt
+**Das Kernobjekt, das ein Bauprojekt repräsentiert.**
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `id` | UUID | ✓ | Eindeutiger Identifier |
+| `label` | String | ✓ | Projektbezeichnung |
+| `statusProzess` | Array[Enum] | - | Projektstatus: Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv |
+| `perimeter` | Array[Parzelle] | - | Betroffene Parzellen (n:m Beziehung) |
+| `dokumenteBauherrschaft` | Array[Dokument] | - | Dokumente vom Bauherrn (n:m Beziehung) |
+| `dokumentePlanung` | Array[Dokument] | - | Planungsdokumente (n:m Beziehung) |
+| `geoBaulinie` | Array[GeoPunkt] | - | Baulinie als Polygonzug (1:n Beziehung) |
+| `kontextInformationen` | Array[Kontext] | - | Projektspezifische Kontextinfos (1:n Beziehung) |
+
+**Beziehungen:**
+- **n:m** zu Parzelle (Perimeter über `projekt_parzelle`)
+- **n:m** zu Dokument (Bauherrschaft über `projekt_dokument_bauherrschaft`)
+- **n:m** zu Dokument (Planung über `projekt_dokument_planung`)
+- **1:n** zu GeoPunkt (Baulinie)
+- **1:n** zu Kontext
+
+---
+
+### 2. Parzelle
+**Repräsentiert ein Grundstück mit allen baurechtlichen Eigenschaften.**
+
+#### Grunddaten
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `id` | UUID | ✓ | Eindeutiger Identifier |
+| `label` | String | ✓ | Parzellenbezeichnung |
+| `parzellenNummern` | Array[String] | - | Offizielle Parzellennummern |
+| `eigentuemerschaaft` | String | - | Eigentümer der Parzelle |
+| `strasseNr` | String | - | Straße und Hausnummer |
+
+#### Geografischer Kontext
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `kontextLand` | Land | - | Land der Parzelle (n:1 Beziehung) |
+| `kontextKanton` | Kanton | - | Kanton der Parzelle (n:1 Beziehung) |
+| `kontextGemeinde` | Gemeinde | - | Gemeinde der Parzelle (n:1 Beziehung) |
+| `geoUmfang` | Array[GeoPunkt] | - | Parzellengrenze als Polygon |
+
+#### Nachbarschaft
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `nachbarEigentuemer` | Array[Parzelle] | - | Selbstreferenz zu angrenzenden Parzellen (n:m Beziehung) |
+
+#### Schutzzonen
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `hochwasserschutzzone` | String | - | Hochwasserschutzzone (falls zutreffend) |
+| `laermschutzzone` | String | - | Lärmschutzzone |
+| `grundwasserschutzzone` | String | - | Grundwasserschutzzone (falls zutreffend) |
+
+#### Bebauungsparameter
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `bauzone` | String | - | Bauzonenbezeichnung (z.B. W3, WG2, etc.) |
+| `az` | Float | - | Ausnützungsziffer |
+| `bz` | Float | - | Bebauungsziffer |
+| `vollgeschossZahl` | Integer | - | Anzahl zulässiger Vollgeschosse |
+| `anrechenbarDachgeschoss` | Float | - | Anrechenbarer Anteil Dachgeschoss (0.0 - 1.0) |
+| `anrechenbarUntergeschoss` | Float | - | Anrechenbarer Anteil Untergeschoss (0.0 - 1.0) |
+| `gebaeudehoehe_max` | Float | - | Maximale Gebäudehöhe in Metern |
+
+#### Abstandsregelungen
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `regelnGrenzabstand` | String | - | Regelungen zum Grenzabstand |
+| `regelnMehrlaengenzuschlag` | String | - | Regelungen zum Mehrlängenzuschlag |
+| `regelnMehrhoehenzuschlag` | String | - | Regelungen zum Mehrhöhenzuschlag |
+
+#### Eigenschaften (Ja/Nein)
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `parzelleBebaut` | JaNein | - | Ist die Parzelle bebaut? ("", "Ja", "Nein") |
+| `parzelleErschlossen` | JaNein | - | Ist die Parzelle erschlossen? ("", "Ja", "Nein") |
+| `hanglage` | JaNein | - | Liegt die Parzelle in Hanglage? ("", "Ja", "Nein") |
+
+#### Weitere Informationen
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `spezifischeDokumente` | Array[Dokument] | - | Parzellenspezifische Dokumente (n:m Beziehung) |
+| `kontextInformationen` | Array[Kontext] | - | Parzellenspezifische Kontextinfos (1:n Beziehung) |
+
+**Beziehungen:**
+- **n:1** zu Land, Kanton, Gemeinde (geografischer Kontext)
+- **n:m** zu Parzelle (Nachbarn über `parzelle_nachbar`)
+- **n:m** zu Dokument (über `parzelle_dokument`)
+- **1:n** zu GeoPunkt (Umfang als Polygon)
+- **1:n** zu Kontext
+
+---
+
+### 3. Dokument
+**Verwaltet Dateien und URLs mit Versionierung.**
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `id` | UUID | ✓ | Eindeutiger Identifier |
+| `label` | String | ✓ | Dokumentbezeichnung |
+| `versionsbezeichnung` | String | - | Versionsnummer oder -bezeichnung (z.B. "v1.0", "Rev. A") |
+| `typ` | Enum | ✓ | Art des Dokuments: `Datei` oder `Url` |
+| `format` | String | - | Dateiformat (z.B. "PDF", "DWG", "IFC", "URL") |
+| `dokumentReferenz` | String | ✓ | Dateipfad oder URL |
+| `tags` | Array[Tag] | - | Kategorisierung (siehe Tag-Enum) |
+
+#### Tag-Enum (Dokumentkategorien)
+
+| Tag | Beschreibung |
+|-----|--------------|
+| `Kataster Objekte` | Amtliche Vermessung |
+| `Kataster Werkeleitungen` | Leitungskataster |
+| `Kataster Belastete Standorte` | Altlasten |
+| `Kataster Bäume` | Baumkataster |
+| `Zonenplan` | Zonenpläne |
+| `Planungs- und Baugesetz (PGB)` | Kantonale Baugesetze |
+| `Bau- und Zonenordnung (BZO)` | Gemeinde BZO |
+| `Parkplatzverordnung` | Parkplatzregelungen |
+| `Eigentümerauskunft` | Grundbuch-Auszüge Eigentümer |
+| `Grundbuchauszug` | Vollständige Grundbuch-Auszüge |
+
+**Beziehungen:**
+- **n:m** zu allen Entitäten, die Dokumente referenzieren (Projekt, Parzelle, Land, Kanton, Gemeinde)
+
+---
+
+### 4. Geografische Entitäten
+
+#### GeoPunkt
+**Repräsentiert einen 3D-Punkt im Schweizer Koordinatensystem LV95 (EPSG:2056).**
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `x` | Float | ✓ | LV95 Ostwert (E), typisch 2'480'000 - 2'840'000 |
+| `y` | Float | ✓ | LV95 Nordwert (N), typisch 1'070'000 - 1'300'000 |
+| `z` | Float | - | Höhe über Meer in Metern |
+| `referenzen` | Array[GeoTag] | - | Kategorisierung des Punktes |
+
+**Verwendung:**
+- Parzellenumfang (Polygon)
+- Baulinie (Linienzug)
+- Einzelne Referenzpunkte
+
+#### GeoTag (Enum)
+
+| Kategorie | Beschreibung |
+|-----------|--------------|
+| `Referenzpunkt Kat. 1` | Fixpunkt höchster Genauigkeit |
+| `Referenzpunkt Kat. 2` | Fixpunkt mittlerer Genauigkeit |
+| `Referenzpunkt Kat. 3` | Fixpunkt niedriger Genauigkeit |
+| `Geometeraufnahme` | Vom Geometer vermessener Punkt |
+
+**Koordinatensystem-Beispiel (Zürich Hauptbahnhof):**
+- X (Ost): 2'683'140
+- Y (Nord): 1'247'850
+- Z (Höhe): 408 m ü. M.
+
+---
+
+### 5. Administrative Hierarchie
+
+#### Land
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `id` | UUID | ✓ | Eindeutiger Identifier |
+| `label` | String | ✓ | Landesname (z.B. "Schweiz") |
+| `dokumente` | Array[Dokument] | - | Nationale Gesetze (1:n Beziehung) |
+| `kontextInformationen` | Array[Kontext] | - | Nationale Kontextinformationen (1:n Beziehung) |
+
+**Beziehungen:**
+- **1:n** zu Kanton
+
+---
+
+#### Kanton
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `id` | UUID | ✓ | Eindeutiger Identifier |
+| `label` | String | ✓ | Kantonsname (z.B. "Zürich") |
+| `dokumente` | Array[Dokument] | - | Kantonale Dokumente (1:n Beziehung) |
+| `kontextInformationen` | Array[Kontext] | - | Kantonsspezifische Kontextinfos (1:n Beziehung) |
+| `baureglementAktuell` | Dokument | - | Aktuelles Baureglement |
+| `baureglementRevision` | Dokument | - | Baureglement in Revision |
+| `bauverordnungAktuell` | Dokument | - | Aktuelle Bauverordnung |
+| `bauverordnungRevision` | Dokument | - | Bauverordnung in Revision |
+
+**Beziehungen:**
+- **n:1** zu Land
+- **1:n** zu Gemeinde
+
+---
+
+#### Gemeinde
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `id` | UUID | ✓ | Eindeutiger Identifier |
+| `label` | String | ✓ | Gemeindename (z.B. "Zürich") |
+| `plz` | String | - | Postleitzahl |
+| `dokumente` | Array[Dokument] | - | Gemeindedokumente (1:n Beziehung) |
+| `kontextInformationen` | Array[Kontext] | - | Gemeindespezifische Kontextinfos (1:n Beziehung) |
+| `bzoAktuell` | Dokument | - | Aktuelle Bau- und Zonenordnung (BZO) |
+| `bzoRevision` | Dokument | - | BZO in Revision |
+
+**Beziehungen:**
+- **n:1** zu Kanton
+- **1:n** zu Parzelle
+
+---
+
+### 6. Kontext
+**Flexibles System für spezifische Informationen und Hinweise.**
+
+| Feld | Datentyp | Pflicht | Beschreibung |
+|------|----------|---------|--------------|
+| `id` | UUID | ✓ | Eindeutiger Identifier |
+| `thema` | String | ✓ | Bezeichnung des Themas |
+| `inhalt` | String | ✓ | Detaillierte Information (Text) |
+
+**Polymorphe Beziehung** - Kontext kann gehören zu:
+- Projekt (n:1)
+- Parzelle (n:1)
+- Land (n:1)
+- Kanton (n:1)
+- Gemeinde (n:1)
+
+#### Beispielthemen (nicht abschliessend)
+
+| Themenbereich | Beispiele |
+|---------------|-----------|
+| **Nutzung** | Vorgaben zur Erdgeschossnutzung (Wohnen erlaubt oder Pflicht für Gewerbe) |
+| **Rechte** | Dienstbarkeiten (Wegrechte, Nähebaurechte, etc.) |
+| **Parkierung** | Anforderung Parkplätze (Berechnung / Reduktionsfaktoren) |
+| **Ausnützung** | Ausnützungsübertragungen |
+| **Umwelt** | Schadstoffbelastungen auf Parzellen |
+| **Planung** | Aktive Gestaltungspläne |
+| **Lärm** | Lärmempfindlichkeitsstufen |
+| **Energie** | Mögliche Wärmenutzung (Wärmeverbundnetze; Fernwärme, Anergie) |
+| **Natur** | Baumbestand auf privaten Grundstücken |
+| **Schutz** | Isos (Ortsbild, Schutzstatus, Denkmalschutz, Weilergebiet, etc.) |
+| **Gefahren** | Naturgefahren (z.B. Objektschutzmassnahmen (Hochwasser)) |
+| **Revision** | Verweis auf aktuell in oder zukünftig in Revision befindlichen Normen/Gesetze (z.B. Revision PBG mit aktuell negativer Vorwirkung) |
+
+**Design-Rationale:**
+Das Kontext-Objekt ermöglicht flexibles Hinzufügen von projektspezifischen, parzellen-spezifischen oder regionalen Informationen ohne Schemaänderungen.
+
+---
+
+### 7. Hilfsentitäten & Enumerationen
+
+#### JaNein (Enum)
+**Drei-wertiger Zustand für optionale Ja/Nein-Fragen.**
+
+| Wert | Bedeutung |
+|------|-----------|
+| `""` (leer) | Unbekannt / Nicht erfasst |
+| `"Ja"` | Ja / Zutreffend |
+| `"Nein"` | Nein / Nicht zutreffend |
+
+**Verwendung:**
+- `parzelleBebaut`: Ist die Parzelle bebaut?
+- `parzelleErschlossen`: Ist die Parzelle erschlossen?
+- `hanglage`: Liegt die Parzelle in Hanglage?
+
+---
+
+#### StatusProzess (Enum)
+**Projektstatus zur Nachverfolgung des Projektfortschritts.**
+
+| Wert | Beschreibung |
+|------|--------------|
+| `Eingang` | Projekt wurde eingereicht |
+| `Analyse` | Projekt wird analysiert |
+| `Studie` | Machbarkeitsstudie läuft |
+| `Planung` | Planungsphase |
+| `Baurechtsverfahren` | Baubewilligung läuft |
+| `Umsetzung` | Bauprojekt in Umsetzung |
+| `Archiv` | Projekt abgeschlossen |
+
+**Besonderheit:**
+Ein Projekt kann mehrere Status gleichzeitig haben (z.B. "Analyse" und "Studie").
+
+---
+
+#### DokumentTyp (Enum)
+
+| Wert | Beschreibung |
+|------|--------------|
+| `Datei` | Physische Datei (PDF, DWG, etc.) |
+| `Url` | Externer Link / URL |
+
+---
+
+## Beziehungsdiagramm
+
+```
+PROJEKT
+├── perimeter [1:n] ──────────> PARZELLE
+│ ├── dokumenteBauherrschaft [1:n] ──> DOKUMENT
+│ ├── dokumentePlanung [1:n] ────────> DOKUMENT
+│ ├── geoBaulinie [1:n] ─────────────> GEO_PUNKT
+│ └── kontextInformationen [1:n] ───> KONTEXT
+
+PARZELLE
+├── nachbarEigentuemer [n:n] ──> PARZELLE (selbst)
+├── kontextLand [n:1] ──────────> LAND
+├── kontextKanton [n:1] ────────> KANTON
+├── kontextGemeinde [n:1] ──────> GEMEINDE
+├── geoUmfang [1:n] ────────────> GEO_PUNKT
+├── spezifischeDokumente [1:n] ─> DOKUMENT
+└── kontextInformationen [1:n] ─> KONTEXT
+
+DOKUMENT
+└── tags [n:n] ─────────────────> TAG
+
+GEO_PUNKT
+└── referenzen [1:n] ───────────> GEO_TAG
+
+LAND / KANTON / GEMEINDE
+├── dokumente [1:n] ────────────> DOKUMENT
+└── kontextInformationen [1:n] ─> KONTEXT
+```
+
+---
+
+## Junction Tables (Many-to-Many Beziehungen)
+
+**Zwischentabellen zur Auflösung von n:m Beziehungen:**
+
+| Tabelle | Verbindet | Felder | Beschreibung |
+|---------|-----------|--------|--------------|
+| `projekt_parzelle` | Projekt ↔ Parzelle | `projekt_id`, `parzelle_id` | Projektperimeter |
+| `projekt_dokument_bauherrschaft` | Projekt ↔ Dokument | `projekt_id`, `dokument_id` | Dokumente der Bauherrschaft |
+| `projekt_dokument_planung` | Projekt ↔ Dokument | `projekt_id`, `dokument_id` | Planungsdokumente |
+| `parzelle_nachbar` | Parzelle ↔ Parzelle | `parzelle_id`, `nachbar_id` | Nachbarschaftsbeziehungen |
+| `parzelle_dokument` | Parzelle ↔ Dokument | `parzelle_id`, `dokument_id` | Parzellenspezifische Dokumente |
+
+**Besonderheiten:**
+- `parzelle_nachbar`: Selbstreferenzierende Tabelle mit Constraint `parzelle_id != nachbar_id`
+- Alle Junction Tables haben zusammengesetzte Primärschlüssel aus beiden Foreign Keys
+- CASCADE DELETE empfohlen für automatische Bereinigung
+
+---
+
+## Implementierungshinweise
+
+### Datenbank-Design
+
+#### Empfehlung: Hybrid-Ansatz
+
+**Relationale Datenbank (PostgreSQL mit PostGIS):**
+- Stammdaten: Projekt, Parzelle, Land, Kanton, Gemeinde
+- Geografische Daten: GeoPunkt mit PostGIS-Geometrie-Typen
+- Strukturierte Queries und Joins
+
+**Dokumenten-Datenbank oder Blob Storage:**
+- Dokumente: S3, Azure Blob Storage oder MinIO
+- Metadaten in relationaler DB, Binärdaten extern
+
+#### Schema-Überlegungen
+
+**Normalisierung:**
+1. Land, Kanton, Gemeinde als separate Tabellen mit Referenzen
+2. Dokument als zentrale Tabelle, referenziert von mehreren Entitäten (Polymorphic Associations oder Junction Tables)
+3. GeoPunkt entweder embedded (JSON) oder separate Tabelle mit Foreign Keys
+
+**Denormalisierung für Performance:**
+- Häufig abgefragte Parzellendaten können gecached werden
+- Gemeinde.plz könnte redundant in Parzelle gespeichert werden
+
+### Geografische Daten
+
+**Koordinatensystem:**
+Schweizer Landessystem LV95 (EPSG:2056):
+- X (Ost): 2'480'000 - 2'840'000
+- Y (Nord): 1'070'000 - 1'300'000
+- Z (Höhe): Meter über Meer
+
+### Validierung
+
+**Pflichtfelder:**
+- Alle IDs (UUID)
+- Alle Labels
+- Dokument: typ, dokumentReferenz
+- GeoPunkt: x, y (z optional)
+- Kontext: thema, inhalt
+
+**Geschäftslogik-Validierungen:**
+- AZ und BZ müssen > 0 sein
+- VollgeschossZahl muss ≥ 0 sein
+- Geo-Koordinaten müssen in gültigem CH-Bereich liegen
+- Parzelle muss mindestens 3 GeoPunkte für gültiges Polygon haben
+
+### Sicherheit & Zugriffskontrolle
+
+**Überlegungen:**
+- Dokumente: Zugriffskontrolle nach Projekt-/Benutzerrolle
+- Eigentümerdaten: DSGVO-konforme Behandlung
+- Audit-Log für Änderungen an Bebauungsparametern
+- Versionierung von Dokumenten (via versionsbezeichnung)
+
+### Erweiterbarkeit
+
+**Flexible Bereiche:**
+1. **Kontext-Objekt**: Neue Themen können ohne Schema-Änderung hinzugefügt werden
+2. **Tag-System**: Erweiterbar um neue Dokumentkategorien
+3. **StatusProzess**: Kann projektspezifisch angepasst werden
+4. **GeoTag**: Neue Kategorien für Vermessungspunkte möglich
+
+**Migration-Strategy:**
+- Verwende Datenbank-Migrationen (z.B. Alembic für Python, Flyway für Java)
+- Behalte alte Enums bei, füge neue hinzu
+- Nutze nullable Felder für neue Eigenschaften
+
+---
+
+## Anwendungsfälle
+
+### Use Case 1: Neues Projekt anlegen
+1. Erstelle Projekt mit Label und Status
+2. Füge Parzellen zum Perimeter hinzu
+3. Lade Dokumente der Bauherrschaft hoch
+4. Verknüpfe Kontext-Informationen
+
+### Use Case 2: Bebaubarkeit prüfen
+1. Lade Parzelle mit allen Eigenschaften
+2. Prüfe AZ, BZ, Vollgeschosszahl
+3. Berücksichtige Hanglage, Schutzzonen
+4. Lade BZO der Gemeinde
+5. Prüfe Kontext-Informationen (Dienstbarkeiten, etc.)
+
+### Use Case 3: Nachbaranalyse
+1. Lade Parzelle
+2. Folge nachbarEigentuemer-Referenzen
+3. Zeige Eigentümer und Bebauung der Nachbarparzellen
+
+### Use Case 4: Dokumentensuche
+1. Suche über Tags (z.B. "Zonenplan")
+2. Filtere nach Format (z.B. PDF)
+3. Gruppiere nach Projekt/Parzelle/Gemeinde
+
+### Use Case 5: Revisionen verfolgen
+1. Prüfe Gemeinde.bzoRevision
+2. Prüfe Kanton.baureglementRevision
+3. Erstelle Kontext-Eintrag mit Hinweis auf negative Vorwirkung
+
+---
+
+## Offene Fragen / Zu klären
+
+1. **Versionierung**: Sollen Änderungen an Parzellen historisiert werden?
+2. **Mehrsprachigkeit**: Labels in DE/FR/IT?
+3. **Benutzer & Rollen**: Wer darf was bearbeiten?
+4. **Workflow-Engine**: Für Statusübergänge und Genehmigungen?
+5. **Integration**: Anbindung an amtliche Geodaten (z.B. Swisstopo API)?
+6. **Berechnungen**: Sollen Ausnützungsberechnungen automatisiert werden?
+
+---
+
+## Nächste Schritte
+
+1. **Validierung**: Review mit PEK
+2. **Prototyp**: Implementierung der Datenmodell-Klassen
+3. **GIS-Integration**: PostGIS aufsetzen, Test-Geodaten importieren
+4. **API-Design**: RESTful API (FastAPI) mit OpenAPI-Dokumentation
diff --git a/mandates/pek/datenmodell/README.md b/mandates/pek/datenmodell/README.md
new file mode 100644
index 0000000..9d59427
--- /dev/null
+++ b/mandates/pek/datenmodell/README.md
@@ -0,0 +1,409 @@
+# Architektur-Planungs-App - Datenmodell
+
+## Übersicht
+
+Dieses Repository enthält das vollständige Datenmodell für eine Schweizer Architektur-Planungs-Applikation zur Verwaltung von Bauprojekten, Parzellen, Dokumenten und regulatorischen Informationen.
+
+## 📁 Dateien
+
+### 1. **DATENMODELL_DOKUMENTATION.md**
+Umfassende Dokumentation mit:
+- Detaillierte Beschreibung aller Entitäten
+- Beziehungsdiagramme
+- Implementierungshinweise
+- Use Cases
+- Best Practices
+- Offene Fragen
+
+**Empfohlene Lesereihenfolge: Zuerst diese Datei lesen!**
+
+### 2. **datenmodell.mermaid**
+Visuelles ER-Diagramm zur Darstellung der Entitäten und Beziehungen.
+
+**Verwendung:**
+```bash
+# In Visual Studio Code mit Mermaid Extension
+# Oder online: https://mermaid.live/
+
+# Datei öffnen und als Diagramm anzeigen
+```
+
+### 3. **datenmodell-schema.json**
+JSON Schema Definition im JSON Schema Draft-07 Format.
+
+**Verwendung:**
+- API-Dokumentation mit Swagger/OpenAPI
+- Validierung von JSON-Payloads
+- Code-Generierung für verschiedene Sprachen
+
+```bash
+# JSON Schema validieren
+npm install -g ajv-cli
+ajv validate -s datenmodell-schema.json -d beispiel-daten.json
+```
+
+### 4. **models.py**
+Python SQLAlchemy Implementation mit PostGIS-Unterstützung.
+
+**Verwendung:**
+```bash
+# Installation
+pip install sqlalchemy geoalchemy2 psycopg2-binary --break-system-packages
+
+# Datenbank erstellen
+python models.py
+
+# In eigener Anwendung verwenden
+from models import Projekt, Parzelle, Dokument
+```
+
+**Tech Stack:**
+- Python 3.10+
+- SQLAlchemy 2.0+
+- PostgreSQL 15+ mit PostGIS 3.4+
+- GeoAlchemy2
+
+### 5. **schema.prisma**
+Prisma Schema für TypeScript/JavaScript Backend.
+
+**Verwendung:**
+```bash
+# Installation
+npm install prisma @prisma/client
+
+# Datenbank migrieren
+npx prisma migrate dev --name init
+
+# Prisma Client generieren
+npx prisma generate
+
+# Prisma Studio öffnen
+npx prisma studio
+```
+
+**Tech Stack:**
+- Node.js 18+
+- Prisma 5+
+- PostgreSQL 15+ mit PostGIS
+
+### 6. **migration_001_initial_schema.sql**
+SQL-Migrationsskript für direkte PostgreSQL-Verwendung.
+
+**Verwendung:**
+```bash
+# PostgreSQL Datenbank erstellen
+createdb architektur_app
+
+# Migration ausführen
+psql -d architektur_app -f migration_001_initial_schema.sql
+
+# Verbinden und testen
+psql architektur_app
+\dt # Tabellen anzeigen
+```
+
+## 🚀 Quick Start
+
+### Option 1: Python mit SQLAlchemy
+
+```bash
+# 1. PostgreSQL mit PostGIS aufsetzen
+docker run --name postgis -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgis/postgis:15-3.4
+
+# 2. Dependencies installieren
+pip install sqlalchemy geoalchemy2 psycopg2-binary --break-system-packages
+
+# 3. Models verwenden
+python models.py
+```
+
+### Option 2: TypeScript mit Prisma
+
+```bash
+# 1. Projekt initialisieren
+npm init -y
+npm install prisma @prisma/client
+
+# 2. Prisma konfigurieren
+cp schema.prisma prisma/schema.prisma
+
+# 3. Database URL setzen
+echo "DATABASE_URL=\"postgresql://user:password@localhost:5432/architektur_app\"" > .env
+
+# 4. Migration ausführen
+npx prisma migrate dev --name init
+```
+
+### Option 3: Direkt mit SQL
+
+```bash
+# 1. Datenbank erstellen
+createdb architektur_app
+
+# 2. Migration ausführen
+psql -d architektur_app -f migration_001_initial_schema.sql
+
+# 3. Daten einfügen und abfragen
+psql architektur_app
+```
+
+## 📊 Datenmodell-Struktur
+
+### Kern-Entitäten
+
+```
+PROJEKT (Bauprojekt)
+├── Perimeter (Parzellen)
+├── Dokumente (Bauherrschaft & Planung)
+├── Baulinie (geografisch)
+└── Kontext-Informationen
+
+PARZELLE (Grundstück)
+├── Geografischer Kontext (Land, Kanton, Gemeinde)
+├── Bauliche Parameter (AZ, BZ, Vollgeschosse, etc.)
+├── Schutzzonen
+├── Nachbarparzellen
+└── Dokumente & Kontext
+
+DOKUMENT (Datei oder URL)
+├── Versionierung
+├── Tags
+└── Format
+
+Administrative Hierarchie:
+LAND → KANTON → GEMEINDE
+```
+
+### Geografische Daten
+
+Das Modell verwendet das **Schweizer Landessystem LV95 (EPSG:2056)**:
+- Ostwert (X): 2'480'000 - 2'840'000
+- Nordwert (Y): 1'070'000 - 1'300'000
+- Höhe (Z): Meter über Meer
+
+## 🗺️ Koordinatensystem
+
+Alle geografischen Daten verwenden **LV95 (Swiss LV95 / EPSG:2056)**.
+
+**Beispiel-Koordinaten (Zürich Hauptbahnhof):**
+```
+X (Ost): 2'683'140
+Y (Nord): 1'247'850
+Z (Höhe): 408 m ü. M.
+```
+
+## 🔍 Wichtige Entscheidungen
+
+### 1. Polymorphe Beziehungen
+Das `Kontext`-Objekt kann zu verschiedenen Entitäten gehören (Projekt, Parzelle, Land, Kanton, Gemeinde). Dies ermöglicht flexible Erweiterungen ohne Schema-Änderungen.
+
+### 2. Array-Felder
+- `statusProzess`: Ein Projekt kann mehrere Status gleichzeitig haben
+- `tags`: Dokumente können mehrere Tags haben
+- `parzellenNummern`: Parzellen können mehrere offizielle Nummern haben
+
+### 3. Selbstreferenzierende Beziehungen
+Parzellen referenzieren sich gegenseitig als Nachbarn (n:m Beziehung).
+
+### 4. Versionierung
+Dokumente haben eine `versionsbezeichnung` für manuelle Versionskontrolle.
+
+### 5. Drei-wertiger Zustand
+`JaNein` Enum erlaubt "", "Ja", "Nein" für unbekannte Zustände.
+
+## 📋 Anwendungsfälle
+
+### UC1: Neues Projekt erstellen
+```python
+# Python Beispiel
+projekt = Projekt(
+ label="Neubau Mehrfamilienhaus",
+ status_prozess=[StatusProzess.EINGANG]
+)
+projekt.perimeter.append(parzelle_1)
+session.add(projekt)
+session.commit()
+```
+
+### UC2: Bebaubarkeit prüfen
+```sql
+-- SQL Beispiel
+SELECT
+ p.label,
+ p.bauzone,
+ p.az,
+ p.bz,
+ p.vollgeschoss_zahl,
+ p.gebaeudehoehe_max,
+ ST_Area(p.geo_umfang) as flaeche_m2
+FROM v_parzelle_vollstaendig p
+WHERE p.id = 'UUID';
+```
+
+### UC3: Nachbaranalyse
+```typescript
+// TypeScript/Prisma Beispiel
+const parzelle = await prisma.parzelle.findUnique({
+ where: { id: parzelleId },
+ include: {
+ nachbarEigentuemer_von: {
+ include: { nachbar: true }
+ }
+ }
+});
+```
+
+## 🎯 Best Practices
+
+### Geometrie-Handling
+```python
+# PostGIS: Parzelle mit Polygon erstellen
+from geoalchemy2.shape import from_shape
+from shapely.geometry import Polygon
+
+polygon = Polygon([
+ (2683140, 1247850),
+ (2683200, 1247850),
+ (2683200, 1247900),
+ (2683140, 1247900),
+ (2683140, 1247850)
+])
+
+parzelle.geo_umfang = from_shape(polygon, srid=2056)
+```
+
+### Kontext-Informationen
+```python
+# Flexibles Hinzufügen von Kontextinformationen
+kontext = Kontext(
+ thema="Dienstbarkeiten",
+ inhalt="Wegrecht zugunsten Parzelle 1235 entlang Ostgrenze, eingetragen am 15.03.2020",
+ parzelle_id=parzelle.id
+)
+```
+
+### Dokumenten-Management
+```python
+# Dokument mit Tags
+dokument = Dokument(
+ label="Zonenplan Gemeinde Zürich",
+ versionsbezeichnung="2024-v1",
+ typ=DokumentTyp.DATEI,
+ format="PDF",
+ dokument_referenz="/storage/docs/zonenplan-zh-2024.pdf",
+ tags=[TagTyp.ZONENPLAN, TagTyp.BZO]
+)
+```
+
+## 🔐 Sicherheit
+
+### Zu beachten:
+- Eigentümerdaten sind personenbezogene Daten (DSGVO/DSG)
+- Dokumente benötigen Zugriffskontrolle
+- Audit-Logging für Änderungen empfohlen
+- Geometriedaten sollten validiert werden
+
+### Empfohlene Maßnahmen:
+```sql
+-- Audit-Log Tabelle hinzufügen
+CREATE TABLE audit_log (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ table_name VARCHAR(100) NOT NULL,
+ record_id UUID NOT NULL,
+ action VARCHAR(20) NOT NULL,
+ changed_by UUID NOT NULL,
+ changed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ old_values JSONB,
+ new_values JSONB
+);
+```
+
+## 📈 Performance-Optimierung
+
+### Indices sind bereits erstellt für:
+- Geografische Suchen (GIST Index auf Geometrien)
+- Array-Suchen (GIN Index auf Arrays)
+- Foreign Key Joins
+
+### Zusätzliche Optimierungen:
+```sql
+-- Materialized View für häufige Reports
+CREATE MATERIALIZED VIEW mv_projekt_statistik AS
+SELECT
+ k.label as kanton,
+ COUNT(DISTINCT pr.id) as anzahl_projekte,
+ COUNT(DISTINCT pa.id) as anzahl_parzellen,
+ SUM(ST_Area(pa.geo_umfang)) as gesamtflaeche_m2
+FROM projekt pr
+JOIN projekt_parzelle pp ON pr.id = pp.projekt_id
+JOIN parzelle pa ON pp.parzelle_id = pa.id
+JOIN kanton k ON pa.kanton_id = k.id
+GROUP BY k.label;
+
+-- Refresh periodisch
+REFRESH MATERIALIZED VIEW mv_projekt_statistik;
+```
+
+## 🧪 Testing
+
+### Unit Tests
+```python
+# pytest Beispiel
+def test_parzelle_creation():
+ parzelle = Parzelle(
+ label="Test Parzelle",
+ parzellen_nummern=["1234"],
+ az=1.5,
+ bz=0.4
+ )
+ assert parzelle.label == "Test Parzelle"
+ assert parzelle.az == 1.5
+```
+
+### Integration Tests
+```typescript
+// Jest Beispiel
+describe('Projekt API', () => {
+ it('should create projekt with parzellen', async () => {
+ const projekt = await createProjekt({
+ label: 'Test Projekt',
+ perimeter: [parzelle1.id, parzelle2.id]
+ });
+ expect(projekt.perimeter).toHaveLength(2);
+ });
+});
+```
+
+## 📚 Weitere Ressourcen
+
+- [PostGIS Dokumentation](https://postgis.net/docs/)
+- [SQLAlchemy ORM](https://docs.sqlalchemy.org/)
+- [Prisma Dokumentation](https://www.prisma.io/docs/)
+- [Swisstopo - Schweizer Koordinatensysteme](https://www.swisstopo.admin.ch/de/wissen-fakten/geodaesie-vermessung/bezugsrahmen/lokal/lv95.html)
+
+## 🤝 Nächste Schritte
+
+1. **Validierung**: Review mit Architekten und Fachexperten
+2. **API-Design**: RESTful oder GraphQL API implementieren
+3. **Frontend-Prototyp**: Kartenansicht mit Leaflet/MapLibre
+4. **GIS-Integration**: Anbindung an Swisstopo-APIs
+5. **Workflow-Engine**: Statusübergänge und Genehmigungen
+6. **Benutzer-Management**: Rollen und Berechtigungen
+
+## 📞 Support
+
+Bei Fragen zum Datenmodell:
+- Öffne ein Issue im Repository
+- Konsultiere die `DATENMODELL_DOKUMENTATION.md`
+- Prüfe die Beispiel-Implementierungen in `models.py` oder `schema.prisma`
+
+## 📝 License
+
+[Lizenz hier einfügen]
+
+---
+
+**Version:** 1.0
+**Letzte Aktualisierung:** 2025-10-24
+**Koordinatensystem:** LV95 (EPSG:2056)
+**Datenbank:** PostgreSQL 15+ mit PostGIS 3.4+
diff --git a/mandates/pek/datenmodell/datenmodell-schema.json b/mandates/pek/datenmodell/datenmodell-schema.json
new file mode 100644
index 0000000..ab27295
--- /dev/null
+++ b/mandates/pek/datenmodell/datenmodell-schema.json
@@ -0,0 +1,462 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Architektur-Planungs-App Datenmodell",
+ "version": "1.0",
+
+ "definitions": {
+ "Projekt": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Eindeutige Projekt-ID"
+ },
+ "label": {
+ "type": "string",
+ "description": "Projektbezeichnung"
+ },
+ "statusProzess": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "Eingang",
+ "Analyse",
+ "Studie",
+ "Planung",
+ "Baurechtsverfahren",
+ "Umsetzung",
+ "Archiv"
+ ]
+ },
+ "description": "Aktuelle(r) Projektstatus/-stati"
+ },
+ "perimeter": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Parzelle"
+ },
+ "description": "Parzellen im Projektperimeter"
+ },
+ "dokumenteBauherrschaft": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Dokument"
+ },
+ "description": "Dokumente der Bauherrschaft"
+ },
+ "dokumentePlanung": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Dokument"
+ },
+ "description": "Planungsdokumente"
+ },
+ "geoBaulinie": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeoPunkt"
+ },
+ "description": "Geografische Punkte der Baulinie"
+ },
+ "kontextInformationen": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Kontext"
+ },
+ "description": "Kontextuelle Projektinformationen"
+ }
+ },
+ "required": ["id", "label"]
+ },
+
+ "Dokument": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Eindeutige Dokument-ID"
+ },
+ "label": {
+ "type": "string",
+ "description": "Dokumentbezeichnung"
+ },
+ "versionsbezeichnung": {
+ "type": "string",
+ "description": "Versionsnummer oder -bezeichnung"
+ },
+ "typ": {
+ "type": "string",
+ "enum": ["Datei", "Url"],
+ "description": "Art des Dokuments"
+ },
+ "format": {
+ "type": "string",
+ "description": "Dateiformat (z.B. PDF, DWG, URL)",
+ "examples": ["PDF", "DWG", "IFC", "DXF", "URL"]
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Tag"
+ },
+ "description": "Kategorisierungs-Tags"
+ },
+ "dokumentReferenz": {
+ "type": "string",
+ "description": "Pfad oder URL zum Dokument"
+ }
+ },
+ "required": ["id", "label", "typ", "dokumentReferenz"]
+ },
+
+ "Tag": {
+ "type": "string",
+ "enum": [
+ "Kataster Objekte",
+ "Kataster Werkeleitungen",
+ "Kataster Belastete Standorte",
+ "Kataster Bäume",
+ "Zonenplan",
+ "Planungs- und Baugesetz (PGB)",
+ "Bau- und Zonenordnung (BZO)",
+ "Parkplatzverordnung",
+ "Eigentümerauskunft",
+ "Grundbuchauszug"
+ ],
+ "description": "Vordefinierte Dokumentkategorien"
+ },
+
+ "Parzelle": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Eindeutige Parzellen-ID"
+ },
+ "label": {
+ "type": "string",
+ "description": "Parzellenbezeichnung"
+ },
+ "parzellenNummern": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Offizielle Parzellennummern"
+ },
+ "eigentuemerschaaft": {
+ "type": "string",
+ "description": "Eigentümer der Parzelle"
+ },
+ "nachbarEigentuemer": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Parzelle"
+ },
+ "description": "Angrenzende Parzellen"
+ },
+ "kontextLand": {
+ "$ref": "#/definitions/Land",
+ "description": "Land der Parzelle"
+ },
+ "kontextKanton": {
+ "$ref": "#/definitions/Kanton",
+ "description": "Kanton der Parzelle"
+ },
+ "kontextGemeinde": {
+ "$ref": "#/definitions/Gemeinde",
+ "description": "Gemeinde der Parzelle"
+ },
+ "kontextInformationen": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Kontext"
+ },
+ "description": "Parzellenspezifische Kontextinformationen"
+ },
+ "strasseNr": {
+ "type": "string",
+ "description": "Straße und Hausnummer"
+ },
+ "geoUmfang": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeoPunkt"
+ },
+ "description": "Geografische Umfangspunkte der Parzelle"
+ },
+ "bauzone": {
+ "type": "string",
+ "description": "Bauzonenbezeichnung"
+ },
+ "spezifischeDokumente": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Dokument"
+ },
+ "description": "Parzellenspezifische Dokumente"
+ },
+ "hochwasserschutzzone": {
+ "type": "string",
+ "description": "Hochwasserschutzzone (falls zutreffend)"
+ },
+ "laermschutzzone": {
+ "type": "string",
+ "description": "Lärmschutzzone"
+ },
+ "grundwasserschutzzone": {
+ "type": "string",
+ "description": "Grundwasserschutzzone (falls zutreffend)"
+ },
+ "parzelleBebaut": {
+ "$ref": "#/definitions/JaNein",
+ "description": "Ist die Parzelle bebaut?"
+ },
+ "parzelleErschlossen": {
+ "$ref": "#/definitions/JaNein",
+ "description": "Ist die Parzelle erschlossen?"
+ },
+ "hanglage": {
+ "$ref": "#/definitions/JaNein",
+ "description": "Liegt die Parzelle in Hanglage?"
+ },
+ "az": {
+ "type": "number",
+ "description": "Ausnützungsziffer"
+ },
+ "bz": {
+ "type": "number",
+ "description": "Bebauungsziffer"
+ },
+ "vollgeschossZahl": {
+ "type": "integer",
+ "description": "Anzahl zulässiger Vollgeschosse"
+ },
+ "anrechenbarDachgeschoss": {
+ "type": "number",
+ "description": "Anrechenbarer Anteil Dachgeschoss"
+ },
+ "anrechenbarUntergeschoss": {
+ "type": "number",
+ "description": "Anrechenbarer Anteil Untergeschoss"
+ },
+ "gebaeudehoehe_max": {
+ "type": "number",
+ "description": "Maximale Gebäudehöhe in Metern"
+ },
+ "regelnGrenzabstand": {
+ "type": "string",
+ "description": "Regelungen zum Grenzabstand"
+ },
+ "regelnMehrlaengenzuschlag": {
+ "type": "string",
+ "description": "Regelungen zum Mehrlängenzuschlag"
+ },
+ "regelnMehrhoehenzuschlag": {
+ "type": "string",
+ "description": "Regelungen zum Mehrhöhenzuschlag"
+ }
+ },
+ "required": ["id", "label"]
+ },
+
+ "Gemeinde": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Eindeutige Gemeinde-ID"
+ },
+ "label": {
+ "type": "string",
+ "description": "Gemeindename"
+ },
+ "plz": {
+ "type": "string",
+ "description": "Postleitzahl"
+ },
+ "dokumente": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Dokument"
+ },
+ "description": "Gemeindedokumente"
+ },
+ "kontextInformationen": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Kontext"
+ },
+ "description": "Gemeindespezifische Kontextinformationen"
+ },
+ "bzoAktuell": {
+ "$ref": "#/definitions/Dokument",
+ "description": "Aktuelle Bau- und Zonenordnung"
+ },
+ "bzoRevision": {
+ "$ref": "#/definitions/Dokument",
+ "description": "BZO in Revision"
+ }
+ },
+ "required": ["id", "label"]
+ },
+
+ "Kanton": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Eindeutige Kanton-ID"
+ },
+ "label": {
+ "type": "string",
+ "description": "Kantonsname"
+ },
+ "dokumente": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Dokument"
+ },
+ "description": "Kantonale Dokumente"
+ },
+ "kontextInformationen": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Kontext"
+ },
+ "description": "Kantonsspezifische Kontextinformationen"
+ },
+ "baureglementAktuell": {
+ "$ref": "#/definitions/Dokument",
+ "description": "Aktuelles Baureglement"
+ },
+ "baureglementRevision": {
+ "$ref": "#/definitions/Dokument",
+ "description": "Baureglement in Revision"
+ },
+ "bauverordnungAktuell": {
+ "$ref": "#/definitions/Dokument",
+ "description": "Aktuelle Bauverordnung"
+ },
+ "bauverordnungRevision": {
+ "$ref": "#/definitions/Dokument",
+ "description": "Bauverordnung in Revision"
+ }
+ },
+ "required": ["id", "label"]
+ },
+
+ "Land": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Eindeutige Land-ID"
+ },
+ "label": {
+ "type": "string",
+ "description": "Landesname"
+ },
+ "dokumente": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Dokument"
+ },
+ "description": "Nationale Dokumente"
+ },
+ "kontextInformationen": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Kontext"
+ },
+ "description": "Nationale Kontextinformationen"
+ }
+ },
+ "required": ["id", "label"]
+ },
+
+ "GeoPunkt": {
+ "type": "object",
+ "properties": {
+ "x": {
+ "type": "number",
+ "description": "X-Koordinate (Ost)"
+ },
+ "y": {
+ "type": "number",
+ "description": "Y-Koordinate (Nord)"
+ },
+ "z": {
+ "type": "number",
+ "description": "Z-Koordinate (Höhe)"
+ },
+ "referenzen": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeoTag"
+ },
+ "description": "Kategorisierung des Geopunkts"
+ }
+ },
+ "required": ["x", "y"]
+ },
+
+ "GeoTag": {
+ "type": "string",
+ "enum": [
+ "Referenzpunkt Kat. 1",
+ "Referenzpunkt Kat. 2",
+ "Referenzpunkt Kat. 3",
+ "Geometeraufnahme"
+ ],
+ "description": "Kategorien für Geopunkte"
+ },
+
+ "JaNein": {
+ "type": "string",
+ "enum": ["", "Ja", "Nein"],
+ "description": "Ja/Nein/Leer Wert"
+ },
+
+ "Kontext": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Eindeutige Kontext-ID"
+ },
+ "thema": {
+ "type": "string",
+ "description": "Thema der Kontextinformation",
+ "examples": [
+ "Vorgaben zur Erdgeschossnutzung",
+ "Dienstbarkeiten",
+ "Anforderung Parkplätze",
+ "Ausnützungsübertragungen",
+ "Schadstoffbelastungen auf Parzellen",
+ "Aktive Gestaltungspläne",
+ "Lärmempfindlichkeitsstufen",
+ "Mögliche Wärmenutzung",
+ "Baumbestand auf privaten Grundstücken",
+ "Isos (Ortsbild, Schutzstatus, Denkmalschutz, Weilergebiet, etc.)",
+ "Naturgefahren",
+ "Verweis auf Revisionen"
+ ]
+ },
+ "inhalt": {
+ "type": "string",
+ "description": "Detaillierter Inhalt der Kontextinformation"
+ }
+ },
+ "required": ["id", "thema", "inhalt"]
+ }
+ }
+}
diff --git a/mandates/pek/datenmodell/datenmodell.mermaid b/mandates/pek/datenmodell/datenmodell.mermaid
new file mode 100644
index 0000000..c3aaf2d
--- /dev/null
+++ b/mandates/pek/datenmodell/datenmodell.mermaid
@@ -0,0 +1,45 @@
+---
+title: Hauptflüsse - Architektur-Planungs-App
+---
+flowchart TD
+ Start([Datenmodell Start])
+
+ subgraph Admin[Administrative Ebene]
+ Land[LAND
Schweiz]
+ Kanton[KANTON
z.B. Zürich]
+ Gemeinde[GEMEINDE
z.B. Zürich Stadt]
+ Land --> Kanton
+ Kanton --> Gemeinde
+ end
+
+ subgraph Geo[Geografische Daten]
+ Parzelle[PARZELLE
Grundstück mit
Bauparametern]
+ GeoPunkt[GEO_PUNKT
Koordinaten]
+ Gemeinde --> Parzelle
+ Parzelle --> GeoPunkt
+ end
+
+ subgraph Core[Kern-Business-Logik]
+ Projekt[PROJEKT
Bauprojekt]
+ Dokument[DOKUMENT
Dateien & URLs]
+ Projekt -.Perimeter.-> Parzelle
+ Projekt -.Dokumente.-> Dokument
+ Parzelle -.Dokumente.-> Dokument
+ end
+
+ subgraph Support[Unterstützende Daten]
+ Kontext[KONTEXT
Zusatzinfos]
+ Projekt --> Kontext
+ Parzelle --> Kontext
+ end
+
+ Start --> Admin
+
+ style Land fill:#50C878,stroke:#2D7A4A,stroke-width:2px,color:#fff
+ style Kanton fill:#50C878,stroke:#2D7A4A,stroke-width:2px,color:#fff
+ style Gemeinde fill:#50C878,stroke:#2D7A4A,stroke-width:2px,color:#fff
+ style Parzelle fill:#4A90E2,stroke:#2E5C8A,stroke-width:3px,color:#fff
+ style Projekt fill:#4A90E2,stroke:#2E5C8A,stroke-width:3px,color:#fff
+ style Dokument fill:#4A90E2,stroke:#2E5C8A,stroke-width:3px,color:#fff
+ style GeoPunkt fill:#F5A623,stroke:#C17D11,stroke-width:2px,color:#fff
+ style Kontext fill:#F5A623,stroke:#C17D11,stroke-width:2px,color:#fff
\ No newline at end of file
diff --git a/mandates/pek/datenmodell/migration_001_initial_schema.sql b/mandates/pek/datenmodell/migration_001_initial_schema.sql
new file mode 100644
index 0000000..68090ec
--- /dev/null
+++ b/mandates/pek/datenmodell/migration_001_initial_schema.sql
@@ -0,0 +1,393 @@
+-- ============================================================================
+-- Architektur-Planungs-App Datenbank Schema
+-- PostgreSQL 15+ mit PostGIS 3.4+
+-- Schweizer Koordinatensystem: LV95 (EPSG:2056)
+-- ============================================================================
+
+-- PostGIS Extension aktivieren
+CREATE EXTENSION IF NOT EXISTS postgis;
+
+-- UUID Extension für uuid_generate_v4()
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+
+-- ============================================================================
+-- ENUMS
+-- ============================================================================
+
+CREATE TYPE status_prozess AS ENUM (
+ 'Eingang',
+ 'Analyse',
+ 'Studie',
+ 'Planung',
+ 'Baurechtsverfahren',
+ 'Umsetzung',
+ 'Archiv'
+);
+
+CREATE TYPE dokument_typ AS ENUM (
+ 'Datei',
+ 'Url'
+);
+
+CREATE TYPE tag_typ AS ENUM (
+ 'Kataster Objekte',
+ 'Kataster Werkeleitungen',
+ 'Kataster Belastete Standorte',
+ 'Kataster Bäume',
+ 'Zonenplan',
+ 'Planungs- und Baugesetz (PGB)',
+ 'Bau- und Zonenordnung (BZO)',
+ 'Parkplatzverordnung',
+ 'Eigentümerauskunft',
+ 'Grundbuchauszug'
+);
+
+CREATE TYPE geo_tag_typ AS ENUM (
+ 'Referenzpunkt Kat. 1',
+ 'Referenzpunkt Kat. 2',
+ 'Referenzpunkt Kat. 3',
+ 'Geometeraufnahme'
+);
+
+CREATE TYPE ja_nein AS ENUM (
+ '',
+ 'Ja',
+ 'Nein'
+);
+
+-- ============================================================================
+-- HAUPTTABELLEN
+-- ============================================================================
+
+-- Land
+CREATE TABLE land (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ label VARCHAR(255) NOT NULL,
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Kanton
+CREATE TABLE kanton (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ label VARCHAR(255) NOT NULL,
+ baureglement_aktuell_id UUID,
+ baureglement_revision_id UUID,
+ bauverordnung_aktuell_id UUID,
+ bauverordnung_revision_id UUID,
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Gemeinde
+CREATE TABLE gemeinde (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ label VARCHAR(255) NOT NULL,
+ plz VARCHAR(10),
+ bzo_aktuell_id UUID,
+ bzo_revision_id UUID,
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Dokument
+CREATE TABLE dokument (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ label VARCHAR(255) NOT NULL,
+ versionsbezeichnung VARCHAR(100),
+ typ dokument_typ NOT NULL,
+ format VARCHAR(50),
+ dokument_referenz TEXT NOT NULL,
+ tags tag_typ[],
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Projekt
+CREATE TABLE projekt (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ label VARCHAR(255) NOT NULL,
+ status_prozess status_prozess[],
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Parzelle
+CREATE TABLE parzelle (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ label VARCHAR(255) NOT NULL,
+ parzellen_nummern VARCHAR(50)[],
+ eigentuemerschaaft VARCHAR(255),
+ strasse_nr VARCHAR(255),
+
+ -- Geografischer Kontext
+ land_id UUID REFERENCES land(id),
+ kanton_id UUID REFERENCES kanton(id),
+ gemeinde_id UUID REFERENCES gemeinde(id),
+
+ -- Geometrie (PostGIS)
+ geo_umfang GEOMETRY(POLYGON, 2056),
+
+ -- Bauliche Parameter
+ bauzone VARCHAR(50),
+ az DECIMAL(5,2),
+ bz DECIMAL(5,2),
+ vollgeschoss_zahl INTEGER,
+ anrechenbar_dachgeschoss DECIMAL(3,2),
+ anrechenbar_untergeschoss DECIMAL(3,2),
+ gebaeudehoehe_max DECIMAL(6,2),
+
+ -- Regelungen
+ regeln_grenzabstand TEXT,
+ regeln_mehrlaengenzuschlag TEXT,
+ regeln_mehrhoehenzuschlag TEXT,
+
+ -- Schutzzonen
+ hochwasserschutzzone VARCHAR(100),
+ laermschutzzone VARCHAR(100),
+ grundwasserschutzzone VARCHAR(100),
+
+ -- Eigenschaften
+ parzelle_bebaut ja_nein,
+ parzelle_erschlossen ja_nein,
+ hanglage ja_nein,
+
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
+);
+
+-- GeoPunkt
+CREATE TABLE geo_punkt (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ x DECIMAL(12,3) NOT NULL, -- LV95 Ostwert
+ y DECIMAL(12,3) NOT NULL, -- LV95 Nordwert
+ z DECIMAL(8,3), -- Höhe über Meer
+ referenzen geo_tag_typ[],
+ projekt_id UUID REFERENCES projekt(id) ON DELETE CASCADE,
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Kontext (Polymorphe Beziehung)
+CREATE TABLE kontext (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ thema VARCHAR(255) NOT NULL,
+ inhalt TEXT NOT NULL,
+
+ -- Polymorphe Foreign Keys
+ projekt_id UUID REFERENCES projekt(id) ON DELETE CASCADE,
+ parzelle_id UUID REFERENCES parzelle(id) ON DELETE CASCADE,
+ land_id UUID REFERENCES land(id) ON DELETE CASCADE,
+ kanton_id UUID REFERENCES kanton(id) ON DELETE CASCADE,
+ gemeinde_id UUID REFERENCES gemeinde(id) ON DELETE CASCADE,
+
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+
+ -- Constraint: Kontext muss zu genau einer Entität gehören
+ CONSTRAINT kontext_single_parent CHECK (
+ (projekt_id IS NOT NULL)::INTEGER +
+ (parzelle_id IS NOT NULL)::INTEGER +
+ (land_id IS NOT NULL)::INTEGER +
+ (kanton_id IS NOT NULL)::INTEGER +
+ (gemeinde_id IS NOT NULL)::INTEGER = 1
+ )
+);
+
+-- ============================================================================
+-- JUNCTION TABLES (Many-to-Many Beziehungen)
+-- ============================================================================
+
+-- Projekt <-> Parzelle
+CREATE TABLE projekt_parzelle (
+ projekt_id UUID NOT NULL REFERENCES projekt(id) ON DELETE CASCADE,
+ parzelle_id UUID NOT NULL REFERENCES parzelle(id) ON DELETE CASCADE,
+ PRIMARY KEY (projekt_id, parzelle_id)
+);
+
+-- Projekt <-> Dokument (Bauherrschaft)
+CREATE TABLE projekt_dokument_bauherrschaft (
+ projekt_id UUID NOT NULL REFERENCES projekt(id) ON DELETE CASCADE,
+ dokument_id UUID NOT NULL REFERENCES dokument(id) ON DELETE CASCADE,
+ PRIMARY KEY (projekt_id, dokument_id)
+);
+
+-- Projekt <-> Dokument (Planung)
+CREATE TABLE projekt_dokument_planung (
+ projekt_id UUID NOT NULL REFERENCES projekt(id) ON DELETE CASCADE,
+ dokument_id UUID NOT NULL REFERENCES dokument(id) ON DELETE CASCADE,
+ PRIMARY KEY (projekt_id, dokument_id)
+);
+
+-- Parzelle <-> Parzelle (Nachbarn)
+CREATE TABLE parzelle_nachbar (
+ parzelle_id UUID NOT NULL REFERENCES parzelle(id) ON DELETE CASCADE,
+ nachbar_id UUID NOT NULL REFERENCES parzelle(id) ON DELETE CASCADE,
+ PRIMARY KEY (parzelle_id, nachbar_id),
+ CHECK (parzelle_id != nachbar_id) -- Parzelle kann nicht ihr eigener Nachbar sein
+);
+
+-- Parzelle <-> Dokument
+CREATE TABLE parzelle_dokument (
+ parzelle_id UUID NOT NULL REFERENCES parzelle(id) ON DELETE CASCADE,
+ dokument_id UUID NOT NULL REFERENCES dokument(id) ON DELETE CASCADE,
+ PRIMARY KEY (parzelle_id, dokument_id)
+);
+
+-- ============================================================================
+-- FOREIGN KEY CONSTRAINTS (nachträglich für Kanton/Gemeinde Dokumente)
+-- ============================================================================
+
+ALTER TABLE kanton
+ ADD CONSTRAINT fk_kanton_baureglement_aktuell
+ FOREIGN KEY (baureglement_aktuell_id) REFERENCES dokument(id),
+ ADD CONSTRAINT fk_kanton_baureglement_revision
+ FOREIGN KEY (baureglement_revision_id) REFERENCES dokument(id),
+ ADD CONSTRAINT fk_kanton_bauverordnung_aktuell
+ FOREIGN KEY (bauverordnung_aktuell_id) REFERENCES dokument(id),
+ ADD CONSTRAINT fk_kanton_bauverordnung_revision
+ FOREIGN KEY (bauverordnung_revision_id) REFERENCES dokument(id);
+
+ALTER TABLE gemeinde
+ ADD CONSTRAINT fk_gemeinde_bzo_aktuell
+ FOREIGN KEY (bzo_aktuell_id) REFERENCES dokument(id),
+ ADD CONSTRAINT fk_gemeinde_bzo_revision
+ FOREIGN KEY (bzo_revision_id) REFERENCES dokument(id);
+
+-- ============================================================================
+-- INDICES für Performance
+-- ============================================================================
+
+-- Projekt Indices
+CREATE INDEX idx_projekt_status ON projekt USING GIN (status_prozess);
+
+-- Parzelle Indices
+CREATE INDEX idx_parzelle_land ON parzelle(land_id);
+CREATE INDEX idx_parzelle_kanton ON parzelle(kanton_id);
+CREATE INDEX idx_parzelle_gemeinde ON parzelle(gemeinde_id);
+CREATE INDEX idx_parzelle_bauzone ON parzelle(bauzone);
+CREATE INDEX idx_parzelle_geo_umfang ON parzelle USING GIST(geo_umfang);
+
+-- Dokument Indices
+CREATE INDEX idx_dokument_typ ON dokument(typ);
+CREATE INDEX idx_dokument_tags ON dokument USING GIN (tags);
+
+-- GeoPunkt Indices
+CREATE INDEX idx_geopunkt_projekt ON geo_punkt(projekt_id);
+CREATE INDEX idx_geopunkt_referenzen ON geo_punkt USING GIN (referenzen);
+
+-- Kontext Indices
+CREATE INDEX idx_kontext_projekt ON kontext(projekt_id);
+CREATE INDEX idx_kontext_parzelle ON kontext(parzelle_id);
+CREATE INDEX idx_kontext_land ON kontext(land_id);
+CREATE INDEX idx_kontext_kanton ON kontext(kanton_id);
+CREATE INDEX idx_kontext_gemeinde ON kontext(gemeinde_id);
+CREATE INDEX idx_kontext_thema ON kontext(thema);
+
+-- ============================================================================
+-- TRIGGER für updated_at
+-- ============================================================================
+
+CREATE OR REPLACE FUNCTION update_updated_at_column()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = CURRENT_TIMESTAMP;
+ RETURN NEW;
+END;
+$$ language 'plpgsql';
+
+CREATE TRIGGER update_land_updated_at BEFORE UPDATE ON land
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_kanton_updated_at BEFORE UPDATE ON kanton
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_gemeinde_updated_at BEFORE UPDATE ON gemeinde
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_dokument_updated_at BEFORE UPDATE ON dokument
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_projekt_updated_at BEFORE UPDATE ON projekt
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_parzelle_updated_at BEFORE UPDATE ON parzelle
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_geo_punkt_updated_at BEFORE UPDATE ON geo_punkt
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_kontext_updated_at BEFORE UPDATE ON kontext
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+-- ============================================================================
+-- VIEWS für häufige Abfragen
+-- ============================================================================
+
+-- View: Parzellen mit vollständigem geografischem Kontext
+CREATE VIEW v_parzelle_vollstaendig AS
+SELECT
+ p.*,
+ l.label as land_name,
+ k.label as kanton_name,
+ g.label as gemeinde_name,
+ g.plz as gemeinde_plz,
+ ST_AsGeoJSON(p.geo_umfang) as geo_umfang_geojson,
+ ST_Area(p.geo_umfang) as flaeche_m2
+FROM parzelle p
+LEFT JOIN land l ON p.land_id = l.id
+LEFT JOIN kanton k ON p.kanton_id = k.id
+LEFT JOIN gemeinde g ON p.gemeinde_id = g.id;
+
+-- View: Projekte mit Perimeter-Information
+CREATE VIEW v_projekt_mit_perimeter AS
+SELECT
+ pr.id,
+ pr.label,
+ pr.status_prozess,
+ COUNT(DISTINCT pp.parzelle_id) as anzahl_parzellen,
+ STRING_AGG(DISTINCT pa.label, ', ') as parzellen_labels
+FROM projekt pr
+LEFT JOIN projekt_parzelle pp ON pr.id = pp.projekt_id
+LEFT JOIN parzelle pa ON pp.parzelle_id = pa.id
+GROUP BY pr.id, pr.label, pr.status_prozess;
+
+-- ============================================================================
+-- BEISPIELDATEN
+-- ============================================================================
+
+-- Land Schweiz
+INSERT INTO land (label) VALUES ('Schweiz');
+
+-- Kantone (Beispiele)
+INSERT INTO kanton (label) VALUES
+ ('Zürich'),
+ ('Bern'),
+ ('Luzern');
+
+-- Gemeinden (Beispiele für Zürich)
+INSERT INTO gemeinde (label, plz) VALUES
+ ('Zürich', '8000'),
+ ('Winterthur', '8400'),
+ ('Uster', '8610');
+
+-- ============================================================================
+-- KOMMENTARE
+-- ============================================================================
+
+COMMENT ON TABLE projekt IS 'Bauprojekte mit Status und Perimeter';
+COMMENT ON TABLE parzelle IS 'Grundstücke mit baulichen und rechtlichen Eigenschaften';
+COMMENT ON TABLE dokument IS 'Dokumente und URLs mit Versionierung';
+COMMENT ON TABLE geo_punkt IS '3D-Punkte im LV95-Koordinatensystem';
+COMMENT ON TABLE kontext IS 'Flexible Kontextinformationen für verschiedene Entitäten';
+
+COMMENT ON COLUMN parzelle.geo_umfang IS 'Parzellengrenze als PostGIS Polygon im LV95 (EPSG:2056)';
+COMMENT ON COLUMN parzelle.az IS 'Ausnützungsziffer';
+COMMENT ON COLUMN parzelle.bz IS 'Bebauungsziffer';
+COMMENT ON COLUMN geo_punkt.x IS 'LV95 Ostwert (E), typisch 2480000-2840000';
+COMMENT ON COLUMN geo_punkt.y IS 'LV95 Nordwert (N), typisch 1070000-1300000';
+COMMENT ON COLUMN geo_punkt.z IS 'Höhe über Meer in Metern';
+
+-- ============================================================================
+-- Ende der Migration
+-- ============================================================================
diff --git a/mandates/pek/datenmodell/models.py b/mandates/pek/datenmodell/models.py
new file mode 100644
index 0000000..3d5598e
--- /dev/null
+++ b/mandates/pek/datenmodell/models.py
@@ -0,0 +1,457 @@
+"""
+SQLAlchemy Datenmodell für Architektur-Planungs-App
+Verwendet PostgreSQL mit PostGIS Extension
+"""
+
+from sqlalchemy import (
+ Column, String, Integer, Float, Enum as SQLEnum,
+ ForeignKey, Table, Text, ARRAY
+)
+from sqlalchemy.dialects.postgresql import UUID, ENUM
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relationship
+from geoalchemy2 import Geometry
+import uuid
+import enum
+
+Base = declarative_base()
+
+
+# ============================================================================
+# ENUMS
+# ============================================================================
+
+class StatusProzess(enum.Enum):
+ EINGANG = "Eingang"
+ ANALYSE = "Analyse"
+ STUDIE = "Studie"
+ PLANUNG = "Planung"
+ BAURECHTSVERFAHREN = "Baurechtsverfahren"
+ UMSETZUNG = "Umsetzung"
+ ARCHIV = "Archiv"
+
+
+class DokumentTyp(enum.Enum):
+ DATEI = "Datei"
+ URL = "Url"
+
+
+class TagTyp(enum.Enum):
+ KATASTER_OBJEKTE = "Kataster Objekte"
+ KATASTER_WERKELEITUNGEN = "Kataster Werkeleitungen"
+ KATASTER_BELASTETE_STANDORTE = "Kataster Belastete Standorte"
+ KATASTER_BAEUME = "Kataster Bäume"
+ ZONENPLAN = "Zonenplan"
+ PGB = "Planungs- und Baugesetz (PGB)"
+ BZO = "Bau- und Zonenordnung (BZO)"
+ PARKPLATZVERORDNUNG = "Parkplatzverordnung"
+ EIGENTUEMER_AUSKUNFT = "Eigentümerauskunft"
+ GRUNDBUCHAUSZUG = "Grundbuchauszug"
+
+
+class GeoTagTyp(enum.Enum):
+ REFERENZPUNKT_KAT1 = "Referenzpunkt Kat. 1"
+ REFERENZPUNKT_KAT2 = "Referenzpunkt Kat. 2"
+ REFERENZPUNKT_KAT3 = "Referenzpunkt Kat. 3"
+ GEOMETER_AUFNAHME = "Geometeraufnahme"
+
+
+class JaNein(enum.Enum):
+ LEER = ""
+ JA = "Ja"
+ NEIN = "Nein"
+
+
+# ============================================================================
+# JUNCTION TABLES (Many-to-Many Beziehungen)
+# ============================================================================
+
+# Projekt <-> Dokument (Bauherrschaft)
+projekt_dokumente_bauherrschaft = Table(
+ 'projekt_dokumente_bauherrschaft',
+ Base.metadata,
+ Column('projekt_id', UUID(as_uuid=True), ForeignKey('projekt.id')),
+ Column('dokument_id', UUID(as_uuid=True), ForeignKey('dokument.id'))
+)
+
+# Projekt <-> Dokument (Planung)
+projekt_dokumente_planung = Table(
+ 'projekt_dokumente_planung',
+ Base.metadata,
+ Column('projekt_id', UUID(as_uuid=True), ForeignKey('projekt.id')),
+ Column('dokument_id', UUID(as_uuid=True), ForeignKey('dokument.id'))
+)
+
+# Projekt <-> Parzelle
+projekt_parzelle = Table(
+ 'projekt_parzelle',
+ Base.metadata,
+ Column('projekt_id', UUID(as_uuid=True), ForeignKey('projekt.id')),
+ Column('parzelle_id', UUID(as_uuid=True), ForeignKey('parzelle.id'))
+)
+
+# Parzelle <-> Parzelle (Nachbarn)
+parzelle_nachbar = Table(
+ 'parzelle_nachbar',
+ Base.metadata,
+ Column('parzelle_id', UUID(as_uuid=True), ForeignKey('parzelle.id')),
+ Column('nachbar_id', UUID(as_uuid=True), ForeignKey('parzelle.id'))
+)
+
+# Parzelle <-> Dokument
+parzelle_dokument = Table(
+ 'parzelle_dokument',
+ Base.metadata,
+ Column('parzelle_id', UUID(as_uuid=True), ForeignKey('parzelle.id')),
+ Column('dokument_id', UUID(as_uuid=True), ForeignKey('dokument.id'))
+)
+
+# Dokument <-> Tag (Many-to-Many, da Tags wiederverwendbar)
+dokument_tag = Table(
+ 'dokument_tag',
+ Base.metadata,
+ Column('dokument_id', UUID(as_uuid=True), ForeignKey('dokument.id')),
+ Column('tag', ENUM(TagTyp, name='tag_typ'))
+)
+
+
+# ============================================================================
+# MODELS
+# ============================================================================
+
+class Projekt(Base):
+ __tablename__ = 'projekt'
+
+ id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ label = Column(String, nullable=False)
+ status_prozess = Column(ARRAY(ENUM(StatusProzess, name='status_prozess')))
+
+ # Relationships
+ perimeter = relationship(
+ 'Parzelle',
+ secondary=projekt_parzelle,
+ back_populates='projekte'
+ )
+
+ dokumente_bauherrschaft = relationship(
+ 'Dokument',
+ secondary=projekt_dokumente_bauherrschaft
+ )
+
+ dokumente_planung = relationship(
+ 'Dokument',
+ secondary=projekt_dokumente_planung
+ )
+
+ geo_baulinie = relationship('GeoPunkt', back_populates='projekt_baulinie')
+
+ kontext_informationen = relationship(
+ 'Kontext',
+ foreign_keys='Kontext.projekt_id',
+ back_populates='projekt'
+ )
+
+
+class Parzelle(Base):
+ __tablename__ = 'parzelle'
+
+ id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ label = Column(String, nullable=False)
+ parzellen_nummern = Column(ARRAY(String))
+ eigentuemerschaaft = Column(String)
+ strasse_nr = Column(String)
+
+ # Geografischer Kontext
+ land_id = Column(UUID(as_uuid=True), ForeignKey('land.id'))
+ kanton_id = Column(UUID(as_uuid=True), ForeignKey('kanton.id'))
+ gemeinde_id = Column(UUID(as_uuid=True), ForeignKey('gemeinde.id'))
+
+ # Geometrie (PostGIS)
+ geo_umfang = Column(Geometry('POLYGON', srid=2056)) # LV95 Koordinatensystem
+
+ # Bauliche Parameter
+ bauzone = Column(String)
+ az = Column(Float) # Ausnützungsziffer
+ bz = Column(Float) # Bebauungsziffer
+ vollgeschoss_zahl = Column(Integer)
+ anrechenbar_dachgeschoss = Column(Float)
+ anrechenbar_untergeschoss = Column(Float)
+ gebaeudehoehe_max = Column(Float)
+
+ # Regelungen
+ regeln_grenzabstand = Column(Text)
+ regeln_mehrlaengenzuschlag = Column(Text)
+ regeln_mehrhoehenzuschlag = Column(Text)
+
+ # Schutzzonen
+ hochwasserschutzzone = Column(String)
+ laermschutzzone = Column(String)
+ grundwasserschutzzone = Column(String)
+
+ # Eigenschaften
+ parzelle_bebaut = Column(ENUM(JaNein, name='ja_nein'))
+ parzelle_erschlossen = Column(ENUM(JaNein, name='ja_nein'))
+ hanglage = Column(ENUM(JaNein, name='ja_nein'))
+
+ # Relationships
+ projekte = relationship(
+ 'Projekt',
+ secondary=projekt_parzelle,
+ back_populates='perimeter'
+ )
+
+ nachbar_eigentuemer = relationship(
+ 'Parzelle',
+ secondary=parzelle_nachbar,
+ primaryjoin=id == parzelle_nachbar.c.parzelle_id,
+ secondaryjoin=id == parzelle_nachbar.c.nachbar_id
+ )
+
+ kontext_land = relationship('Land', back_populates='parzellen')
+ kontext_kanton = relationship('Kanton', back_populates='parzellen')
+ kontext_gemeinde = relationship('Gemeinde', back_populates='parzellen')
+
+ spezifische_dokumente = relationship(
+ 'Dokument',
+ secondary=parzelle_dokument
+ )
+
+ kontext_informationen = relationship(
+ 'Kontext',
+ foreign_keys='Kontext.parzelle_id',
+ back_populates='parzelle'
+ )
+
+
+class Dokument(Base):
+ __tablename__ = 'dokument'
+
+ id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ label = Column(String, nullable=False)
+ versionsbezeichnung = Column(String)
+ typ = Column(ENUM(DokumentTyp, name='dokument_typ'), nullable=False)
+ format = Column(String)
+ dokument_referenz = Column(String, nullable=False) # Pfad oder URL
+
+ # Tags als Array (einfache Variante)
+ # Alternative: Many-to-Many über Junction Table
+ tags = Column(ARRAY(ENUM(TagTyp, name='tag_typ')))
+
+
+class GeoPunkt(Base):
+ __tablename__ = 'geo_punkt'
+
+ id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+
+ # Koordinaten (einzeln gespeichert für Flexibilität)
+ x = Column(Float, nullable=False)
+ y = Column(Float, nullable=False)
+ z = Column(Float) # Optional
+
+ # Alternative: PostGIS Point
+ # koordinaten = Column(Geometry('POINTZ', srid=2056))
+
+ # Kategorisierung
+ referenzen = Column(ARRAY(ENUM(GeoTagTyp, name='geo_tag_typ')))
+
+ # Foreign Keys (je nach Verwendung)
+ projekt_id = Column(UUID(as_uuid=True), ForeignKey('projekt.id'))
+
+ # Relationships
+ projekt_baulinie = relationship('Projekt', back_populates='geo_baulinie')
+
+
+class Kontext(Base):
+ __tablename__ = 'kontext'
+
+ id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ thema = Column(String, nullable=False)
+ inhalt = Column(Text, nullable=False)
+
+ # Polymorphe Beziehung (kann zu verschiedenen Entitäten gehören)
+ projekt_id = Column(UUID(as_uuid=True), ForeignKey('projekt.id'))
+ parzelle_id = Column(UUID(as_uuid=True), ForeignKey('parzelle.id'))
+ land_id = Column(UUID(as_uuid=True), ForeignKey('land.id'))
+ kanton_id = Column(UUID(as_uuid=True), ForeignKey('kanton.id'))
+ gemeinde_id = Column(UUID(as_uuid=True), ForeignKey('gemeinde.id'))
+
+ # Relationships
+ projekt = relationship('Projekt', back_populates='kontext_informationen')
+ parzelle = relationship('Parzelle', back_populates='kontext_informationen')
+ land = relationship('Land', back_populates='kontext_informationen')
+ kanton = relationship('Kanton', back_populates='kontext_informationen')
+ gemeinde = relationship('Gemeinde', back_populates='kontext_informationen')
+
+
+class Gemeinde(Base):
+ __tablename__ = 'gemeinde'
+
+ id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ label = Column(String, nullable=False)
+ plz = Column(String)
+
+ # BZO Dokumente
+ bzo_aktuell_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id'))
+ bzo_revision_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id'))
+
+ # Relationships
+ parzellen = relationship('Parzelle', back_populates='kontext_gemeinde')
+ kontext_informationen = relationship(
+ 'Kontext',
+ foreign_keys='Kontext.gemeinde_id',
+ back_populates='gemeinde'
+ )
+
+ bzo_aktuell = relationship('Dokument', foreign_keys=[bzo_aktuell_id])
+ bzo_revision = relationship('Dokument', foreign_keys=[bzo_revision_id])
+
+
+class Kanton(Base):
+ __tablename__ = 'kanton'
+
+ id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ label = Column(String, nullable=False)
+
+ # Regelwerke
+ baureglement_aktuell_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id'))
+ baureglement_revision_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id'))
+ bauverordnung_aktuell_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id'))
+ bauverordnung_revision_id = Column(UUID(as_uuid=True), ForeignKey('dokument.id'))
+
+ # Relationships
+ parzellen = relationship('Parzelle', back_populates='kontext_kanton')
+ kontext_informationen = relationship(
+ 'Kontext',
+ foreign_keys='Kontext.kanton_id',
+ back_populates='kanton'
+ )
+
+ baureglement_aktuell = relationship('Dokument', foreign_keys=[baureglement_aktuell_id])
+ baureglement_revision = relationship('Dokument', foreign_keys=[baureglement_revision_id])
+ bauverordnung_aktuell = relationship('Dokument', foreign_keys=[bauverordnung_aktuell_id])
+ bauverordnung_revision = relationship('Dokument', foreign_keys=[bauverordnung_revision_id])
+
+
+class Land(Base):
+ __tablename__ = 'land'
+
+ id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
+ label = Column(String, nullable=False)
+
+ # Relationships
+ parzellen = relationship('Parzelle', back_populates='kontext_land')
+ kontext_informationen = relationship(
+ 'Kontext',
+ foreign_keys='Kontext.land_id',
+ back_populates='land'
+ )
+
+
+# ============================================================================
+# DATENBANK-SETUP
+# ============================================================================
+
+def create_database_engine():
+ """
+ Erstellt die Database Engine mit PostGIS Support
+ """
+ from sqlalchemy import create_engine
+
+ # Beispiel Connection String
+ DATABASE_URL = "postgresql://user:password@localhost:5432/architektur_app"
+
+ engine = create_engine(
+ DATABASE_URL,
+ echo=True # SQL Logging für Development
+ )
+
+ return engine
+
+
+def init_database(engine):
+ """
+ Initialisiert die Datenbank und erstellt alle Tabellen
+ """
+ # PostGIS Extension aktivieren (manuell oder via SQL)
+ with engine.connect() as conn:
+ conn.execute("CREATE EXTENSION IF NOT EXISTS postgis;")
+ conn.commit()
+
+ # Tabellen erstellen
+ Base.metadata.create_all(engine)
+
+
+# ============================================================================
+# BEISPIEL USAGE
+# ============================================================================
+
+if __name__ == "__main__":
+ from sqlalchemy.orm import sessionmaker
+
+ # Engine erstellen
+ engine = create_database_engine()
+
+ # Datenbank initialisieren
+ init_database(engine)
+
+ # Session erstellen
+ Session = sessionmaker(bind=engine)
+ session = Session()
+
+ # Beispiel: Schweiz erstellen
+ schweiz = Land(
+ label="Schweiz"
+ )
+ session.add(schweiz)
+
+ # Beispiel: Kanton Zürich erstellen
+ kanton_zh = Kanton(
+ label="Zürich"
+ )
+ session.add(kanton_zh)
+
+ # Beispiel: Gemeinde Zürich erstellen
+ gemeinde_zh = Gemeinde(
+ label="Zürich",
+ plz="8000"
+ )
+ session.add(gemeinde_zh)
+
+ # Beispiel: Parzelle erstellen
+ parzelle = Parzelle(
+ label="Bahnhofstrasse 1",
+ parzellen_nummern=["1234"],
+ eigentuemerschaaft="Mustermann AG",
+ strasse_nr="Bahnhofstrasse 1",
+ land_id=schweiz.id,
+ kanton_id=kanton_zh.id,
+ gemeinde_id=gemeinde_zh.id,
+ bauzone="W3",
+ az=1.5,
+ bz=0.4,
+ vollgeschoss_zahl=4,
+ parzelle_bebaut=JaNein.NEIN,
+ parzelle_erschlossen=JaNein.JA
+ )
+ session.add(parzelle)
+
+ # Beispiel: Projekt erstellen
+ projekt = Projekt(
+ label="Neubau Wohnhaus",
+ status_prozess=[StatusProzess.EINGANG, StatusProzess.ANALYSE]
+ )
+ projekt.perimeter.append(parzelle)
+ session.add(projekt)
+
+ # Beispiel: Kontext hinzufügen
+ kontext = Kontext(
+ thema="Dienstbarkeiten",
+ inhalt="Wegrecht zugunsten Parzelle 1235 entlang Ostgrenze",
+ parzelle_id=parzelle.id
+ )
+ session.add(kontext)
+
+ # Speichern
+ session.commit()
+
+ print("Datenbank erfolgreich initialisiert und Beispieldaten eingefügt!")
diff --git a/mandates/pek/datenmodell/schema.prisma b/mandates/pek/datenmodell/schema.prisma
new file mode 100644
index 0000000..783257b
--- /dev/null
+++ b/mandates/pek/datenmodell/schema.prisma
@@ -0,0 +1,323 @@
+// Prisma Schema für Architektur-Planungs-App
+// Unterstützt PostgreSQL mit PostGIS
+
+generator client {
+ provider = "prisma-client-js"
+ previewFeatures = ["postgresqlExtensions"]
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+ extensions = [postgis]
+}
+
+// ============================================================================
+// ENUMS
+// ============================================================================
+
+enum StatusProzess {
+ EINGANG
+ ANALYSE
+ STUDIE
+ PLANUNG
+ BAURECHTSVERFAHREN
+ UMSETZUNG
+ ARCHIV
+}
+
+enum DokumentTyp {
+ DATEI
+ URL
+}
+
+enum TagTyp {
+ KATASTER_OBJEKTE
+ KATASTER_WERKELEITUNGEN
+ KATASTER_BELASTETE_STANDORTE
+ KATASTER_BAEUME
+ ZONENPLAN
+ PGB
+ BZO
+ PARKPLATZVERORDNUNG
+ EIGENTUEMER_AUSKUNFT
+ GRUNDBUCHAUSZUG
+}
+
+enum GeoTagTyp {
+ REFERENZPUNKT_KAT1
+ REFERENZPUNKT_KAT2
+ REFERENZPUNKT_KAT3
+ GEOMETER_AUFNAHME
+}
+
+enum JaNein {
+ LEER
+ JA
+ NEIN
+}
+
+// ============================================================================
+// MODELS
+// ============================================================================
+
+model Projekt {
+ id String @id @default(uuid()) @db.Uuid
+ label String
+ statusProzess StatusProzess[]
+
+ // Relationships
+ perimeter ParzelleInProjekt[]
+ dokumenteBauherrschaft ProjektDokumentBauherrschaft[]
+ dokumentePlanung ProjektDokumentPlanung[]
+ geoBaulinie GeoPunkt[]
+ kontextInformationen Kontext[]
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@map("projekt")
+}
+
+model Parzelle {
+ id String @id @default(uuid()) @db.Uuid
+ label String
+ parzellenNummern String[]
+ eigentuemerschaaft String?
+ strasseNr String?
+
+ // Geografischer Kontext
+ landId String? @db.Uuid
+ kantonId String? @db.Uuid
+ gemeindeId String? @db.Uuid
+
+ // Geometrie (als GeoJSON oder WKT gespeichert)
+ // Für echte PostGIS Integration: Unsupported.("geometry(POLYGON, 2056)")
+ geoUmfangJson Json? // Alternative: GeoJSON Format
+
+ // Bauliche Parameter
+ bauzone String?
+ az Float?
+ bz Float?
+ vollgeschossZahl Int?
+ anrechenbarDachgeschoss Float?
+ anrechenbarUntergeschoss Float?
+ gebaeudehoehe_max Float?
+
+ // Regelungen
+ regelnGrenzabstand String?
+ regelnMehrlaengenzuschlag String?
+ regelnMehrhoehenzuschlag String?
+
+ // Schutzzonen
+ hochwasserschutzzone String?
+ laermschutzzone String?
+ grundwasserschutzzone String?
+
+ // Eigenschaften
+ parzelleBebaut JaNein?
+ parzelleErschlossen JaNein?
+ hanglage JaNein?
+
+ // Relationships
+ projekte ParzelleInProjekt[]
+ nachbarEigentuemer_von ParzelleNachbar[] @relation("ParzelleNachbarVon")
+ nachbarEigentuemer_zu ParzelleNachbar[] @relation("ParzelleNachbarZu")
+
+ kontextLand Land? @relation(fields: [landId], references: [id])
+ kontextKanton Kanton? @relation(fields: [kantonId], references: [id])
+ kontextGemeinde Gemeinde? @relation(fields: [gemeindeId], references: [id])
+
+ spezifischeDokumente ParzelleDokument[]
+ kontextInformationen Kontext[]
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@map("parzelle")
+}
+
+model Dokument {
+ id String @id @default(uuid()) @db.Uuid
+ label String
+ versionsbezeichnung String?
+ typ DokumentTyp
+ format String?
+ dokumentReferenz String
+ tags TagTyp[]
+
+ // Relationships
+ projekteBauherrschaft ProjektDokumentBauherrschaft[]
+ projektePlanung ProjektDokumentPlanung[]
+ parzellen ParzelleDokument[]
+
+ // Spezifische Dokumente für Gemeinde/Kanton
+ gemeindeBzoAktuell Gemeinde[] @relation("GemeindeBZOAktuell")
+ gemeindeBzoRevision Gemeinde[] @relation("GemeindeBZORevision")
+ kantonBaureglementAktuell Kanton[] @relation("KantonBaureglementAktuell")
+ kantonBaureglementRevision Kanton[] @relation("KantonBaureglementRevision")
+ kantonBauverordnungAktuell Kanton[] @relation("KantonBauverordnungAktuell")
+ kantonBauverordnungRevision Kanton[] @relation("KantonBauverordnungRevision")
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@map("dokument")
+}
+
+model GeoPunkt {
+ id String @id @default(uuid()) @db.Uuid
+ x Float
+ y Float
+ z Float?
+ referenzen GeoTagTyp[]
+
+ // Foreign Keys
+ projektId String? @db.Uuid
+ projekt Projekt? @relation(fields: [projektId], references: [id])
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@map("geo_punkt")
+}
+
+model Kontext {
+ id String @id @default(uuid()) @db.Uuid
+ thema String
+ inhalt String @db.Text
+
+ // Polymorphe Beziehung (kann zu verschiedenen Entitäten gehören)
+ projektId String? @db.Uuid
+ parzelleId String? @db.Uuid
+ landId String? @db.Uuid
+ kantonId String? @db.Uuid
+ gemeindeId String? @db.Uuid
+
+ // Relationships
+ projekt Projekt? @relation(fields: [projektId], references: [id], onDelete: Cascade)
+ parzelle Parzelle? @relation(fields: [parzelleId], references: [id], onDelete: Cascade)
+ land Land? @relation(fields: [landId], references: [id], onDelete: Cascade)
+ kanton Kanton? @relation(fields: [kantonId], references: [id], onDelete: Cascade)
+ gemeinde Gemeinde? @relation(fields: [gemeindeId], references: [id], onDelete: Cascade)
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@map("kontext")
+}
+
+model Gemeinde {
+ id String @id @default(uuid()) @db.Uuid
+ label String
+ plz String?
+
+ // BZO Dokumente
+ bzoAktuellId String? @db.Uuid
+ bzoRevisionId String? @db.Uuid
+
+ // Relationships
+ parzellen Parzelle[]
+ kontextInformationen Kontext[]
+
+ bzoAktuell Dokument? @relation("GemeindeBZOAktuell", fields: [bzoAktuellId], references: [id])
+ bzoRevision Dokument? @relation("GemeindeBZORevision", fields: [bzoRevisionId], references: [id])
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@map("gemeinde")
+}
+
+model Kanton {
+ id String @id @default(uuid()) @db.Uuid
+ label String
+
+ // Regelwerke
+ baureglementAktuellId String? @db.Uuid
+ baureglementRevisionId String? @db.Uuid
+ bauverordnungAktuellId String? @db.Uuid
+ bauverordnungRevisionId String? @db.Uuid
+
+ // Relationships
+ parzellen Parzelle[]
+ kontextInformationen Kontext[]
+
+ baureglementAktuell Dokument? @relation("KantonBaureglementAktuell", fields: [baureglementAktuellId], references: [id])
+ baureglementRevision Dokument? @relation("KantonBaureglementRevision", fields: [baureglementRevisionId], references: [id])
+ bauverordnungAktuell Dokument? @relation("KantonBauverordnungAktuell", fields: [bauverordnungAktuellId], references: [id])
+ bauverordnungRevision Dokument? @relation("KantonBauverordnungRevision", fields: [bauverordnungRevisionId], references: [id])
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@map("kanton")
+}
+
+model Land {
+ id String @id @default(uuid()) @db.Uuid
+ label String
+
+ // Relationships
+ parzellen Parzelle[]
+ kontextInformationen Kontext[]
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@map("land")
+}
+
+// ============================================================================
+// JUNCTION TABLES (Many-to-Many)
+// ============================================================================
+
+model ParzelleInProjekt {
+ projektId String @db.Uuid
+ parzelleId String @db.Uuid
+ projekt Projekt @relation(fields: [projektId], references: [id], onDelete: Cascade)
+ parzelle Parzelle @relation(fields: [parzelleId], references: [id], onDelete: Cascade)
+
+ @@id([projektId, parzelleId])
+ @@map("projekt_parzelle")
+}
+
+model ParzelleNachbar {
+ parzelleId String @db.Uuid
+ nachbarId String @db.Uuid
+ parzelle Parzelle @relation("ParzelleNachbarVon", fields: [parzelleId], references: [id], onDelete: Cascade)
+ nachbar Parzelle @relation("ParzelleNachbarZu", fields: [nachbarId], references: [id], onDelete: Cascade)
+
+ @@id([parzelleId, nachbarId])
+ @@map("parzelle_nachbar")
+}
+
+model ProjektDokumentBauherrschaft {
+ projektId String @db.Uuid
+ dokumentId String @db.Uuid
+ projekt Projekt @relation(fields: [projektId], references: [id], onDelete: Cascade)
+ dokument Dokument @relation(fields: [dokumentId], references: [id], onDelete: Cascade)
+
+ @@id([projektId, dokumentId])
+ @@map("projekt_dokument_bauherrschaft")
+}
+
+model ProjektDokumentPlanung {
+ projektId String @db.Uuid
+ dokumentId String @db.Uuid
+ projekt Projekt @relation(fields: [projektId], references: [id], onDelete: Cascade)
+ dokument Dokument @relation(fields: [dokumentId], references: [id], onDelete: Cascade)
+
+ @@id([projektId, dokumentId])
+ @@map("projekt_dokument_planung")
+}
+
+model ParzelleDokument {
+ parzelleId String @db.Uuid
+ dokumentId String @db.Uuid
+ parzelle Parzelle @relation(fields: [parzelleId], references: [id], onDelete: Cascade)
+ dokument Dokument @relation(fields: [dokumentId], references: [id], onDelete: Cascade)
+
+ @@id([parzelleId, dokumentId])
+ @@map("parzelle_dokument")
+}
diff --git a/mandates/pek/bostich_hole_punch.3ds b/mandates/pek/testing/bostich_hole_punch.3ds
similarity index 100%
rename from mandates/pek/bostich_hole_punch.3ds
rename to mandates/pek/testing/bostich_hole_punch.3ds
diff --git a/mandates/pek/create_bostich_3ds.py b/mandates/pek/testing/create_bostich_3ds.py
similarity index 100%
rename from mandates/pek/create_bostich_3ds.py
rename to mandates/pek/testing/create_bostich_3ds.py
diff --git a/mandates/pek/create_bostich_3ds_fixed.py b/mandates/pek/testing/create_bostich_3ds_fixed.py
similarity index 100%
rename from mandates/pek/create_bostich_3ds_fixed.py
rename to mandates/pek/testing/create_bostich_3ds_fixed.py
diff --git a/mandates/pek/fotos_bostich/20250925_205036.jpg b/mandates/pek/testing/fotos_bostich/20250925_205036.jpg
similarity index 100%
rename from mandates/pek/fotos_bostich/20250925_205036.jpg
rename to mandates/pek/testing/fotos_bostich/20250925_205036.jpg
diff --git a/mandates/pek/fotos_bostich/20250925_205044.jpg b/mandates/pek/testing/fotos_bostich/20250925_205044.jpg
similarity index 100%
rename from mandates/pek/fotos_bostich/20250925_205044.jpg
rename to mandates/pek/testing/fotos_bostich/20250925_205044.jpg
diff --git a/mandates/pek/fotos_bostich/20250925_205050.jpg b/mandates/pek/testing/fotos_bostich/20250925_205050.jpg
similarity index 100%
rename from mandates/pek/fotos_bostich/20250925_205050.jpg
rename to mandates/pek/testing/fotos_bostich/20250925_205050.jpg
diff --git a/mandates/pek/haus_final.dxf b/mandates/pek/testing/haus_final.dxf
similarity index 100%
rename from mandates/pek/haus_final.dxf
rename to mandates/pek/testing/haus_final.dxf