From 30db7a310cab0b3649375e2520c4cbe22e0efdad Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Tue, 9 Jun 2026 23:52:50 +0200 Subject: [PATCH] fix: resolve all deprecation warnings and remove dead test scripts Co-authored-by: Cursor --- .../realEstate/datamodelFeatureRealEstate.py | 438 +++--------------- .../features/redmine/routeFeatureRedmine.py | 4 +- tests/functional/test_kpi_full.py | 97 ---- tests/functional/test_kpi_incomplete.py | 134 ------ tests/functional/test_kpi_path.py | 68 --- .../test_featureCatalogLabels_i18n.py | 6 +- 6 files changed, 76 insertions(+), 671 deletions(-) delete mode 100644 tests/functional/test_kpi_full.py delete mode 100644 tests/functional/test_kpi_incomplete.py delete mode 100644 tests/functional/test_kpi_path.py diff --git a/modules/features/realEstate/datamodelFeatureRealEstate.py b/modules/features/realEstate/datamodelFeatureRealEstate.py index 346e998b..cf2c6c67 100644 --- a/modules/features/realEstate/datamodelFeatureRealEstate.py +++ b/modules/features/realEstate/datamodelFeatureRealEstate.py @@ -57,37 +57,17 @@ class GeoTag(str, Enum): 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, - ) + description="Coordinate system (e.g. 'LV95', 'EPSG:2056')", json_schema_extra={"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, - ) + description="East value (E) [m], typically 2'480'000 - 2'840'000", json_schema_extra={"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, - ) + description="North value (N) [m], typically 1'070'000 - 1'300'000", json_schema_extra={"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, - ) + description="Height above sea level [m]", json_schema_extra={"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, - ) + description="Point categorization", json_schema_extra={"frontend_type": "select", "frontend_readonly": False, "frontend_required": False}) class GeoPolylinie(BaseModel): @@ -97,18 +77,10 @@ class GeoPolylinie(BaseModel): description="Primary key", ) closed: bool = Field( - description="Is the GeoPolylinie closed (polygon)?", - frontend_type="boolean", - frontend_readonly=False, - frontend_required=True, - ) + description="Is the GeoPolylinie closed (polygon)?", json_schema_extra={"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, - ) + description="List of GeoPunkte forming the GeoPolylinie", json_schema_extra={"frontend_type": "json", "frontend_readonly": False, "frontend_required": True}) @i18nModel("Dokument") @@ -117,73 +89,33 @@ class Dokument(PowerOnModel): id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", - frontend_type="text", - frontend_readonly=True, - frontend_required=False, - label="ID", - ) + json_schema_extra={"label": "ID", "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="Mandats-ID", - ) + json_schema_extra={"label": "Mandats-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False}) featureInstanceId: str = Field( description="ID of the feature instance this document belongs to", - frontend_type="text", - frontend_readonly=True, - frontend_required=False, - label="Feature-Instanz-ID", - ) + json_schema_extra={"label": "Feature-Instanz-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False}) label: str = Field( description="Document label", - frontend_type="text", - frontend_readonly=False, - frontend_required=True, - label="Bezeichnung", - ) + json_schema_extra={"label": "Bezeichnung", "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, - ) + description="Version number or designation (e.g. 'v1.0', 'Rev. A')", json_schema_extra={"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, - ) + description="Document type", json_schema_extra={"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, - ) + description="File path or URL", json_schema_extra={"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, - ) + description="Source of the document", json_schema_extra={"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, - ) + description="MIME type of the document (e.g. 'application/pdf', 'image/png')", json_schema_extra={"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, - ) + description="Document categorization tags", json_schema_extra={"frontend_type": "json", "frontend_readonly": False, "frontend_required": False}) class Kontext(PowerOnModel): @@ -193,78 +125,38 @@ class Kontext(PowerOnModel): description="Primary key", ) thema: str = Field( - description="Theme designation", - frontend_type="text", - frontend_readonly=False, - frontend_required=True, - ) + description="Theme designation", json_schema_extra={"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, - ) + description="Detailed information (text)", json_schema_extra={"frontend_type": "textarea", "frontend_readonly": False, "frontend_required": True}) class Land(PowerOnModel): """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, - ) + description="Primary key", json_schema_extra={"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, - ) + description="ID of the mandate", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False}) featureInstanceId: str = Field( - description="ID of the feature instance", - frontend_type="text", - frontend_readonly=True, - frontend_required=False, - ) + description="ID of the feature instance", json_schema_extra={"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, - ) + description="Country name (e.g. 'Schweiz')", json_schema_extra={"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, - ) + description="Abbreviation (e.g. 'CH')", json_schema_extra={"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, - ) + description="National laws/documents", json_schema_extra={"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, - ) + description="National context information", json_schema_extra={"frontend_type": "json", "frontend_readonly": False, "frontend_required": False}) class Kanton(PowerOnModel): """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, - ) + description="Primary key", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False}) mandateId: str = Field( description="ID of the mandate", json_schema_extra={ @@ -282,11 +174,7 @@ class Kanton(PowerOnModel): }, ) label: str = Field( - description="Canton name (e.g. 'Zürich')", - frontend_type="text", - frontend_readonly=False, - frontend_required=True, - ) + description="Canton name (e.g. 'Zürich')", json_schema_extra={"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", @@ -299,54 +187,26 @@ class Kanton(PowerOnModel): ) abk: Optional[str] = Field( None, - description="Abbreviation (e.g. 'ZH')", - frontend_type="text", - frontend_readonly=False, - frontend_required=False, - ) + description="Abbreviation (e.g. 'ZH')", json_schema_extra={"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, - ) + description="Cantonal documents", json_schema_extra={"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, - ) + description="Canton-specific context information", json_schema_extra={"frontend_type": "json", "frontend_readonly": False, "frontend_required": False}) class Gemeinde(PowerOnModel): """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, - ) + description="Primary key", json_schema_extra={"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, - ) + description="ID of the mandate", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False}) featureInstanceId: str = Field( - description="ID of the feature instance", - frontend_type="text", - frontend_readonly=True, - frontend_required=False, - ) + description="ID of the feature instance", json_schema_extra={"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, - ) + description="Municipality name (e.g. 'Zürich')", json_schema_extra={"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", @@ -359,25 +219,13 @@ class Gemeinde(PowerOnModel): ) 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, - ) + 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.", json_schema_extra={"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, - ) + description="Municipal documents", json_schema_extra={"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, - ) + description="Municipality-specific context information", json_schema_extra={"frontend_type": "json", "frontend_readonly": False, "frontend_required": False}) # ===== Main Models (use ForwardRef for circular references) ===== @@ -392,11 +240,7 @@ class Parzelle(PowerOnModel): id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", - frontend_type="text", - frontend_readonly=True, - frontend_required=False, - label="ID", - ) + json_schema_extra={"label": "ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False}) mandateId: str = Field( description="ID of the mandate", json_schema_extra={ @@ -421,55 +265,27 @@ class Parzelle(PowerOnModel): # Grunddaten label: str = Field( description="Plot designation", - frontend_type="text", - frontend_readonly=False, - frontend_required=True, - label="Bezeichnung", - ) + json_schema_extra={"label": "Bezeichnung", "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, - ) + description="Additional plot names or field names", json_schema_extra={"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, - ) + description="Owner of the plot", json_schema_extra={"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, - ) + description="Street and house number", json_schema_extra={"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, - ) + description="Postal code of the plot", json_schema_extra={"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, - ) + description="Plot boundary as closed GeoPolylinie", json_schema_extra={"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, - ) + description="Building line of the plot", json_schema_extra={"frontend_type": "json", "frontend_readonly": False, "frontend_required": False}) kontextGemeinde: Optional[str] = Field( None, @@ -485,145 +301,69 @@ class Parzelle(PowerOnModel): # Bebauungsparameter bauzone: Optional[str] = Field( None, - description="Building zone designation (e.g. W3, WG2, etc.)", - frontend_type="text", - frontend_readonly=False, - frontend_required=False, - ) + description="Building zone designation (e.g. W3, WG2, etc.)", json_schema_extra={"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, - ) + description="Ausnützungsziffer", json_schema_extra={"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, - ) + description="Bebauungsziffer", json_schema_extra={"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, - ) + description="Number of allowed full floors", json_schema_extra={"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, - ) + description="Accountable portion of attic (0.0 - 1.0)", json_schema_extra={"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, - ) + description="Accountable portion of basement (0.0 - 1.0)", json_schema_extra={"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, - ) + description="Maximum building height in meters", json_schema_extra={"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, - ) + description="Regulations for boundary distance", json_schema_extra={"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, - ) + description="Regulations for additional length surcharge", json_schema_extra={"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, - ) + description="Regulations for additional height surcharge", json_schema_extra={"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, - ) + description="Is the plot built?", json_schema_extra={"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, - ) + description="Is the plot developed?", json_schema_extra={"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, - ) + description="Is the plot on a slope?", json_schema_extra={"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, - ) + description="Noise protection zone (e.g. 'II')", json_schema_extra={"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, - ) + description="Flood protection zone (e.g. 'tief')", json_schema_extra={"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, - ) + description="Groundwater protection zone", json_schema_extra={"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, - ) + description="Neighboring plots (stored as list of Parzelle IDs or full objects)", json_schema_extra={"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, - ) + description="Plot-specific documents", json_schema_extra={"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, - ) + description="Plot-specific context information", json_schema_extra={"frontend_type": "json", "frontend_readonly": False, "frontend_required": False}) @i18nModel("Projekt") @@ -632,11 +372,7 @@ class Projekt(PowerOnModel): id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", - frontend_type="text", - frontend_readonly=True, - frontend_required=False, - label="ID", - ) + json_schema_extra={"label": "ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False}) mandateId: str = Field( description="ID of the mandate", json_schema_extra={ @@ -659,54 +395,26 @@ class Projekt(PowerOnModel): ) label: str = Field( description="Project designation", - frontend_type="text", - frontend_readonly=False, - frontend_required=True, - label="Bezeichnung", - ) + json_schema_extra={"label": "Bezeichnung", "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, - label="Prozessstatus", - ) + json_schema_extra={"label": "Prozessstatus", "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, - ) + description="Envelope of all plots in the project", json_schema_extra={"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, - ) + description="Building line of the project", json_schema_extra={"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, - ) + description="All plots of the project", json_schema_extra={"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, - ) + description="Project-specific documents", json_schema_extra={"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, - ) + description="Project-specific context information", json_schema_extra={"frontend_type": "json", "frontend_readonly": False, "frontend_required": False}) # Resolve forward references diff --git a/modules/features/redmine/routeFeatureRedmine.py b/modules/features/redmine/routeFeatureRedmine.py index 86ac8d30..d3f5b771 100644 --- a/modules/features/redmine/routeFeatureRedmine.py +++ b/modules/features/redmine/routeFeatureRedmine.py @@ -457,10 +457,10 @@ async def getStats( instanceId: str, dateFrom: Optional[str] = Query(default=None, description="ISO date YYYY-MM-DD"), dateTo: Optional[str] = Query(default=None, description="ISO date YYYY-MM-DD"), - bucket: str = Query(default="week", regex="^(day|week|month)$"), + bucket: str = Query(default="week", pattern="^(day|week|month)$"), trackerIds: Optional[List[int]] = Query(default=None), categoryIds: Optional[List[int]] = Query(default=None, description="Filter by Redmine issue categories"), - statusFilter: str = Query(default="*", regex="^(\\*|open|closed)$", description="Restrict to open/closed/all tickets"), + statusFilter: str = Query(default="*", pattern="^(\\*|open|closed)$", description="Restrict to open/closed/all tickets"), context: RequestContext = Depends(getRequestContext), ) -> RedmineStatsDto: mandateId = _validateInstanceAccess(instanceId, context) diff --git a/tests/functional/test_kpi_full.py b/tests/functional/test_kpi_full.py deleted file mode 100644 index aa8d8540..00000000 --- a/tests/functional/test_kpi_full.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (c) 2026 PowerOn AG -# All rights reserved. -"""Test full KPI extraction and validation flow""" -import json -import sys -import os -import pytest - -# Add gateway directory to path -_gateway_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) -if _gateway_path not in sys.path: - sys.path.insert(0, _gateway_path) - -from modules.serviceCenter.services.serviceAi.subJsonResponseHandling import JsonResponseHandler -from modules.datamodels.datamodelAi import JsonAccumulationState - -# Load actual JSON response -json_file = os.path.join( - os.path.dirname(__file__), - "..", "..", "..", "local", "debug", "prompts", - "20251130-211706-078-document_generation_response.txt" -) - -if not os.path.exists(json_file): - pytest.skip(f"Test data file not found: {json_file}", allow_module_level=True) - -with open(json_file, 'r', encoding='utf-8') as f: - content = f.read() - -# Extract JSON -from modules.shared.jsonUtils import extractJsonString -extracted = extractJsonString(content) -parsedJson = json.loads(extracted) - -# KPI definition from the response -kpiDefinitions = [{ - "id": "prime_numbers_count", - "description": "Number of prime numbers generated and organized in the table", - "jsonPath": "documents[0].sections[0].elements[0].rows", - "targetValue": 4000 -}] - -print("="*60) -print("KPI EXTRACTION AND VALIDATION TEST") -print("="*60) - -# Step 1: Initialize accumulation state with KPIs -accumulationState = JsonAccumulationState( - accumulatedJsonString="", - isAccumulationMode=True, - lastParsedResult=None, - allSections=[], - kpis=[{**kpi, "currentValue": 0} for kpi in kpiDefinitions] -) - -print(f"\nStep 1: Initialized KPIs") -for kpi in accumulationState.kpis: - print(f" KPI {kpi['id']}: currentValue={kpi.get('currentValue', 'N/A')}, targetValue={kpi.get('targetValue', 'N/A')}") - -# Step 2: Extract KPI values from parsed JSON -print(f"\nStep 2: Extracting KPI values from JSON...") -updatedKpis = JsonResponseHandler.extractKpiValuesFromJson( - parsedJson, - accumulationState.kpis -) - -print(f" Extracted {len(updatedKpis)} KPIs") -for kpi in updatedKpis: - print(f" KPI {kpi['id']}: currentValue={kpi.get('currentValue', 'N/A')}, targetValue={kpi.get('targetValue', 'N/A')}") - -# Step 3: Validate progression -print(f"\nStep 3: Validating KPI progression...") -shouldProceed, reason = JsonResponseHandler.validateKpiProgression( - accumulationState, - updatedKpis -) - -print(f" Result: shouldProceed={shouldProceed}, reason={reason}") - -# Step 4: Check what's in accumulationState.kpis vs updatedKpis -print(f"\nStep 4: Comparing state...") -print(f" accumulationState.kpis[0].currentValue = {accumulationState.kpis[0].get('currentValue', 'N/A')}") -print(f" updatedKpis[0].currentValue = {updatedKpis[0].get('currentValue', 'N/A')}") - -# Step 5: Check if we need to update accumulationState.kpis -print(f"\nStep 5: Updating accumulationState.kpis...") -accumulationState.kpis = updatedKpis -print(f" Updated accumulationState.kpis[0].currentValue = {accumulationState.kpis[0].get('currentValue', 'N/A')}") - -# Step 6: Validate again (should show progress) -print(f"\nStep 6: Validating again after update...") -shouldProceed2, reason2 = JsonResponseHandler.validateKpiProgression( - accumulationState, - updatedKpis -) -print(f" Result: shouldProceed={shouldProceed2}, reason={reason2}") - diff --git a/tests/functional/test_kpi_incomplete.py b/tests/functional/test_kpi_incomplete.py deleted file mode 100644 index b9125d9f..00000000 --- a/tests/functional/test_kpi_incomplete.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright (c) 2026 PowerOn AG -# All rights reserved. -"""Test KPI extraction with incomplete JSON""" -import json -import sys -import os -import pytest - -# Add gateway directory to path -_gateway_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) -if _gateway_path not in sys.path: - sys.path.insert(0, _gateway_path) - -from modules.serviceCenter.services.serviceAi.subJsonResponseHandling import JsonResponseHandler -from modules.datamodels.datamodelAi import JsonAccumulationState -from modules.shared.jsonUtils import extractJsonString, repairBrokenJson - -# Load actual incomplete JSON response -json_file = os.path.join( - os.path.dirname(__file__), - "..", "..", "..", "local", "debug", "prompts", - "20251130-211706-078-document_generation_response.txt" -) - -if not os.path.exists(json_file): - pytest.skip(f"Test data file not found: {json_file}", allow_module_level=True) - -with open(json_file, 'r', encoding='utf-8') as f: - content = f.read() - -print("="*60) -print("KPI EXTRACTION WITH INCOMPLETE JSON TEST") -print("="*60) - -# Step 1: Try to extract and parse JSON -print(f"\nStep 1: Extracting JSON string...") -extracted = extractJsonString(content) -print(f" Extracted length: {len(extracted)} chars") - -# Step 2: Try to parse -print(f"\nStep 2: Attempting to parse...") -parsedJson = None -try: - parsedJson = json.loads(extracted) - print(f" ✅ JSON parsed successfully") -except json.JSONDecodeError as e: - print(f" ❌ JSON parsing failed: {e}") - print(f" Attempting repair...") - try: - parsedJson = repairBrokenJson(extracted) - if parsedJson: - print(f" ✅ JSON repaired successfully") - else: - print(f" ❌ JSON repair failed") - except Exception as e2: - print(f" ❌ Repair error: {e2}") - -if not parsedJson: - pytest.skip("Cannot proceed - JSON cannot be parsed or repaired", allow_module_level=True) - -# Step 3: Check if path exists -print(f"\nStep 3: Checking if KPI path exists...") -path = "documents[0].sections[0].elements[0].rows" -try: - value = JsonResponseHandler._extractValueByPath(parsedJson, path) - print(f" ✅ Path exists: {type(value)}") - if isinstance(value, list): - print(f" ✅ Value is list with {len(value)} items") - if len(value) > 0: - print(f" ✅ First item: {value[0]}") - else: - print(f" ⚠️ Value is not a list: {value}") -except Exception as e: - print(f" ❌ Path extraction failed: {e}") - import traceback - traceback.print_exc() - pytest.skip(f"Path extraction failed: {e}", allow_module_level=True) - -# Step 4: Test KPI extraction -print(f"\nStep 4: Testing KPI extraction...") -kpiDefinitions = [{ - "id": "prime_numbers_count", - "description": "Number of prime numbers generated and organized in the table", - "jsonPath": "documents[0].sections[0].elements[0].rows", - "targetValue": 4000 -}] - -accumulationState = JsonAccumulationState( - accumulatedJsonString="", - isAccumulationMode=True, - lastParsedResult=parsedJson, - allSections=[], - kpis=[{**kpi, "currentValue": 0} for kpi in kpiDefinitions] -) - -print(f" Initial KPI currentValue: {accumulationState.kpis[0].get('currentValue', 'N/A')}") - -updatedKpis = JsonResponseHandler.extractKpiValuesFromJson( - parsedJson, - accumulationState.kpis -) - -print(f" Updated KPI currentValue: {updatedKpis[0].get('currentValue', 'N/A')}") - -# Step 5: Test validation -print(f"\nStep 5: Testing KPI validation...") -shouldProceed, reason = JsonResponseHandler.validateKpiProgression( - accumulationState, - updatedKpis -) - -print(f" Result: shouldProceed={shouldProceed}, reason={reason}") - -if not shouldProceed: - print(f"\n❌ VALIDATION FAILED - This is the problem!") - print(f" Let's debug why...") - - # Check what's being compared - lastValues = {kpi.get("id"): kpi.get("currentValue", 0) for kpi in accumulationState.kpis} - print(f" Last values from accumulationState: {lastValues}") - - for updatedKpi in updatedKpis: - kpiId = updatedKpi.get("id") - currentValue = updatedKpi.get("currentValue", 0) - print(f" Updated KPI {kpiId}: currentValue={currentValue}") - - if kpiId in lastValues: - lastValue = lastValues[kpiId] - print(f" Comparing: {lastValue} vs {currentValue}") - if currentValue > lastValue: - print(f" ✅ Should detect progress!") - else: - print(f" ❌ No progress detected (currentValue <= lastValue)") - diff --git a/tests/functional/test_kpi_path.py b/tests/functional/test_kpi_path.py deleted file mode 100644 index c0a32862..00000000 --- a/tests/functional/test_kpi_path.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (c) 2026 PowerOn AG -# All rights reserved. -"""Test KPI path extraction""" -import json -import sys -import os - -# Add gateway directory to path -_gateway_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) -if _gateway_path not in sys.path: - sys.path.insert(0, _gateway_path) - -from modules.serviceCenter.services.serviceAi.subJsonResponseHandling import JsonResponseHandler - -# Test JSON matching the actual response -test_json = { - "metadata": { - "split_strategy": "single_document", - "source_documents": [], - "extraction_method": "ai_generation" - }, - "documents": [ - { - "id": "doc_1", - "title": "Prime Numbers Table", - "filename": "prime_numbers.json", - "sections": [ - { - "id": "section_prime_numbers_table", - "content_type": "table", - "elements": [ - { - "headers": ["Column 1", "Column 2"], - "rows": [ - [2, 3, 5, 7, 11], - [13, 17, 19, 23, 29] - ] - } - ] - } - ] - } - ] -} - -# Test path from KPI definition -path = "documents[0].sections[0].elements[0].rows" - -print(f"Testing path: {path}") -print(f"JSON structure: documents[0].sections[0].elements[0].rows") -print() - -try: - value = JsonResponseHandler._extractValueByPath(test_json, path) - print(f"✅ Extracted value: {type(value)}") - print(f" Value: {value}") - - if isinstance(value, list): - count = len(value) - print(f" Count: {count}") - else: - print(f" Not a list!") - -except Exception as e: - print(f"❌ Error: {e}") - import traceback - traceback.print_exc() - diff --git a/tests/validation/test_featureCatalogLabels_i18n.py b/tests/validation/test_featureCatalogLabels_i18n.py index ffdf1c2b..f5b2b490 100644 --- a/tests/validation/test_featureCatalogLabels_i18n.py +++ b/tests/validation/test_featureCatalogLabels_i18n.py @@ -30,11 +30,7 @@ _FEATURES_DIR = Path(__file__).resolve().parents[2] / "modules" / "features" _BARE_LABEL_PATTERN = re.compile(r'^\s*"label"\s*:\s*"[^"]+"', re.MULTILINE) -# mainRealEstate.py contains "label": "AA1704" inside a multi-line f-string -# that is used as a JSON example in an AI prompt -- not a real catalog entry. -_ALLOWED_FILES_WITH_BARE_LABELS: set[str] = { - "mainRealEstate.py", -} +_ALLOWED_FILES_WITH_BARE_LABELS: set[str] = set() def _findFeatureMainFiles() -> list[Path]: