integrated PEK data modell and according routes to get data

This commit is contained in:
Ida Dittrich 2025-11-19 16:02:12 +01:00
parent ff6fc3f96c
commit 7c5debe7f0
49 changed files with 5917 additions and 0 deletions

418
docs/PEK_datamodel_desc.md Normal file
View file

@ -0,0 +1,418 @@
# 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.
## Wichtige Hinweise zum Datenmodell
**Objektmodell vs. Datenbank-Repräsentation:**
Dieses Dokument beschreibt ein **Objektmodell** für die Arbeit im Code. Es handelt sich **NICHT** um ein relationales Datenbankmodell mit Junction Tables.
- **Im Code-Modell**: Alle Beziehungen werden als Objektreferenzen oder Listen von Objekten dargestellt (z.B. `dokumente: list[Dokument]`, `parzellen: list[Parzelle]`).
- **Für die Datenbank-Serialisierung**: Bei der Persistierung können Junction Tables verwendet werden, um n:m-Beziehungen in der Datenbank abzubilden. Dies ist jedoch ein Implementierungsdetail der Datenbank-Schicht und gehört nicht zum Hauptmodell.
**Systemattribute:**
Alle Datenobjekte haben automatisch die folgenden Systemattribute:
- `_createdAt`: Float (Timestamp UTC)
- `_createdBy`: String (User-ID)
- `_modifiedAt`: Float (Timestamp UTC)
- `_modifiedBy`: String (User-ID)
**Timestamps:**
- Alle Timestamps sind im Float-Format UTC im Datenmodell gespeichert.
- Die Darstellung im UI erfolgt mit der lokalen Zeitzone des Benutzers.
## Datenfluss-Diagramm
```mermaid
---
title: Hauptflüsse - Architektur-Planungs-App
---
flowchart LR
subgraph Admin[Administrative Ebene]
Land[Land<br/>Schweiz]
Kanton[Kanton<br/>z.B. Zürich]
Gemeinde[Gemeinde<br/>z.B. Zürich Stadt]
Land --> Kanton
Kanton --> Gemeinde
end
subgraph Geo[Geografische Daten]
GeoPolylinie[GeoPolylinie<br/>Linie/Polygon]
GeoPunkt[GeoPunkt<br/>Koordinaten]
GeoPolylinie --> GeoPunkt
end
subgraph Core[Kern-Business-Logik]
Projekt[Projekt<br/>Bauprojekt]
Parzelle[Parzelle<br/>Grundstück mit<br/>Bauparametern]
Gemeinde --> Parzelle
Projekt --> Parzelle
Projekt --> GeoPolylinie
Parzelle --> GeoPolylinie
end
subgraph Support[Unterstützende Daten]
Dokument[Dokument<br/>Dateien & URLs]
Kontext[Kontext<br/>Zusatzinfos]
end
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:#FF6B6B,stroke:#C92A2A,stroke-width:4px,color:#fff
style Projekt fill:#FF6B6B,stroke:#C92A2A,stroke-width:4px,color:#fff
style Dokument fill:#F5A623,stroke:#C17D11,stroke-width:2px,color:#fff
style GeoPolylinie fill:#F5A623,stroke:#C17D11,stroke-width:2px,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** | Kernentität | Bauprojekt mit Status und Perimeter | id, label, statusProzess, perimeter, baulinie, parzellen |
| **Parzelle** | Hauptentität | Grundstück mit Bauparametern | id, label, plz, bauzone, AZ, BZ, perimeter, baulinie, laermschutzzone, hochwasserschutzzone, grundwasserschutzzone |
| **Dokument** | Unterstützend | Dateien und URLs mit Versionierung | id, label, dokumentTyp, quelle, mimeType, kategorienTags |
| **Kontext** | Unterstützend | Flexible Zusatzinformationen | id, thema, inhalt |
| **GeoPolylinie** | Hilfsobjekt | Geometrische Linie/Polygon | id, closed, punkte |
| **Land** | Admin | Nationale Ebene | id, label, abk |
| **Kanton** | Admin | Kantonale Ebene mit Baurecht | id, label, abk, Baureglement |
| **Gemeinde** | Admin | Gemeinde-Ebene mit BZO | id, label, plz, BZO |
| **GeoPunkt** | Hilfsobjekt | 3D-Koordinate | koordinatensystem, x, y, z, referenz |
| **GeoTag** | Enum | Geopunkt-Kategorien | - |
| **JaNein** | Enum | Drei-wertiger Status | "", "Ja", "Nein" |
| **StatusProzess** | Enum | Projektstatus | 7 Werte |
| **DokumentTyp** | Enum | Dokumenttyp | 6 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` | Enum[StatusProzess] | - | Projektstatus: Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv |
| `perimeter` | GeoPolylinie | - | Umhüllende aller Parzellen des Projektes |
| `baulinie` | GeoPolylinie | - | Baulinie des Projektes |
| `parzellen` | list[Parzelle] | - | Alle Parzellen des Projektes |
| `dokumente` | list[Dokument] | - | Projektspezifische Dokumente |
| `kontextInformationen` | list[Kontext] | - | Projektspezifische Kontextinfos |
---
### 2. Parzelle
**Repräsentiert ein Grundstück mit allen baurechtlichen Eigenschaften als ein einheitliches Objekt.**
#### Grunddaten
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `id` | UUID | ✓ | Eindeutiger Identifier |
| `label` | String | ✓ | Parzellenbezeichnung |
| `parzellenAliasTags` | list[String] | - | Weitere Parzellennamen oder Flurnamen |
| `eigentuemerschaft` | String | - | Eigentümer der Parzelle |
| `strasseNr` | String | - | Straße und Hausnummer |
| `plz` | String | - | Postleitzahl der Parzelle (insbesondere bei Gemeinden mit mehreren PLZ) |
#### Geografischer Kontext
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `perimeter` | GeoPolylinie | - | Parzellengrenze als geschlossene GeoPolylinie |
| `baulinie` | GeoPolylinie | - | Baulinie der Parzelle |
| `kontextGemeinde` | Gemeinde | - | Gemeinde der Parzelle |
#### 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) |
| `gebaeudehoeheMax` | Float | - | Maximale Gebäudehöhe in Metern |
#### Abstandsregelungen
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `regelnGrenzabstand` | list[String] | - | Regelungen zum Grenzabstand |
| `regelnMehrlaengenzuschlag` | list[String] | - | Regelungen zum Mehrlängenzuschlag |
| `regelnMehrhoehenzuschlag` | list[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") |
| `parzelleHanglage` | JaNein | - | Liegt die Parzelle in Hanglage? ("", "Ja", "Nein") |
#### Schutzzonen
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `laermschutzzone` | String | - | Lärmschutzzone (z.B. "II") |
| `hochwasserschutzzone` | String | - | Hochwasserschutzzone (z.B. "tief") |
| `grundwasserschutzzone` | String | - | Grundwasserschutzzone |
#### Beziehungen
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `parzellenNachbarschaft` | list[Parzelle] | - | Nachbarparzellen |
| `dokumente` | list[Dokument] | - | Parzellenspezifische Dokumente |
| `kontextInformationen` | list[Kontext] | - | Parzellenspezifische Kontextinfos |
---
### 3. Dokument
**Unterstützendes Datenobjekt zur Verwaltung von 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") |
| `dokumentTyp` | Enum[DokumentTyp] | - | Typ des Dokuments (siehe DokumentTyp-Enum) |
| `dokumentReferenz` | String | ✓ | Dateipfad oder URL |
| `quelle` | String | - | Quelle des Dokuments |
| `mimeType` | String | - | MIME-Type des Dokuments (z.B. "application/pdf", "image/png") |
| `kategorienTags` | list[String] | - | Kategorisierung des Dokuments |
**Hinweis:**
Aktuelle Dokumente (z.B. aktuelle Baureglemente, BZO) können anhand des `dokumentTyp`-Attributs identifiziert werden. Die entsprechenden Dokumente finden sich in der `dokumente`-Liste der jeweiligen Entität (Kanton, Gemeinde).
#### Beispiel-Kategorien (nicht abschliessend)
| Kategorie | 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 |
| `Bauherrschaft` | Dokumente von der Bauherrschaft |
| `Planung` | Planungsdokumente |
---
### 4. Geografische Entitäten
#### GeoPolylinie
**Repräsentiert eine Linie oder ein Polygon aus mehreren GeoPunkten.**
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `id` | UUID | ✓ | Eindeutiger Identifier |
| `closed` | Boolean | ✓ | Ist die GeoPolylinie geschlossen (Polygon)? |
| `punkte` | list[GeoPunkt] | ✓ | Liste der GeoPunkte, die die GeoPolylinie bilden |
**Verwendung:**
- Parzellenperimeter (geschlossene GeoPolylinie)
- Baulinie (offene oder geschlossene GeoPolylinie)
- Projektperimeter (geschlossene GeoPolylinie)
#### GeoPunkt
**Repräsentiert einen 3D-Punkt mit Referenzangabe.**
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `koordinatensystem` | String | ✓ | Koordinatensystem (z.B. "LV95", "EPSG:2056") |
| `x` | Float | ✓ | Ostwert (E) [m], typisch 2'480'000 - 2'840'000 |
| `y` | Float | ✓ | Nordwert (N) [m], typisch 1'070'000 - 1'300'000 |
| `z` | Float | - | Höhe über Meer [m] |
| `referenz` | Enum[GeoTag] | - | Kategorisierung des Punktes |
**Verwendung:**
- Als Teil einer GeoPolylinie (Parzellenperimeter, Baulinie)
- Einzelne Referenzpunkte
#### GeoTag (Enum)
| Kategorie | Beschreibung |
|-----------|--------------|
| `K1` | Fixpunkt höchster Genauigkeit |
| `K2` | Fixpunkt mittlerer Genauigkeit |
| `K3` | Fixpunkt niedriger Genauigkeit |
| `Geometer` | Vom Geometer vermessener Punkt |
**Beispiel (Zürich Hauptbahnhof):**
- `koordinatensystem`: "LV95" oder "EPSG:2056"
- `x`: 2'683'140 [m]
- `y`: 1'247'850 [m]
- `z`: 408 [m]
- `referenz`: "K1" (oder ein anderer GeoTag-Wert)
---
### 5. Administrative Hierarchie
#### Land
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `id` | UUID | ✓ | Eindeutiger Identifier |
| `label` | String | ✓ | Landesname (z.B. "Schweiz") |
| `abk` | String | - | Abkürzung (z.B. "CH") |
| `dokumente` | list[Dokument] | - | Nationale Gesetze |
| `kontextInformationen` | list[Kontext] | - | Nationale Kontextinformationen |
---
#### Kanton
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `id` | UUID | ✓ | Eindeutiger Identifier |
| `label` | String | ✓ | Kantonsname (z.B. "Zürich") |
| `id_land` | [land] | eindeutiger Link zum land, also in welchem land kanton liegt |
| `abk` | String | - | Abkürzung (z.B. "ZH") |
| `dokumente` | list[Dokument] | - | Kantonale Dokumente |
| `kontextInformationen` | list[Kontext] | - | Kantonsspezifische Kontextinfos |
---
#### Gemeinde
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `id` | UUID | ✓ | Eindeutiger Identifier |
| `label` | String | ✓ | Gemeindename (z.B. "Zürich") |
| `id_kanton` | [kanton] | eindeutiger Link zur gemeinde, also im welchem kanton gemeinde liegt |
| `plz` | String | - | Postleitzahl (bei Gemeinden mit mehreren PLZ kann dies eine Haupt-PLZ sein) |
| `dokumente` | list[Dokument] | - | Gemeindedokumente |
| `kontextInformationen` | list[Kontext] | - | Gemeindespezifische Kontextinfos |
**Hinweis:**
Bei Gemeinden mit mehreren Postleitzahlen (z.B. Zürich, Bern) wird die konkrete PLZ der Parzelle im Attribut `plz` der Parzelle erfasst.
---
### 6. Kontext
**Unterstützendes Datenobjekt für flexible Zusatzinformationen und Hinweise.**
| Feld | Datentyp | Pflicht | Beschreibung |
|------|----------|---------|--------------|
| `id` | UUID | ✓ | Eindeutiger Identifier |
| `thema` | String | ✓ | Bezeichnung des Themas |
| `inhalt` | String | ✓ | Detaillierte Information (Text) |
**Verwendung:**
Kontext-Objekte werden als Listen in den jeweiligen Entitäten gespeichert:
- `projekt.kontextInformationen: list[Kontext]`
- `parzelle.kontextInformationen: list[Kontext]`
- `land.kontextInformationen: list[Kontext]`
- `kanton.kontextInformationen: list[Kontext]`
- `gemeinde.kontextInformationen: list[Kontext]`
#### 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?
- `parzelleHanglage`: 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 |
---
#### DokumentTyp (Enum)
**Dokumenttyp zur Kategorisierung von Dokumenten, insbesondere für kantonale und kommunale Dokumente.**
| Wert | Beschreibung |
|------|--------------|
| `kantonBaureglementAktuell` | Aktuelles Baureglement (Kanton) |
| `kantonBaureglementRevision` | Baureglement in Revision (Kanton) |
| `kantonBauverordnungAktuell` | Aktuelle Bauverordnung (Kanton) |
| `kantonBauverordnungRevision` | Bauverordnung in Revision (Kanton) |
| `gemeindeBzoAktuell` | Aktuelle Bau- und Zonenordnung (BZO) (Gemeinde) |
| `gemeindeBzoRevision` | BZO in Revision (Gemeinde) |
**Verwendung:**
- Dokumente in der `dokumente`-Liste von Kanton oder Gemeinde können über `dokumentTyp` identifiziert werden
- Ermöglicht die Suche nach aktuellen oder in Revision befindlichen Dokumenten
---
## Q & A
1. **Versionierung**: Sollen Änderungen an Parzellen historisiert werden? --> Vorerst nicht
2. **Mehrsprachigkeit**: Labels in DE/FR/IT? --> Wir im Pydantic Model später umgesetzt
3. **Benutzer & Rollen**: Wer darf was bearbeiten? --> In der App über Roles und Permissions gesteuert
4. **Workflow-Engine**: Für Statusübergänge und Genehmigungen? --> In der App über Workflow-Engine gesteuert
5. **Integration**: Anbindung an amtliche Geodaten (z.B. Swisstopo API)? --> In der App über Integrationen gesteuert
6. **Berechnungen**: Sollen Ausnützungsberechnungen automatisiert werden? --> In der App über Berechnungen gesteuert
---
## 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

View file

@ -0,0 +1,169 @@
# Überblick und Projektstruktur
[← Zurück zum Inhaltsverzeichnis](README.md) | [Weiter: Datenmodell erstellen →](02-datamodels.md)
## Überblick
Das Feature "realEstate" bietet eine **stateless API** für Real Estate-Datenbankoperationen mit AI-basierter natürlicher Sprachverarbeitung:
### Hauptfunktionalität
- **Natürliche Sprache → CRUD-Operationen**: User-Input wird mit AI analysiert und in entsprechende Datenbankoperationen übersetzt
- **Direkte Datenbankabfragen**: SQL-Queries können direkt ausgeführt werden
- **CRUD-Operationen**: Erstellen, Lesen, Aktualisieren und Löschen von Real Estate-Entitäten
- **PostgreSQL-Datenbankzugriff**: Über den bestehenden DatabaseConnector
- **RESTful API-Endpunkte**: Einfache, stateless Endpunkte ohne Session-Management
- **Benutzerauthentifizierung und Zugriffskontrolle**: Integriert in bestehende UAM-Struktur
### Architektur-Ansatz
**Stateless Design:**
- Keine Chat-Sessions notwendig (optional für zukünftige Erweiterungen)
- Jeder Request ist unabhängig
- Direkter Flow: User-Input → AI-Analyse → CRUD-Operation → Ergebnis
- Keine Query-History (kann optional später hinzugefügt werden)
**AI-Integration:**
- Nutzt `serviceAi` für Intent-Erkennung
- Übersetzt natürliche Sprache in CRUD-Operationen
- Unterstützt CREATE, READ, UPDATE, DELETE, QUERY-Intents
**Real Estate-Datenmodell-Entitäten:**
- **Kernentitäten**: Projekt, Parzelle
- **Unterstützend**: Dokument, Kontext
- **Geografisch**: GeoPolylinie, GeoPunkt
- **Administrativ**: Land, Kanton, Gemeinde
### Architektur-Komponenten
Die Architektur folgt dem Muster bestehender Features:
- **Routes** (`modules/routes/`) - API-Endpunkte (stateless)
- **Features** (`modules/features/`) - Geschäftslogik mit AI-Integration
- **Interfaces** (`modules/interfaces/`) - Datenbankzugriff (CRUD-Operationen)
- **DataModels** (`modules/datamodels/`) - Pydantic-Modelle für Real Estate-Entitäten
- **Services** (`modules/services/`) - AI-Service für Intent-Analyse
---
## Projektstruktur
```
gateway/
├── modules/
│ ├── routes/
│ │ └── routeRealEstate.py # NEU: Stateless API-Endpunkte
│ │ ├── POST /api/realestate/command # Natürliche Sprache → CRUD
│ │ └── POST /api/realestate/query # Direkte SQL-Query
│ │
│ ├── features/
│ │ └── realEstate/
│ │ └── mainRealEstate.py # NEU: Feature-Logik mit AI-Integration
│ │ ├── processNaturalLanguageCommand() # Hauptfunktion
│ │ ├── analyzeUserIntent() # AI-basierte Intent-Analyse
│ │ └── executeIntentBasedOperation() # CRUD-Ausführung
│ │
│ ├── interfaces/
│ │ ├── interfaceDbRealEstateAccess.py # NEU: Zugriffskontrolle
│ │ ├── interfaceDbRealEstateObjects.py # NEU: CRUD-Interface
│ │ └── interfaceDbRealEstateChatObjects.py # OPTIONAL: Für Session-Support
│ │
│ ├── datamodels/
│ │ ├── datamodelRealEstate.py # NEU: Real Estate Datenmodelle
│ │ │ ├── Projekt, Parzelle, Dokument, etc.
│ │ └── datamodelRealEstateChat.py # OPTIONAL: Für Session-Support
│ │
│ ├── services/
│ │ └── serviceAi/ # BEREITS VORHANDEN
│ │ └── mainServiceAi.py # Wird für Intent-Analyse genutzt
│ │
│ └── connectors/
│ └── connectorDbPostgre.py # BEREITS VORHANDEN
├── app.py # Router-Registrierung
├── env_dev.env # Environment-Konfiguration
└── modules/features/featuresLifecycle.py # Feature-Lifecycle
```
### Wichtige Dateien
**Erforderlich:**
- `routeRealEstate.py` - API-Endpunkte (stateless)
- `mainRealEstate.py` - Feature-Logik mit AI-Integration
- `interfaceDbRealEstateAccess.py` - Zugriffskontrolle
- `interfaceDbRealEstateObjects.py` - CRUD-Interface
- `datamodelRealEstate.py` - Datenmodelle
**Optional (für zukünftige Session-Unterstützung):**
- `interfaceDbRealEstateChatObjects.py` - Session-Management
- `datamodelRealEstateChat.py` - Session-Modelle
---
## Datenfluss: User-Input → Ergebnis
### Flow: Natürliche Sprache ohne Session
```
1. User sendet Request
POST /api/realestate/command
Body: { "userInput": "Erstelle ein neues Projekt namens 'Hauptstrasse 42'" }
2. Route empfängt Request
routeRealEstate.process_command()
→ Auth: getCurrentUser()
3. Feature-Logik verarbeitet
mainRealEstate.processNaturalLanguageCommand()
→ Services initialisieren: getServices(currentUser, workflow=None)
4. AI analysiert Intent
analyzeUserIntent(services.ai, userInput)
→ services.ai.callAiPlanning(intentPrompt)
→ AI gibt zurück: { "intent": "CREATE", "entity": "Projekt", "parameters": {...} }
5. CRUD-Operation ausführen
executeIntentBasedOperation(intent, entity, parameters)
→ getRealEstateInterface(currentUser)
→ RealEstateObjects.createProjekt(projekt)
→ DatabaseConnector.recordCreate(Projekt)
6. Ergebnis zurückgeben
HTTP 200 OK
{ "success": true, "intent": "CREATE", "result": {...} }
```
### Flow: Direkte SQL-Query
```
1. User sendet Request
POST /api/realestate/query
Body: { "queryText": "SELECT * FROM Projekt WHERE plz = '8000'" }
2. Route empfängt Request
routeRealEstate.execute_direct_query()
→ Auth: getCurrentUser()
3. Query ausführen
getChatInterface(currentUser)
→ RealEstateChatObjects.executeQuery(queryText)
→ DatabaseConnector.executeQuery(sql)
4. Ergebnis zurückgeben
HTTP 200 OK
{ "rows": [...], "columns": [...], "rowCount": 15 }
```
---
## Vorteile des stateless Ansatzes
- **Einfachheit**: Kein Session-Management notwendig
- **Performance**: Weniger Datenbank-Operationen pro Request
- **Skalierbarkeit**: Stateless Requests sind einfacher zu skalieren
- **Flexibilität**: Jeder Request ist unabhängig
- **Erweiterbarkeit**: Session-Support kann später optional hinzugefügt werden
---
**Nächster Schritt:** [02-datamodels.md](02-datamodels.md)

View file

@ -0,0 +1,976 @@
# Schritt 1: Datenmodell erstellen
[← Zurück zur Übersicht](README.md) | [Weiter: Interface erstellen →](03-interfaces.md)
## Real Estate-Datenmodelle
**Datei:** `modules/datamodels/datamodelRealEstate.py`
Das Feature arbeitet **stateless** ohne Session-Management. Die Datenmodelle definieren die Struktur der Real Estate-Entitäten, die über die API verwaltet werden können.
**Hinweis:** Die Real Estate-Datenmodell-Entitäten (Projekt, Parzelle, Dokument, etc.) werden in `datamodelRealEstate.py` definiert. Diese werden direkt über CRUD-Operationen verwaltet, ohne zusätzliche Chat-Interface-Modelle.
### Warum keine Chat-Interface-Modelle?
Das Feature arbeitet **stateless** ohne Session-Management. Alle Operationen arbeiten direkt auf den Real Estate-Datenmodellen:
#### Stateless Design
- **Keine Session-Modelle**: Keine `RealEstateChatSession` notwendig
- **Keine Query-History**: Queries werden nicht gespeichert (kann optional später hinzugefügt werden)
- **Direkte CRUD-Operationen**: User-Input → AI-Analyse → CRUD → Ergebnis
- **Einfache Architektur**: Weniger Komplexität, bessere Performance
#### Real Estate-Modelle
Die Real Estate-Modelle (`Projekt`, `Parzelle`, `Dokument`, etc.):
- Repräsentieren die **tatsächlichen Geschäftsdaten** der Immobilien-Projekte
- Werden über lange Zeiträume gepflegt und verändert
- Haben komplexe Beziehungen zueinander (Projekt → Parzellen → Dokumente)
- Werden direkt über CRUD-Operationen verwaltet
#### Datenfluss (stateless)
```
User Input (natürliche Sprache)
AI-Analyse (Intent-Erkennung)
CRUD-Operation identifizieren
Real Estate-Modelle
Datenbank-Operation
Ergebnis zurückgeben
(keine Session, keine History)
```
---
### Stateless vs. Session-basiert
**Real Estate Feature (stateless):**
- Direkte CRUD-Operationen auf Real Estate-Modellen
- Keine Session-Modelle notwendig
- Keine Query-History
- Einfacher und schneller
**Chat-System (session-basiert):**
- Verwendet `ChatWorkflow` für komplexe AI-Workflows
- Verwendet `ChatDocument` für Datei-Verknüpfungen
- Session-Management für Multi-Step-Operationen
- Für komplexe Workflows mit Planung und Review
**Unterschied:**
- Real Estate Feature ist für **einfache CRUD-Operationen** optimiert
- Chat-System ist für **komplexe AI-Workflows** optimiert
- Beide können parallel existieren und für verschiedene Use Cases genutzt werden
---
### Warum nicht das bestehende `ChatWorkflow` verwenden?
Sie fragen sich vielleicht: **Kann ich nicht einfach das bestehende `ChatWorkflow` aus `datamodelChat.py` verwenden?**
Die kurze Antwort: **Für stateless CRUD-Operationen ist `ChatWorkflow` zu komplex**. Das Real Estate Feature arbeitet ohne Session-Management und nutzt direkt die Real Estate-Modelle.
#### Unterschiedliche Anwendungsfälle
| **Aspekt** | **ChatWorkflow (bestehend)** | **Real Estate Feature (stateless)** |
|------------|------------------------------|-------------------------------------|
| **Zweck** | Komplexe AI-gesteuerte Workflows mit mehreren Tasks/Actions | Einfache CRUD-Operationen |
| **Komplexität** | Hoch: Tasks, Actions, Rounds, Workflow-Modi, Retries | Niedrig: Direkte CRUD-Operationen |
| **Session** | Session-Management für Multi-Step-Workflows | Keine Session, stateless |
| **Verarbeitung** | Multi-Step AI-Workflows mit Planung, Review, Iteration | Direkte CRUD: User-Input → AI-Analyse → CRUD → Ergebnis |
| **Ergebnisse** | `ChatMessage` mit `documents`, `ActionResult` | Direkte CRUD-Ergebnisse (Projekt, Parzelle, etc.) |
#### Warum `ChatWorkflow` nicht passt:
1. **Zu komplex**: `ChatWorkflow` hat viele Felder, die für einfache CRUD-Operationen nicht relevant sind
2. **Session-basiert**: `ChatWorkflow` benötigt Session-Management, das wir nicht brauchen
3. **Falsches Abstraktionsniveau**: `ChatWorkflow` ist für komplexe AI-Workflows, Real Estate braucht einfache CRUD-Operationen
#### Die richtige Lösung: Direkte CRUD-Operationen
Stattdessen arbeiten wir **direkt** mit den Real Estate-Modellen:
```python
# Stateless CRUD-Operationen
User Input → AI-Analyse → CRUD-Operation → Ergebnis
# Keine Session, keine History, einfach und schnell
```
#### Wann könnte man `ChatWorkflow` verwenden?
Sie könnten `ChatWorkflow` verwenden, wenn Sie:
- ✅ **Komplexe AI-Workflows** für Real Estate implementieren wollen (z.B. "Analysiere alle Projekte und erstelle einen Bericht")
- ✅ **Multi-Step-Verarbeitung** benötigen (z.B. "Lade Daten → Transformiere → Erstelle Visualisierung")
- ✅ **Planung und Review** brauchen (z.B. "Prüfe alle Parzellen auf Konformität")
Aber für **einfache CRUD-Operationen** ist der stateless Ansatz die bessere Wahl.
---
## Real Estate-Datenmodell-Implementierung:
Die Real Estate-Datenmodell-Entitäten müssen separat in `modules/datamodels/datamodelRealEstate.py` implementiert werden.
Siehe `../PEK_datamodel_desc.md` für die vollständige Spezifikation aller Felder und Beziehungen (PEK ist ein Beispiel für eine Real Estate-Firma, das Modell ist aber allgemein verwendbar).
### Wichtige Hinweise zum Datenmodell
**Objektmodell vs. Datenbank-Repräsentation:**
Dieses Dokument beschreibt ein **Objektmodell** für die Arbeit im Code. Es handelt sich **NICHT** um ein relationales Datenbankmodell mit Junction Tables.
- **Im Code-Modell**: Alle Beziehungen werden als Objektreferenzen oder Listen von Objekten dargestellt (z.B. `dokumente: list[Dokument]`, `parzellen: list[Parzelle]`).
- **Für die Datenbank-Serialisierung**: Bei der Persistierung können Junction Tables verwendet werden, um n:m-Beziehungen in der Datenbank abzubilden. Dies ist jedoch ein Implementierungsdetail der Datenbank-Schicht und gehört nicht zum Hauptmodell.
**Systemattribute:**
Alle Datenobjekte haben automatisch die folgenden Systemattribute:
- `_createdAt`: Float (Timestamp UTC)
- `_createdBy`: String (User-ID)
- `_modifiedAt`: Float (Timestamp UTC)
- `_modifiedBy`: String (User-ID)
**Timestamps:**
- Alle Timestamps sind im Float-Format UTC im Datenmodell gespeichert.
- Die Darstellung im UI erfolgt mit der lokalen Zeitzone des Benutzers.
**Wichtige Punkte für die Implementierung:**
- Objektbeziehungen wie `parzellen: list[Parzelle]` werden als JSONB in PostgreSQL gespeichert
- Einzelne Objektreferenzen wie `kontextKanton: Optional[str]` werden als String-ID (Foreign Key) gespeichert
- Administrative Hierarchie: `Kanton` benötigt `id_land` (Foreign Key zu Land), `Gemeinde` benötigt `id_kanton` (Foreign Key zu Kanton)
- Alle Entitäten benötigen `mandateId` für Mandaten-Isolation
- Systemattribute (`_createdAt`, `_createdBy`, etc.) werden automatisch vom DatabaseConnector hinzugefügt
### Datenfluss-Diagramm
```mermaid
---
title: Hauptflüsse - Architektur-Planungs-App
---
flowchart LR
subgraph Admin[Administrative Ebene]
Land[Land<br/>Schweiz]
Kanton[Kanton<br/>z.B. Zürich]
Gemeinde[Gemeinde<br/>z.B. Zürich Stadt]
Land --> Kanton
Kanton --> Gemeinde
end
subgraph Geo[Geografische Daten]
GeoPolylinie[GeoPolylinie<br/>Linie/Polygon]
GeoPunkt[GeoPunkt<br/>Koordinaten]
GeoPolylinie --> GeoPunkt
end
subgraph Core[Kern-Business-Logik]
Projekt[Projekt<br/>Bauprojekt]
Parzelle[Parzelle<br/>Grundstück mit<br/>Bauparametern]
Gemeinde --> Parzelle
Projekt --> Parzelle
Projekt --> GeoPolylinie
Parzelle --> GeoPolylinie
end
subgraph Support[Unterstützende Daten]
Dokument[Dokument<br/>Dateien & URLs]
Kontext[Kontext<br/>Zusatzinfos]
end
```
### Übersichtstabelle aller Entitäten
| Objekt | Typ | Beschreibung | Hauptfelder |
|--------|-----|--------------|-------------|
| **Projekt** | Kernentität | Bauprojekt mit Status und Perimeter | id, label, statusProzess, perimeter, baulinie, parzellen |
| **Parzelle** | Hauptentität | Grundstück mit Bauparametern | id, label, plz, bauzone, AZ, BZ, perimeter, baulinie, laermschutzzone, hochwasserschutzzone, grundwasserschutzzone |
| **Dokument** | Unterstützend | Dateien und URLs mit Versionierung | id, label, dokumentTyp, quelle, mimeType, kategorienTags |
| **Kontext** | Unterstützend | Flexible Zusatzinformationen | id, thema, inhalt |
| **GeoPolylinie** | Hilfsobjekt | Geometrische Linie/Polygon | id, closed, punkte |
| **Land** | Admin | Nationale Ebene | id, label, abk |
| **Kanton** | Admin | Kantonale Ebene mit Baurecht | id, label, id_land, abk |
| **Gemeinde** | Admin | Gemeinde-Ebene mit BZO | id, label, id_kanton, plz |
| **GeoPunkt** | Hilfsobjekt | 3D-Koordinate | koordinatensystem, x, y, z, referenz |
| **GeoTag** | Enum | Geopunkt-Kategorien | K1, K2, K3, Geometer |
| **JaNein** | Enum | Drei-wertiger Status | "", "Ja", "Nein" |
| **StatusProzess** | Enum | Projektstatus | 7 Werte (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv) |
| **DokumentTyp** | Enum | Dokumenttyp | 6 Werte (kantonBaureglementAktuell, kantonBaureglementRevision, etc.) |
### Beispiel: Vollständige Pydantic-Modelle für Real Estate-Entitäten
Hier ist ein Beispiel, wie die Pydantic-Modelle für die Real Estate-Entitäten aussehen sollten:
```python
"""
Real Estate data models for Architektur-Planungs-App.
Implements a general Swiss architecture planning data model.
(PEK is one example implementation, but the model is general-purpose)
"""
from typing import List, Dict, Any, Optional, ForwardRef
from enum import Enum
from pydantic import BaseModel, Field
from modules.shared.attributeUtils import registerModelLabels
from modules.shared.timeUtils import getUtcTimestamp
import uuid
# ===== Enums =====
class StatusProzess(str, Enum):
"""Project process status"""
EINGANG = "Eingang"
ANALYSE = "Analyse"
STUDIE = "Studie"
PLANUNG = "Planung"
BAURECHTSVERFAHREN = "Baurechtsverfahren"
UMSETZUNG = "Umsetzung"
ARCHIV = "Archiv"
class DokumentTyp(str, Enum):
"""Document type for categorization"""
KANTON_BAUREGLEMENT_AKTUELL = "kantonBaureglementAktuell"
KANTON_BAUREGLEMENT_REVISION = "kantonBaureglementRevision"
KANTON_BAUVERORDNUNG_AKTUELL = "kantonBauverordnungAktuell"
KANTON_BAUVERORDNUNG_REVISION = "kantonBauverordnungRevision"
GEMEINDE_BZO_AKTUELL = "gemeindeBzoAktuell"
GEMEINDE_BZO_REVISION = "gemeindeBzoRevision"
class JaNein(str, Enum):
"""Three-valued state for optional yes/no questions"""
UNBEKANNT = "" # Empty string for unknown/not captured
JA = "Ja"
NEIN = "Nein"
class GeoTag(str, Enum):
"""Geopoint categories"""
K1 = "K1" # Fixpunkt höchster Genauigkeit
K2 = "K2" # Fixpunkt mittlerer Genauigkeit
K3 = "K3" # Fixpunkt niedriger Genauigkeit
GEOMETER = "Geometer" # Vom Geometer vermessener Punkt
# ===== Helper Models (must be defined before main models) =====
class GeoPunkt(BaseModel):
"""Represents a 3D point with reference."""
koordinatensystem: str = Field(
description="Coordinate system (e.g. 'LV95', 'EPSG:2056')",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
x: float = Field(
description="East value (E) [m], typically 2'480'000 - 2'840'000",
frontend_type="number",
frontend_readonly=False,
frontend_required=True,
)
y: float = Field(
description="North value (N) [m], typically 1'070'000 - 1'300'000",
frontend_type="number",
frontend_readonly=False,
frontend_required=True,
)
z: Optional[float] = Field(
None,
description="Height above sea level [m]",
frontend_type="number",
frontend_readonly=False,
frontend_required=False,
)
referenz: Optional[GeoTag] = Field(
None,
description="Point categorization",
frontend_type="select",
frontend_readonly=False,
frontend_required=False,
)
class GeoPolylinie(BaseModel):
"""Represents a line or polygon from multiple GeoPunkte."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
)
closed: bool = Field(
description="Is the GeoPolylinie closed (polygon)?",
frontend_type="boolean",
frontend_readonly=False,
frontend_required=True,
)
punkte: List[GeoPunkt] = Field(
default_factory=list,
description="List of GeoPunkte forming the GeoPolylinie",
frontend_type="json",
frontend_readonly=False,
frontend_required=True,
)
class Dokument(BaseModel):
"""Supporting data object for file and URL management with versioning."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
mandateId: str = Field(
description="ID of the mandate this document belongs to",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
label: str = Field(
description="Document label",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
versionsbezeichnung: Optional[str] = Field(
None,
description="Version number or designation (e.g. 'v1.0', 'Rev. A')",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
dokumentTyp: Optional[DokumentTyp] = Field(
None,
description="Document type",
frontend_type="select",
frontend_readonly=False,
frontend_required=False,
)
dokumentReferenz: str = Field(
description="File path or URL",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
quelle: Optional[str] = Field(
None,
description="Source of the document",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
mimeType: Optional[str] = Field(
None,
description="MIME type of the document (e.g. 'application/pdf', 'image/png')",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
kategorienTags: List[str] = Field(
default_factory=list,
description="Document categorization tags",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
# Beispiel-Kategorien für Dokumente (nicht abschließend):
# - "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
# - "Bauherrschaft" - Dokumente von der Bauherrschaft
# - "Planung" - Planungsdokumente
#
# Hinweis: Aktuelle Dokumente (z.B. aktuelle Baureglemente, BZO) können anhand des
# `dokumentTyp`-Attributs identifiziert werden. Die entsprechenden Dokumente finden sich
# in der `dokumente`-Liste der jeweiligen Entität (Kanton, Gemeinde).
class Kontext(BaseModel):
"""Supporting data object for flexible additional information."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
)
thema: str = Field(
description="Theme designation",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
inhalt: str = Field(
description="Detailed information (text)",
frontend_type="textarea",
frontend_readonly=False,
frontend_required=True,
)
# Beispielthemen für Kontext (nicht abschließend):
# - "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
#
# Verwendung: Kontext-Objekte werden als Listen in den jeweiligen Entitäten gespeichert:
# - projekt.kontextInformationen: list[Kontext]
# - parzelle.kontextInformationen: list[Kontext]
# - land.kontextInformationen: list[Kontext]
# - kanton.kontextInformationen: list[Kontext]
# - gemeinde.kontextInformationen: list[Kontext]
#
# Design-Rationale: Das Kontext-Objekt ermöglicht flexibles Hinzufügen von projektspezifischen,
# parzellen-spezifischen oder regionalen Informationen ohne Schemaänderungen.
class Land(BaseModel):
"""National level administrative entity."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
mandateId: str = Field(
description="ID of the mandate",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
label: str = Field(
description="Country name (e.g. 'Schweiz')",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
abk: Optional[str] = Field(
None,
description="Abbreviation (e.g. 'CH')",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
dokumente: List[Dokument] = Field(
default_factory=list,
description="National laws/documents",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
kontextInformationen: List[Kontext] = Field(
default_factory=list,
description="National context information",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
class Kanton(BaseModel):
"""Cantonal level administrative entity."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
mandateId: str = Field(
description="ID of the mandate",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
label: str = Field(
description="Canton name (e.g. 'Zürich')",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
id_land: Optional[str] = Field(
None,
description="Land ID (Foreign Key) - eindeutiger Link zum Land, in welchem Land der Kanton liegt",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
abk: Optional[str] = Field(
None,
description="Abbreviation (e.g. 'ZH')",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
dokumente: List[Dokument] = Field(
default_factory=list,
description="Cantonal documents",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
kontextInformationen: List[Kontext] = Field(
default_factory=list,
description="Canton-specific context information",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
class Gemeinde(BaseModel):
"""Municipal level administrative entity."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
mandateId: str = Field(
description="ID of the mandate",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
label: str = Field(
description="Municipality name (e.g. 'Zürich')",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
id_kanton: Optional[str] = Field(
None,
description="Kanton ID (Foreign Key) - eindeutiger Link zum Kanton, in welchem Kanton die Gemeinde liegt",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
plz: Optional[str] = Field(
None,
description="Postal code (for municipalities with multiple PLZ, this can be a main PLZ). Bei Gemeinden mit mehreren Postleitzahlen wird die konkrete PLZ der Parzelle im Attribut `plz` der Parzelle erfasst.",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
dokumente: List[Dokument] = Field(
default_factory=list,
description="Municipal documents",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
kontextInformationen: List[Kontext] = Field(
default_factory=list,
description="Municipality-specific context information",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
# ===== Main Models (use ForwardRef for circular references) =====
# Forward references for circular dependencies
ParzelleRef = ForwardRef('Parzelle')
class Parzelle(BaseModel):
"""Represents a plot with all building law properties."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
mandateId: str = Field(
description="ID of the mandate",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
# Grunddaten
label: str = Field(
description="Plot designation",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
parzellenAliasTags: List[str] = Field(
default_factory=list,
description="Additional plot names or field names",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
eigentuemerschaft: Optional[str] = Field(
None,
description="Owner of the plot",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
strasseNr: Optional[str] = Field(
None,
description="Street and house number",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
plz: Optional[str] = Field(
None,
description="Postal code of the plot",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
# Geografischer Kontext
perimeter: Optional[GeoPolylinie] = Field(
None,
description="Plot boundary as closed GeoPolylinie",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
baulinie: Optional[GeoPolylinie] = Field(
None,
description="Building line of the plot",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
kontextLand: Optional[str] = Field(
None,
description="Land ID (Foreign Key)",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
kontextKanton: Optional[str] = Field(
None,
description="Canton ID (Foreign Key)",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
kontextGemeinde: Optional[str] = Field(
None,
description="Municipality ID (Foreign Key)",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
# Bebauungsparameter
bauzone: Optional[str] = Field(
None,
description="Building zone designation (e.g. W3, WG2, etc.)",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
az: Optional[float] = Field(
None,
description="Ausnützungsziffer",
frontend_type="number",
frontend_readonly=False,
frontend_required=False,
)
bz: Optional[float] = Field(
None,
description="Bebauungsziffer",
frontend_type="number",
frontend_readonly=False,
frontend_required=False,
)
vollgeschossZahl: Optional[int] = Field(
None,
description="Number of allowed full floors",
frontend_type="number",
frontend_readonly=False,
frontend_required=False,
)
anrechenbarDachgeschoss: Optional[float] = Field(
None,
description="Accountable portion of attic (0.0 - 1.0)",
frontend_type="number",
frontend_readonly=False,
frontend_required=False,
)
anrechenbarUntergeschoss: Optional[float] = Field(
None,
description="Accountable portion of basement (0.0 - 1.0)",
frontend_type="number",
frontend_readonly=False,
frontend_required=False,
)
gebaeudehoeheMax: Optional[float] = Field(
None,
description="Maximum building height in meters",
frontend_type="number",
frontend_readonly=False,
frontend_required=False,
)
# Abstandsregelungen
regelnGrenzabstand: List[str] = Field(
default_factory=list,
description="Regulations for boundary distance",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
regelnMehrlaengenzuschlag: List[str] = Field(
default_factory=list,
description="Regulations for additional length surcharge",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
regelnMehrhoehenzuschlag: List[str] = Field(
default_factory=list,
description="Regulations for additional height surcharge",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
# Eigenschaften (Ja/Nein)
parzelleBebaut: Optional[JaNein] = Field(
None,
description="Is the plot built?",
frontend_type="select",
frontend_readonly=False,
frontend_required=False,
)
parzelleErschlossen: Optional[JaNein] = Field(
None,
description="Is the plot developed?",
frontend_type="select",
frontend_readonly=False,
frontend_required=False,
)
parzelleHanglage: Optional[JaNein] = Field(
None,
description="Is the plot on a slope?",
frontend_type="select",
frontend_readonly=False,
frontend_required=False,
)
# Schutzzonen
laermschutzzone: Optional[str] = Field(
None,
description="Noise protection zone (e.g. 'II')",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
hochwasserschutzzone: Optional[str] = Field(
None,
description="Flood protection zone (e.g. 'tief')",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
grundwasserschutzzone: Optional[str] = Field(
None,
description="Groundwater protection zone",
frontend_type="text",
frontend_readonly=False,
frontend_required=False,
)
# Beziehungen (stored as JSONB in database)
parzellenNachbarschaft: List[Dict[str, Any]] = Field(
default_factory=list,
description="Neighboring plots (stored as list of Parzelle IDs or full objects)",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
dokumente: List[Dokument] = Field(
default_factory=list,
description="Plot-specific documents",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
kontextInformationen: List[Kontext] = Field(
default_factory=list,
description="Plot-specific context information",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
class Projekt(BaseModel):
"""Core object representing a construction project."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
mandateId: str = Field(
description="ID of the mandate",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
label: str = Field(
description="Project designation",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
statusProzess: Optional[StatusProzess] = Field(
None,
description="Project status",
frontend_type="select",
frontend_readonly=False,
frontend_required=False,
)
perimeter: Optional[GeoPolylinie] = Field(
None,
description="Envelope of all plots in the project",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
baulinie: Optional[GeoPolylinie] = Field(
None,
description="Building line of the project",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
parzellen: List[Parzelle] = Field(
default_factory=list,
description="All plots of the project",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
dokumente: List[Dokument] = Field(
default_factory=list,
description="Project-specific documents",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
kontextInformationen: List[Kontext] = Field(
default_factory=list,
description="Project-specific context information",
frontend_type="json",
frontend_readonly=False,
frontend_required=False,
)
# Resolve forward references
Parzelle.model_rebuild()
Projekt.model_rebuild()
# Register labels for frontend
registerModelLabels(
"Projekt",
{"en": "Project", "fr": "Projet", "de": "Projekt"},
{
"id": {"en": "ID", "fr": "ID", "de": "ID"},
"label": {"en": "Label", "fr": "Libellé", "de": "Bezeichnung"},
"statusProzess": {"en": "Process Status", "fr": "Statut du processus", "de": "Prozessstatus"},
# ... more labels
},
)
# Similar registerModelLabels calls for all other models...
```
**Wichtige Hinweise zur Implementierung:**
1. **Forward References**: Für zirkuläre Referenzen (z.B. `Parzelle``parzellenNachbarschaft: list[Parzelle]`) verwenden Sie `ForwardRef` oder speichern Sie nur IDs als Strings.
2. **JSONB-Speicherung**: Listen von Objekten (`list[Parzelle]`, `list[Dokument]`) werden automatisch als JSONB gespeichert. Der DatabaseConnector erkennt `List`-Typen automatisch.
3. **Foreign Keys**: Einzelne Objektreferenzen wie `kontextKanton: Optional[str]` werden als String-ID gespeichert. Sie können später im Interface die vollständigen Objekte laden.
4. **MandateId**: Alle Entitäten benötigen `mandateId` für Mandaten-Isolation.
5. **Systemattribute**: `_createdAt`, `_createdBy`, `_modifiedAt`, `_modifiedBy` werden automatisch vom DatabaseConnector hinzugefügt - Sie müssen sie nicht im Modell definieren.
---
**WICHTIG:** Die obigen Real Estate-Modelle (`Projekt`, `Parzelle`, etc.) sind die **tatsächlichen Datenmodelle**, die Sie implementieren müssen. Diese werden in `modules/datamodels/datamodelRealEstate.py` erstellt.
**Keine Chat-Interface-Modelle notwendig:**
- Das Feature arbeitet **stateless** ohne Session-Management
- Alle Operationen arbeiten direkt auf den Real Estate-Modellen
- Keine `RealEstateChatSession`, `RealEstateQuery` oder `RealEstateQueryResult` notwendig
- CRUD-Operationen werden direkt ausgeführt und Ergebnisse direkt zurückgegeben
### Wichtige Punkte:
1. **UUID als ID**: Alle Modelle verwenden `uuid.uuid4()` für eindeutige IDs
2. **MandateId**: Jedes Modell benötigt `mandateId` für Mandaten-Isolation
3. **Frontend-Metadaten**: `frontend_type`, `frontend_readonly`, `frontend_required` für UI-Generierung
4. **registerModelLabels**: Registriert Labels für Mehrsprachigkeit
5. **JSONB-Felder**: `Dict[str, Any]` und `List[...]` werden automatisch als JSONB in PostgreSQL gespeichert
6. **Foreign Keys**: Administrative Hierarchie wird über Foreign Keys abgebildet:
- `Kanton.id_land``Land.id`
- `Gemeinde.id_kanton``Kanton.id`
- `Parzelle.kontextLand``Land.id` (Optional)
- `Parzelle.kontextKanton``Kanton.id` (Optional)
- `Parzelle.kontextGemeinde``Gemeinde.id` (Optional)
---
## Q & A - Häufige Fragen
1. **Versionierung**: Sollen Änderungen an Parzellen historisiert werden?
→ Vorerst nicht
2. **Mehrsprachigkeit**: Labels in DE/FR/IT?
→ Wird im Pydantic Model über `registerModelLabels` umgesetzt
3. **Benutzer & Rollen**: Wer darf was bearbeiten?
→ In der App über Roles und Permissions gesteuert (UAM-System)
4. **Workflow-Engine**: Für Statusübergänge und Genehmigungen?
→ In der App über Workflow-Engine gesteuert (optional, kann später integriert werden)
5. **Integration**: Anbindung an amtliche Geodaten (z.B. Swisstopo API)?
→ In der App über Integrationen gesteuert (optional)
6. **Berechnungen**: Sollen Ausnützungsberechnungen automatisiert werden?
→ In der App über Berechnungen gesteuert (optional)
---
[← Zurück zur Übersicht](README.md) | [Weiter: Interface erstellen →](03-interfaces.md)

View file

@ -0,0 +1,845 @@
# Schritt 2: Interface erstellen
[← Zurück: Datenmodell erstellen](02-datamodels.md) | [Weiter: Feature-Logik implementieren →](04-feature-logic.md)
## Übersicht: Was sind Interfaces?
**Interfaces** sind aktive Klassen, die den **Datenbankzugriff** implementieren. Sie unterscheiden sich von **Datamodels** (die nur die Datenstruktur definieren):
| **Aspekt** | **Datamodels** | **Interfaces** |
|------------|----------------|----------------|
| **Zweck** | Definiert **WAS** (Datenstruktur) | Implementiert **WIE** (Datenzugriff) |
| **Inhalt** | Pydantic-Modelle mit Feldern und Validierung | Klassen mit CRUD-Methoden (`create`, `get`, `update`, `delete`) |
| **Beispiel** | `class Projekt(BaseModel): ...` | `def createProjekt(...) -> Projekt: ...` |
| **Aktivität** | Passiv (nur Struktur) | Aktiv (führt Operationen aus) |
**Analogie:**
- **Datamodel** = Bauplan (beschreibt das Haus)
- **Interface** = Bauunternehmer (baut das Haus)
---
## Struktur: Real Estate CRUD-Interface
Da das Feature **stateless** arbeitet, benötigen wir nur **ein Interface** für CRUD-Operationen auf Real Estate-Entitäten:
### Real Estate-Datenmodelle → Real Estate CRUD-Interface
**Datamodel:** `datamodelRealEstate.py`
- `Projekt`
- `Parzelle`
- `Dokument`
- `Kanton`, `Gemeinde`, `Land`
- `GeoPolylinie`, `GeoPunkt`
- `Kontext`
- etc.
**Interface:** `interfaceDbRealEstateObjects.py`
- `RealEstateObjects` (Haupt-Interface)
- `RealEstateAccess` (Zugriffskontrolle)
- Methoden: `createProjekt()`, `getParzelle()`, `updateDokument()`, etc.
**Optional:** `interfaceDbRealEstateChatObjects.py` (nur für direkte SQL-Queries)
- `RealEstateChatObjects` - Für direkte Query-Ausführung ohne Session
- Methoden: `executeQuery()` - Führt SQL direkt aus
---
## Warum nur ein Haupt-Interface?
1. **Stateless Design**:
- Keine Session-Verwaltung notwendig
- Direkte CRUD-Operationen auf Real Estate-Modellen
2. **Einfache Architektur**:
- Ein Interface für alle CRUD-Operationen
- Weniger Komplexität, bessere Wartbarkeit
3. **Optionales Query-Interface**:
- Nur für direkte SQL-Queries (stateless)
- Keine Session-Management-Funktionen
---
## Zu erstellende Dateien
### Schritt 2a: Real Estate CRUD-Interface (ERFORDERLICH)
**Zwei separate Dateien** (wie bei anderen Features):
#### Datei 1: `modules/interfaces/interfaceDbRealEstateAccess.py`
**Enthält:**
- `RealEstateAccess` - Zugriffskontrolle für Real Estate-Entitäten
- Methoden: `uam()`, `canModify()`
**Zweck:** Prüft Zugriffsrechte und filtert Daten basierend auf Benutzerprivilegien
#### Datei 2: `modules/interfaces/interfaceDbRealEstateObjects.py`
**Enthält:**
- `RealEstateObjects` - Haupt-Interface für CRUD-Operationen
- `getInterface()` - Factory-Funktion
- Nutzt `RealEstateAccess` aus der Access-Datei
**Zweck:** Verwaltet Real Estate-Entitäten (Projekt, Parzelle, Dokument, etc.)
**Nutzt:**
- `datamodelRealEstate.py` (Projekt, Parzelle, Dokument, etc.)
- `interfaceDbRealEstateAccess.py` (für Zugriffskontrolle)
**Wann benötigt:** Für alle CRUD-Operationen auf Real Estate-Entitäten (z.B. Projekte erstellen/bearbeiten, Parzellen verwalten). Dies ist das Haupt-Interface für das Feature.
---
### Schritt 2b: Query-Interface (OPTIONAL - nur für direkte SQL-Queries)
**Eine Datei** für stateless Query-Ausführung:
#### Datei: `modules/interfaces/interfaceDbRealEstateChatObjects.py`
**Enthält:**
- `RealEstateChatObjects` - Interface für direkte SQL-Query-Ausführung
- `getInterface()` - Factory-Funktion
- Methoden: `executeQuery()` - Führt SQL direkt aus (stateless)
**Zweck:** Direkte SQL-Query-Ausführung ohne Session-Management
**Nutzt:**
- `connectorDbPostgre.DatabaseConnector` für direkte SQL-Ausführung
- Keine Chat-Modelle (stateless)
**Wann benötigt:** Nur wenn Sie direkte SQL-Queries ausführen möchten (z.B. für komplexe SELECT-Queries). Für CRUD-Operationen verwenden Sie das Real Estate CRUD-Interface.
---
## Übersicht: Dateien und ihre Beziehungen
```
┌─────────────────────────────────────────────────────────────┐
│ DATAMODELS (Struktur) │
├─────────────────────────────────────────────────────────────┤
│ datamodelRealEstate.py │
│ ├── Projekt │
│ ├── Parzelle │
│ ├── Dokument │
│ ├── Kanton, Gemeinde, Land │
│ ├── GeoPolylinie, GeoPunkt │
│ ├── Kontext │
│ └── ... │
└─────────────────────────────────────────────────────────────┘
│ nutzt
┌─────────────────────────────────────────────────────────────┐
│ INTERFACES (Zugriff) │
├─────────────────────────────────────────────────────────────┤
│ REAL ESTATE CRUD-INTERFACE (ERFORDERLICH) │
│ │
│ interfaceDbRealEstateAccess.py │
│ └── RealEstateAccess │
│ ├── uam() │
│ └── canModify() │
│ │
│ interfaceDbRealEstateObjects.py │
│ ├── RealEstateObjects │
│ │ ├── createProjekt() │
│ │ ├── getProjekt() │
│ │ ├── updateProjekt() │
│ │ ├── deleteProjekt() │
│ │ ├── createParzelle() │
│ │ ├── getParzelle() │
│ │ └── ... (CRUD für alle Entitäten) │
│ └── getInterface() │
│ └── nutzt RealEstateAccess │
│ │
│ QUERY-INTERFACE (OPTIONAL - nur für direkte SQL) │
│ │
│ interfaceDbRealEstateChatObjects.py │
│ ├── RealEstateChatObjects │
│ │ └── executeQuery() # Direkte SQL-Ausführung │
│ └── getInterface() │
│ └── Keine Access-Klasse (stateless) │
└─────────────────────────────────────────────────────────────┘
```
---
## Interface-Struktur: Access vs. Objects
Jedes Interface besteht aus **zwei Klassen**:
### 1. `*Access` Klasse (Zugriffskontrolle)
**Zweck:** Prüft, wer was sehen/dürfen darf
**Methoden:**
- `uam()` - Filtert Daten basierend auf Benutzerprivilegien
- `canModify()` - Prüft, ob Benutzer ändern darf
**Beispiel:** `RealEstateChatAccess`
### 2. `*Objects` Klasse (Haupt-Interface)
**Zweck:** Führt CRUD-Operationen aus
**Methoden:**
- `create*()` - Erstellt neue Einträge
- `get*()` - Lädt Einträge
- `update*()` - Aktualisiert Einträge
- `delete*()` - Löscht Einträge
**Nutzt:** `*Access` für Zugriffskontrolle
**Beispiel:** `RealEstateChatObjects`
**Warum getrennt?**
- Separation of Concerns: Zugriffskontrolle ist separate Verantwortlichkeit
- Wiederverwendbarkeit: Access-Klasse kann von mehreren Interfaces genutzt werden
- Testbarkeit: Zugriffskontrolle kann unabhängig getestet werden
---
## Implementierung: Real Estate CRUD-Interface
Das Real Estate CRUD-Interface besteht aus **zwei separaten Dateien**, genau wie bei anderen Features (`interfaceDbAppObjects.py` + `interfaceDbAppAccess.py`).
### Datei 1: Access-Implementierung
**Datei:** `modules/interfaces/interfaceDbRealEstateAccess.py`
```python
"""
Access control for Real Estate interface.
Handles user access management and permission checks.
"""
import logging
from typing import Dict, Any, List, Optional
from modules.datamodels.datamodelUam import User, UserPrivilege
logger = logging.getLogger(__name__)
class RealEstateAccess:
"""
Access control class for Real Estate interface.
Handles user access management and permission checks.
"""
def __init__(self, currentUser: User, db):
"""Initialize with user context."""
self.currentUser = currentUser
self.mandateId = currentUser.mandateId
self.userId = currentUser.id
if not self.mandateId or not self.userId:
raise ValueError("Invalid user context: mandateId and userId are required")
self.db = db
def uam(self, model_class: type, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Unified user access management function that filters data based on user privileges.
Args:
model_class: Pydantic model class for the table
recordset: Recordset to filter based on access rules
Returns:
Filtered recordset with access control attributes
"""
from modules.datamodels.datamodelUam import UserPrivilege
userPrivilege = self.currentUser.privilege
filtered_records = []
# System admins see all records
if userPrivilege == UserPrivilege.SYSADMIN:
filtered_records = recordset
# Admins see records in their mandate
elif userPrivilege == UserPrivilege.ADMIN:
filtered_records = [r for r in recordset if r.get("mandateId", "-") == self.mandateId]
# Regular users see only their records
else:
filtered_records = [
r for r in recordset
if r.get("mandateId", "-") == self.mandateId and r.get("_createdBy") == self.userId
]
# Add access control attributes
for record in filtered_records:
record["_hideView"] = False
record["_hideEdit"] = not self.canModify(model_class, record.get("id"))
record["_hideDelete"] = not self.canModify(model_class, record.get("id"))
return filtered_records
def canModify(self, model_class: type, recordId: Optional[str] = None) -> bool:
"""Checks if the current user can modify records."""
from modules.datamodels.datamodelUam import UserPrivilege
userPrivilege = self.currentUser.privilege
if userPrivilege == UserPrivilege.SYSADMIN:
return True
if recordId is not None:
records = self.db.getRecordset(model_class, recordFilter={"id": recordId})
if not records:
return False
record = records[0]
if userPrivilege == UserPrivilege.ADMIN and record.get("mandateId", "-") == self.mandateId:
return True
if (record.get("mandateId", "-") == self.mandateId and
record.get("_createdBy") == self.userId):
return True
return False
else:
return True # Regular users can create records
```
---
### Datei 2: Objects-Implementierung
**Datei:** `modules/interfaces/interfaceDbRealEstateObjects.py`
```python
"""
Interface to Real Estate database objects.
Uses PostgreSQL connector for data access with user/mandate filtering.
Handles CRUD operations on Real Estate entities (Projekt, Parzelle, etc.).
"""
import logging
from typing import Dict, Any, List, Optional
from modules.datamodels.datamodelRealEstate import (
Projekt,
Parzelle,
Dokument,
Kanton,
Gemeinde,
Land,
GeoPolylinie,
GeoPunkt,
Kontext,
StatusProzess,
)
from modules.datamodels.datamodelUam import User
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
# Import Access-Klasse aus separater Datei
from modules.interfaces.interfaceDbRealEstateAccess import RealEstateAccess
logger = logging.getLogger(__name__)
# Singleton factory for Real Estate interfaces
_realEstateInterfaces = {}
class RealEstateObjects:
"""
Interface to Real Estate database objects.
Uses PostgreSQL connector for data access with user/mandate filtering.
Handles CRUD operations on Real Estate entities.
"""
def __init__(self, currentUser: Optional[User] = None):
"""Initializes the Real Estate Interface."""
self.currentUser = currentUser
self.userId = currentUser.id if currentUser else None
self.mandateId = currentUser.mandateId if currentUser else None
self.access = None
# Initialize database
self._initializeDatabase()
# Set user context if provided
if currentUser:
self.setUserContext(currentUser)
def _initializeDatabase(self):
"""Initialize PostgreSQL database connection."""
try:
# Get database configuration from environment
dbHost = APP_CONFIG.get("DB_APP_HOST", "localhost")
dbDatabase = APP_CONFIG.get("DB_APP_DATABASE", "poweron_app")
dbUser = APP_CONFIG.get("DB_APP_USER")
dbPassword = APP_CONFIG.get("DB_APP_PASSWORD_SECRET")
dbPort = int(APP_CONFIG.get("DB_APP_PORT", 5432))
# Initialize database connector
self.db = DatabaseConnector(
dbHost=dbHost,
dbDatabase=dbDatabase,
dbUser=dbUser,
dbPassword=dbPassword,
dbPort=dbPort,
userId=self.userId if self.userId else None,
)
logger.info(f"Real Estate database connector initialized for database: {dbDatabase}")
except Exception as e:
logger.error(f"Error initializing Real Estate database: {e}")
raise
def setUserContext(self, currentUser: User):
"""Sets the user context for the interface."""
self.currentUser = currentUser
self.userId = currentUser.id
self.mandateId = currentUser.mandateId
if not self.userId or not self.mandateId:
raise ValueError("Invalid user context: id and mandateId are required")
# Initialize access control
self.access = RealEstateAccess(self.currentUser, self.db)
# Update database context
self.db.updateContext(self.userId)
# ===== Projekt Methods =====
def createProjekt(self, projekt: Projekt) -> Projekt:
"""Create a new project."""
# Ensure mandateId is set
if not projekt.mandateId:
projekt.mandateId = self.mandateId
# Apply access control
self.access.uam(Projekt, [])
# Save to database
self.db.recordCreate(Projekt, projekt.model_dump())
return projekt
def getProjekt(self, projektId: str) -> Optional[Projekt]:
"""Get a project by ID."""
records = self.db.getRecordset(
Projekt,
recordFilter={"id": projektId}
)
if not records:
return None
# Apply access control
filtered = self.access.uam(Projekt, records)
if not filtered:
return None
return Projekt(**filtered[0])
def updateProjekt(self, projektId: str, updateData: Dict[str, Any]) -> Optional[Projekt]:
"""Update a project."""
projekt = self.getProjekt(projektId)
if not projekt:
return None
# Check if user can modify
if not self.access.canModify(Projekt, projektId):
raise PermissionError(f"User {self.userId} cannot modify project {projektId}")
# Update fields
for key, value in updateData.items():
if hasattr(projekt, key):
setattr(projekt, key, value)
# Save to database
self.db.recordModify(Projekt, projektId, projekt.model_dump())
return projekt
def deleteProjekt(self, projektId: str) -> bool:
"""Delete a project."""
projekt = self.getProjekt(projektId)
if not projekt:
return False
# Check if user can modify
if not self.access.canModify(Projekt, projektId):
raise PermissionError(f"User {self.userId} cannot delete project {projektId}")
return self.db.recordDelete(Projekt, projektId)
# ===== Parzelle Methods =====
def createParzelle(self, parzelle: Parzelle) -> Parzelle:
"""Create a new plot."""
if not parzelle.mandateId:
parzelle.mandateId = self.mandateId
self.access.uam(Parzelle, [])
self.db.recordCreate(Parzelle, parzelle.model_dump())
return parzelle
def getParzelle(self, parzelleId: str) -> Optional[Parzelle]:
"""Get a plot by ID."""
records = self.db.getRecordset(
Parzelle,
recordFilter={"id": parzelleId}
)
if not records:
return None
filtered = self.access.uam(Parzelle, records)
if not filtered:
return None
return Parzelle(**filtered[0])
def updateParzelle(self, parzelleId: str, updateData: Dict[str, Any]) -> Optional[Parzelle]:
"""Update a plot."""
parzelle = self.getParzelle(parzelleId)
if not parzelle:
return None
if not self.access.canModify(Parzelle, parzelleId):
raise PermissionError(f"User {self.userId} cannot modify plot {parzelleId}")
for key, value in updateData.items():
if hasattr(parzelle, key):
setattr(parzelle, key, value)
self.db.recordModify(Parzelle, parzelleId, parzelle.model_dump())
return parzelle
def deleteParzelle(self, parzelleId: str) -> bool:
"""Delete a plot."""
parzelle = self.getParzelle(parzelleId)
if not parzelle:
return False
if not self.access.canModify(Parzelle, parzelleId):
raise PermissionError(f"User {self.userId} cannot delete plot {parzelleId}")
return self.db.recordDelete(Parzelle, parzelleId)
# ===== Dokument Methods =====
def createDokument(self, dokument: Dokument) -> Dokument:
"""Create a new document."""
if not dokument.mandateId:
dokument.mandateId = self.mandateId
self.access.uam(Dokument, [])
self.db.recordCreate(Dokument, dokument.model_dump())
return dokument
def getDokument(self, dokumentId: str) -> Optional[Dokument]:
"""Get a document by ID."""
records = self.db.getRecordset(
Dokument,
recordFilter={"id": dokumentId}
)
if not records:
return None
filtered = self.access.uam(Dokument, records)
if not filtered:
return None
return Dokument(**filtered[0])
# ... weitere CRUD-Methoden für andere Entitäten (Kanton, Gemeinde, Land, etc.)
def getInterface(currentUser: User) -> RealEstateObjects:
"""
Factory function to get or create a Real Estate interface instance for a user.
Uses singleton pattern per user.
"""
userKey = f"{currentUser.id}_{currentUser.mandateId}"
if userKey not in _realEstateInterfaces:
_realEstateInterfaces[userKey] = RealEstateObjects(currentUser)
return _realEstateInterfaces[userKey]
```
## Wichtige Punkte:
1. **DatabaseConnector**: Nutzt `connectorDbPostgre.DatabaseConnector` für Datenbankzugriff
2. **Access Control**: `RealEstateAccess` implementiert Benutzer- und Mandaten-Filterung
3. **Singleton Pattern**: `getInterface()` erstellt pro User eine Instanz
4. **CRUD-Operationen**: `recordCreate`, `recordModify`, `recordDelete`, `getRecordset` vom Connector
5. **MandateId**: Wird automatisch gesetzt, wenn nicht vorhanden
---
## Schritt 2b: Query-Interface (OPTIONAL - nur für direkte SQL-Queries)
### Wann benötigt?
**Kurze Antwort:** Nur wenn Sie **direkte SQL-Queries** ausführen möchten (z.B. für komplexe SELECT-Queries). Für CRUD-Operationen verwenden Sie das Real Estate CRUD-Interface.
#### Szenario 1: Alles über CRUD-Interface (EMPFOHLEN)
**Strukturiert und sicher:**
```python
# User schreibt: "Erstelle Projekt 'Test'"
# Feature-Logik nutzt CRUD-Interface:
realEstateInterface = getRealEstateInterface(currentUser)
projekt = realEstateInterface.createProjekt(Projekt(
mandateId=currentUser.mandateId, # Automatisch gesetzt
label="Test" # Validierung durch Pydantic
))
```
**Vorteile:**
- ✅ **Validierung**: Pydantic-Modelle prüfen alle Felder automatisch
- ✅ **Zugriffskontrolle**: `RealEstateAccess` prüft Berechtigungen
- ✅ **Sicherheit**: Kein SQL-Injection-Risiko
- ✅ **Geschäftslogik**: Automatisches Setzen von Systemfeldern (`_createdBy`, `_createdAt`)
- ✅ **Typsicherheit**: Fehler werden zur Entwicklungszeit erkannt
- ✅ **Wartbarkeit**: Zentrale CRUD-Methoden, einfach zu testen
#### Szenario 2: Direkte SQL-Queries (OPTIONAL)
**Nur für komplexe SELECT-Queries:**
```python
# Für komplexe Queries, die nicht über CRUD-Methoden abgedeckt sind
chatInterface = getChatInterface(currentUser)
results = chatInterface.executeQuery(
"SELECT p.*, COUNT(parz.id) as parzellen_count FROM Projekt p LEFT JOIN Parzelle parz ON parz.projektId = p.id GROUP BY p.id"
)
```
**Warnung:**
- ⚠️ **Nur für SELECT-Queries**: Keine INSERT/UPDATE/DELETE über direkte SQL-Queries
- ⚠️ **Validierung erforderlich**: Queries sollten validiert werden
- ⚠️ **SQL-Injection-Risiko**: Immer Parameterisierung verwenden
#### Empfehlung
**Sie benötigen das Query-Interface, wenn Sie:**
- ✅ **Komplexe SELECT-Queries** benötigen (z.B. JOINs, Aggregationen)
- ✅ **Flexible Query-Ausführung** benötigen (nicht über CRUD-Methoden abgedeckt)
**Sie benötigen es NICHT, wenn Sie:**
- ✅ **Nur CRUD-Operationen** benötigen (verwenden Sie das CRUD-Interface)
- ✅ **Einfache Queries** haben (können über CRUD-Methoden abgedeckt werden)
**Für Production-Systeme:**
- **CRUD-Operationen**: Immer über CRUD-Interface
- **Komplexe Queries**: Optional über Query-Interface (nur SELECT)
### Struktur: Query-Interface (stateless)
**Eine Datei** für direkte SQL-Query-Ausführung:
#### Datei: `modules/interfaces/interfaceDbRealEstateChatObjects.py`
```python
"""
Interface for direct SQL query execution (stateless).
Uses PostgreSQL connector for direct query execution without session management.
"""
import logging
from typing import Dict, Any, Optional
from modules.datamodels.datamodelUam import User
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
# Singleton factory
_realEstateChatInterfaces = {}
class RealEstateChatObjects:
"""Interface for direct SQL query execution (stateless)."""
def __init__(self, currentUser: Optional[User] = None):
"""Initialize the Query Interface."""
self.currentUser = currentUser
self.userId = currentUser.id if currentUser else None
self.mandateId = currentUser.mandateId if currentUser else None
# Initialize database
self._initializeDatabase()
# Set user context if provided
if currentUser:
self.setUserContext(currentUser)
def _initializeDatabase(self):
"""Initialize PostgreSQL database connection."""
try:
dbHost = APP_CONFIG.get("DB_APP_HOST", "localhost")
dbDatabase = APP_CONFIG.get("DB_APP_DATABASE", "poweron_app")
dbUser = APP_CONFIG.get("DB_APP_USER")
dbPassword = APP_CONFIG.get("DB_APP_PASSWORD_SECRET")
dbPort = int(APP_CONFIG.get("DB_APP_PORT", 5432))
self.db = DatabaseConnector(
dbHost=dbHost,
dbDatabase=dbDatabase,
dbUser=dbUser,
dbPassword=dbPassword,
dbPort=dbPort,
userId=self.userId if self.userId else None,
)
logger.info(f"Real Estate Query database connector initialized for database: {dbDatabase}")
except Exception as e:
logger.error(f"Error initializing Real Estate Query database: {e}")
raise
def setUserContext(self, currentUser: User):
"""Sets the user context for the interface."""
self.currentUser = currentUser
self.userId = currentUser.id
self.mandateId = currentUser.mandateId
if not self.userId or not self.mandateId:
raise ValueError("Invalid user context: id and mandateId are required")
# Update database context
self.db.updateContext(self.userId)
# ===== Database Query Execution =====
def executeQuery(self, queryText: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""
Execute a SQL query directly on the database (stateless).
WARNING: This method executes raw SQL. Ensure proper validation and sanitization
before calling this method. Consider implementing query whitelisting or
only allowing SELECT statements for production use.
Args:
queryText: SQL query string (preferably SELECT only)
parameters: Optional parameters for parameterized queries
Returns:
Dictionary with 'rows' (list of dicts), 'columns' (list of column names),
'rowCount' (int), and 'executionTime' (float)
"""
import time
try:
start_time = time.time()
# Ensure connection is alive
self.db._ensure_connection()
with self.db.connection.cursor() as cursor:
# Execute query
if parameters:
# Use parameterized query for safety
cursor.execute(queryText, parameters)
else:
cursor.execute(queryText)
# Fetch results
rows = cursor.fetchall()
# Convert to list of dictionaries
result_rows = [dict(row) for row in rows]
# Get column names
columns = [desc[0] for desc in cursor.description] if cursor.description else []
execution_time = time.time() - start_time
return {
"rows": result_rows,
"columns": columns,
"rowCount": len(result_rows),
"executionTime": execution_time,
}
except Exception as e:
logger.error(f"Error executing query: {e}")
raise
def getInterface(currentUser: User) -> RealEstateChatObjects:
"""
Factory function to get or create a Query interface instance for a user.
Uses singleton pattern per user.
"""
userKey = f"{currentUser.id}_{currentUser.mandateId}"
if userKey not in _realEstateChatInterfaces:
_realEstateChatInterfaces[userKey] = RealEstateChatObjects(currentUser)
return _realEstateChatInterfaces[userKey]
```
### Hinweise zur Implementierung
1. **Stateless**: Keine Session-Management-Funktionen
2. **Nur für Queries**: Primär für SELECT-Queries gedacht
3. **Sicherheit**: Immer Parameterisierung verwenden
4. **Validierung**: Queries sollten validiert werden (z.B. nur SELECT erlauben)
### Beispiel: Beide Interfaces zusammen nutzen
```python
from modules.interfaces.interfaceDbRealEstateChatObjects import getInterface as getChatInterface
from modules.interfaces.interfaceDbRealEstateObjects import getInterface as getRealEstateInterface
# CRUD-Interface für strukturierte Operationen
realEstateInterface = getRealEstateInterface(currentUser)
projekt = realEstateInterface.createProjekt(Projekt(
mandateId=currentUser.mandateId,
label="Neues Projekt"
))
# Query-Interface für komplexe SELECT-Queries (optional)
chatInterface = getChatInterface(currentUser)
results = chatInterface.executeQuery(
"SELECT p.*, COUNT(parz.id) as parzellen_count FROM Projekt p LEFT JOIN Parzelle parz ON parz.projektId = p.id GROUP BY p.id"
)
```
---
## Zusammenfassung: Benötigte Dateien
### Erforderlich (für CRUD-Operationen):
1. ✅ `modules/datamodels/datamodelRealEstate.py`
- Real Estate-Datenmodelle (Projekt, Parzelle, Dokument, etc.)
2. ✅ `modules/interfaces/interfaceDbRealEstateAccess.py`
- Zugriffskontrolle für Real Estate-Entitäten (RealEstateAccess)
3. ✅ `modules/interfaces/interfaceDbRealEstateObjects.py`
- Real Estate CRUD-Interface (RealEstateObjects)
- Nutzt `interfaceDbRealEstateAccess.py`
- Haupt-Interface für alle CRUD-Operationen
### Optional (für direkte SQL-Queries):
4. ⚠️ `modules/interfaces/interfaceDbRealEstateChatObjects.py`
- Query-Interface für direkte SQL-Ausführung (RealEstateChatObjects)
- Stateless, keine Session-Management
- Nur wenn Sie komplexe SELECT-Queries benötigen
---
[← Zurück: Datenmodell erstellen](02-datamodels.md) | [Weiter: Feature-Logik implementieren →](04-feature-logic.md)

View file

@ -0,0 +1,780 @@
# Schritt 3: Feature-Logik implementieren
[← Zurück: Interface erstellen](03-interfaces.md) | [Weiter: Routen erstellen →](05-routes.md)
**Datei:** `modules/features/realEstate/mainRealEstate.py`
Die Feature-Logik enthält die Geschäftslogik für das Feature. Sie wird von den Routen aufgerufen und arbeitet **stateless** ohne Session-Management.
## Übersicht: Stateless Feature-Logik mit AI-Integration
Die Feature-Logik verwendet **AI**, um natürliche Sprache direkt in CRUD-Operationen zu übersetzen - ohne Session-Management:
```
User Input (natürliche Sprache)
AI-Analyse (Intent-Erkennung)
CRUD-Operation identifizieren
Parameter extrahieren
Interface CRUD-Methode aufrufen
Datenbank-Operation ausführen
Ergebnis zurückgeben (keine Session-Speicherung)
```
**Beispiel:**
- User: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
- AI analysiert → Intent: CREATE, Entity: Projekt, Parameter: {label: "Hauptstrasse 42"}
- Feature-Logik ruft auf → `interface.createProjekt(Projekt(label="Hauptstrasse 42"))`
- Ergebnis wird direkt zurückgegeben (keine Session, keine History)
## AI-Integration: Services initialisieren
Um AI zu verwenden, müssen Sie die **Services** initialisieren. Services sind eine zentrale Schnittstelle zu verschiedenen Systemkomponenten (AI, Chat, Database, etc.).
### Services-Initialisierung
```python
from modules.services import getInterface as getServices
# Services für einen User erhalten
services = getServices(currentUser, workflow=None)
# AI-Service verfügbar über:
aiService = services.ai # Für AI-Aufrufe
```
**Wichtig:** Services werden normalerweise im Feature-Logik-Modul initialisiert und an Funktionen weitergegeben.
---
## AI-basierte Intent-Erkennung und CRUD-Operationen
### Schritt 1: Intent-Analyse mit AI
Die AI analysiert User-Input und identifiziert:
- **Intent**: CREATE, READ, UPDATE, DELETE, QUERY
- **Entity**: Projekt, Parzelle, Dokument, etc.
- **Parameter**: Extrahierte Werte aus dem User-Input
### Schritt 2: CRUD-Operation ausführen
Basierend auf der AI-Analyse wird die entsprechende Interface-Methode aufgerufen.
---
## Beispiel-Implementierung:
```python
"""
Real Estate feature main logic.
Handles chat interface for database queries with AI-powered natural language processing.
"""
import logging
import json
from typing import Optional, Dict, Any, List
from modules.datamodels.datamodelUam import User
from modules.datamodels.datamodelRealEstate import (
Projekt,
Parzelle,
StatusProzess,
)
from modules.interfaces.interfaceDbRealEstateChatObjects import getInterface as getChatInterface
from modules.interfaces.interfaceDbRealEstateObjects import getInterface as getRealEstateInterface
from modules.services import getInterface as getServices
logger = logging.getLogger(__name__)
# ===== Direkte Query-Ausführung (stateless) =====
async def executeDirectQuery(
currentUser: User,
queryText: str,
parameters: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""
Execute a database query directly without session management.
Args:
currentUser: Current authenticated user
queryText: SQL query text
parameters: Optional parameters for parameterized queries
Returns:
Dictionary containing query result (rows, columns, rowCount)
Note:
- No session or query history is saved
- Query is executed directly and result is returned
- For production, validate and sanitize queries before execution
"""
try:
chatInterface = getChatInterface(currentUser)
# Execute query directly (no session tracking)
result = chatInterface.executeQuery(queryText, parameters)
logger.info(
f"Query executed successfully: {result['rowCount']} rows in {result.get('executionTime', 0):.3f}s"
)
return {
"status": "success",
"rows": result["rows"],
"columns": result["columns"],
"rowCount": result["rowCount"],
"executionTime": result.get("executionTime", 0),
}
except Exception as e:
logger.error(f"Error executing query: {str(e)}")
raise
# ===== AI-basierte Intent-Erkennung und CRUD-Operationen =====
async def processNaturalLanguageCommand(
currentUser: User,
userInput: str,
) -> Dict[str, Any]:
"""
Process natural language user input and execute corresponding CRUD operations.
Uses AI to analyze user intent and extract parameters, then executes the appropriate
CRUD operation through the interface. Works stateless without session management.
Args:
currentUser: Current authenticated user
userInput: Natural language command from user
Returns:
Dictionary containing operation result and metadata
Example user inputs:
- "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
- "Zeige mir alle Projekte in Zürich"
- "Aktualisiere Projekt XYZ mit Status 'Planung'"
- "Lösche Parzelle ABC"
- "SELECT * FROM Projekt WHERE plz = '8000'"
"""
try:
# Initialize services for AI access
services = getServices(currentUser, workflow=None)
aiService = services.ai
# Step 1: Analyze user intent with AI
intentAnalysis = await analyzeUserIntent(aiService, userInput)
logger.info(f"Intent analysis result: {intentAnalysis}")
# Step 2: Execute CRUD operation based on intent
result = await executeIntentBasedOperation(
currentUser=currentUser,
intent=intentAnalysis["intent"],
entity=intentAnalysis["entity"],
parameters=intentAnalysis["parameters"],
)
return {
"success": True,
"intent": intentAnalysis["intent"],
"entity": intentAnalysis["entity"],
"result": result,
}
except Exception as e:
logger.error(f"Error processing natural language command: {str(e)}")
raise
async def analyzeUserIntent(
aiService,
userInput: str
) -> Dict[str, Any]:
"""
Use AI to analyze user input and extract intent, entity, and parameters.
Args:
aiService: AI service instance
userInput: Natural language user input
Returns:
Dictionary with 'intent', 'entity', and 'parameters'
"""
# Create a structured prompt for intent analysis
intentPrompt = f"""
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "{userInput}"
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query
Available entities:
- Projekt: Real estate project
- Parzelle: Plot/parcel
- Dokument: Document
- Kanton: Canton
- Gemeinde: Municipality
Return a JSON object with the following structure:
{{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {{
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields
// For READ: include filter criteria
// For DELETE: include entity ID if mentioned
// For QUERY: include query text or natural language query
}},
"confidence": 0.0-1.0 // Confidence score for the analysis
}}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {{"intent": "CREATE", "entity": "Projekt", "parameters": {{"label": "Hauptstrasse 42"}}, "confidence": 0.95}}
- Input: "Zeige mir alle Projekte"
Output: {{"intent": "READ", "entity": "Projekt", "parameters": {{}}, "confidence": 0.9}}
- Input: "SELECT * FROM Projekt WHERE plz = '8000'"
Output: {{"intent": "QUERY", "entity": null, "parameters": {{"queryText": "SELECT * FROM Projekt WHERE plz = '8000'", "queryType": "sql"}}, "confidence": 1.0}}
"""
try:
# Use AI planning call for structured JSON response
response = await aiService.callAiPlanning(
prompt=intentPrompt,
debugType="intentanalysis"
)
# Parse JSON response
intentData = json.loads(response)
# Validate response structure
if "intent" not in intentData or "entity" not in intentData:
raise ValueError("Invalid intent analysis response structure")
return intentData
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AI intent analysis response: {e}")
logger.error(f"Raw response: {response}")
raise ValueError(f"AI returned invalid JSON: {str(e)}")
except Exception as e:
logger.error(f"Error analyzing user intent: {str(e)}")
raise
async def executeIntentBasedOperation(
currentUser: User,
intent: str,
entity: Optional[str],
parameters: Dict[str, Any],
) -> Dict[str, Any]:
"""
Execute CRUD operation based on analyzed intent.
Args:
currentUser: Current authenticated user
intent: Intent from AI analysis (CREATE, READ, UPDATE, DELETE, QUERY)
entity: Entity type from AI analysis
parameters: Extracted parameters from AI analysis
Returns:
Operation result
"""
try:
if intent == "QUERY":
# Execute database query directly (stateless)
queryText = parameters.get("queryText", "")
result = await executeDirectQuery(
currentUser=currentUser,
queryText=queryText,
parameters=parameters.get("queryParameters"),
)
return result
elif intent == "CREATE":
# Create new entity
realEstateInterface = getRealEstateInterface(currentUser)
if entity == "Projekt":
projekt = Projekt(
mandateId=currentUser.mandateId,
label=parameters.get("label", ""),
statusProzess=StatusProzess(parameters.get("statusProzess", "EINGANG")) if parameters.get("statusProzess") else None,
)
created = realEstateInterface.createProjekt(projekt)
return {"operation": "CREATE", "entity": "Projekt", "result": created.model_dump()}
elif entity == "Parzelle":
parzelle = Parzelle(
mandateId=currentUser.mandateId,
label=parameters.get("label", ""),
# ... weitere Parameter
)
created = realEstateInterface.createParzelle(parzelle)
return {"operation": "CREATE", "entity": "Parzelle", "result": created.model_dump()}
else:
raise ValueError(f"CREATE operation not supported for entity: {entity}")
elif intent == "READ":
# Read entities
realEstateInterface = getRealEstateInterface(currentUser)
if entity == "Projekt":
# Apply filters from parameters
projektId = parameters.get("id")
if projektId:
projekt = realEstateInterface.getProjekt(projektId)
return {"operation": "READ", "entity": "Projekt", "result": projekt.model_dump() if projekt else None}
else:
# List all projects (with optional filters)
# Note: You may need to implement getProjekte() method
raise NotImplementedError("List operation needs to be implemented")
else:
raise ValueError(f"READ operation not supported for entity: {entity}")
elif intent == "UPDATE":
# Update existing entity
realEstateInterface = getRealEstateInterface(currentUser)
if entity == "Projekt":
projektId = parameters.get("id")
if not projektId:
raise ValueError("UPDATE operation requires entity ID")
# Get existing projekt
projekt = realEstateInterface.getProjekt(projektId)
if not projekt:
raise ValueError(f"Projekt {projektId} not found")
# Update fields
updateData = {k: v for k, v in parameters.items() if k != "id"}
updated = realEstateInterface.updateProjekt(projektId, updateData)
return {"operation": "UPDATE", "entity": "Projekt", "result": updated.model_dump()}
else:
raise ValueError(f"UPDATE operation not supported for entity: {entity}")
elif intent == "DELETE":
# Delete entity
realEstateInterface = getRealEstateInterface(currentUser)
if entity == "Projekt":
projektId = parameters.get("id")
if not projektId:
raise ValueError("DELETE operation requires entity ID")
success = realEstateInterface.deleteProjekt(projektId)
return {"operation": "DELETE", "entity": "Projekt", "success": success}
else:
raise ValueError(f"DELETE operation not supported for entity: {entity}")
else:
raise ValueError(f"Unknown intent: {intent}")
except Exception as e:
logger.error(f"Error executing intent-based operation: {str(e)}")
raise
# ===== Erweiterte Query-Funktion mit AI-Unterstützung =====
async def executeNaturalLanguageQuery(
currentUser: User,
naturalLanguageQuery: str,
) -> Dict[str, Any]:
"""
Execute a natural language query by translating it to SQL using AI.
Args:
currentUser: Current authenticated user
naturalLanguageQuery: Natural language query (e.g., "Zeige mir alle Projekte in Zürich")
Returns:
Query result with metadata (stateless, no session)
"""
try:
services = getServices(currentUser, workflow=None)
aiService = services.ai
# Step 1: Translate natural language to SQL using AI
sqlQuery = await translateNaturalLanguageToSQL(aiService, naturalLanguageQuery, currentUser.mandateId)
logger.info(f"Translated '{naturalLanguageQuery}' to SQL: {sqlQuery}")
# Step 2: Execute the SQL query directly (stateless)
result = await executeDirectQuery(
currentUser=currentUser,
queryText=sqlQuery,
)
return result
except Exception as e:
logger.error(f"Error executing natural language query: {str(e)}")
raise
async def translateNaturalLanguageToSQL(
aiService,
naturalLanguageQuery: str,
mandateId: str
) -> str:
"""
Use AI to translate natural language query to SQL.
Args:
aiService: AI service instance
naturalLanguageQuery: Natural language query
mandateId: User's mandate ID for filtering
Returns:
SQL query string with mandateId filter applied
"""
translationPrompt = f"""
Translate the following natural language query into a valid PostgreSQL SQL SELECT statement.
Natural Language Query: "{naturalLanguageQuery}"
Available tables and their fields:
- Projekt: id, mandateId, label, statusProzess, perimeter, baulinie, parzellen (JSONB), dokumente (JSONB)
- Parzelle: id, mandateId, label, strasseNr, plz, bauzone, az, bz, kontextKanton, kontextGemeinde
- Dokument: id, mandateId, label, dokumentTyp, dokumentReferenz, mimeType
- Kanton: id, mandateId, label, abk
- Gemeinde: id, mandateId, label, plz
Rules:
1. Always include 'mandateId' filter based on user context (use placeholder {{mandateId}})
2. Only use SELECT statements (no INSERT, UPDATE, DELETE)
3. Return ONLY the SQL query, no explanations
4. Use proper PostgreSQL syntax
5. For text searches, use ILIKE for case-insensitive matching
Examples:
- Input: "Zeige mir alle Projekte"
Output: SELECT * FROM Projekt WHERE mandateId = '{{mandateId}}'
- Input: "Zeige mir alle Parzellen in Zürich"
Output: SELECT p.* FROM Parzelle p JOIN Gemeinde g ON p.kontextGemeinde = g.id WHERE g.label ILIKE '%Zürich%' AND p.mandateId = '{{mandateId}}'
- Input: "Wie viele Projekte haben Status 'Planung'?"
Output: SELECT COUNT(*) as count FROM Projekt WHERE statusProzess = 'Planung' AND mandateId = '{{mandateId}}'
Now translate this query:
"""
try:
# Use AI planning call for SQL generation
response = await aiService.callAiPlanning(
prompt=translationPrompt,
debugType="sqltranslation"
)
# Clean response (remove markdown code blocks if present)
sqlQuery = response.strip()
if sqlQuery.startswith("```sql"):
sqlQuery = sqlQuery[6:]
if sqlQuery.startswith("```"):
sqlQuery = sqlQuery[3:]
if sqlQuery.endswith("```"):
sqlQuery = sqlQuery[:-3]
sqlQuery = sqlQuery.strip()
# Replace placeholder with actual mandateId
sqlQuery = sqlQuery.replace("{{mandateId}}", mandateId)
return sqlQuery
except Exception as e:
logger.error(f"Error translating natural language to SQL: {str(e)}")
raise ValueError(f"Failed to translate query: {str(e)}")
```
## Wichtige Punkte:
### 1. Services-Initialisierung
- **`getServices(currentUser, workflow=None)`** - Initialisiert Services für AI-Zugriff
- **`services.ai`** - Zugriff auf AI-Service für AI-Aufrufe
### 2. AI-Aufrufe
- **`callAiPlanning()`** - Für strukturierte JSON-Antworten (Intent-Analyse, SQL-Übersetzung)
- **`callAiText()`** - Für einfache Text-Generierung
- **`callAiDocuments()`** - Für Dokumenten-Verarbeitung
### 3. Intent-Analyse
Die AI analysiert User-Input und gibt zurück:
- **Intent**: CREATE, READ, UPDATE, DELETE, QUERY
- **Entity**: Projekt, Parzelle, Dokument, etc.
- **Parameters**: Extrahierte Werte aus dem Input
### 4. CRUD-Operationen
Basierend auf der Intent-Analyse:
- **CREATE**`interface.createProjekt()`, `interface.createParzelle()`, etc.
- **READ**`interface.getProjekt()`, `interface.getParzelle()`, etc.
- **UPDATE**`interface.updateProjekt()`, etc.
- **DELETE**`interface.deleteProjekt()`, etc.
- **QUERY**`interface.executeQuery()` oder `executeDatabaseQuery()`
### 5. Natural Language to SQL
- AI übersetzt natürliche Sprache in SQL-Queries
- Automatische Validierung und Sanitization empfohlen
- MandateId-Filter wird automatisch hinzugefügt
### 6. Error Handling
- Umfassendes Error Handling für AI-Aufrufe
- JSON-Parsing mit Fallback
- Logging für Debugging
---
## Beispiel-Verwendung:
```python
# In einer Route (stateless):
@router.post("/command")
async def process_command(
userInput: str = Body(...),
currentUser: User = Depends(getCurrentUser)
):
result = await processNaturalLanguageCommand(
currentUser=currentUser,
userInput=userInput
)
return result
# Direkte Query (stateless):
@router.post("/query")
async def execute_query(
queryText: str = Body(...),
currentUser: User = Depends(getCurrentUser)
):
result = await executeDirectQuery(
currentUser=currentUser,
queryText=queryText
)
return result
```
**User-Input-Beispiele:**
- `"Erstelle ein neues Projekt namens 'Hauptstrasse 42'"`
- `"Zeige mir alle Projekte in Zürich"`
- `"Aktualisiere Projekt XYZ mit Status 'Planung'"`
- `"Wie viele Parzellen haben Bauzone W3?"`
---
## Vollständiger Flow: User-Input → CRUD-Operation (stateless)
### Beispiel: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
```
1. User sendet HTTP POST Request
POST /api/realestate/command
Body: {"userInput": "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"}
2. Route ruft Feature-Logik auf
→ processNaturalLanguageCommand(currentUser, userInput)
# Keine Session-ID notwendig!
3. Feature-Logik initialisiert Services
→ services = getServices(currentUser, workflow=None)
→ aiService = services.ai
4. AI analysiert User-Input
→ analyzeUserIntent(aiService, userInput)
→ AI gibt zurück:
{
"intent": "CREATE",
"entity": "Projekt",
"parameters": {"label": "Hauptstrasse 42"},
"confidence": 0.95
}
5. Feature-Logik führt CRUD-Operation aus
→ executeIntentBasedOperation(intent="CREATE", entity="Projekt", ...)
→ realEstateInterface = getRealEstateInterface(currentUser)
→ projekt = Projekt(mandateId=..., label="Hauptstrasse 42")
→ created = realEstateInterface.createProjekt(projekt)
6. Interface speichert in Datenbank
→ DatabaseConnector.recordCreate(Projekt, projekt.model_dump())
→ PostgreSQL INSERT INTO Projekt ...
7. Ergebnis wird direkt zurückgegeben
→ Route gibt HTTP Response zurück
→ Keine Session-Speicherung, keine History
→ Frontend zeigt Erfolg
```
---
## AI-Service Methoden im Detail
### `callAiPlanning()` - Für strukturierte Antworten
**Verwendung:** Intent-Analyse, SQL-Übersetzung, strukturierte Daten-Extraktion
```python
response = await aiService.callAiPlanning(
prompt=intentPrompt,
debugType="intentanalysis" # Optional: für Debug-Dateien
)
# Response ist JSON-String, muss geparst werden
intentData = json.loads(response)
```
**Vorteile:**
- Optimiert für strukturierte JSON-Antworten
- Verwendet beste Modelle für Planungs-Aufgaben
- Automatisches Debug-File-Writing
### `callAiText()` - Für einfache Text-Generierung
**Verwendung:** Text-Generierung, Zusammenfassungen, Erklärungen
```python
response = await aiService.callAiText(
prompt="Erkläre mir...",
documents=None, # Optional: Dokumente für Kontext
options=AiCallOptions(...)
)
# Response ist direkt Text-String
```
### `callAiDocuments()` - Für Dokumenten-Verarbeitung
**Verwendung:** Dokumenten-Analyse, Extraktion, Generierung mit Dokumenten-Kontext
```python
response = await aiService.callAiDocuments(
prompt="Analysiere diese Dokumente...",
documents=[ChatDocument(...), ...],
options=AiCallOptions(...),
outputFormat="json" # Optional: Format für Output
)
```
---
## Best Practices für AI-Integration
### 1. Prompt-Engineering
- **Klare Struktur**: Definieren Sie genau, welche Antwort Sie erwarten
- **Beispiele**: Geben Sie Beispiele für bessere Ergebnisse
- **Format**: Spezifizieren Sie das erwartete Format (JSON, SQL, etc.)
### 2. Error Handling
- **JSON-Parsing**: Immer try/except für JSON-Parsing
- **Fallback**: Planen Sie Fallback-Strategien bei AI-Fehlern
- **Validierung**: Validieren Sie AI-Antworten vor Verwendung
### 3. Sicherheit
- **Query-Validierung**: Validieren Sie SQL-Queries vor Ausführung
- **Parameter-Sanitization**: Sanitizen Sie alle Parameter
- **MandateId-Filter**: Stellen Sie sicher, dass MandateId immer gefiltert wird
### 4. Performance
- **Caching**: Cache häufige AI-Antworten wenn möglich
- **Model-Auswahl**: Lassen Sie das System automatisch das beste Modell wählen
- **Async**: Nutzen Sie async/await für nicht-blockierende Operationen
### 5. Debugging
- **Debug-Files**: Nutzen Sie `debugType` Parameter für Debug-Dateien
- **Logging**: Loggen Sie alle AI-Aufrufe und Antworten
- **Confidence-Scores**: Nutzen Sie Confidence-Scores für Fehlerbehandlung
---
## Erweiterte Features
### Schema-Aware Prompting
Sie können das Datenbank-Schema in Prompts einbinden:
```python
# Lade Schema-Informationen
schemaInfo = getDatabaseSchema() # Ihre Funktion
prompt = f"""
Available database schema:
{schemaInfo}
User query: "{userInput}"
...
"""
```
### Context-Aware Operations (Optional)
Falls Sie später Kontext zwischen Queries benötigen, können Sie optional eine Session verwenden:
```python
# Optional: Session für Kontext (nur wenn nötig)
# Für stateless Operationen nicht notwendig
# Falls Session gewünscht:
sessionId = parameters.get("sessionId") # Optional
if sessionId:
previousQueries = interface.getQueries(sessionId=sessionId)
context = "\n".join([q.queryText for q in previousQueries[-5:]])
else:
context = "" # Kein Kontext bei stateless Operationen
prompt = f"""
{context if context else ""}
User query: "{userInput}"
...
"""
```
### Multi-Step Operations
Für komplexe Operationen können Sie mehrere AI-Calls machen:
```python
# Schritt 1: Intent-Analyse
intent = await analyzeUserIntent(aiService, userInput)
# Schritt 2: Parameter-Validierung
if intent["intent"] == "CREATE":
validatedParams = await validateParameters(aiService, intent["parameters"])
# Schritt 3: CRUD-Operation
result = await executeIntentBasedOperation(...)
```
---
[← Zurück: Interface erstellen](03-interfaces.md) | [Weiter: Routen erstellen →](05-routes.md)

View file

@ -0,0 +1,332 @@
# Schritt 4: Routen erstellen
[← Zurück: Feature-Logik implementieren](04-feature-logic.md) | [Weiter: Router registrieren →](06-router-registration.md)
**Datei:** `modules/routes/routeRealEstate.py`
Die Routen definieren die REST-API-Endpunkte für das Feature. Das Feature arbeitet **stateless** ohne Session-Management.
## Route-Struktur
```
/api/realestate/
├── POST /command → Natürliche Sprache → CRUD-Operation
└── POST /query → Direkte SQL-Query
```
## Beispiel-Implementierung:
```python
"""
Real Estate routes for the backend API.
Implements stateless endpoints for real estate database operations with AI-powered natural language processing.
"""
import logging
from typing import Optional, Dict, Any
from fastapi import APIRouter, HTTPException, Depends, Body, Request
from modules.security.auth import limiter, getCurrentUser
from modules.datamodels.datamodelUam import User
from modules.features.realEstate.mainRealEstate import (
processNaturalLanguageCommand,
executeDirectQuery,
)
# Configure logger
logger = logging.getLogger(__name__)
# Create router for real estate endpoints
router = APIRouter(
prefix="/api/realestate",
tags=["Real Estate"],
responses={404: {"description": "Not found"}}
)
# ===== Stateless Command Endpoint =====
@router.post("/command", response_model=Dict[str, Any])
@limiter.limit("120/minute")
async def process_command(
request: Request,
userInput: str = Body(..., embed=True, description="Natural language command"),
currentUser: User = Depends(getCurrentUser)
) -> Dict[str, Any]:
"""
Process natural language command and execute corresponding CRUD operation.
Uses AI to analyze user intent and extract parameters, then executes the appropriate
CRUD operation. Works stateless without session management.
Example user inputs:
- "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
- "Zeige mir alle Projekte in Zürich"
- "Aktualisiere Projekt XYZ mit Status 'Planung'"
- "Lösche Parzelle ABC"
- "SELECT * FROM Projekt WHERE plz = '8000'"
Returns:
{
"success": true,
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|...|null",
"result": {...}
}
"""
try:
result = await processNaturalLanguageCommand(
currentUser=currentUser,
userInput=userInput
)
return result
except ValueError as e:
logger.error(f"Validation error: {str(e)}")
raise HTTPException(
status_code=400,
detail=str(e)
)
except Exception as e:
logger.error(f"Error processing command: {str(e)}")
raise HTTPException(
status_code=500,
detail=str(e)
)
# ===== Stateless Query Endpoint =====
@router.post("/query", response_model=Dict[str, Any])
@limiter.limit("120/minute")
async def execute_query(
request: Request,
queryText: str = Body(..., embed=True, description="SQL query text"),
parameters: Optional[Dict[str, Any]] = Body(None, embed=True, description="Optional query parameters for parameterized queries"),
currentUser: User = Depends(getCurrentUser)
) -> Dict[str, Any]:
"""
Execute a direct SQL query without session management.
Executes the query directly and returns the result. No query history is saved.
WARNING: This endpoint executes raw SQL queries. Ensure proper validation
and sanitization on the frontend. Consider implementing query whitelisting
or only allowing SELECT statements for production use.
Returns:
{
"status": "success",
"rows": [...],
"columns": [...],
"rowCount": 15,
"executionTime": 0.123
}
"""
try:
result = await executeDirectQuery(
currentUser=currentUser,
queryText=queryText,
parameters=parameters,
)
return result
except ValueError as e:
logger.error(f"Validation error: {str(e)}")
raise HTTPException(
status_code=400,
detail=str(e)
)
except Exception as e:
logger.error(f"Error executing query: {str(e)}")
raise HTTPException(
status_code=500,
detail=str(e)
)
```
## Wichtige Punkte:
### 1. Stateless Design
- **Keine Session-Management**: Alle Endpunkte arbeiten stateless
- **Direkte Verarbeitung**: User-Input wird direkt verarbeitet und Ergebnis zurückgegeben
- **Keine History**: Queries werden nicht gespeichert (kann optional später hinzugefügt werden)
### 2. API-Endpunkte
**`POST /api/realestate/command`**
- Verarbeitet natürliche Sprache
- Nutzt AI für Intent-Analyse
- Führt CRUD-Operationen aus
- Gibt Ergebnis direkt zurück
**`POST /api/realestate/query`**
- Führt direkte SQL-Queries aus
- Keine Session notwendig
- Gibt Query-Ergebnis direkt zurück
### 3. Sicherheit
- **Rate Limiting**: `@limiter.limit("120/minute")` für API-Schutz
- **Authentication**: `Depends(getCurrentUser)` für alle Endpunkte
- **Query-Validierung**: WICHTIG - Validieren Sie SQL-Queries vor Ausführung
- **MandateId-Filter**: Wird automatisch durch Interfaces angewendet
### 4. Error Handling
- Umfassendes Error Handling mit HTTPException
- Unterschiedliche Status-Codes: 400 (Validation), 404 (Not Found), 500 (Server Error)
- Detaillierte Fehlermeldungen für Debugging
### 5. Response-Struktur
**Command-Endpunkt:**
```json
{
"success": true,
"intent": "CREATE",
"entity": "Projekt",
"result": {
"operation": "CREATE",
"entity": "Projekt",
"result": {
"id": "projekt_123",
"label": "Hauptstrasse 42",
...
}
}
}
```
**Query-Endpunkt:**
```json
{
"status": "success",
"rows": [
{"id": "...", "label": "...", ...}
],
"columns": ["id", "label", ...],
"rowCount": 15,
"executionTime": 0.123
}
```
---
## Beispiel-Requests
### Command-Endpunkt
```bash
# CREATE Operation
POST /api/realestate/command
Content-Type: application/json
Authorization: Bearer <token>
{
"userInput": "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
}
# READ Operation
POST /api/realestate/command
{
"userInput": "Zeige mir alle Projekte in Zürich"
}
# UPDATE Operation
POST /api/realestate/command
{
"userInput": "Aktualisiere Projekt XYZ mit Status 'Planung'"
}
# DELETE Operation
POST /api/realestate/command
{
"userInput": "Lösche Parzelle ABC"
}
# QUERY Operation (SQL wird erkannt)
POST /api/realestate/command
{
"userInput": "SELECT * FROM Projekt WHERE plz = '8000'"
}
```
### Query-Endpunkt
```bash
# Direkte SQL-Query
POST /api/realestate/query
Content-Type: application/json
Authorization: Bearer <token>
{
"queryText": "SELECT * FROM Projekt WHERE plz = '8000'"
}
# Parameterized Query
POST /api/realestate/query
{
"queryText": "SELECT * FROM Projekt WHERE plz = $1",
"parameters": {"$1": "8000"}
}
```
---
## Flow: Route → Feature-Logik
### Command-Endpunkt Flow
```
POST /api/realestate/command
routeRealEstate.process_command()
getCurrentUser() # Auth
processNaturalLanguageCommand(currentUser, userInput)
mainRealEstate.processNaturalLanguageCommand()
analyzeUserIntent() → executeIntentBasedOperation()
return Dict mit Ergebnis
```
### Query-Endpunkt Flow
```
POST /api/realestate/query
routeRealEstate.execute_query()
getCurrentUser() # Auth
executeDirectQuery(currentUser, queryText, parameters)
mainRealEstate.executeDirectQuery()
getChatInterface(currentUser)
RealEstateChatObjects.executeQuery(queryText)
DatabaseConnector.executeQuery(sql)
return Dict mit rows, columns, rowCount
```
---
## Vorteile des stateless Ansatzes
- **Einfachheit**: Kein Session-Management notwendig
- **Performance**: Weniger Datenbank-Operationen pro Request
- **Skalierbarkeit**: Stateless Requests sind einfacher zu skalieren
- **Flexibilität**: Jeder Request ist unabhängig
- **Schnell**: Direkte Verarbeitung ohne Overhead
---
[← Zurück: Feature-Logik implementieren](04-feature-logic.md) | [Weiter: Router registrieren →](06-router-registration.md)

View file

@ -0,0 +1,45 @@
# Schritt 5: Router registrieren
[← Zurück: Routen erstellen](05-routes.md) | [Weiter: Environment-Konfiguration →](07-environment.md)
**Datei:** `app.py`
Der Router muss in der Hauptanwendung registriert werden.
## Änderung in app.py:
```python
# ... existing imports ...
# Include all routers
from modules.routes.routeAdmin import router as generalRouter
app.include_router(generalRouter)
# ... existing routers ...
from modules.routes.routeChatPlayground import router as chatPlaygroundRouter
app.include_router(chatPlaygroundRouter)
# NEU: Real Estate Router hinzufügen (Chat-Interface)
from modules.routes.routeRealEstate import router as realEstateRouter
app.include_router(realEstateRouter)
# NEU: Real Estate Data Router hinzufügen (falls CRUD-API gewünscht)
# from modules.routes.routeRealEstateData import router as realEstateDataRouter
# app.include_router(realEstateDataRouter)
from modules.routes.routeSecurityLocal import router as localRouter
app.include_router(localRouter)
# ... rest of routers ...
```
**Wichtig**: Die Reihenfolge der Router-Registrierung kann wichtig sein, wenn es Überschneidungen in den Pfaden gibt. Allgemeinere Routen sollten nach spezifischeren Routen kommen.
---
[← Zurück: Routen erstellen](05-routes.md) | [Weiter: Environment-Konfiguration →](07-environment.md)

View file

@ -0,0 +1,50 @@
# Schritt 6: Environment-Konfiguration
[← Zurück: Router registrieren](06-router-registration.md) | [Weiter: Feature Lifecycle →](08-lifecycle.md)
**Datei:** `env_dev.env`
Für das realEstate-Feature benötigen wir keine zusätzlichen Environment-Variablen, da es die bereits vorhandenen PostgreSQL-Konfigurationen nutzt:
```env
# PostgreSQL Storage (bereits vorhanden)
DB_APP_HOST=localhost
DB_APP_DATABASE=poweron_app
DB_APP_USER=poweron_dev
DB_APP_PASSWORD_SECRET = DEV_ENC:...
DB_APP_PORT=5432
```
**Optional**: Falls Sie eine separate Datenbank für Real Estate verwenden möchten, können Sie zusätzliche Variablen hinzufügen:
```env
# Optional: Separate Real Estate Database
DB_REALESTATE_HOST=localhost
DB_REALESTATE_DATABASE=poweron_realestate
DB_REALESTATE_USER=poweron_dev
DB_REALESTATE_PASSWORD_SECRET = DEV_ENC:...
DB_REALESTATE_PORT=5432
```
In diesem Fall müssten Sie die `_initializeDatabase()` Methode im Interface anpassen:
```python
def _initializeDatabase(self):
"""Initialize PostgreSQL database connection."""
try:
# Use Real Estate specific config if available, otherwise fall back to APP config
dbHost = APP_CONFIG.get("DB_REALESTATE_HOST") or APP_CONFIG.get("DB_APP_HOST", "localhost")
dbDatabase = APP_CONFIG.get("DB_REALESTATE_DATABASE") or APP_CONFIG.get("DB_APP_DATABASE", "poweron_app")
dbUser = APP_CONFIG.get("DB_REALESTATE_USER") or APP_CONFIG.get("DB_APP_USER")
dbPassword = APP_CONFIG.get("DB_REALESTATE_PASSWORD_SECRET") or APP_CONFIG.get("DB_APP_PASSWORD_SECRET")
dbPort = int(APP_CONFIG.get("DB_REALESTATE_PORT") or APP_CONFIG.get("DB_APP_PORT", 5432))
# ... rest of initialization ...
```
---
[← Zurück: Router registrieren](06-router-registration.md) | [Weiter: Feature Lifecycle →](08-lifecycle.md)

View file

@ -0,0 +1,43 @@
# Schritt 7: Feature Lifecycle (optional)
[← Zurück: Environment-Konfiguration](07-environment.md) | [Weiter: Datenbank-Schema →](09-database-schema.md)
**Datei:** `modules/features/featuresLifecycle.py`
Falls Ihr Feature Hintergrundprozesse oder Initialisierung beim Start benötigt, können Sie diese hier hinzufügen:
```python
async def start() -> None:
""" Start feature triggers and background managers """
# Provide Event User
rootInterface = getRootInterface()
eventUser = rootInterface.getUserByUsername("event")
# ... existing features ...
# Feature RealEstate (optional)
# from modules.features.realEstate import mainRealEstate
# mainRealEstate.initializeFeature(eventUser)
# logger.info("Real Estate feature initialized")
return True
async def stop() -> None:
""" Stop feature triggers and background managers """
# Feature RealEstate cleanup (optional)
# from modules.features.realEstate import mainRealEstate
# mainRealEstate.cleanupFeature()
# logger.info("Real Estate feature cleaned up")
return True
```
---
[← Zurück: Environment-Konfiguration](07-environment.md) | [Weiter: Datenbank-Schema →](09-database-schema.md)

View file

@ -0,0 +1,247 @@
# Datenbank-Schema
[← Zurück: Feature Lifecycle](08-lifecycle.md) | [Weiter: Sicherheitshinweise →](10-security.md)
Die Datenbank-Tabellen werden automatisch vom `DatabaseConnector` erstellt, basierend auf den Pydantic-Modellen:
## Chat-Interface Tabellen:
- **RealEstateQuery**: Speichert Abfragen
- **RealEstateQueryResult**: Speichert Abfrageergebnisse (mit JSONB für `rowData`)
- **RealEstateChatSession**: Speichert Chat-Sessions
## Real Estate-Datenmodell Tabellen:
Die folgenden Tabellen werden basierend auf den Real Estate-Datenmodell-Entitäten erstellt:
- **Projekt**: Bauprojekte (mit `parzellen`, `dokumente`, `kontextInformationen` als JSONB)
- **Parzelle**: Grundstücke mit Bauparametern (mit `parzellenNachbarschaft`, `dokumente`, `kontextInformationen` als JSONB)
- **Dokument**: Dateien und URLs
- **Kontext**: Zusatzinformationen
- **GeoPolylinie**: Geometrische Linien/Polygone (mit `punkte` als JSONB)
- **GeoPunkt**: 3D-Koordinaten
- **Land**: Nationale Ebene (mit `dokumente`, `kontextInformationen` als JSONB)
- **Kanton**: Kantonale Ebene (mit `dokumente`, `kontextInformationen` als JSONB)
- **Gemeinde**: Gemeinde-Ebene (mit `dokumente`, `kontextInformationen` als JSONB)
---
## Automatische Tabellenerstellung
### Wie funktioniert die automatische Tabellenerstellung?
Der `DatabaseConnector` erstellt Tabellen **automatisch beim ersten Zugriff** auf ein Pydantic-Modell. Sie müssen keine SQL-CREATE-TABLE-Statements manuell schreiben.
#### 1. Ablauf der Tabellenerstellung:
```
1. Code ruft z.B. `db.recordCreate(Projekt, projekt_data)` auf
2. DatabaseConnector ruft `_ensureTableExists(Projekt)` auf
3. Prüft ob Tabelle "Projekt" existiert (über information_schema)
4. Wenn NICHT vorhanden:
→ Ruft `_create_table_from_model()` auf
→ Extrahiert Felder aus Pydantic-Modell mit `_get_model_fields()`
→ Mappt Python-Typen zu SQL-Typen
→ Erstellt CREATE TABLE Statement
→ Führt SQL aus
→ Erstellt Indexes für Foreign Keys
```
#### 2. Typ-Mapping (Python → PostgreSQL):
Der `DatabaseConnector` mappt automatisch Pydantic-Feldtypen zu PostgreSQL-Datentypen:
| Python/Pydantic Typ | PostgreSQL Typ | Beispiel |
|---------------------|----------------|----------|
| `str` oder `Optional[str]` | `TEXT` | `label: str``"label" TEXT` |
| `int` | `INTEGER` | `vollgeschossZahl: int``"vollgeschossZahl" INTEGER` |
| `float` | `DOUBLE PRECISION` | `az: float``"az" DOUBLE PRECISION` |
| `bool` | `BOOLEAN` | `closed: bool``"closed" BOOLEAN` |
| `Dict[str, Any]` oder `dict` | `JSONB` | `parameters: Dict[str, Any]``"parameters" JSONB` |
| `List[...]` oder `list` | `JSONB` | `parzellen: List[Parzelle]``"parzellen" JSONB` |
| `Optional[Enum]` | `TEXT` | `statusProzess: StatusProzess``"statusProzess" TEXT` |
**Spezielle Felder:**
- Felder mit Namen `*Id` (z.B. `kontextKantonId`) erhalten automatisch einen Index
- Systemfelder werden automatisch hinzugefügt: `_createdAt`, `_createdBy`, `_modifiedAt`, `_modifiedBy`
#### 3. Beispiel: CREATE TABLE Statement
Für das `Projekt`-Modell würde automatisch folgendes SQL erstellt:
```sql
CREATE TABLE IF NOT EXISTS "Projekt" (
"id" VARCHAR(255) PRIMARY KEY,
"mandateId" TEXT,
"label" TEXT,
"statusProzess" TEXT,
"perimeter" JSONB,
"baulinie" JSONB,
"parzellen" JSONB,
"dokumente" JSONB,
"kontextInformationen" JSONB,
"_createdAt" DOUBLE PRECISION,
"_modifiedAt" DOUBLE PRECISION,
"_createdBy" VARCHAR(255),
"_modifiedBy" VARCHAR(255)
);
-- Automatisch erstellte Indexes für Foreign Keys:
CREATE INDEX IF NOT EXISTS "idx_Projekt_mandateId" ON "Projekt" ("mandateId");
```
#### 4. Automatische Schema-Migrationen
**Wichtig:** Der Connector unterstützt **additive Migrationen**:
- Wenn eine Tabelle bereits existiert, werden **fehlende Spalten automatisch hinzugefügt**
- **Bestehende Spalten werden NICHT gelöscht oder geändert**
- Wenn Sie ein neues Feld zum Pydantic-Modell hinzufügen, wird es beim nächsten Zugriff automatisch als Spalte hinzugefügt
**Beispiel:**
```python
# Ursprüngliches Modell
class Projekt(BaseModel):
id: str
label: str
statusProzess: Optional[StatusProzess]
# Später: Neues Feld hinzugefügt
class Projekt(BaseModel):
id: str
label: str
statusProzess: Optional[StatusProzess]
beschreibung: Optional[str] # NEU
# Beim nächsten recordCreate() wird automatisch ausgeführt:
# ALTER TABLE "Projekt" ADD COLUMN "beschreibung" TEXT
```
#### 5. Wann werden Tabellen erstellt?
Tabellen werden erstellt, wenn Sie **zum ersten Mal** eine der folgenden Operationen ausführen:
- `db.recordCreate(model_class, data)` - Erstellt Record
- `db.recordUpdate(model_class, recordId, data)` - Aktualisiert Record
- `db.getRecordset(model_class)` - Lädt Records
- `db.getRecord(model_class, recordId)` - Lädt einen Record
**Beispiel:**
```python
# Beim ersten Aufruf wird die Tabelle "Projekt" automatisch erstellt
interface = getInterface(currentUser)
projekt = interface.createProjekt(label="Mein Projekt")
# → Tabelle "Projekt" wird jetzt in PostgreSQL erstellt
```
#### 6. Manuelle Tabellenerstellung (optional)
Falls Sie Tabellen manuell erstellen möchten (z.B. für Initialisierung), können Sie:
```python
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.datamodels.datamodelRealEstate import Projekt, Parzelle
# Connector initialisieren
db = DatabaseConnector(
dbHost="localhost",
dbDatabase="poweron_app",
dbUser="poweron_dev",
dbPassword="...",
dbPort=5432
)
# Tabellen explizit erstellen
db._ensureTableExists(Projekt)
db._ensureTableExists(Parzelle)
# ... weitere Modelle
```
#### 7. Wichtige Hinweise:
✅ **Automatisch:**
- Tabellenerstellung beim ersten Zugriff
- Spalten-Erstellung basierend auf Pydantic-Feldern
- Index-Erstellung für Foreign Keys (`*Id` Felder)
- Systemfelder (`_createdAt`, etc.) werden automatisch hinzugefügt
❌ **NICHT automatisch:**
- Foreign Key Constraints (werden nicht erstellt - Sie müssen sie manuell hinzufügen falls gewünscht)
- Unique Constraints (außer PRIMARY KEY auf `id`)
- Check Constraints
- Trigger oder Stored Procedures
⚠️ **Einschränkungen:**
- **Keine Schema-Änderungen**: Wenn Sie einen Feldtyp ändern (z.B. `str``int`), wird die Spalte NICHT automatisch geändert
- **Keine Spalten-Löschung**: Gelöschte Felder im Modell werden nicht aus der Datenbank entfernt
- **Case-Sensitive**: Tabellennamen werden exakt wie der Klassenname verwendet (z.B. `Projekt`, nicht `projekt`)
#### 8. Beispiel: Vollständiger Ablauf
```python
# 1. Pydantic-Modell definieren
class Projekt(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
mandateId: str
label: str
statusProzess: Optional[StatusProzess]
parzellen: List[Parzelle] = Field(default_factory=list)
# 2. Interface initialisieren (erstellt noch keine Tabellen)
interface = getInterface(currentUser)
# 3. Ersten Record erstellen (erstellt jetzt die Tabelle!)
projekt = interface.createProjekt(
label="Mein erstes Projekt",
statusProzess=StatusProzess.PLANUNG
)
# → Intern wird ausgeführt:
# 1. _ensureTableExists(Projekt) aufgerufen
# 2. Tabelle "Projekt" existiert nicht → wird erstellt
# 3. CREATE TABLE "Projekt" (...) wird ausgeführt
# 4. Record wird eingefügt
# 4. Weitere Records können jetzt ohne Tabellenerstellung erstellt werden
projekt2 = interface.createProjekt(label="Zweites Projekt")
# → Tabelle existiert bereits, nur INSERT wird ausgeführt
```
---
## Zusammenfassung:
- ✅ **Tabellenname** = Klassenname des Pydantic-Modells (z.B. `Projekt`)
- ✅ **Spalten** = Alle Felder aus dem Pydantic-Modell
- ✅ **Typen** = Automatisch gemappt (str→TEXT, List→JSONB, etc.)
- ✅ **Systemfelder** = Automatisch hinzugefügt (`_createdAt`, `_createdBy`, etc.)
- ✅ **Indexes** = Automatisch für Felder mit `*Id` Suffix
- ✅ **Migrationen** = Additive Migrationen (neue Spalten werden hinzugefügt)
- ⚠️ **Keine Constraints** = Foreign Keys, Unique, Check müssen manuell erstellt werden
## Beispiel-Abfragen auf Real Estate-Datenmodell:
```sql
-- Alle Parzellen in einer bestimmten Gemeinde
SELECT * FROM Parzelle WHERE plz = '8000' ORDER BY label;
-- Projekte mit Status "Planung"
SELECT * FROM Projekt WHERE "statusProzess" = 'Planung';
-- Parzellen mit bestimmter Bauzone
SELECT label, az, bz, gebaeudehoeheMax FROM Parzelle WHERE bauzone = 'W3';
-- Dokumente eines Projekts
SELECT * FROM Dokument WHERE id IN (
SELECT unnest(dokumente::jsonb->>'id') FROM Projekt WHERE id = '...'
);
```
---
[← Zurück: Feature Lifecycle](08-lifecycle.md) | [Weiter: Sicherheitshinweise →](10-security.md)

View file

@ -0,0 +1,90 @@
# Sicherheitshinweise
[← Zurück: Datenbank-Schema](09-database-schema.md) | [Weiter: Testing →](11-testing.md)
## ⚠️ WICHTIG: Query-Validierung
Die aktuelle Implementierung erlaubt die Ausführung von **rohen SQL-Queries**. Für Produktion sollten Sie:
1. **Query-Whitelisting**: Nur erlaubte Queries zulassen
2. **Nur SELECT**: Nur SELECT-Statements erlauben (keine INSERT/UPDATE/DELETE)
3. **Parameterized Queries**: Immer Parameterized Queries verwenden
4. **Query-Parsing**: SQL-Parser verwenden zur Validierung
5. **Rate Limiting**: Strikte Rate Limits setzen (bereits implementiert)
## Beispiel für Query-Validierung:
```python
def validateQuery(queryText: str) -> bool:
"""
Validate that query is safe to execute.
Only allows SELECT statements on Real Estate data model tables.
"""
query_lower = queryText.strip().lower()
# Only allow SELECT statements
if not query_lower.startswith('select'):
return False
# Block dangerous keywords
dangerous_keywords = [
'drop', 'delete', 'insert', 'update', 'alter', 'create',
'truncate', 'grant', 'revoke', 'exec', 'execute', 'call'
]
for keyword in dangerous_keywords:
if keyword in query_lower:
return False
# Only allow queries on Real Estate data model tables
allowed_tables = [
'projekt', 'parzelle', 'dokument', 'kontext',
'geopolylinie', 'geopunkt', 'land', 'kanton', 'gemeinde'
]
# Check if query references allowed tables
# Simple check - in production, use SQL parser
query_contains_allowed_table = any(
f'from {table}' in query_lower or f'join {table}' in query_lower
for table in allowed_tables
)
if not query_contains_allowed_table:
# Allow queries that don't specify table explicitly (might be subqueries)
# But log for review
logger.warning(f"Query does not reference known Real Estate tables: {queryText[:100]}")
return True
```
## Erweiterte Validierung mit SQL-Parser:
Für Produktion sollten Sie einen SQL-Parser verwenden:
```python
from sqlparse import parse, tokens
def validateQueryAdvanced(queryText: str) -> bool:
"""Advanced query validation using SQL parser."""
try:
parsed = parse(queryText)[0]
# Check statement type
if parsed.get_type() != 'SELECT':
return False
# Extract table names and validate
# Implementation depends on SQL parser library
# ...
return True
except Exception as e:
logger.error(f"Query parsing failed: {e}")
return False
```
---
[← Zurück: Datenbank-Schema](09-database-schema.md) | [Weiter: Testing →](11-testing.md)

View file

@ -0,0 +1,51 @@
# Testing
[← Zurück: Sicherheitshinweise](10-security.md) | [Weiter: Troubleshooting →](12-troubleshooting.md)
## Manuelle API-Tests mit curl:
```bash
# 1. Login (erhalten Sie Token)
curl -X POST "http://localhost:8000/api/local/auth/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=youruser&password=yourpass"
# 2. Session erstellen
curl -X POST "http://localhost:8000/api/realestate/sessions" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Parzellen-Analyse Zürich"}'
# 3. Query ausführen - Beispiel: Alle Parzellen in Zürich
curl -X POST "http://localhost:8000/api/realestate/sessions/SESSION_ID/queries" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"queryText": "SELECT label, plz, bauzone, az, bz, gebaeudehoeheMax FROM Parzelle WHERE plz = ''8000'' ORDER BY label LIMIT 20",
"queryType": "sql"
}'
# 4. Query ausführen - Beispiel: Projekte mit Status "Planung"
curl -X POST "http://localhost:8000/api/realestate/sessions/SESSION_ID/queries" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"queryText": "SELECT id, label, \"statusProzess\" FROM Projekt WHERE \"statusProzess\" = ''Planung''",
"queryType": "sql"
}'
# 5. Queries abrufen
curl -X GET "http://localhost:8000/api/realestate/sessions/SESSION_ID/queries" \
-H "Authorization: Bearer YOUR_TOKEN"
```
## Swagger UI:
Nach dem Start der Anwendung können Sie die API unter `http://localhost:8000/docs` testen.
---
[← Zurück: Sicherheitshinweise](10-security.md) | [Weiter: Troubleshooting →](12-troubleshooting.md)

