From e3e4fde51a74d61e81c36fd5caa173106a4a2c22 Mon Sep 17 00:00:00 2001 From: jakeross Date: Tue, 10 Mar 2026 00:05:30 -0600 Subject: [PATCH] feat: fix water elevation units to feet in materialized view and update related tests --- ...0d1e2_fix_water_elevation_units_to_feet.py | 122 ++++++++++++++++++ tests/test_ogc.py | 3 +- 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 alembic/versions/n7a8b9c0d1e2_fix_water_elevation_units_to_feet.py diff --git a/alembic/versions/n7a8b9c0d1e2_fix_water_elevation_units_to_feet.py b/alembic/versions/n7a8b9c0d1e2_fix_water_elevation_units_to_feet.py new file mode 100644 index 00000000..2a1b94b7 --- /dev/null +++ b/alembic/versions/n7a8b9c0d1e2_fix_water_elevation_units_to_feet.py @@ -0,0 +1,122 @@ +"""fix water elevation units to feet + +Revision ID: n7a8b9c0d1e2 +Revises: m6f7a8b9c0d1 +Create Date: 2026-03-10 11:10:00.000000 +""" + +from typing import Sequence, Union + +from alembic import op +from sqlalchemy import inspect, text + +# revision identifiers, used by Alembic. +revision: str = "n7a8b9c0d1e2" +down_revision: Union[str, Sequence[str], None] = "m6f7a8b9c0d1" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + +METERS_TO_FEET = 3.28084 + +LATEST_LOCATION_CTE = """ +SELECT DISTINCT ON (lta.thing_id) + lta.thing_id, + lta.location_id, + lta.effective_start +FROM location_thing_association AS lta +WHERE lta.effective_end IS NULL +ORDER BY lta.thing_id, lta.effective_start DESC +""".strip() + + +def _create_water_elevation_view() -> str: + return f""" + CREATE MATERIALIZED VIEW ogc_water_elevation_wells AS + WITH latest_location AS ( +{LATEST_LOCATION_CTE} + ), + ranked_obs AS ( + SELECT + fe.thing_id, + o.id AS observation_id, + o.observation_datetime, + (o.value - COALESCE(o.measuring_point_height, 0)) + AS depth_to_water_below_ground_surface + FROM observation AS o + JOIN sample AS s ON s.id = o.sample_id + JOIN field_activity AS fa ON fa.id = s.field_activity_id + JOIN field_event AS fe ON fe.id = fa.field_event_id + JOIN thing AS t ON t.id = fe.thing_id + WHERE + t.thing_type = 'water well' + AND fa.activity_type = 'groundwater level' + AND o.value IS NOT NULL + AND o.observation_datetime IS NOT NULL + ), + latest_obs AS ( + SELECT + ro.*, + ROW_NUMBER() OVER ( + PARTITION BY ro.thing_id + ORDER BY ro.observation_datetime DESC, ro.observation_id DESC + ) AS rn + FROM ranked_obs AS ro + ) + SELECT + t.id AS id, + t.name, + t.thing_type, + lo.observation_id, + lo.observation_datetime, + l.elevation, + lo.depth_to_water_below_ground_surface, + ((l.elevation * {METERS_TO_FEET}) - lo.depth_to_water_below_ground_surface) + AS water_elevation, + l.point + FROM latest_obs AS lo + JOIN thing AS t ON t.id = lo.thing_id + JOIN latest_location AS ll ON ll.thing_id = t.id + JOIN location AS l ON l.id = ll.location_id + WHERE lo.rn = 1 + """ + + +def upgrade() -> None: + bind = op.get_bind() + inspector = inspect(bind) + existing_tables = set(inspector.get_table_names(schema="public")) + required_tables = { + "thing", + "location", + "location_thing_association", + "observation", + "sample", + "field_activity", + "field_event", + } + + if not required_tables.issubset(existing_tables): + missing = sorted(t for t in required_tables if t not in existing_tables) + raise RuntimeError( + "Cannot create ogc_water_elevation_wells. Missing required tables: " + + ", ".join(missing) + ) + + op.execute(text("DROP MATERIALIZED VIEW IF EXISTS ogc_water_elevation_wells")) + op.execute(text(_create_water_elevation_view())) + op.execute( + text( + "COMMENT ON MATERIALIZED VIEW ogc_water_elevation_wells IS " + "'Latest water elevation per well in feet; computed as (elevation_m * 3.28084) - depth_to_water_below_ground_surface_ft.'" + ) + ) + op.execute( + text( + "CREATE UNIQUE INDEX ux_ogc_water_elevation_wells_id " + "ON ogc_water_elevation_wells (id)" + ) + ) + + +def downgrade() -> None: + op.execute(text("DROP MATERIALIZED VIEW IF EXISTS ogc_water_elevation_wells")) diff --git a/tests/test_ogc.py b/tests/test_ogc.py index 6f35d21a..1cf0059c 100644 --- a/tests/test_ogc.py +++ b/tests/test_ogc.py @@ -344,7 +344,8 @@ def test_ogc_water_elevation_wells_computes_elevation_minus_depth_to_water( assert float(row.depth_to_water_below_ground_surface) == 5.0 assert float(row.elevation) == 2464.9 - assert abs(float(row.water_elevation) - 2459.9) < 1e-9 + expected_water_elevation_ft = (2464.9 * 3.28084) - 5.0 + assert abs(float(row.water_elevation) - expected_water_elevation_ft) < 1e-9 def test_ogc_collections():