From 99c9049c5ce7b0ad8d0acdba62ce266fbbe781c4 Mon Sep 17 00:00:00 2001 From: Ida Dittrich Date: Mon, 1 Dec 2025 17:24:56 +0100 Subject: [PATCH] =?UTF-8?q?feinschliff=20f=C3=BCr=20pek?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...51201-160716-003-intentanalysis_prompt.txt | 89 ++++ ...201-160722-004-intentanalysis_response.txt | 17 + ...51201-160744-005-intentanalysis_prompt.txt | 89 ++++ ...201-160752-006-intentanalysis_response.txt | 17 + ...51201-160806-007-intentanalysis_prompt.txt | 89 ++++ ...201-160813-008-intentanalysis_response.txt | 17 + ...51201-161019-009-intentanalysis_prompt.txt | 89 ++++ ...201-161025-010-intentanalysis_response.txt | 17 + ...51201-161508-011-intentanalysis_prompt.txt | 89 ++++ ...201-161517-012-intentanalysis_response.txt | 17 + ...51201-161718-013-intentanalysis_prompt.txt | 106 +++++ ...201-161727-014-intentanalysis_response.txt | 37 ++ ...51201-162158-015-intentanalysis_prompt.txt | 131 ++++++ ...201-162205-016-intentanalysis_response.txt | 63 +++ ...51201-162358-017-intentanalysis_prompt.txt | 131 ++++++ ...201-162405-018-intentanalysis_response.txt | 63 +++ ...51201-162418-019-intentanalysis_prompt.txt | 137 ++++++ ...201-162424-020-intentanalysis_response.txt | 47 +++ modules/features/realEstate/mainRealEstate.py | 398 ++++++++++++++++-- .../interfaceDbRealEstateObjects.py | 18 + 20 files changed, 1637 insertions(+), 24 deletions(-) create mode 100644 logs/debug/prompts/20251201-160716-003-intentanalysis_prompt.txt create mode 100644 logs/debug/prompts/20251201-160722-004-intentanalysis_response.txt create mode 100644 logs/debug/prompts/20251201-160744-005-intentanalysis_prompt.txt create mode 100644 logs/debug/prompts/20251201-160752-006-intentanalysis_response.txt create mode 100644 logs/debug/prompts/20251201-160806-007-intentanalysis_prompt.txt create mode 100644 logs/debug/prompts/20251201-160813-008-intentanalysis_response.txt create mode 100644 logs/debug/prompts/20251201-161019-009-intentanalysis_prompt.txt create mode 100644 logs/debug/prompts/20251201-161025-010-intentanalysis_response.txt create mode 100644 logs/debug/prompts/20251201-161508-011-intentanalysis_prompt.txt create mode 100644 logs/debug/prompts/20251201-161517-012-intentanalysis_response.txt create mode 100644 logs/debug/prompts/20251201-161718-013-intentanalysis_prompt.txt create mode 100644 logs/debug/prompts/20251201-161727-014-intentanalysis_response.txt create mode 100644 logs/debug/prompts/20251201-162158-015-intentanalysis_prompt.txt create mode 100644 logs/debug/prompts/20251201-162205-016-intentanalysis_response.txt create mode 100644 logs/debug/prompts/20251201-162358-017-intentanalysis_prompt.txt create mode 100644 logs/debug/prompts/20251201-162405-018-intentanalysis_response.txt create mode 100644 logs/debug/prompts/20251201-162418-019-intentanalysis_prompt.txt create mode 100644 logs/debug/prompts/20251201-162424-020-intentanalysis_response.txt diff --git a/logs/debug/prompts/20251201-160716-003-intentanalysis_prompt.txt b/logs/debug/prompts/20251201-160716-003-intentanalysis_prompt.txt new file mode 100644 index 00000000..4e4a69f4 --- /dev/null +++ b/logs/debug/prompts/20251201-160716-003-intentanalysis_prompt.txt @@ -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" or "Wie viele Projekte in Zürich" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {"location_filter": "Zürich"}, "confidence": 0.9} + Note: For project location queries, use Projekt entity with location_filter parameter + +- 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} diff --git a/logs/debug/prompts/20251201-160722-004-intentanalysis_response.txt b/logs/debug/prompts/20251201-160722-004-intentanalysis_response.txt new file mode 100644 index 00000000..e0011291 --- /dev/null +++ b/logs/debug/prompts/20251201-160722-004-intentanalysis_response.txt @@ -0,0 +1,17 @@ +```json +{ + "intent": "READ", + "entity": "Projekt", + "parameters": { + "location_filter": "Zürich" + }, + "confidence": 0.9 +} +``` + +**Analysis:** +- **Intent**: READ - The user wants to query/count existing projects ("wie viele ... gibt es?" = "how many ... are there?") +- **Entity**: Projekt - The user is asking about "Projekte" (projects) +- **Parameters**: + - `location_filter: "Zürich"` - The user wants to filter projects by location (Zürich). Since Projekt doesn't have direct location fields, this will need to be resolved through the associated Parzellen's kontextGemeinde relationship +- **Confidence**: 0.9 - High confidence as the intent and entity are clear, though the query involves a relationship traversal (Projekt → Parzelle → Gemeinde) \ No newline at end of file diff --git a/logs/debug/prompts/20251201-160744-005-intentanalysis_prompt.txt b/logs/debug/prompts/20251201-160744-005-intentanalysis_prompt.txt new file mode 100644 index 00000000..7bf71841 --- /dev/null +++ b/logs/debug/prompts/20251201-160744-005-intentanalysis_prompt.txt @@ -0,0 +1,89 @@ + +Analyze the following user command and extract the intent, entity, and parameters. + +User Command: "Erstelle ein neues Projekt 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" or "Wie viele Projekte in Zürich" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {"location_filter": "Zürich"}, "confidence": 0.9} + Note: For project location queries, use Projekt entity with location_filter parameter + +- 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} diff --git a/logs/debug/prompts/20251201-160752-006-intentanalysis_response.txt b/logs/debug/prompts/20251201-160752-006-intentanalysis_response.txt new file mode 100644 index 00000000..2e1fc3e5 --- /dev/null +++ b/logs/debug/prompts/20251201-160752-006-intentanalysis_response.txt @@ -0,0 +1,17 @@ +```json +{ + "intent": "CREATE", + "entity": "Projekt", + "parameters": { + "location_filter": "Zürich" + }, + "confidence": 0.85 +} +``` + +**Explanation:** +- **Intent**: CREATE - The user wants to create a new project ("Erstelle ein neues Projekt") +- **Entity**: Projekt - The entity type is clearly specified +- **Parameters**: + - `location_filter`: "Zürich" - The user mentions "in Zürich" as the location. Since Projekt doesn't have direct location fields (location is stored in associated Parzellen), I'm using `location_filter` to indicate the intended location context. This will need to be handled by creating associated Parzellen with the appropriate `kontextGemeinde` value. +- **Confidence**: 0.85 - High confidence in intent and entity, slightly lower because the location reference for a new project is indirect (projects get location through their parcels) and no project name/label was provided. \ No newline at end of file diff --git a/logs/debug/prompts/20251201-160806-007-intentanalysis_prompt.txt b/logs/debug/prompts/20251201-160806-007-intentanalysis_prompt.txt new file mode 100644 index 00000000..92dd7b3a --- /dev/null +++ b/logs/debug/prompts/20251201-160806-007-intentanalysis_prompt.txt @@ -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 jetzt?" + +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" or "Wie viele Projekte in Zürich" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {"location_filter": "Zürich"}, "confidence": 0.9} + Note: For project location queries, use Projekt entity with location_filter parameter + +- 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} diff --git a/logs/debug/prompts/20251201-160813-008-intentanalysis_response.txt b/logs/debug/prompts/20251201-160813-008-intentanalysis_response.txt new file mode 100644 index 00000000..c861edee --- /dev/null +++ b/logs/debug/prompts/20251201-160813-008-intentanalysis_response.txt @@ -0,0 +1,17 @@ +```json +{ + "intent": "READ", + "entity": "Projekt", + "parameters": { + "location_filter": "zürich" + }, + "confidence": 0.9 +} +``` + +**Analysis:** +- **Intent**: READ - The user wants to query/count existing projects ("wie viele Projekte ... gibt es") +- **Entity**: Projekt - The query is about projects ("Projekte") +- **Parameters**: + - `location_filter`: "zürich" - The user is asking about projects in Zürich. Since Projekt doesn't have direct location fields but location is stored in associated Parzellen (via kontextGemeinde), we use the `location_filter` parameter to indicate this geographic filter needs to be resolved through the Parzelle relationship. +- **Confidence**: 0.9 - High confidence. The intent is clear (counting/querying projects), the location is explicitly mentioned, and the temporal indicator "jetzt" (now) suggests current/active projects. \ No newline at end of file diff --git a/logs/debug/prompts/20251201-161019-009-intentanalysis_prompt.txt b/logs/debug/prompts/20251201-161019-009-intentanalysis_prompt.txt new file mode 100644 index 00000000..f9b6b59d --- /dev/null +++ b/logs/debug/prompts/20251201-161019-009-intentanalysis_prompt.txt @@ -0,0 +1,89 @@ + +Analyze the following user command and extract the intent, entity, and parameters. + +User Command: "wie viele parzellen 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" or "Wie viele Projekte in Zürich" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {"location_filter": "Zürich"}, "confidence": 0.9} + Note: For project location queries, use Projekt entity with location_filter parameter + +- 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} diff --git a/logs/debug/prompts/20251201-161025-010-intentanalysis_response.txt b/logs/debug/prompts/20251201-161025-010-intentanalysis_response.txt new file mode 100644 index 00000000..0d25fef0 --- /dev/null +++ b/logs/debug/prompts/20251201-161025-010-intentanalysis_response.txt @@ -0,0 +1,17 @@ +```json +{ + "intent": "READ", + "entity": "Parzelle", + "parameters": { + "kontextGemeinde": "Zürich" + }, + "confidence": 0.9 +} +``` + +**Reasoning:** +- The user is asking "wie viele parzellen in zürich gibt es?" (how many plots are there in Zürich?) +- **Intent**: READ - The user wants to query/read information about existing entities +- **Entity**: Parzelle - The user is specifically asking about "parzellen" (plots/parcels) +- **Parameters**: The location "Zürich" refers to a municipality (Gemeinde). Since Parzelle has the field `kontextGemeinde` which links to the municipality, this is the appropriate field to use for filtering by location +- **Confidence**: 0.9 - High confidence as the intent and entity are clearly stated, though the exact count operation is implicit \ No newline at end of file diff --git a/logs/debug/prompts/20251201-161508-011-intentanalysis_prompt.txt b/logs/debug/prompts/20251201-161508-011-intentanalysis_prompt.txt new file mode 100644 index 00000000..f9b6b59d --- /dev/null +++ b/logs/debug/prompts/20251201-161508-011-intentanalysis_prompt.txt @@ -0,0 +1,89 @@ + +Analyze the following user command and extract the intent, entity, and parameters. + +User Command: "wie viele parzellen 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" or "Wie viele Projekte in Zürich" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {"location_filter": "Zürich"}, "confidence": 0.9} + Note: For project location queries, use Projekt entity with location_filter parameter + +- 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} diff --git a/logs/debug/prompts/20251201-161517-012-intentanalysis_response.txt b/logs/debug/prompts/20251201-161517-012-intentanalysis_response.txt new file mode 100644 index 00000000..0d25fef0 --- /dev/null +++ b/logs/debug/prompts/20251201-161517-012-intentanalysis_response.txt @@ -0,0 +1,17 @@ +```json +{ + "intent": "READ", + "entity": "Parzelle", + "parameters": { + "kontextGemeinde": "Zürich" + }, + "confidence": 0.9 +} +``` + +**Reasoning:** +- The user is asking "wie viele parzellen in zürich gibt es?" (how many plots are there in Zürich?) +- **Intent**: READ - The user wants to query/read information about existing entities +- **Entity**: Parzelle - The user is specifically asking about "parzellen" (plots/parcels) +- **Parameters**: The location "Zürich" refers to a municipality (Gemeinde). Since Parzelle has the field `kontextGemeinde` which links to the municipality, this is the appropriate field to use for filtering by location +- **Confidence**: 0.9 - High confidence as the intent and entity are clearly stated, though the exact count operation is implicit \ No newline at end of file diff --git a/logs/debug/prompts/20251201-161718-013-intentanalysis_prompt.txt b/logs/debug/prompts/20251201-161718-013-intentanalysis_prompt.txt new file mode 100644 index 00000000..b644016c --- /dev/null +++ b/logs/debug/prompts/20251201-161718-013-intentanalysis_prompt.txt @@ -0,0 +1,106 @@ + +Analyze the following user command and extract the intent, entity, and parameters. + +User Command: "add a new parzelle with these information into the database: Parzellen-Informationen +Ausgewählte Parzelle +ID:AA1704 +Nummer:AA1704 +Name:AA1704 +EGRID:CH887199917793 +IdentND:ZH0200000261 +Kanton:ZH +Gemeinde:Zürich +Gemeinde-Code:261 +Fläche:6514.99 m² +Zentrum (LV95):2682951.44, 1247622.91 +Geoportal: +Link öffnen +Kartenansicht +Zentrum:2682951.44, 1247622.91 +Bounds Min:2682914.00, 1247582.00 +Bounds Max:2683024.30, 1247667.50" + +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" or "Wie viele Projekte in Zürich" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {"location_filter": "Zürich"}, "confidence": 0.9} + Note: For project location queries, use Projekt entity with location_filter parameter + +- 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} diff --git a/logs/debug/prompts/20251201-161727-014-intentanalysis_response.txt b/logs/debug/prompts/20251201-161727-014-intentanalysis_response.txt new file mode 100644 index 00000000..5f12fc54 --- /dev/null +++ b/logs/debug/prompts/20251201-161727-014-intentanalysis_response.txt @@ -0,0 +1,37 @@ +```json +{ + "intent": "CREATE", + "entity": "Parzelle", + "parameters": { + "id": "AA1704", + "label": "AA1704", + "kontextGemeinde": "Zürich" + }, + "confidence": 0.75 +} +``` + +**Analysis:** + +The user wants to add a new "Parzelle" (plot/parcel) to the database. The command provides extensive information about the parcel, but only a subset can be mapped to the available fields in the **Parzelle** entity definition: + +**Mapped fields:** +- `id`: "AA1704" (from ID/Nummer) +- `label`: "AA1704" (from Name) +- `kontextGemeinde`: "Zürich" (from Gemeinde - will need to be resolved to municipality ID) + +**Unmapped information** (not available in the Parzelle entity schema): +- EGRID: CH887199917793 +- IdentND: ZH0200000261 +- Kanton: ZH +- Gemeinde-Code: 261 +- Fläche: 6514.99 m² +- Zentrum coordinates (LV95): 2682951.44, 1247622.91 +- Bounds coordinates + +**Confidence reasoning:** +The confidence is set to 0.75 because: +- The intent (CREATE) is clear +- The entity (Parzelle) is explicitly mentioned +- However, most of the provided information cannot be stored in the current Parzelle schema +- The user may expect all this data to be stored, but the system can only capture id, label, and kontextGemeinde from the provided information \ No newline at end of file diff --git a/logs/debug/prompts/20251201-162158-015-intentanalysis_prompt.txt b/logs/debug/prompts/20251201-162158-015-intentanalysis_prompt.txt new file mode 100644 index 00000000..edd50448 --- /dev/null +++ b/logs/debug/prompts/20251201-162158-015-intentanalysis_prompt.txt @@ -0,0 +1,131 @@ + +Analyze the following user command and extract the intent, entity, and parameters. + +User Command: "erstelle einen neuen parzelleneintrag mit diesen informationen: ID:AA4198 +Nummer:AA4198 +Name:AA4198 +EGRID:CH879177719964 +IdentND:ZH0200000261 +Adresse:Steinmühleplatz 3, 8001 Zürich +Kanton:ZH +Gemeinde:Zürich +Gemeinde-Code:261 +Fläche:1972.83 m² +Zentrum (LV95):2682910.35, 1247566.80" + +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: "Erstelle eine Parzelle mit Label 123, PLZ 8000, Gemeinde Zürich, Bauzone W3" + Output: {"intent": "CREATE", "entity": "Parzelle", "parameters": {"label": "123", "plz": "8000", "kontextGemeinde": "Zürich", "bauzone": "W3"}, "confidence": 0.95} + +- Input: "Parzellen-Informationen: ID:AA1704, Nummer:AA1704, EGRID:CH887199917793, Kanton:ZH, Gemeinde:Zürich, Gemeinde-Code:261, Fläche:6514.99 m², Zentrum:2682951.44,1247622.91" + Output: { + "intent": "CREATE", + "entity": "Parzelle", + "parameters": { + "label": "AA1704", + "parzellenAliasTags": ["AA1704"], + "kontextGemeinde": "Zürich", + "kontextInformationen": [ + {"kategorie": "Swiss Topo", "schluessel": "EGRID", "wert": "CH887199917793"}, + {"kategorie": "Kanton", "schluessel": "Abkürzung", "wert": "ZH"}, + {"kategorie": "Gemeinde", "schluessel": "BFS-Nummer", "wert": "261"}, + {"kategorie": "Parzelle", "schluessel": "Fläche", "wert": "6514.99", "einheit": "m²"}, + {"kategorie": "Parzelle", "schluessel": "Zentrum_X", "wert": "2682951.44", "crs": "EPSG:2056"}, + {"kategorie": "Parzelle", "schluessel": "Zentrum_Y", "wert": "1247622.91", "crs": "EPSG:2056"} + ] + }, + "confidence": 0.9 + } + Note: Extract structured data from detailed input. Use kontextInformationen for metadata like EGRID, coordinates, area, etc. + +- Input: "Zeige mir alle Projekte" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9} + +- Input: "Zeige mir Projekte in Zürich" or "Wie viele Projekte in Zürich" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {"location_filter": "Zürich"}, "confidence": 0.9} + Note: For project location queries, use Projekt entity with location_filter parameter + +- 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} + +IMPORTANT EXTRACTION RULES: +1. For CREATE operations, extract ALL mentioned data fields from the user input +2. Use kontextInformationen array for metadata that doesn't have dedicated fields (EGRID, BFS numbers, area, coordinates, etc.) +3. Match field names EXACTLY to the entity definition above +4. Convert data types correctly (strings for text, numbers for numeric values) +5. Extract coordinates, areas, and other numeric values from text +6. When multiple values are mentioned for the same concept (ID, Nummer, Name), use the most relevant one for 'label' and put alternatives in parzellenAliasTags diff --git a/logs/debug/prompts/20251201-162205-016-intentanalysis_response.txt b/logs/debug/prompts/20251201-162205-016-intentanalysis_response.txt new file mode 100644 index 00000000..b5c8b495 --- /dev/null +++ b/logs/debug/prompts/20251201-162205-016-intentanalysis_response.txt @@ -0,0 +1,63 @@ +```json +{ + "intent": "CREATE", + "entity": "Parzelle", + "parameters": { + "label": "AA4198", + "strasseNr": "Steinmühleplatz 3", + "plz": "8001", + "kontextGemeinde": "Zürich", + "kontextInformationen": [ + { + "kategorie": "Parzelle", + "schluessel": "ID", + "wert": "AA4198" + }, + { + "kategorie": "Parzelle", + "schluessel": "Nummer", + "wert": "AA4198" + }, + { + "kategorie": "Swiss Topo", + "schluessel": "EGRID", + "wert": "CH879177719964" + }, + { + "kategorie": "Parzelle", + "schluessel": "IdentND", + "wert": "ZH0200000261" + }, + { + "kategorie": "Kanton", + "schluessel": "Abkürzung", + "wert": "ZH" + }, + { + "kategorie": "Gemeinde", + "schluessel": "BFS-Nummer", + "wert": "261" + }, + { + "kategorie": "Parzelle", + "schluessel": "Fläche", + "wert": "1972.83", + "einheit": "m²" + }, + { + "kategorie": "Parzelle", + "schluessel": "Zentrum_X", + "wert": "2682910.35", + "crs": "EPSG:2056" + }, + { + "kategorie": "Parzelle", + "schluessel": "Zentrum_Y", + "wert": "1247566.80", + "crs": "EPSG:2056" + } + ] + }, + "confidence": 0.95 +} +``` \ No newline at end of file diff --git a/logs/debug/prompts/20251201-162358-017-intentanalysis_prompt.txt b/logs/debug/prompts/20251201-162358-017-intentanalysis_prompt.txt new file mode 100644 index 00000000..edd50448 --- /dev/null +++ b/logs/debug/prompts/20251201-162358-017-intentanalysis_prompt.txt @@ -0,0 +1,131 @@ + +Analyze the following user command and extract the intent, entity, and parameters. + +User Command: "erstelle einen neuen parzelleneintrag mit diesen informationen: ID:AA4198 +Nummer:AA4198 +Name:AA4198 +EGRID:CH879177719964 +IdentND:ZH0200000261 +Adresse:Steinmühleplatz 3, 8001 Zürich +Kanton:ZH +Gemeinde:Zürich +Gemeinde-Code:261 +Fläche:1972.83 m² +Zentrum (LV95):2682910.35, 1247566.80" + +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: "Erstelle eine Parzelle mit Label 123, PLZ 8000, Gemeinde Zürich, Bauzone W3" + Output: {"intent": "CREATE", "entity": "Parzelle", "parameters": {"label": "123", "plz": "8000", "kontextGemeinde": "Zürich", "bauzone": "W3"}, "confidence": 0.95} + +- Input: "Parzellen-Informationen: ID:AA1704, Nummer:AA1704, EGRID:CH887199917793, Kanton:ZH, Gemeinde:Zürich, Gemeinde-Code:261, Fläche:6514.99 m², Zentrum:2682951.44,1247622.91" + Output: { + "intent": "CREATE", + "entity": "Parzelle", + "parameters": { + "label": "AA1704", + "parzellenAliasTags": ["AA1704"], + "kontextGemeinde": "Zürich", + "kontextInformationen": [ + {"kategorie": "Swiss Topo", "schluessel": "EGRID", "wert": "CH887199917793"}, + {"kategorie": "Kanton", "schluessel": "Abkürzung", "wert": "ZH"}, + {"kategorie": "Gemeinde", "schluessel": "BFS-Nummer", "wert": "261"}, + {"kategorie": "Parzelle", "schluessel": "Fläche", "wert": "6514.99", "einheit": "m²"}, + {"kategorie": "Parzelle", "schluessel": "Zentrum_X", "wert": "2682951.44", "crs": "EPSG:2056"}, + {"kategorie": "Parzelle", "schluessel": "Zentrum_Y", "wert": "1247622.91", "crs": "EPSG:2056"} + ] + }, + "confidence": 0.9 + } + Note: Extract structured data from detailed input. Use kontextInformationen for metadata like EGRID, coordinates, area, etc. + +- Input: "Zeige mir alle Projekte" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9} + +- Input: "Zeige mir Projekte in Zürich" or "Wie viele Projekte in Zürich" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {"location_filter": "Zürich"}, "confidence": 0.9} + Note: For project location queries, use Projekt entity with location_filter parameter + +- 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} + +IMPORTANT EXTRACTION RULES: +1. For CREATE operations, extract ALL mentioned data fields from the user input +2. Use kontextInformationen array for metadata that doesn't have dedicated fields (EGRID, BFS numbers, area, coordinates, etc.) +3. Match field names EXACTLY to the entity definition above +4. Convert data types correctly (strings for text, numbers for numeric values) +5. Extract coordinates, areas, and other numeric values from text +6. When multiple values are mentioned for the same concept (ID, Nummer, Name), use the most relevant one for 'label' and put alternatives in parzellenAliasTags diff --git a/logs/debug/prompts/20251201-162405-018-intentanalysis_response.txt b/logs/debug/prompts/20251201-162405-018-intentanalysis_response.txt new file mode 100644 index 00000000..b5c8b495 --- /dev/null +++ b/logs/debug/prompts/20251201-162405-018-intentanalysis_response.txt @@ -0,0 +1,63 @@ +```json +{ + "intent": "CREATE", + "entity": "Parzelle", + "parameters": { + "label": "AA4198", + "strasseNr": "Steinmühleplatz 3", + "plz": "8001", + "kontextGemeinde": "Zürich", + "kontextInformationen": [ + { + "kategorie": "Parzelle", + "schluessel": "ID", + "wert": "AA4198" + }, + { + "kategorie": "Parzelle", + "schluessel": "Nummer", + "wert": "AA4198" + }, + { + "kategorie": "Swiss Topo", + "schluessel": "EGRID", + "wert": "CH879177719964" + }, + { + "kategorie": "Parzelle", + "schluessel": "IdentND", + "wert": "ZH0200000261" + }, + { + "kategorie": "Kanton", + "schluessel": "Abkürzung", + "wert": "ZH" + }, + { + "kategorie": "Gemeinde", + "schluessel": "BFS-Nummer", + "wert": "261" + }, + { + "kategorie": "Parzelle", + "schluessel": "Fläche", + "wert": "1972.83", + "einheit": "m²" + }, + { + "kategorie": "Parzelle", + "schluessel": "Zentrum_X", + "wert": "2682910.35", + "crs": "EPSG:2056" + }, + { + "kategorie": "Parzelle", + "schluessel": "Zentrum_Y", + "wert": "1247566.80", + "crs": "EPSG:2056" + } + ] + }, + "confidence": 0.95 +} +``` \ No newline at end of file diff --git a/logs/debug/prompts/20251201-162418-019-intentanalysis_prompt.txt b/logs/debug/prompts/20251201-162418-019-intentanalysis_prompt.txt new file mode 100644 index 00000000..0862c3d5 --- /dev/null +++ b/logs/debug/prompts/20251201-162418-019-intentanalysis_prompt.txt @@ -0,0 +1,137 @@ + +Analyze the following user command and extract the intent, entity, and parameters. + +User Command: "erstelle einen neuen parzelleneintrag mit diesen informationen: ID:AA4198 +Nummer:AA4198 +Name:AA4198 +EGRID:CH879177719964 +IdentND:ZH0200000261 +Adresse:Steinmühleplatz 3, 8001 Zürich +Kanton:ZH +Gemeinde:Zürich +Gemeinde-Code:261 +Fläche:1972.83 m² +Zentrum (LV95):2682910.35, 1247566.80" + +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) +- kontextInformationen: List[Kontext] (metadata - each item has 'thema' and 'inhalt' fields only) + +**Kontext** (Context information for metadata): +- thema: string (topic/subject, e.g. "EGRID", "Fläche", "Zentrum") +- inhalt: string (content as text, e.g. "CH887199917793", "6514.99 m²", "X: 123, Y: 456") + +**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: "Erstelle eine Parzelle mit Label 123, PLZ 8000, Gemeinde Zürich, Bauzone W3" + Output: {"intent": "CREATE", "entity": "Parzelle", "parameters": {"label": "123", "plz": "8000", "kontextGemeinde": "Zürich", "bauzone": "W3"}, "confidence": 0.95} + +- Input: "Parzellen-Informationen: ID:AA1704, Nummer:AA1704, EGRID:CH887199917793, Kanton:ZH, Gemeinde:Zürich, Gemeinde-Code:261, Fläche:6514.99 m², Zentrum:2682951.44,1247622.91" + Output: { + "intent": "CREATE", + "entity": "Parzelle", + "parameters": { + "label": "AA1704", + "parzellenAliasTags": ["AA1704"], + "kontextGemeinde": "Zürich", + "kontextInformationen": [ + {"thema": "EGRID", "inhalt": "CH887199917793"}, + {"thema": "Kanton", "inhalt": "ZH"}, + {"thema": "BFS-Nummer", "inhalt": "261"}, + {"thema": "Fläche", "inhalt": "6514.99 m²"}, + {"thema": "Zentrum (LV95)", "inhalt": "X: 2682951.44 m, Y: 1247622.91 m (EPSG:2056)"} + ] + }, + "confidence": 0.9 + } + Note: Extract structured data from detailed input. Use kontextInformationen for metadata. Each item has 'thema' (topic) and 'inhalt' (content as text). + +- Input: "Zeige mir alle Projekte" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {}, "confidence": 0.9} + +- Input: "Zeige mir Projekte in Zürich" or "Wie viele Projekte in Zürich" + Output: {"intent": "READ", "entity": "Projekt", "parameters": {"location_filter": "Zürich"}, "confidence": 0.9} + Note: For project location queries, use Projekt entity with location_filter parameter + +- 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} + +IMPORTANT EXTRACTION RULES: +1. For CREATE operations, extract ALL mentioned data fields from the user input +2. Use kontextInformationen array for metadata that doesn't have dedicated fields (EGRID, BFS numbers, area, coordinates, etc.) +3. Each kontextInformationen item MUST have exactly two fields: 'thema' (topic/subject) and 'inhalt' (content as text string) +4. Format kontextInformationen values as readable text strings, including units (e.g., "6514.99 m²", "X: 123, Y: 456") +5. Match field names EXACTLY to the entity definition above +6. Convert data types correctly (strings for text, numbers for numeric values) +7. Extract coordinates, areas, and other numeric values from text +8. When multiple values are mentioned for the same concept (ID, Nummer, Name), use the most relevant one for 'label' and put alternatives in parzellenAliasTags diff --git a/logs/debug/prompts/20251201-162424-020-intentanalysis_response.txt b/logs/debug/prompts/20251201-162424-020-intentanalysis_response.txt new file mode 100644 index 00000000..dbe4c09c --- /dev/null +++ b/logs/debug/prompts/20251201-162424-020-intentanalysis_response.txt @@ -0,0 +1,47 @@ +```json +{ + "intent": "CREATE", + "entity": "Parzelle", + "parameters": { + "label": "AA4198", + "strasseNr": "Steinmühleplatz 3", + "plz": "8001", + "kontextGemeinde": "Zürich", + "kontextInformationen": [ + { + "thema": "ID", + "inhalt": "AA4198" + }, + { + "thema": "Nummer", + "inhalt": "AA4198" + }, + { + "thema": "EGRID", + "inhalt": "CH879177719964" + }, + { + "thema": "IdentND", + "inhalt": "ZH0200000261" + }, + { + "thema": "Kanton", + "inhalt": "ZH" + }, + { + "thema": "BFS-Nummer", + "inhalt": "261" + }, + { + "thema": "Fläche", + "inhalt": "1972.83 m²" + }, + { + "thema": "Zentrum (LV95)", + "inhalt": "X: 2682910.35 m, Y: 1247566.80 m (EPSG:2056)" + } + ] + }, + "confidence": 0.95 +} +``` \ No newline at end of file diff --git a/modules/features/realEstate/mainRealEstate.py b/modules/features/realEstate/mainRealEstate.py index 5396a20a..4c7c994d 100644 --- a/modules/features/realEstate/mainRealEstate.py +++ b/modules/features/realEstate/mainRealEstate.py @@ -6,7 +6,7 @@ Stateless implementation without session management. import logging import json -from typing import Optional, Dict, Any +from typing import Optional, Dict, Any, List from modules.datamodels.datamodelUam import User from modules.datamodels.datamodelRealEstate import ( Projekt, @@ -72,6 +72,138 @@ async def executeDirectQuery( # ===== AI-basierte Intent-Erkennung und CRUD-Operationen ===== +def _formatEntitySummary(entity_type: str, items: List[Dict[str, Any]], filters: Dict[str, Any]) -> str: + """ + Format a human-readable summary of query results. + + Args: + entity_type: Type of entity (Projekt, Parzelle, etc.) + items: List of entity data dictionaries + filters: Filter parameters used in the query + + Returns: + Human-readable summary string + """ + if not items: + return f"Keine {entity_type} gefunden" + + count = len(items) + filter_desc = "" + if filters: + # Format filter description + if "kontextGemeinde" in filters: + filter_desc = f" in {filters['kontextGemeinde']}" + elif "plz" in filters: + filter_desc = f" mit PLZ {filters['plz']}" + elif "location_filter" in filters: + filter_desc = f" in {filters['location_filter']}" + + # Start with count + summary = f"Gefunden: {count} {entity_type}{filter_desc}" + + # Add details based on entity type + if entity_type == "Parzelle": + summary += "\n\nDetails:" + for i, item in enumerate(items[:10], 1): # Limit to first 10 + parts = [] + + # Add label or ID + if item.get("label"): + parts.append(f"Parzelle '{item['label']}'") + elif item.get("id"): + parts.append(f"Parzelle {item['id'][:8]}...") + + # Add address + if item.get("strasseNr"): + parts.append(item["strasseNr"]) + + # Add PLZ and municipality + location_parts = [] + if item.get("plz"): + location_parts.append(item["plz"]) + if item.get("kontextGemeinde"): + location_parts.append(item["kontextGemeinde"]) + if location_parts: + parts.append(" ".join(location_parts)) + + # Add building zone + if item.get("bauzone"): + parts.append(f"Bauzone: {item['bauzone']}") + + summary += f"\n{i}. {', '.join(parts)}" + + if count > 10: + summary += f"\n... und {count - 10} weitere" + + elif entity_type == "Projekt": + summary += "\n\nDetails:" + for i, item in enumerate(items[:10], 1): + parts = [] + + # Add label + if item.get("label"): + parts.append(f"'{item['label']}'") + + # Add status + if item.get("statusProzess"): + parts.append(f"Status: {item['statusProzess']}") + + # Add parcel count + parzellen = item.get("parzellen", []) + if parzellen: + parts.append(f"{len(parzellen)} Parzelle(n)") + + summary += f"\n{i}. {' - '.join(parts)}" + + if count > 10: + summary += f"\n... und {count - 10} weitere" + + elif entity_type == "Gemeinde": + summary += "\n\nDetails:" + for i, item in enumerate(items[:10], 1): + parts = [] + + if item.get("label"): + parts.append(item["label"]) + if item.get("plz"): + parts.append(f"PLZ: {item['plz']}") + if item.get("abk"): + parts.append(f"Abk: {item['abk']}") + + summary += f"\n{i}. {', '.join(parts)}" + + if count > 10: + summary += f"\n... und {count - 10} weitere" + + elif entity_type == "Dokument": + summary += "\n\nDetails:" + for i, item in enumerate(items[:10], 1): + parts = [] + + if item.get("label"): + parts.append(item["label"]) + if item.get("dokumentTyp"): + parts.append(f"Typ: {item['dokumentTyp']}") + if item.get("quelle"): + parts.append(f"Quelle: {item['quelle']}") + + summary += f"\n{i}. {', '.join(parts)}" + + if count > 10: + summary += f"\n... und {count - 10} weitere" + + else: + # Generic format for other entity types + if count <= 5: + summary += "\n\nDetails:" + for i, item in enumerate(items, 1): + label = item.get("label") or item.get("id", "") + if label: + summary += f"\n{i}. {label}" + + return summary + + async def processNaturalLanguageCommand( currentUser: User, userInput: str, @@ -117,13 +249,82 @@ async def processNaturalLanguageCommand( parameters=intentAnalysis.get("parameters", {}), ) - return { + # Build user-friendly response + response = { "success": True, "intent": intentAnalysis["intent"], "entity": intentAnalysis.get("entity"), "result": result, } + # Add human-readable summary for operations + if intentAnalysis["intent"] == "CREATE" and isinstance(result, dict): + # Add confirmation message for CREATE operations + operation_result = result.get("result") + if isinstance(operation_result, dict): + entity_name = intentAnalysis.get('entity', 'Eintrag') + label = operation_result.get("label", operation_result.get("id", "")) + + # Build detailed message + msg_parts = [f"✅ {entity_name} '{label}' erfolgreich erstellt"] + + if entity_name == "Parzelle": + if operation_result.get("plz"): + msg_parts.append(f"PLZ: {operation_result['plz']}") + if operation_result.get("kontextGemeinde"): + msg_parts.append(f"Gemeinde: {operation_result['kontextGemeinde']}") + if operation_result.get("bauzone"): + msg_parts.append(f"Bauzone: {operation_result['bauzone']}") + + kontext_items = operation_result.get("kontextInformationen", []) + if kontext_items: + msg_parts.append(f"\n📋 {len(kontext_items)} Kontextinformationen gespeichert:") + for kontext in kontext_items[:5]: # Show first 5 + thema = kontext.get("thema", "") + inhalt = kontext.get("inhalt", "") + if thema and inhalt: + msg_parts.append(f" • {thema}: {inhalt}") + if len(kontext_items) > 5: + msg_parts.append(f" • ... und {len(kontext_items) - 5} weitere") + + elif entity_name == "Projekt": + if operation_result.get("statusProzess"): + msg_parts.append(f"Status: {operation_result['statusProzess']}") + parzellen = operation_result.get("parzellen", []) + if parzellen: + msg_parts.append(f"{len(parzellen)} Parzelle(n)") + + response["message"] = "\n".join(msg_parts) + + elif intentAnalysis["intent"] == "READ" and isinstance(result, dict): + operation_result = result.get("result") + if isinstance(operation_result, list): + response["count"] = len(operation_result) + entity_name = intentAnalysis.get('entity', 'Einträge') + + if len(operation_result) == 0: + # Provide helpful message for empty results + filter_info = intentAnalysis.get('parameters', {}) + if filter_info: + filter_desc = ", ".join([f"{k}={v}" for k, v in filter_info.items()]) + response["message"] = f"Keine {entity_name} gefunden mit Filter: {filter_desc}. Möglicherweise sind noch keine Daten vorhanden oder der Filter ist zu spezifisch." + else: + response["message"] = f"Keine {entity_name} vorhanden. Erstellen Sie zuerst neue Einträge." + else: + # Create detailed summary based on entity type + response["message"] = _formatEntitySummary( + entity_name, + operation_result, + intentAnalysis.get('parameters', {}) + ) + elif isinstance(operation_result, dict): + response["count"] = 1 + # Format single entity + entity_name = intentAnalysis.get('entity', 'Eintrag') + response["message"] = _formatEntitySummary(entity_name, [operation_result], {}) + + return response + except Exception as e: logger.error(f"Error processing natural language command: {str(e)}", exc_info=True) raise @@ -187,6 +388,11 @@ Available entities and their fields: - parzelleBebaut: JaNein enum (is plot built) - parzelleErschlossen: JaNein enum (is plot developed) - parzelleHanglage: JaNein enum (is plot on slope) +- kontextInformationen: List[Kontext] (metadata - each item has 'thema' and 'inhalt' fields only) + +**Kontext** (Context information for metadata): +- thema: string (topic/subject, e.g. "EGRID", "Fläche", "Zentrum") +- inhalt: string (content as text, e.g. "CH887199917793", "6514.99 m²", "X: 123, Y: 456") **Important relationships:** - Projekte contain Parzellen (projects have plots) @@ -215,12 +421,35 @@ Examples: - Input: "Erstelle ein neues Projekt namens 'Hauptstrasse 42'" Output: {{"intent": "CREATE", "entity": "Projekt", "parameters": {{"label": "Hauptstrasse 42"}}, "confidence": 0.95}} +- Input: "Erstelle eine Parzelle mit Label 123, PLZ 8000, Gemeinde Zürich, Bauzone W3" + Output: {{"intent": "CREATE", "entity": "Parzelle", "parameters": {{"label": "123", "plz": "8000", "kontextGemeinde": "Zürich", "bauzone": "W3"}}, "confidence": 0.95}} + +- Input: "Parzellen-Informationen: ID:AA1704, Nummer:AA1704, EGRID:CH887199917793, Kanton:ZH, Gemeinde:Zürich, Gemeinde-Code:261, Fläche:6514.99 m², Zentrum:2682951.44,1247622.91" + Output: {{ + "intent": "CREATE", + "entity": "Parzelle", + "parameters": {{ + "label": "AA1704", + "parzellenAliasTags": ["AA1704"], + "kontextGemeinde": "Zürich", + "kontextInformationen": [ + {{"thema": "EGRID", "inhalt": "CH887199917793"}}, + {{"thema": "Kanton", "inhalt": "ZH"}}, + {{"thema": "BFS-Nummer", "inhalt": "261"}}, + {{"thema": "Fläche", "inhalt": "6514.99 m²"}}, + {{"thema": "Zentrum (LV95)", "inhalt": "X: 2682951.44 m, Y: 1247622.91 m (EPSG:2056)"}} + ] + }}, + "confidence": 0.9 + }} + Note: Extract structured data from detailed input. Use kontextInformationen for metadata. Each item has 'thema' (topic) and 'inhalt' (content as text). + - 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 Projekte in Zürich" or "Wie viele Projekte in Zürich" + Output: {{"intent": "READ", "entity": "Projekt", "parameters": {{"location_filter": "Zürich"}}, "confidence": 0.9}} + Note: For project location queries, use Projekt entity with location_filter parameter - Input: "Zeige mir Parzellen mit PLZ 8000" Output: {{"intent": "READ", "entity": "Parzelle", "parameters": {{"plz": "8000"}}, "confidence": 0.95}} @@ -233,6 +462,16 @@ Examples: - Input: "Lösche Parzelle ABC" Output: {{"intent": "DELETE", "entity": "Parzelle", "parameters": {{"id": "ABC"}}, "confidence": 0.9}} + +IMPORTANT EXTRACTION RULES: +1. For CREATE operations, extract ALL mentioned data fields from the user input +2. Use kontextInformationen array for metadata that doesn't have dedicated fields (EGRID, BFS numbers, area, coordinates, etc.) +3. Each kontextInformationen item MUST have exactly two fields: 'thema' (topic/subject) and 'inhalt' (content as text string) +4. Format kontextInformationen values as readable text strings, including units (e.g., "6514.99 m²", "X: 123, Y: 456") +5. Match field names EXACTLY to the entity definition above +6. Convert data types correctly (strings for text, numbers for numeric values) +7. Extract coordinates, areas, and other numeric values from text +8. When multiple values are mentioned for the same concept (ID, Nummer, Name), use the most relevant one for 'label' and put alternatives in parzellenAliasTags """ try: @@ -335,15 +574,56 @@ async def executeIntentBasedOperation( elif entity == "Parzelle": # Create Parzelle from parameters - parzelle = Parzelle( - mandateId=currentUser.mandateId, - label=parameters.get("label", ""), - strasseNr=parameters.get("strasseNr"), - plz=parameters.get("plz"), - bauzone=parameters.get("bauzone"), - kontextGemeinde=parameters.get("kontextGemeinde"), - ) + # Import Kontext for kontextInformationen + from modules.datamodels.datamodelRealEstate import Kontext, GeoPolylinie + + # Build parzelle data with all extracted parameters + parzelle_data = { + "mandateId": currentUser.mandateId, + "label": parameters.get("label", ""), + } + + # Add optional fields if present + optional_fields = [ + "parzellenAliasTags", "eigentuemerschaft", "strasseNr", "plz", + "bauzone", "az", "bz", "vollgeschossZahl", "anrechenbarDachgeschoss", + "anrechenbarUntergeschoss", "gebaeudehoeheMax", "kontextGemeinde", + "regelnGrenzabstand", "regelnMehrlaengenzuschlag", "regelnMehrhoehenzuschlag", + "parzelleBebaut", "parzelleErschlossen", "parzelleHanglage", + "laermschutzzone", "hochwasserschutzzone", "grundwasserschutzzone" + ] + + for field in optional_fields: + if field in parameters and parameters[field] is not None: + parzelle_data[field] = parameters[field] + + # Handle complex objects + if "perimeter" in parameters and parameters["perimeter"]: + parzelle_data["perimeter"] = GeoPolylinie(**parameters["perimeter"]) + + if "baulinie" in parameters and parameters["baulinie"]: + parzelle_data["baulinie"] = GeoPolylinie(**parameters["baulinie"]) + + # Handle kontextInformationen (convert dicts to Kontext objects) + if "kontextInformationen" in parameters and parameters["kontextInformationen"]: + kontext_list = [] + for kontext_data in parameters["kontextInformationen"]: + if isinstance(kontext_data, dict): + # Ensure only thema and inhalt are passed (Kontext model only has these fields) + kontext_obj = Kontext( + thema=kontext_data.get("thema", ""), + inhalt=kontext_data.get("inhalt", "") + ) + kontext_list.append(kontext_obj) + else: + kontext_list.append(kontext_data) + parzelle_data["kontextInformationen"] = kontext_list + + parzelle = Parzelle(**parzelle_data) created = realEstateInterface.createParzelle(parzelle) + + logger.info(f"Created Parzelle '{created.label}' with {len(created.kontextInformationen)} context items") + return { "operation": "CREATE", "entity": "Parzelle", @@ -438,17 +718,67 @@ async def executeIntentBasedOperation( k: v for k, v in parameters.items() if k != "id" and k in validProjektFields } - # Warn about invalid fields - invalidFields = {k: v for k, v in parameters.items() if k not in validProjektFields and k != "id"} - if invalidFields: - logger.warning(f"Invalid filter fields for Projekt ignored: {list(invalidFields.keys())}") - logger.info("Note: Location queries should use Parzelle entity, not Projekt") + # Handle location_filter specially (filter projects by parcel location) + location_filter = parameters.get("location_filter") + + # Get all projects first projekte = realEstateInterface.getProjekte(recordFilter=recordFilter if recordFilter else None) + + # If location filter is present, filter by parcels in that location + if location_filter: + logger.info(f"Filtering projects by location: {location_filter}") + + # Try to resolve location to Gemeinde ID for UUID comparison + location_id = None + try: + # Check if it's already a UUID + import re + uuid_pattern = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', re.IGNORECASE) + if not uuid_pattern.match(location_filter): + # Try to resolve name to ID + gemeinde_records = realEstateInterface.getGemeinden(recordFilter={"label": location_filter}) + if gemeinde_records: + location_id = gemeinde_records[0].id + logger.debug(f"Resolved location '{location_filter}' to ID '{location_id}'") + except Exception as e: + logger.debug(f"Could not resolve location filter: {e}") + + filtered_projekte = [] + + for projekt in projekte: + # Check if any parcel in the project matches the location + for parzelle in projekt.parzellen: + # Check kontextGemeinde (both UUID and string), plz, or strasseNr for location match + location_lower = location_filter.lower() + matches = False + + # Check if kontextGemeinde matches (as UUID or string) + if parzelle.kontextGemeinde: + if (parzelle.kontextGemeinde == location_id or # UUID match + parzelle.kontextGemeinde == location_filter or # Exact match + location_lower in parzelle.kontextGemeinde.lower()): # Partial string match + matches = True + + # Check PLZ or address + if not matches and ( + (parzelle.plz and location_lower in parzelle.plz) or + (parzelle.strasseNr and location_lower in parzelle.strasseNr.lower()) + ): + matches = True + + if matches: + filtered_projekte.append(projekt) + break # Found a matching parcel, no need to check more + + projekte = filtered_projekte + logger.info(f"Found {len(projekte)} projects in location '{location_filter}'") + return { "operation": "READ", "entity": "Projekt", - "result": [p.model_dump() for p in projekte] + "result": [p.model_dump() for p in projekte], + "count": len(projekte) } elif entity == "Parzelle": parzelleId = parameters.get("id") @@ -484,10 +814,26 @@ async def executeIntentBasedOperation( logger.warning(f"Invalid filter fields for Parzelle ignored: {list(invalidFields.keys())}") parzellen = realEstateInterface.getParzellen(recordFilter=recordFilter if recordFilter else None) + + # Debug logging for empty results + if not parzellen and recordFilter: + logger.info(f"No Parzellen found matching filter: {recordFilter}") + # Get total count to help debug + all_parzellen = realEstateInterface.getParzellen(recordFilter=None) + logger.info(f"Total Parzellen in database: {len(all_parzellen)}") + if all_parzellen: + # Show some sample kontextGemeinde values + sample_gemeinden = set() + for p in all_parzellen[:10]: + if p.kontextGemeinde: + sample_gemeinden.add(p.kontextGemeinde) + logger.info(f"Sample kontextGemeinde values in database: {sample_gemeinden}") + return { "operation": "READ", "entity": "Parzelle", - "result": [p.model_dump() for p in parzellen] + "result": [p.model_dump() for p in parzellen], + "count": len(parzellen) } elif entity == "Gemeinde": from modules.datamodels.datamodelRealEstate import Gemeinde @@ -507,7 +853,8 @@ async def executeIntentBasedOperation( return { "operation": "READ", "entity": "Gemeinde", - "result": [g.model_dump() for g in gemeinden] + "result": [g.model_dump() for g in gemeinden], + "count": len(gemeinden) } elif entity == "Kanton": from modules.datamodels.datamodelRealEstate import Kanton @@ -527,7 +874,8 @@ async def executeIntentBasedOperation( return { "operation": "READ", "entity": "Kanton", - "result": [k.model_dump() for k in kantone] + "result": [k.model_dump() for k in kantone], + "count": len(kantone) } elif entity == "Land": from modules.datamodels.datamodelRealEstate import Land @@ -547,7 +895,8 @@ async def executeIntentBasedOperation( return { "operation": "READ", "entity": "Land", - "result": [l.model_dump() for l in laender] + "result": [l.model_dump() for l in laender], + "count": len(laender) } elif entity == "Dokument": from modules.datamodels.datamodelRealEstate import Dokument @@ -567,7 +916,8 @@ async def executeIntentBasedOperation( return { "operation": "READ", "entity": "Dokument", - "result": [d.model_dump() for d in dokumente] + "result": [d.model_dump() for d in dokumente], + "count": len(dokumente) } else: raise ValueError(f"READ operation not supported for entity: {entity}") diff --git a/modules/interfaces/interfaceDbRealEstateObjects.py b/modules/interfaces/interfaceDbRealEstateObjects.py index 2c6d1554..5589e518 100644 --- a/modules/interfaces/interfaceDbRealEstateObjects.py +++ b/modules/interfaces/interfaceDbRealEstateObjects.py @@ -220,12 +220,30 @@ class RealEstateObjects: def getParzellen(self, recordFilter: Optional[Dict[str, Any]] = None) -> List[Parzelle]: """Get all plots matching the filter.""" + original_gemeinde_value = None + # Resolve location names to IDs if needed if recordFilter: + # Save original value before resolution for fallback search + if "kontextGemeinde" in recordFilter: + original_gemeinde_value = recordFilter["kontextGemeinde"] + recordFilter = self._resolveLocationFilters(recordFilter) records = self.db.getRecordset(Parzelle, recordFilter=recordFilter or {}) + # Fallback: If no records found and we resolved a Gemeinde name, + # try searching with the original name for backwards compatibility + # (handles case where data has string names instead of UUIDs) + if not records and original_gemeinde_value and recordFilter and "kontextGemeinde" in recordFilter: + if recordFilter["kontextGemeinde"] != original_gemeinde_value: + logger.info(f"No results with resolved UUID, trying with original name '{original_gemeinde_value}'") + fallback_filter = recordFilter.copy() + fallback_filter["kontextGemeinde"] = original_gemeinde_value + records = self.db.getRecordset(Parzelle, recordFilter=fallback_filter) + if records: + logger.info(f"Found {len(records)} records using original name (legacy data format)") + # Apply access control filtered = self.access.uam(Parzelle, records)