View file

@ -0,0 +1,33 @@
# Troubleshooting
[← Zurück: Testing](11-testing.md) | [Weiter: Zusammenfassung →](13-summary.md)
## Problem: Datenbankverbindung schlägt fehl
**Lösung**: Überprüfen Sie die Environment-Variablen in `env_dev.env`:
- `DB_APP_HOST`
- `DB_APP_DATABASE`
- `DB_APP_USER`
- `DB_APP_PASSWORD_SECRET`
- `DB_APP_PORT`
## Problem: Tabellen werden nicht erstellt
**Lösung**: Der Connector erstellt Tabellen beim ersten Zugriff. Stellen Sie sicher, dass:
- Die Datenbank existiert
- Der Benutzer CREATE-Rechte hat
- Die Verbindung erfolgreich ist
## Problem: Access Denied Fehler
**Lösung**: Überprüfen Sie:
- User hat gültiges `mandateId`
- User hat entsprechende Privilegien
- Access Control Logik im Interface
---
[← Zurück: Testing](11-testing.md) | [Weiter: Zusammenfassung →](13-summary.md)

View file

@ -0,0 +1,136 @@
# Zusammenfassung
[← Zurück: Troubleshooting](12-troubleshooting.md) | [← Zurück zur Übersicht](README.md)
## Dateinamen-Konvention:
**Wichtig:** Die Dateien sind nach Funktionalität benannt:
| Datei | Zweck | Enthält |
|-------|-------|---------|
| `datamodelRealEstateChat.py` | Chat-Interface Modelle | `RealEstateQuery`, `RealEstateQueryResult`, `RealEstateChatSession` |
| `datamodelRealEstate.py` | Real Estate-Datenmodelle | `Projekt`, `Parzelle`, `Dokument`, etc. (allgemein verwendbar) |
| `interfaceDbRealEstateChatObjects.py` | Chat-Interface Interface | Methoden für Sessions und Queries |
| `interfaceDbRealEstateObjects.py` | Real Estate CRUD Interface | Methoden für Projekt, Parzelle, etc. (optional) |
**Hinweis:** Das Modell ist allgemein für alle Real Estate-Firmen verwendbar. PEK ist nur ein Beispiel.
---
## Zu erstellende Dateien:
1. **`modules/datamodels/datamodelRealEstateChat.py`** (Chat-Interface Modelle)
- Pydantic-Modelle: `RealEstateQuery`, `RealEstateQueryResult`, `RealEstateChatSession`
- Enums: `QueryStatusEnum`
2. **`modules/datamodels/datamodelRealEstate.py`** (Real Estate-Datenmodell)
- Pydantic-Modelle: `Projekt`, `Parzelle`, `Dokument`, `Kontext`, `GeoPolylinie`, `GeoPunkt`, `Land`, `Kanton`, `Gemeinde`
- Enums: `StatusProzess`, `DokumentTyp`, `JaNein`, `GeoTag`
- Siehe `../PEK_datamodel_desc.md` für vollständige Spezifikation (PEK ist ein Beispiel, das Modell ist allgemein verwendbar)
3. **`modules/interfaces/interfaceDbRealEstateChatObjects.py`** (Chat-Interface)
- `RealEstateChatObjects` Klasse für Datenbankzugriff (Chat-Sessions, Queries)
- `RealEstateChatAccess` Klasse für Zugriffskontrolle
- `getInterface()` Factory-Funktion
4. **`modules/interfaces/interfaceDbRealEstateObjects.py`** (NEU - für Real Estate-Datenmodell CRUD)
- `RealEstateObjects` Klasse für CRUD-Operationen auf Real Estate-Entitäten (Projekt, Parzelle, etc.)
- `RealEstateAccess` Klasse für Zugriffskontrolle
- Methoden für Projekt, Parzelle, Dokument, etc.
- **Hinweis:** Diese Datei ist für CRUD-Operationen auf die Real Estate-Entitäten. Das Chat-Interface nutzt `interfaceDbRealEstateChatObjects.py` (siehe Punkt 3).
- **Optional:** Falls Sie eine separate CRUD-API benötigen (das Chat-Interface kann auch direkt SQL-Queries verwenden)
5. **`modules/features/realEstate/mainRealEstate.py`**
- Feature-Logik-Funktionen: `createSession`, `executeDatabaseQuery`, etc.
6. **`modules/routes/routeRealEstate.py`**
- FastAPI Router mit allen Endpunkten für Chat-Interface
7. **`modules/routes/routeRealEstateData.py`** (NEU - für Real Estate-Datenmodell)
- FastAPI Router für CRUD-Operationen auf Real Estate-Entitäten
- Endpunkte für Projekt, Parzelle, Dokument, etc.
- **Optional:** Falls Sie eine separate CRUD-API benötigen (das Chat-Interface kann auch direkt SQL-Queries verwenden)
## Zu modifizierende Dateien:
1. **`app.py`**
- Router-Registrierung für `routeRealEstate` hinzufügen (Chat-Interface)
- Router-Registrierung für `routeRealEstateData` hinzufügen (falls CRUD-API gewünscht)
2. **`env_dev.env`** (optional)
- Separate Datenbank-Konfiguration falls gewünscht
- PostGIS-Konfiguration falls geografische Abfragen benötigt werden
3. **`modules/features/featuresLifecycle.py`** (optional)
- Feature-Initialisierung falls benötigt
- Initialisierung von Standard-Daten (z.B. Land "Schweiz", Kantone, Gemeinden)
## Datenmodell-Implementierung:
**Wichtig:** Bevor Sie das Chat-Interface nutzen können, müssen Sie die Real Estate-Datenmodell-Entitäten implementieren:
1. **Erstellen Sie `modules/datamodels/datamodelRealEstate.py`** mit allen Entitäten aus `../PEK_datamodel_desc.md`
- **Hinweis:** PEK ist ein Beispiel für eine Real Estate-Firma, aber das Modell ist allgemein verwendbar für alle Real Estate-Firmen
2. **Beachten Sie die Objektbeziehungen**:
- `parzellen: list[Parzelle]` wird als JSONB gespeichert
- `kontextKanton: Kanton` wird als String-ID gespeichert (Foreign Key)
3. **Implementieren Sie die Enums** entsprechend der Spezifikation
4. **Testen Sie die Tabellenerstellung** durch den DatabaseConnector
---
## Nächste Schritte
1. **Real Estate-Datenmodell-Implementierung**:
- Erstellen Sie die Pydantic-Modelle für alle Real Estate-Entitäten (`Projekt`, `Parzelle`, `Dokument`, `Kontext`, `GeoPolylinie`, `GeoPunkt`, `Land`, `Kanton`, `Gemeinde`)
- Implementieren Sie die Enums (`StatusProzess`, `DokumentTyp`, `JaNein`, `GeoTag`)
- Siehe `../PEK_datamodel_desc.md` für vollständige Spezifikation (PEK ist ein Beispiel, das Modell ist allgemein verwendbar)
2. **Query-Validierung implementieren**: Siehe [Sicherheitshinweise](10-security.md)
- Besonders wichtig für Real Estate-Datenmodell: Nur SELECT-Statements erlauben
- Whitelist für erlaubte Tabellen (Projekt, Parzelle, etc.)
3. **Natural Language Processing**:
- Implementieren Sie NLP für `queryType="natural"`
- Beispiele: "Zeige mir alle Parzellen in Zürich" → SQL-Query
- Nutzen Sie AI-Modelle zur SQL-Generierung aus natürlicher Sprache
4. **Geografische Abfragen**:
- PostGIS-Integration für räumliche Abfragen
- Beispiel: "Zeige alle Parzellen innerhalb eines bestimmten Perimeters"
- Nutzung von GeoPolylinie und GeoPunkt für GIS-Funktionen
5. **Query-History**: Erweiterte Historie-Funktionen
- Speichern häufig verwendeter Queries
- Query-Templates für häufige Abfragen (z.B. "Parzellen nach Bauzone")
6. **Export-Funktionen**: CSV/Excel-Export von Ergebnissen
- Export von Parzellen-Listen
- Export von Projekt-Übersichten
7. **Caching**: Query-Ergebnisse cachen für wiederholte Abfragen
- Besonders für administrative Daten (Land, Kanton, Gemeinde)
8. **Permissions**: Erweiterte Berechtigungen für bestimmte Tabellen
- Mandaten-basierte Filterung für Projekte und Parzellen
- Rollen-basierte Zugriffe (z.B. nur Leserechte für bestimmte Benutzer)
---
## Architektur-Zusammenfassung
Dieses Feature folgt dem etablierten Muster des Projekts:
- **Separation of Concerns**: Routes → Features → Interfaces → Connectors
- **Dependency Injection**: Interfaces werden über Factory-Funktionen erstellt
- **Access Control**: Mandaten- und Benutzer-basierte Filterung
- **Type Safety**: Pydantic-Modelle für Validierung
- **Async Support**: Asynchrone Verarbeitung für Skalierbarkeit
Die Implementierung ist modular und erweiterbar. Sie können weitere Funktionen hinzufügen, ohne die bestehende Struktur zu ändern.
---
[← Zurück: Troubleshooting](12-troubleshooting.md) | [← Zurück zur Übersicht](README.md)

