From c0c516f66213768faad78401dd829d7cbdc72e4e Mon Sep 17 00:00:00 2001
From: margrietpalm <margrietpalm@users.noreply.github.com>
Date: Tue, 7 Jan 2025 22:54:55 -0800
Subject: [PATCH] Fix grid-builder for schema 229 (#407)

* Clean up changes made to process new schema

* Fix get_surfaces depency on foreign key relationship between surface and surface parameters
---
 .github/workflows/test.yml                    |   2 +-
 CHANGES.rst                                   |   2 +-
 setup.py                                      |   2 +-
 .../grid/boundary_conditions.py               |   4 +-
 threedigrid_builder/grid/zero_d.py            |   4 +-
 threedigrid_builder/interface/db.py           | 282 ++++++------------
 .../tests/test_boundary_conditions.py         |   8 +-
 threedigrid_builder/tests/test_db.py          |   4 +-
 threedigrid_builder/tests/test_zero_d.py      |   2 +-
 9 files changed, 106 insertions(+), 204 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d84e1b5b..ba3fe807 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -15,7 +15,7 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          # 2022
+          # 2022 - 3.9
           - python: 3.9
             os: ubuntu-22.04
             setuptools: setuptools==63.*
diff --git a/CHANGES.rst b/CHANGES.rst
index 824df243..40adfdc4 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,7 +5,7 @@ Changelog of threedigrid-builder
 1.21.4 (unreleased)
 -------------------
 
-- Nothing changed yet.
+- Remove internal changes made for previous schema upgrades and limit changes to db interface
 
 
 1.21.3 (2024-12-17)
diff --git a/setup.py b/setup.py
index 723a2391..85714f57 100644
--- a/setup.py
+++ b/setup.py
@@ -53,7 +53,7 @@ def get_version():
 
 install_requires = [
     "numpy>=1.15,<3.0",
-    "threedi-schema==0.228.*",
+    "threedi-schema==0.229.*",
     "shapely>=2",
     "pyproj>=3",
     "condenser[geo]>=0.1.1",
diff --git a/threedigrid_builder/grid/boundary_conditions.py b/threedigrid_builder/grid/boundary_conditions.py
index 7113af51..f2766022 100644
--- a/threedigrid_builder/grid/boundary_conditions.py
+++ b/threedigrid_builder/grid/boundary_conditions.py
@@ -102,14 +102,14 @@ def apply(self, grid):
 class BoundaryCondition2D:
     id: int
     type: BoundaryType
-    geom: shapely.Geometry
+    the_geom: shapely.Geometry
 
 
 class BoundaryConditions2D(Array[BoundaryCondition2D]):
     def get_intersecting_node_idx(
         self, idx: int, cell_tree: shapely.STRtree
     ) -> np.ndarray:
-        bc_geom = self.geom[idx]
+        bc_geom = self.the_geom[idx]
 
         x1, y1, x2, y2 = shapely.bounds(bc_geom)
         is_horizontal = (x2 - x1) > (y2 - y1)
diff --git a/threedigrid_builder/grid/zero_d.py b/threedigrid_builder/grid/zero_d.py
index a4cbbdb8..d82dbbdd 100644
--- a/threedigrid_builder/grid/zero_d.py
+++ b/threedigrid_builder/grid/zero_d.py
@@ -208,7 +208,7 @@ class BaseSurface:
     code: str
     display_name: str
     area: float
-    geom: shapely.Geometry
+    the_geom: shapely.Geometry
 
     connection_node_id: int
     connection_node_the_geom: shapely.Geometry
@@ -267,7 +267,7 @@ def as_grid_surfaces(
         if extra_fields is None:
             extra_fields = {}
 
-        centroids = shapely.centroid(self.geom)
+        centroids = shapely.centroid(self.the_geom)
         no_centroid_mask = shapely.is_missing(centroids)
 
         if np.any(no_centroid_mask):
diff --git a/threedigrid_builder/interface/db.py b/threedigrid_builder/interface/db.py
index d68c3c3f..ffc02a53 100644
--- a/threedigrid_builder/interface/db.py
+++ b/threedigrid_builder/interface/db.py
@@ -4,7 +4,7 @@
 from contextlib import contextmanager
 from enum import Enum
 from functools import lru_cache
-from typing import Callable, ContextManager, Dict, List, Optional, Union
+from typing import Callable, ContextManager, Dict, List, Union
 
 import numpy as np
 import shapely
@@ -43,7 +43,7 @@
 # hardcoded source projection
 SOURCE_EPSG = 4326
 
-MIN_SQLITE_VERSION = 228
+MIN_SQLITE_VERSION = 229
 
 DAY_IN_SECONDS = 24.0 * 3600.0
 
@@ -94,20 +94,6 @@ def _set_initialization_type(
         dct[type_field] = None
 
 
-def arr_to_attr_dict(
-    arr: np.ndarray, rename_dict: Optional[Dict[str, str]] = None
-) -> Dict[str, str]:
-    """
-    Convert structured array to dict with optional rename of the keys
-    """
-    if rename_dict is None:
-        rename_dict = {}
-    return {
-        rename_dict[name] if name in rename_dict else name: arr[name]
-        for name in arr.dtype.names
-    }
-
-
 def map_cross_section_definition(
     objects: List[Union[CrossSectionLocations, Pipes, Weirs, Orifices, Culverts]],
     definition_map: Dict[str, Dict[int, int]],
@@ -356,7 +342,7 @@ def get_surfaces(self) -> Surfaces:
                     models.Surface.code,
                     models.Surface.display_name,
                     models.Surface.area,
-                    models.Surface.geom,
+                    models.Surface.geom.label("the_geom"),
                     models.SurfaceParameter.outflow_delay,
                     models.SurfaceParameter.surface_layer_thickness,
                     models.SurfaceParameter.infiltration,
@@ -369,7 +355,10 @@ def get_surfaces(self) -> Surfaces:
                     models.ConnectionNode.geom.label("connection_node_the_geom"),
                 )
                 .select_from(models.Surface)
-                .join(models.SurfaceParameter)
+                .join(
+                    models.SurfaceParameter,
+                    models.Surface.surface_parameters_id == models.SurfaceParameter.id,
+                )
                 .join(
                     models.SurfaceMap, models.SurfaceMap.surface_id == models.Surface.id
                 )
@@ -382,8 +371,7 @@ def get_surfaces(self) -> Surfaces:
             )
 
         # reproject
-        arr["geom"] = self.reproject(arr["geom"])
-
+        arr["the_geom"] = self.reproject(arr["the_geom"])
         return Surfaces(
             id=np.arange(0, len(arr["surface_id"] + 1), dtype=int),
             **{name: arr[name] for name in arr.dtype.names},
@@ -412,12 +400,12 @@ def get_boundary_conditions_2d(self) -> BoundaryConditions2D:
                 session.query(
                     models.BoundaryConditions2D.id,
                     models.BoundaryConditions2D.type,
-                    models.BoundaryConditions2D.geom,
+                    models.BoundaryConditions2D.geom.label("the_geom"),
                 )
                 .order_by(models.BoundaryConditions2D.id)
                 .as_structarray()
             )
-        arr["geom"] = self.reproject(arr["geom"])
+        arr["the_geom"] = self.reproject(arr["the_geom"])
 
         # transform to a BoundaryConditions1D object
         return BoundaryConditions2D(**{name: arr[name] for name in arr.dtype.names})
@@ -425,13 +413,13 @@ def get_boundary_conditions_2d(self) -> BoundaryConditions2D:
     def get_channels(self) -> Channels:
         """Return Channels"""
         cols = [
-            models.Channel.geom,
-            models.Channel.calculation_point_distance,
+            models.Channel.geom.label("the_geom"),
+            models.Channel.calculation_point_distance.label("dist_calc_points"),
             models.Channel.id,
             models.Channel.code,
-            models.Channel.connection_node_id_start,
-            models.Channel.connection_node_id_end,
-            models.Channel.exchange_type,
+            models.Channel.connection_node_id_start.label("connection_node_start_id"),
+            models.Channel.connection_node_id_end.label("connection_node_end_id"),
+            models.Channel.exchange_type.label("calculation_type"),
             models.Channel.display_name,
             models.Channel.exchange_thickness,
             models.Channel.hydraulic_conductivity_out,
@@ -441,39 +429,28 @@ def get_channels(self) -> Channels:
         with self.get_session() as session:
             arr = session.query(*cols).order_by(models.Channel.id).as_structarray()
 
-        arr["geom"] = self.reproject(arr["geom"])
+        arr["the_geom"] = self.reproject(arr["the_geom"])
         # map "old" calculation types (100, 101, 102, 105) to (0, 1, 2, 5)
-        arr["exchange_type"][arr["exchange_type"] >= 100] -= 100
+        arr["calculation_type"][arr["calculation_type"] >= 100] -= 100
         arr["hydraulic_conductivity_out"] /= DAY_IN_SECONDS
         arr["hydraulic_conductivity_in"] /= DAY_IN_SECONDS
 
-        attr_dict = arr_to_attr_dict(
-            arr,
-            {
-                "geom": "the_geom",
-                "exchange_type": "calculation_type",
-                "calculation_point_distance": "dist_calc_points",
-                "connection_node_id_start": "connection_node_start_id",
-                "connection_node_id_end": "connection_node_end_id",
-            },
-        )
-
         # transform to a Channels object
-        return Channels(**attr_dict)
+        return Channels(**{name: arr[name] for name in arr.dtype.names})
 
     def get_connection_nodes(self) -> ConnectionNodes:
         """Return ConnectionNodes"""
         cols = [
-            models.ConnectionNode.geom,
+            models.ConnectionNode.geom.label("the_geom"),
             models.ConnectionNode.id,
             models.ConnectionNode.code,
             models.ConnectionNode.storage_area,
-            models.ConnectionNode.initial_water_level,
-            models.ConnectionNode.exchange_type,
+            models.ConnectionNode.initial_water_level.label("initial_waterlevel"),
+            models.ConnectionNode.exchange_type.label("calculation_type"),
             models.ConnectionNode.bottom_level,
-            models.ConnectionNode.exchange_level,
-            models.ConnectionNode.visualisation,
-            models.ConnectionNode.manhole_surface_level,
+            models.ConnectionNode.exchange_level.label("drain_level"),
+            models.ConnectionNode.visualisation.label("manhole_indicator"),
+            models.ConnectionNode.manhole_surface_level.label("surface_level"),
             models.ConnectionNode.display_name,
             models.ConnectionNode.exchange_thickness,
             models.ConnectionNode.hydraulic_conductivity_out,
@@ -485,26 +462,14 @@ def get_connection_nodes(self) -> ConnectionNodes:
                 session.query(*cols).order_by(models.ConnectionNode.id).as_structarray()
             )
 
-        arr["geom"] = self.reproject(arr["geom"])
+        arr["the_geom"] = self.reproject(arr["the_geom"])
 
         # replace -9999.0 with NaN in initial_water_level
-        arr["initial_water_level"][arr["initial_water_level"] == -9999.0] = np.nan
+        arr["initial_waterlevel"][arr["initial_waterlevel"] == -9999.0] = np.nan
         arr["hydraulic_conductivity_out"] /= DAY_IN_SECONDS
         arr["hydraulic_conductivity_in"] /= DAY_IN_SECONDS
 
-        attr_dict = arr_to_attr_dict(
-            arr,
-            {
-                "geom": "the_geom",
-                "initial_water_level": "initial_waterlevel",
-                "exchange_type": "calculation_type",
-                "exchange_level": "drain_level",
-                "visualisation": "manhole_indicator",
-                "manhole_surface_level": "surface_level",
-            },
-        )
-
-        return ConnectionNodes(**attr_dict)
+        return ConnectionNodes(**{name: arr[name] for name in arr.dtype.names})
 
     def get_cross_section_definition_for_table(self, table) -> np.ndarray:
         with self.get_session() as session:
@@ -575,7 +540,7 @@ def get_cross_section_locations(self) -> CrossSectionLocations:
                 session.query(
                     models.CrossSectionLocation.id,
                     models.CrossSectionLocation.code,
-                    models.CrossSectionLocation.geom,
+                    models.CrossSectionLocation.geom.label("the_geom"),
                     models.CrossSectionLocation.channel_id,
                     models.CrossSectionLocation.reference_level,
                     models.CrossSectionLocation.bank_level,
@@ -589,25 +554,23 @@ def get_cross_section_locations(self) -> CrossSectionLocations:
                 .order_by(models.CrossSectionLocation.id)
                 .as_structarray()
             )
-        arr["geom"] = self.reproject(arr["geom"])
-
-        attr_dict = arr_to_attr_dict(arr, {"geom": "the_geom"})
+        arr["the_geom"] = self.reproject(arr["the_geom"])
 
         # transform to a CrossSectionLocations object
-        return CrossSectionLocations(**attr_dict)
+        return CrossSectionLocations(**{name: arr[name] for name in arr.dtype.names})
 
     def get_culverts(self) -> Culverts:
         """Return Culverts"""
         cols = [
             models.Culvert.id,
             models.Culvert.code,
-            models.Culvert.geom,
-            models.Culvert.calculation_point_distance,
-            models.Culvert.connection_node_id_start,
-            models.Culvert.connection_node_id_end,
-            models.Culvert.exchange_type,
-            models.Culvert.invert_level_start,
-            models.Culvert.invert_level_end,
+            models.Culvert.geom.label("the_geom"),
+            models.Culvert.calculation_point_distance.label("dist_calc_points"),
+            models.Culvert.connection_node_id_start.label("connection_node_start_id"),
+            models.Culvert.connection_node_id_end.label("connection_node_end_id"),
+            models.Culvert.exchange_type.label("calculation_type"),
+            models.Culvert.invert_level_start.label("invert_level_start_point"),
+            models.Culvert.invert_level_end.label("invert_level_end_point"),
             models.Culvert.discharge_coefficient_negative,
             models.Culvert.discharge_coefficient_positive,
             models.Culvert.display_name,
@@ -641,31 +604,20 @@ def get_culverts(self) -> Culverts:
                 .as_structarray()
             )
 
-        arr["geom"] = self.reproject(arr["geom"])
+        arr["the_geom"] = self.reproject(arr["the_geom"])
 
         # map friction_type 4 to friction_type 2 to match crosssectionlocation enum
         arr["friction_type"][arr["friction_type"] == 4] = 2
 
         # When no calculation type is provides we default to isolated
-        arr["exchange_type"][arr["exchange_type"] == -9999] = LineType.LINE_1D_ISOLATED
+        arr["calculation_type"][
+            arr["calculation_type"] == -9999
+        ] = LineType.LINE_1D_ISOLATED
         # map "old" calculation types (100, 101, 102, 105) to (0, 1, 2, 5)
-        arr["exchange_type"][arr["exchange_type"] >= 100] -= 100
-
-        attr_dict = arr_to_attr_dict(
-            arr,
-            {
-                "exchange_type": "calculation_type",
-                "calculation_point_distance": "dist_calc_points",
-                "invert_level_start": "invert_level_start_point",
-                "invert_level_end": "invert_level_end_point",
-                "geom": "the_geom",
-                "connection_node_id_start": "connection_node_start_id",
-                "connection_node_id_end": "connection_node_end_id",
-            },
-        )
+        arr["calculation_type"][arr["calculation_type"] >= 100] -= 100
 
-        # transform to a CrossSectionLocations object
-        return Culverts(**attr_dict)
+        # transform to a Culverts object
+        return Culverts(**{name: arr[name] for name in arr.dtype.names})
 
     def get_exchange_lines(self) -> ExchangeLines:
         with self.get_session() as session:
@@ -673,28 +625,27 @@ def get_exchange_lines(self) -> ExchangeLines:
                 session.query(
                     models.ExchangeLine.id,
                     models.ExchangeLine.channel_id,
-                    models.ExchangeLine.geom,
+                    models.ExchangeLine.geom.label("the_geom"),
                     models.ExchangeLine.exchange_level,
                 )
                 .order_by(models.ExchangeLine.id)
                 .as_structarray()
             )
 
-        arr["geom"] = self.reproject(arr["geom"])
-        attr_dict = arr_to_attr_dict(arr, {"geom": "the_geom"})
+        arr["the_geom"] = self.reproject(arr["the_geom"])
         # transform to a Channels object
-        return ExchangeLines(**attr_dict)
+        return ExchangeLines(**{name: arr[name] for name in arr.dtype.names})
 
     def get_grid_refinements(self) -> GridRefinements:
         """Return Gridrefinement and GridRefinementArea concatenated into one array."""
         with self.get_session() as session:
             arr1 = (
                 session.query(
-                    models.GridRefinementLine.geom,
+                    models.GridRefinementLine.geom.label("the_geom"),
                     models.GridRefinementLine.id,
                     models.GridRefinementLine.code,
                     models.GridRefinementLine.display_name,
-                    models.GridRefinementLine.grid_level,
+                    models.GridRefinementLine.grid_level.label("refinement_level"),
                 )
                 .filter(
                     models.GridRefinementLine.geom.isnot(None),
@@ -705,11 +656,11 @@ def get_grid_refinements(self) -> GridRefinements:
             )
             arr2 = (
                 session.query(
-                    models.GridRefinementArea.geom,
+                    models.GridRefinementArea.geom.label("the_geom"),
                     models.GridRefinementArea.id,
                     models.GridRefinementArea.code,
                     models.GridRefinementArea.display_name,
-                    models.GridRefinementArea.grid_level,
+                    models.GridRefinementArea.grid_level.label("refinement_level"),
                 )
                 .filter(
                     models.GridRefinementArea.geom.isnot(None),
@@ -721,13 +672,10 @@ def get_grid_refinements(self) -> GridRefinements:
             arr = np.concatenate((arr1, arr2))
 
         # reproject
-        arr["geom"] = self.reproject(arr["geom"])
-        arr["id"] = np.arange(len(arr["grid_level"]))
+        arr["the_geom"] = self.reproject(arr["the_geom"])
+        arr["id"] = np.arange(len(arr["refinement_level"]))
 
-        attr_dict = arr_to_attr_dict(
-            arr, {"geom": "the_geom", "grid_level": "refinement_level"}
-        )
-        return GridRefinements(**attr_dict)
+        return GridRefinements(**{name: arr[name] for name in arr.dtype.names})
 
     def get_dem_average_areas(self) -> DemAverageAreas:
         """Return DemAverageAreas"""
@@ -735,20 +683,19 @@ def get_dem_average_areas(self) -> DemAverageAreas:
             arr = (
                 session.query(
                     models.DemAverageArea.id,
-                    models.DemAverageArea.geom,
+                    models.DemAverageArea.geom.label("the_geom"),
                 )
                 .order_by(models.DemAverageArea.id)
                 .as_structarray()
             )
-            arr["geom"] = self.reproject(arr["geom"])
-        attr_dict = arr_to_attr_dict(arr, {"geom": "the_geom"})
-        return DemAverageAreas(**attr_dict)
+            arr["the_geom"] = self.reproject(arr["the_geom"])
+        return DemAverageAreas(**{name: arr[name] for name in arr.dtype.names})
 
     def get_obstacles(self) -> Obstacles:
         with self.get_session() as session:
             arr = (
                 session.query(
-                    models.Obstacle.geom,
+                    models.Obstacle.geom.label("the_geom"),
                     models.Obstacle.id,
                     models.Obstacle.crest_level,
                     models.Obstacle.affects_2d,
@@ -760,18 +707,17 @@ def get_obstacles(self) -> Obstacles:
             )
 
         # reproject
-        arr["geom"] = self.reproject(arr["geom"])
-        attr_dict = arr_to_attr_dict(arr, {"geom": "the_geom"})
-        # transform to a Channels object
-        return Obstacles(**attr_dict)
+        arr["the_geom"] = self.reproject(arr["the_geom"])
+        # transform to a Obstacles object
+        return Obstacles(**{name: arr[name] for name in arr.dtype.names})
 
     def get_orifices(self) -> Orifices:
         """Return Orifices"""
         cols = [
             models.Orifice.id,
             models.Orifice.code,
-            models.Orifice.connection_node_id_start,
-            models.Orifice.connection_node_id_end,
+            models.Orifice.connection_node_id_start.label("connection_node_start_id"),
+            models.Orifice.connection_node_id_end.label("connection_node_end_id"),
             models.Orifice.crest_level,
             models.Orifice.crest_type,
             models.Orifice.discharge_coefficient_negative,
@@ -808,17 +754,8 @@ def get_orifices(self) -> Orifices:
             )
         # map friction_type 4 to friction_type 2 to match crosssectionlocation enum
         arr["friction_type"][arr["friction_type"] == 4] = 2
-        attr_dict = arr_to_attr_dict(
-            arr,
-            {
-                "exchange_type": "calculation_type",
-                "calculation_point_distance": "dist_calc_points",
-                "connection_node_id_start": "connection_node_start_id",
-                "connection_node_id_end": "connection_node_end_id",
-                "geom": "the_geom",
-            },
-        )
-        return Orifices(**attr_dict)
+
+        return Orifices(**{name: arr[name] for name in arr.dtype.names})
 
     def get_pipes(self) -> Pipes:
         """Return Pipes"""
@@ -826,17 +763,17 @@ def get_pipes(self) -> Pipes:
             models.Pipe.id,
             models.Pipe.code,
             models.Pipe.sewerage_type,
-            models.Pipe.exchange_type,
-            models.Pipe.invert_level_start,
-            models.Pipe.invert_level_end,
-            models.Pipe.calculation_point_distance,
-            models.Pipe.connection_node_id_start,
-            models.Pipe.connection_node_id_end,
+            models.Pipe.exchange_type.label("calculation_type"),
+            models.Pipe.invert_level_start.label("invert_level_start_point"),
+            models.Pipe.invert_level_end.label("invert_level_end_point"),
+            models.Pipe.calculation_point_distance.label("dist_calc_points"),
+            models.Pipe.connection_node_id_start.label("connection_node_start_id"),
+            models.Pipe.connection_node_id_end.label("connection_node_end_id"),
             models.Pipe.display_name,
             models.Pipe.exchange_thickness,
             models.Pipe.hydraulic_conductivity_out,
             models.Pipe.hydraulic_conductivity_in,
-            models.Pipe.material_id,
+            models.Pipe.material_id.label("material"),
             case(
                 {
                     models.Pipe.friction_value.isnot(None)
@@ -867,22 +804,8 @@ def get_pipes(self) -> Pipes:
         arr["hydraulic_conductivity_out"] /= DAY_IN_SECONDS
         arr["hydraulic_conductivity_in"] /= DAY_IN_SECONDS
 
-        attr_dict = arr_to_attr_dict(
-            arr,
-            {
-                "exchange_type": "calculation_type",
-                "calculation_point_distance": "dist_calc_points",
-                "material_id": "material",
-                "invert_level_start": "invert_level_start_point",
-                "invert_level_end": "invert_level_end_point",
-                "connection_node_id_start": "connection_node_start_id",
-                "connection_node_id_end": "connection_node_end_id",
-                "geom": "the_geom",
-            },
-        )
-
         # transform to a Pipes object
-        return Pipes(**attr_dict)
+        return Pipes(**{name: arr[name] for name in arr.dtype.names})
 
     def get_pumps(self) -> Pumps:
         with self.get_session() as session:
@@ -891,8 +814,10 @@ def get_pumps(self) -> Pumps:
                     models.Pump.id,
                     models.Pump.code,
                     models.Pump.capacity,
-                    models.Pump.connection_node_id,
-                    models.PumpMap.connection_node_id_end,
+                    models.Pump.connection_node_id.label("connection_node_start_id"),
+                    models.PumpMap.connection_node_id_end.label(
+                        "connection_node_end_id"
+                    ),
                     models.Pump.type_,
                     models.Pump.start_level,
                     models.Pump.lower_stop_level,
@@ -907,25 +832,16 @@ def get_pumps(self) -> Pumps:
         # Pump capacity is entered as L/s but we need m3/s.
         arr["capacity"] = arr["capacity"] / 1000
 
-        attr_dict = arr_to_attr_dict(
-            arr,
-            {
-                "connection_node_id": "connection_node_start_id",
-                "connection_node_id_end": "connection_node_end_id",
-                "geom": "the_geom",
-            },
-        )
-
         # transform to a Pumps object
-        return Pumps(**attr_dict)
+        return Pumps(**{name: arr[name] for name in arr.dtype.names})
 
     def get_weirs(self) -> Weirs:
         """Return Weirs"""
         cols = [
             models.Weir.id,
             models.Weir.code,
-            models.Weir.connection_node_id_start,
-            models.Weir.connection_node_id_end,
+            models.Weir.connection_node_id_start.label("connection_node_start_id"),
+            models.Weir.connection_node_id_end.label("connection_node_end_id"),
             models.Weir.crest_level,
             models.Weir.crest_type,
             models.Weir.discharge_coefficient_negative,
@@ -958,17 +874,8 @@ def get_weirs(self) -> Weirs:
             )
         # map friction_type 4 to friction_type 2 to match crosssectionlocation enum
         arr["friction_type"][arr["friction_type"] == 4] = 2
-        attr_dict = arr_to_attr_dict(
-            arr,
-            {
-                "exchange_type": "calculation_type",
-                "calculation_point_distance": "dist_calc_points",
-                "connection_node_id_start": "connection_node_start_id",
-                "connection_node_id_end": "connection_node_end_id",
-                "geom": "the_geom",
-            },
-        )
-        return Weirs(**attr_dict)
+
+        return Weirs(**{name: arr[name] for name in arr.dtype.names})
 
     def get_windshieldings(self) -> Windshieldings:
         with self.get_session() as session:
@@ -1000,14 +907,16 @@ def get_potential_breaches(self) -> PotentialBreaches:
             models.PotentialBreach.id,
             models.PotentialBreach.code,
             models.PotentialBreach.display_name,
-            models.PotentialBreach.geom,
+            models.PotentialBreach.geom.label("the_geom"),
             models.PotentialBreach.channel_id,
         ]
 
         if self.get_version() >= 212:
             cols += [
-                models.PotentialBreach.initial_exchange_level,
-                models.PotentialBreach.final_exchange_level,
+                models.PotentialBreach.initial_exchange_level.label("exchange_level"),
+                models.PotentialBreach.final_exchange_level.label(
+                    "maximum_breach_depth"
+                ),
                 models.PotentialBreach.levee_material,
             ]
 
@@ -1019,21 +928,14 @@ def get_potential_breaches(self) -> PotentialBreaches:
             )
 
         # reproject
-        arr["geom"] = self.reproject(arr["geom"])
+        arr["the_geom"] = self.reproject(arr["the_geom"])
         # derive maximum_breach_depth from initial and final exchange level
         # and overwrite final_exchange_level because adding a field is more work
-        arr["final_exchange_level"] = (
-            arr["initial_exchange_level"] - arr["final_exchange_level"]
-        )
-        attr_dict = arr_to_attr_dict(
-            arr,
-            {
-                "geom": "the_geom",
-                "initial_exchange_level": "exchange_level",
-                "final_exchange_level": "maximum_breach_depth",
-            },
+        arr["maximum_breach_depth"] = (
+            arr["exchange_level"] - arr["maximum_breach_depth"]
         )
-        return PotentialBreaches(**attr_dict)
+
+        return PotentialBreaches(**{name: arr[name] for name in arr.dtype.names})
 
 
 # Constructing a Transformer takes quite long, so we use caching here. The
diff --git a/threedigrid_builder/tests/test_boundary_conditions.py b/threedigrid_builder/tests/test_boundary_conditions.py
index d7b8a52c..1c531472 100644
--- a/threedigrid_builder/tests/test_boundary_conditions.py
+++ b/threedigrid_builder/tests/test_boundary_conditions.py
@@ -279,7 +279,7 @@ def test_2d_boundary_condition(
     boundary_conditions_2d = BoundaryConditions2D(
         id=range(len(bc_coords)),
         type=[3] * len(bc_coords),
-        geom=shapely.linestrings(bc_coords),
+        the_geom=shapely.linestrings(bc_coords),
     )
 
     nodes, lines = boundary_conditions_2d.get_nodes_and_lines(
@@ -320,7 +320,7 @@ def test_2d_boundary_condition_err(grid2d, bc_coords, expected_message):
     boundary_conditions_2d = BoundaryConditions2D(
         id=range(len(bc_coords)),
         type=[3] * len(bc_coords),
-        geom=shapely.linestrings(bc_coords),
+        the_geom=shapely.linestrings(bc_coords),
     )
 
     with pytest.raises(SchematisationError, match=expected_message):
@@ -396,7 +396,7 @@ def test_2d_boundary_condition_types(grid2d_gw, boundary_type, node_type, kcu, l
     boundary_conditions_2d = BoundaryConditions2D(
         id=[1],
         type=[boundary_type],
-        geom=[shapely.linestrings([(12.0, 3.0), (12.0, 8.0)])],
+        the_geom=[shapely.linestrings([(12.0, 3.0), (12.0, 8.0)])],
     )
     nodes, lines = boundary_conditions_2d.get_nodes_and_lines(
         grid2d_gw.nodes,
@@ -424,7 +424,7 @@ def test_2d_boundary_condition_combined(grid2d_gw):
     boundary_conditions_2d = BoundaryConditions2D(
         id=[1, 2],
         type=[BoundaryType.WATERLEVEL, BoundaryType.GROUNDWATERLEVEL],
-        geom=[shapely.linestrings([(12.0, 3.0), (12.0, 8.0)])] * 2,
+        the_geom=[shapely.linestrings([(12.0, 3.0), (12.0, 8.0)])] * 2,
     )
 
     nodes, lines = boundary_conditions_2d.get_nodes_and_lines(
diff --git a/threedigrid_builder/tests/test_db.py b/threedigrid_builder/tests/test_db.py
index 7b03980d..dc3c6a30 100644
--- a/threedigrid_builder/tests/test_db.py
+++ b/threedigrid_builder/tests/test_db.py
@@ -39,7 +39,7 @@ def test_init(tmp_path):
     with mock.patch(
         "threedigrid_builder.interface.db.ThreediDatabase"
     ) as db, mock.patch.object(SQLite, "get_version") as get_version:
-        get_version.return_value = 228
+        get_version.return_value = 229
         sqlite = SQLite(path)
 
     db.assert_called_with(path)
@@ -67,7 +67,7 @@ def test_init_bad_version(tmp_path):
 
 
 def test_get_version(db):
-    assert db.get_version() == 228
+    assert db.get_version() == 229
 
 
 def test_get_boundary_conditions_1d(db):
diff --git a/threedigrid_builder/tests/test_zero_d.py b/threedigrid_builder/tests/test_zero_d.py
index 86405a62..4eb282e6 100644
--- a/threedigrid_builder/tests/test_zero_d.py
+++ b/threedigrid_builder/tests/test_zero_d.py
@@ -57,7 +57,7 @@ def test_surfaces(grid_all):
         # nr_of_inhabitants=[1000.0, 2000.0, 2000.0],
         area=[100.0, 200.0, 200.0],
         # dry_weather_flow=[1.0, 2.0, 2.0],
-        geom=[shapely.points([1.0, 2.0]), None, None],
+        the_geom=[shapely.points([1.0, 2.0]), None, None],
         connection_node_id=[1, 2, 1],
         connection_node_the_geom=[
             shapely.points([1.0, 2.0]),