integrated PEK data modell and according routes to get data
This commit is contained in:
parent
ff6fc3f96c
commit
7c5debe7f0
49 changed files with 5917 additions and 0 deletions
418
docs/PEK_datamodel_desc.md
Normal file
418
docs/PEK_datamodel_desc.md
Normal 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
|
||||
169
docs/real-estate-feature-integration-guide/01-overview.md
Normal file
169
docs/real-estate-feature-integration-guide/01-overview.md
Normal 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)
|
||||
|
||||
976
docs/real-estate-feature-integration-guide/02-datamodels.md
Normal file
976
docs/real-estate-feature-integration-guide/02-datamodels.md
Normal 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)
|
||||
|
||||
845
docs/real-estate-feature-integration-guide/03-interfaces.md
Normal file
845
docs/real-estate-feature-integration-guide/03-interfaces.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
780
docs/real-estate-feature-integration-guide/04-feature-logic.md
Normal file
780
docs/real-estate-feature-integration-guide/04-feature-logic.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
332
docs/real-estate-feature-integration-guide/05-routes.md
Normal file
332
docs/real-estate-feature-integration-guide/05-routes.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
50
docs/real-estate-feature-integration-guide/07-environment.md
Normal file
50
docs/real-estate-feature-integration-guide/07-environment.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
43
docs/real-estate-feature-integration-guide/08-lifecycle.md
Normal file
43
docs/real-estate-feature-integration-guide/08-lifecycle.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
247
docs/real-estate-feature-integration-guide/09-database-schema.md
Normal file
247
docs/real-estate-feature-integration-guide/09-database-schema.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
90
docs/real-estate-feature-integration-guide/10-security.md
Normal file
90
docs/real-estate-feature-integration-guide/10-security.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
51
docs/real-estate-feature-integration-guide/11-testing.md
Normal file
51
docs/real-estate-feature-integration-guide/11-testing.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
136
docs/real-estate-feature-integration-guide/13-summary.md
Normal file
136
docs/real-estate-feature-integration-guide/13-summary.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
42
docs/real-estate-feature-integration-guide/README.md
Normal file
42
docs/real-estate-feature-integration-guide/README.md
Normal 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)
|
||||
|
||||
|
||||
|
||||
|
|
@ -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}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
```json
|
||||
{
|
||||
"intent": "CREATE",
|
||||
"entity": "Projekt",
|
||||
"parameters": {
|
||||
"label": "Hauptstrasse 42"
|
||||
},
|
||||
"confidence": 0.95
|
||||
}
|
||||
```
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
```json
|
||||
{
|
||||
"intent": "CREATE",
|
||||
"entity": "Parzelle",
|
||||
"parameters": {
|
||||
"kontextGemeinde": "Zürich",
|
||||
"strasseNr": "Ueberlandstrasse 11",
|
||||
"plz": "8050"
|
||||
},
|
||||
"confidence": 0.95
|
||||
}
|
||||
```
|
||||
Loading…
Reference in a new issue