View file

@ -0,0 +1,42 @@
# Feature Integration Guide: realEstate
Diese Dokumentation erklärt Schritt für Schritt, wie Sie ein neues Feature "realEstate" in das Gateway-Projekt integrieren. Das Feature ermöglicht es, über ein Chat-Interface Datenbankabfragen auf Real Estate-Daten (Architektur-Planungs-App) durchzuführen.
**Referenz:** Das zugrundeliegende Datenmodell ist in `../PEK_datamodel_desc.md` beschrieben (PEK ist ein Beispiel für eine Real Estate-Firma, das Modell ist aber allgemein verwendbar).
## Inhaltsverzeichnis
1. [Überblick und Projektstruktur](01-overview.md)
2. [Schritt 1: Datenmodell erstellen](02-datamodels.md)
3. [Schritt 2: Interface erstellen](03-interfaces.md)
4. [Schritt 3: Feature-Logik implementieren](04-feature-logic.md)
5. [Schritt 4: Routen erstellen](05-routes.md)
6. [Schritt 5: Router registrieren](06-router-registration.md)
7. [Schritt 6: Environment-Konfiguration](07-environment.md)
8. [Schritt 7: Feature Lifecycle (optional)](08-lifecycle.md)
9. [Datenbank-Schema und Tabellenerstellung](09-database-schema.md)
10. [Sicherheitshinweise](10-security.md)
11. [Testing](11-testing.md)
12. [Troubleshooting](12-troubleshooting.md)
13. [Zusammenfassung](13-summary.md)
---
## Schnellstart
Für eine schnelle Übersicht über alle zu erstellenden Dateien, siehe [Zusammenfassung](13-summary.md).
## Architektur-Überblick
Die Architektur folgt dem Muster bestehender Features wie `chatPlayground`:
- **Routes** (`modules/routes/`) - API-Endpunkte
- **Features** (`modules/features/`) - Geschäftslogik
- **Interfaces** (`modules/interfaces/`) - Datenbankzugriff
- **DataModels** (`modules/datamodels/`) - Pydantic-Modelle
---
**Nächster Schritt:** [01-overview.md](01-overview.md)

