From e415f2abcfe23b5919e1018e3acc0da459aa0bed Mon Sep 17 00:00:00 2001 From: Kimball Bighorse Date: Wed, 26 Nov 2025 18:13:00 -0800 Subject: [PATCH 1/2] Add test that reproduces the bug --- tests/test_thing.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_thing.py b/tests/test_thing.py index 378f72d02..28290dada 100644 --- a/tests/test_thing.py +++ b/tests/test_thing.py @@ -152,6 +152,35 @@ def test_add_water_well(location, group): cleanup_post_test(Thing, data["id"]) +def test_add_water_well_with_measuring_point(location, group): + """ + Test creating a well with measuring_point_height and measuring_point_description. + + This reproduces the bug where measuring_point fields are properties (from MeasuringPointHistory table) + and cannot be set directly on Thing objects. + + Expected error (before fix): AttributeError: property 'measuring_point_height' of 'Thing' object has no setter + """ + payload = { + "location_id": location.id, + "group_id": group.id, + "release_status": "draft", + "name": "Test Well with Measuring Point", + "measuring_point_height": 2.5, + "measuring_point_description": "top of casing", + } + + response = client.post("/thing/water-well", json=payload) + assert response.status_code == 201 + data = response.json() + + assert data["name"] == payload["name"] + assert data["measuring_point_height"] == 2.5 + assert data["measuring_point_description"] == "top of casing" + + cleanup_post_test(Thing, data["id"]) + + @pytest.mark.skip("Needs to be updated per changes made from feature files") def test_add_water_well_409_bad_group_id(location): bad_group_id = 9999 From 89cc46150da5d8bf72611e0247e826b3f758cb59 Mon Sep 17 00:00:00 2001 From: Kimball Bighorse Date: Wed, 26 Nov 2025 18:16:31 -0800 Subject: [PATCH 2/2] Implement the solution --- services/thing_helper.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/services/thing_helper.py b/services/thing_helper.py index 53ce54577..fdd0424db 100644 --- a/services/thing_helper.py +++ b/services/thing_helper.py @@ -13,6 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # =============================================================================== +from datetime import datetime +from zoneinfo import ZoneInfo + from fastapi import Request from fastapi_pagination.ext.sqlalchemy import paginate from pydantic import BaseModel @@ -32,6 +35,7 @@ WellCasingMaterial, ) from db.group import GroupThingAssociation +from db.measuring_point_history import MeasuringPointHistory from services.audit_helper import audit_add from services.crud_helper import model_patcher from services.exceptions_helper import PydanticStyleException @@ -159,6 +163,10 @@ def add_thing( location_id = data.pop("location_id", None) group_id = data.pop("group_id", None) + # Extract measuring point data (stored in separate history table, not as Thing columns) + measuring_point_height = data.pop("measuring_point_height", None) + measuring_point_description = data.pop("measuring_point_description", None) + try: thing = Thing(**data) thing.thing_type = thing_type @@ -169,6 +177,18 @@ def add_thing( session.flush() session.refresh(thing) + # Create MeasuringPointHistory record if measuring_point_height provided + if measuring_point_height is not None: + measuring_point_history = MeasuringPointHistory( + thing_id=thing.id, + measuring_point_height=measuring_point_height, + measuring_point_description=measuring_point_description, + start_date=datetime.now(tz=ZoneInfo("UTC")), + end_date=None, + ) + audit_add(user, measuring_point_history) + session.add(measuring_point_history) + # endpoint catches ProgrammingError if location_id or group_id do not exist if group_id: assoc = GroupThingAssociation()