View file

@ -0,0 +1,48 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities:
- Projekt: Real estate project
- Parzelle: Plot/parcel
- Dokument: Document
- Kanton: Canton
- Gemeinde: Municipality
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields (label, statusProzess, etc.)
// For READ: include filter criteria (id, label, plz, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE plz = '8000'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE plz = '8000'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,10 @@
```json
{
"intent": "CREATE",
"entity": "Projekt",
"parameters": {
"label": "Hauptstrasse 42"
},
"confidence": 0.95
}
```

View file

@ -0,0 +1,48 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Zeige mir alle Projekte in Zürich."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities:
- Projekt: Real estate project
- Parzelle: Plot/parcel
- Dokument: Document
- Kanton: Canton
- Gemeinde: Municipality
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields (label, statusProzess, etc.)
// For READ: include filter criteria (id, label, plz, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE plz = '8000'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE plz = '8000'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,16 @@
```json
{
"intent": "READ",
"entity": "Projekt",
"parameters": {
"location": "Zürich"
},
"confidence": 0.95
}
```
**Analysis:**
- **Intent**: READ - The command "Zeige mir" (Show me) clearly indicates a query/read operation
- **Entity**: Projekt - The user explicitly asks for "Projekte" (projects)
- **Parameters**: The location filter "in Zürich" is extracted as a search criterion. This could refer to a municipality (Gemeinde) named Zürich or projects located in the Zürich area
- **Confidence**: 0.95 - High confidence as the intent and entity are clearly stated, with an explicit location filter

View file

@ -0,0 +1,48 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Zeige mir alle Projekte in Zürich."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities:
- Projekt: Real estate project
- Parzelle: Plot/parcel
- Dokument: Document
- Kanton: Canton
- Gemeinde: Municipality
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields (label, statusProzess, etc.)
// For READ: include filter criteria (id, label, plz, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE plz = '8000'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE plz = '8000'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,16 @@
```json
{
"intent": "READ",
"entity": "Projekt",
"parameters": {
"location": "Zürich"
},
"confidence": 0.95
}
```
**Analysis:**
- **Intent**: READ - The command "Zeige mir" (Show me) clearly indicates a query/read operation
- **Entity**: Projekt - The user explicitly asks for "Projekte" (projects)
- **Parameters**: The location filter "in Zürich" is extracted as a search criterion. This could refer to a municipality (Gemeinde) named Zürich or projects located in the Zürich area
- **Confidence**: 0.95 - High confidence as the intent and entity are clearly stated in the command

View file

@ -0,0 +1,88 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Zeige mir alle Projekte in Zürich."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key)
- kontextKanton: string (canton ID, Foreign Key)
- kontextLand: string (country ID, Foreign Key)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Location queries (city, postal code) should use Parzelle fields (plz, kontextGemeinde), NOT Projekt fields
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,18 @@
```json
{
"intent": "READ",
"entity": "Parzelle",
"parameters": {
"kontextGemeinde": "Zürich"
},
"confidence": 0.9
}
```
**Reasoning:**
- The user wants to see/display ("Zeige mir") projects, which indicates a READ intent
- The location filter "in Zürich" refers to a city/municipality
- According to the schema, **Projekt does NOT have location fields directly** - location information is stored in the associated **Parzelle** entity through the `kontextGemeinde` field
- Therefore, to find projects in Zürich, we need to query **Parzelle** with the filter `kontextGemeinde: "Zürich"`
- The system can then retrieve the associated projects through the relationship (Projekte contain Parzellen)
- Confidence is 0.9 because the intent is clear, though the user said "Projekte" while we're technically querying "Parzelle" first to filter by location

View file

@ -0,0 +1,88 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Zeige mir alle Projekte in Zürich."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key)
- kontextKanton: string (canton ID, Foreign Key)
- kontextLand: string (country ID, Foreign Key)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Location queries (city, postal code) should use Parzelle fields (plz, kontextGemeinde), NOT Projekt fields
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,17 @@
```json
{
"intent": "READ",
"entity": "Parzelle",
"parameters": {
"kontextGemeinde": "Zürich"
},
"confidence": 0.9
}
```
**Reasoning:**
- The user wants to see/display ("Zeige mir") projects, which indicates a READ intent
- The location filter "in Zürich" refers to a city/municipality
- According to the schema, **Projekt does NOT have location fields directly** - location information is stored in the associated **Parzelle** entity through the `kontextGemeinde` field
- Therefore, to find projects in Zürich, we need to query **Parzelle** entities with `kontextGemeinde: "Zürich"`, which will give us the plots (and their associated projects) in that municipality
- The confidence is 0.9 because the intent and location are clear, though the user said "Projekte" but we need to query via "Parzelle" due to the data model structure

View file

@ -0,0 +1,88 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Erstell eine neue Parzelle in Zürich."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key)
- kontextKanton: string (canton ID, Foreign Key)
- kontextLand: string (country ID, Foreign Key)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Location queries (city, postal code) should use Parzelle fields (plz, kontextGemeinde), NOT Projekt fields
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,17 @@
```json
{
"intent": "CREATE",
"entity": "Parzelle",
"parameters": {
"kontextGemeinde": "Zürich"
},
"confidence": 0.9
}
```
**Analysis:**
- **Intent**: CREATE - The user explicitly wants to create ("Erstell") a new entity
- **Entity**: Parzelle - The user specifies "eine neue Parzelle" (a new plot/parcel)
- **Parameters**:
- `kontextGemeinde`: "Zürich" - The location (municipality) where the plot should be created
- **Confidence**: 0.9 - High confidence as the intent and entity are clearly stated, though only minimal information (location) is provided for the creation

View file

@ -0,0 +1,88 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Zeige mir alle Parzellen in Zürich."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key)
- kontextKanton: string (canton ID, Foreign Key)
- kontextLand: string (country ID, Foreign Key)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Location queries (city, postal code) should use Parzelle fields (plz, kontextGemeinde), NOT Projekt fields
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,17 @@
```json
{
"intent": "READ",
"entity": "Parzelle",
"parameters": {
"kontextGemeinde": "Zürich"
},
"confidence": 0.95
}
```
**Reasoning:**
- The user command "Zeige mir alle Parzellen in Zürich" translates to "Show me all plots in Zurich"
- **Intent**: READ - The user wants to query/retrieve existing entities
- **Entity**: Parzelle - The user explicitly asks for "Parzellen" (plots/parcels)
- **Parameters**: The location filter "in Zürich" maps to the `kontextGemeinde` field of the Parzelle entity, which stores the municipality information
- **Confidence**: 0.95 - High confidence because the intent is clear (show/display), the entity is explicitly mentioned (Parzellen), and the location parameter (Zürich) clearly maps to the kontextGemeinde field

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Zeige mir alle Parzellen in Zürich."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,17 @@
```json
{
"intent": "READ",
"entity": "Parzelle",
"parameters": {
"kontextGemeinde": "Zürich"
},
"confidence": 0.95
}
```
**Explanation:**
- **Intent**: READ - The user wants to query/view existing data ("Zeige mir" = "Show me")
- **Entity**: Parzelle - The user explicitly asks for "Parzellen" (plots/parcels)
- **Parameters**:
- `kontextGemeinde`: "Zürich" - The location filter is specified as Zürich, which is a municipality (Gemeinde). According to the schema, location information for Parzellen is stored in the `kontextGemeinde` field
- **Confidence**: 0.95 - High confidence as the command is clear and unambiguous, directly requesting plots in a specific location

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Füge die Gemeinde Zürich in das System hinzu."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,16 @@
```json
{
"intent": "CREATE",
"entity": "Gemeinde",
"parameters": {
"label": "Zürich"
},
"confidence": 0.95
}
```
**Analysis:**
- **Intent**: CREATE - The verb "Füge...hinzu" (add) clearly indicates the user wants to create a new entity
- **Entity**: Gemeinde - The user explicitly mentions "die Gemeinde Zürich" (the municipality Zürich)
- **Parameters**: The municipality name "Zürich" is extracted as the label/identifier
- **Confidence**: 0.95 - High confidence due to clear intent and explicit entity mention

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Füge die Gemeinde Zürich in das System hinzu."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,16 @@
```json
{
"intent": "CREATE",
"entity": "Gemeinde",
"parameters": {
"label": "Zürich"
},
"confidence": 0.95
}
```
**Analysis:**
- **Intent**: CREATE - The user explicitly wants to add ("Füge...hinzu") a new entity to the system
- **Entity**: Gemeinde - The user specifically mentions "die Gemeinde Zürich" (the municipality Zürich)
- **Parameters**: The municipality name "Zürich" is extracted as the label/identifier
- **Confidence**: 0.95 - High confidence as the command is clear and unambiguous with explicit CREATE intent and entity type

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Füge ein neues Projekt mit Parzelle in Zürich hinzu."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,20 @@
```json
{
"intent": "CREATE",
"entity": "Projekt",
"parameters": {
"parzellen": [
{
"kontextGemeinde": "Zürich"
}
]
},
"confidence": 0.85
}
```
**Reasoning:**
- The user wants to create ("Füge hinzu") a new project ("neues Projekt")
- The project should include a plot ("mit Parzelle") in Zürich
- Since Projekt contains Parzellen as a nested list, and the location information (Zürich) belongs to Parzelle (via kontextGemeinde field), I've structured the parameters to reflect this relationship
- The confidence is 0.85 because while the intent is clear, the user hasn't provided other typical project details like a label/name for the project itself

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Füge die Gemeinde Winterthur hinzu"
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,16 @@
```json
{
"intent": "CREATE",
"entity": "Gemeinde",
"parameters": {
"label": "Winterthur"
},
"confidence": 0.95
}
```
**Analysis:**
- **Intent**: CREATE - The verb "Füge...hinzu" (add) clearly indicates the user wants to create a new entity
- **Entity**: Gemeinde - The user explicitly mentions "die Gemeinde Winterthur" (the municipality Winterthur)
- **Parameters**: The name/label of the municipality is "Winterthur"
- **Confidence**: 0.95 - High confidence as the command is clear and unambiguous

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Wie viele Projekte in Zürich gibt es?"
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,17 @@
```json
{
"intent": "READ",
"entity": "Parzelle",
"parameters": {
"kontextGemeinde": "Zürich"
},
"confidence": 0.9
}
```
**Reasoning:**
- The user asks "How many projects in Zürich are there?" which is a READ/query operation
- Since location information (city) is stored in Parzelle via `kontextGemeinde`, not directly in Projekt, we need to query Parzelle entities
- The parameter `kontextGemeinde` is set to "Zürich" to filter plots by municipality
- To get projects in Zürich, the system will need to find all Parzellen with kontextGemeinde="Zürich" and then identify the associated projects
- Confidence is 0.9 because the intent is clear, though the user asks about "projects" while we're querying "Parzelle" (which is the correct approach given the data model)

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "welche projekte in Zürich gibt es?"
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,17 @@
```json
{
"intent": "READ",
"entity": "Parzelle",
"parameters": {
"kontextGemeinde": "Zürich"
},
"confidence": 0.9
}
```
**Reasoning:**
- The user is asking "which projects in Zürich exist?" which is a READ/query operation
- Since location information (city/municipality) is stored in Parzelle (via `kontextGemeinde`), not directly in Projekt, we need to query Parzelle entities
- "Zürich" is the municipality name that maps to the `kontextGemeinde` field
- The system will need to find all Parzellen in Zürich and then retrieve their associated Projekte
- High confidence (0.9) as the intent and location are clearly stated

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Welche Projekte in Zürich gibt es?"
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,18 @@
```json
{
"intent": "READ",
"entity": "Parzelle",
"parameters": {
"kontextGemeinde": "Zürich"
},
"confidence": 0.9
}
```
**Reasoning:**
- The user is asking "Which projects in Zürich exist?" (Welche Projekte in Zürich gibt es?)
- This is a READ operation (querying for existing data)
- Since location information (city/municipality) is stored in **Parzelle** via the `kontextGemeinde` field, not directly in Projekt, we need to query Parzelle entities
- The parameter `kontextGemeinde` is set to "Zürich" to filter plots by municipality
- To get projects in Zürich, the system will need to find Parzellen with kontextGemeinde="Zürich" and then retrieve their associated Projekte
- Confidence is 0.9 because the intent is clear, though the user asked about "Projekte" while we're technically querying via "Parzelle" due to the data model structure

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Welche Gemeinden haben wir bereits hinzugefügt?"
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,14 @@
```json
{
"intent": "READ",
"entity": "Gemeinde",
"parameters": {},
"confidence": 0.95
}
```
**Reasoning:**
- The user is asking "Which municipalities have we already added?" (Welche Gemeinden haben wir bereits hinzugefügt?)
- This is clearly a READ operation to query existing Gemeinde entities
- No specific filters are mentioned, so the user wants to see all Gemeinde records
- The confidence is high (0.95) because the intent and entity are explicitly stated in the question

View file

@ -0,0 +1,89 @@
Analyze the following user command and extract the intent, entity, and parameters.
User Command: "Füge eine neue Parzelle hinzu die in Zürich liegt und die Adresse Ueberlandstrasse 11 8050 hat."
Available intents:
- CREATE: User wants to create a new entity
- READ: User wants to read/query entities
- UPDATE: User wants to update an existing entity
- DELETE: User wants to delete an entity
- QUERY: User wants to execute a database query (SQL statements)
Available entities and their fields:
**Projekt** (Real estate project):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (project designation/name)
- statusProzess: string enum (Eingang, Analyse, Studie, Planung, Baurechtsverfahren, Umsetzung, Archiv)
- perimeter: GeoPolylinie (geographic boundary, JSONB)
- baulinie: GeoPolylinie (building line, JSONB)
- parzellen: List[Parzelle] (plots belonging to project, JSONB)
- dokumente: List[Dokument] (documents, JSONB)
- kontextInformationen: List[Kontext] (context info, JSONB)
**Parzelle** (Plot/parcel):
- id: string (primary key)
- mandateId: string (mandate ID)
- label: string (plot designation)
- strasseNr: string (street and house number)
- plz: string (postal code)
- kontextGemeinde: string (municipality ID, Foreign Key to Gemeinde table)
- bauzone: string (building zone, e.g. W3, WG2)
- az: float (Ausnützungsziffer)
- bz: float (Bebauungsziffer)
- vollgeschossZahl: int (number of allowed full floors)
- gebaeudehoeheMax: float (maximum building height in meters)
- laermschutzzone: string (noise protection zone)
- hochwasserschutzzone: string (flood protection zone)
- grundwasserschutzzone: string (groundwater protection zone)
- parzelleBebaut: JaNein enum (is plot built)
- parzelleErschlossen: JaNein enum (is plot developed)
- parzelleHanglage: JaNein enum (is plot on slope)
**Important relationships:**
- Projekte contain Parzellen (projects have plots)
- Parzelle links to Gemeinde (via kontextGemeinde)
- Gemeinde links to Kanton (via id_kanton)
- Kanton links to Land (via id_land)
- Location queries (city, postal code) should use Parzelle.kontextGemeinde (municipality name will be resolved to ID)
- Projekt does NOT have location fields directly - location is stored in associated Parzellen
Return a JSON object with the following structure:
{
"intent": "CREATE|READ|UPDATE|DELETE|QUERY",
"entity": "Projekt|Parzelle|Dokument|Kanton|Gemeinde|null",
"parameters": {
// Extracted parameters from user input
// For CREATE/UPDATE: include all relevant fields using EXACT field names from above
// For READ: include filter criteria using EXACT field names (id, label, plz, kontextGemeinde, etc.)
// For DELETE: include entity ID if mentioned
// For QUERY: include queryText if SQL is detected
// IMPORTANT: Use only field names that exist in the entity definition above
},
"confidence": 0.0-1.0 // Confidence score for the analysis
}
Examples:
- Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'"
Output: {"intent": "CREATE", "entity": "Projekt", "parameters": {"label": "Hauptstrasse 42"}, "confidence": 0.95}
- Input: "Zeige mir alle Projekte"
Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9}
- Input: "Zeige mir Projekte in Zürich"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"kontextGemeinde": "Zürich"}, "confidence": 0.9}
Note: Location queries should query Parzelle, not Projekt directly
- Input: "Zeige mir Parzellen mit PLZ 8000"
Output: {"intent": "READ", "entity": "Parzelle", "parameters": {"plz": "8000"}, "confidence": 0.95}
- Input: "Aktualisiere Projekt XYZ mit Status 'Planung'"
Output: {"intent": "UPDATE", "entity": "Projekt", "parameters": {"id": "XYZ", "statusProzess": "Planung"}, "confidence": 0.85}
- Input: "SELECT * FROM Projekt WHERE label = 'Test'"
Output: {"intent": "QUERY", "entity": null, "parameters": {"queryText": "SELECT * FROM Projekt WHERE label = 'Test'", "queryType": "sql"}, "confidence": 1.0}
- Input: "Lösche Parzelle ABC"
Output: {"intent": "DELETE", "entity": "Parzelle", "parameters": {"id": "ABC"}, "confidence": 0.9}

View file

@ -0,0 +1,12 @@
```json
{
"intent": "CREATE",
"entity": "Parzelle",
"parameters": {
"kontextGemeinde": "Zürich",
"strasseNr": "Ueberlandstrasse 11",
"plz": "8050"
},
"confidence": 0.95
}
```