Skip to content

Commit 60614ea

Browse files
committed
feat: update well inventory fields and improve validation error handling
1 parent 335ae97 commit 60614ea

32 files changed

Lines changed: 352 additions & 120 deletions
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""update group uniqueness from name to (name, group_type)
2+
3+
Revision ID: h1b2c3d4e5f6
4+
Revises: 7b8c9d0e1f2a
5+
Create Date: 2026-02-07 13:15:00.000000
6+
"""
7+
8+
from typing import Sequence, Union
9+
10+
import sqlalchemy as sa
11+
from alembic import op
12+
13+
revision: str = "h1b2c3d4e5f6"
14+
down_revision: Union[str, Sequence[str], None] = "7b8c9d0e1f2a"
15+
branch_labels: Union[str, Sequence[str], None] = None
16+
depends_on: Union[str, Sequence[str], None] = None
17+
18+
19+
def _existing_unique_constraints() -> list[dict]:
20+
bind = op.get_bind()
21+
inspector = sa.inspect(bind)
22+
return inspector.get_unique_constraints("group")
23+
24+
25+
def _drop_name_only_unique_constraints() -> None:
26+
# Drop any existing unique constraint that enforces uniqueness on name only.
27+
for constraint in _existing_unique_constraints():
28+
columns = constraint.get("column_names") or []
29+
name = constraint.get("name")
30+
if name and columns == ["name"]:
31+
op.drop_constraint(name, "group", type_="unique")
32+
33+
34+
def _ensure_no_duplicate_name_group_type_pairs() -> None:
35+
bind = op.get_bind()
36+
duplicate = bind.execute(
37+
sa.text(
38+
"""
39+
SELECT name, group_type, COUNT(*) AS cnt
40+
FROM "group"
41+
WHERE group_type IS NOT NULL
42+
GROUP BY name, group_type
43+
HAVING COUNT(*) > 1
44+
LIMIT 1
45+
"""
46+
)
47+
).first()
48+
if duplicate:
49+
raise RuntimeError(
50+
"Cannot create uq_group_name_type: duplicate (name, group_type) rows exist."
51+
)
52+
53+
54+
def _ensure_no_duplicate_names() -> None:
55+
bind = op.get_bind()
56+
duplicate = bind.execute(
57+
sa.text(
58+
"""
59+
SELECT name, COUNT(*) AS cnt
60+
FROM "group"
61+
GROUP BY name
62+
HAVING COUNT(*) > 1
63+
LIMIT 1
64+
"""
65+
)
66+
).first()
67+
if duplicate:
68+
raise RuntimeError(
69+
"Cannot recreate uq_group_name: duplicate group names exist."
70+
)
71+
72+
73+
def upgrade() -> None:
74+
_drop_name_only_unique_constraints()
75+
_ensure_no_duplicate_name_group_type_pairs()
76+
77+
constraint_names = {
78+
c.get("name") for c in _existing_unique_constraints() if c.get("name")
79+
}
80+
if "uq_group_name_type" not in constraint_names:
81+
op.create_unique_constraint(
82+
"uq_group_name_type", "group", ["name", "group_type"]
83+
)
84+
85+
86+
def downgrade() -> None:
87+
constraint_names = {
88+
c.get("name") for c in _existing_unique_constraints() if c.get("name")
89+
}
90+
if "uq_group_name_type" in constraint_names:
91+
op.drop_constraint("uq_group_name_type", "group", type_="unique")
92+
93+
_ensure_no_duplicate_names()
94+
95+
constraint_names = {
96+
c.get("name") for c in _existing_unique_constraints() if c.get("name")
97+
}
98+
if "uq_group_name" not in constraint_names:
99+
op.create_unique_constraint("uq_group_name", "group", ["name"])

api/well_inventory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@
371371
# well_pump_depth=model.well_pump_depth_ft,
372372
# is_suitable_for_datalogger=model.datalogger_possible,
373373
# is_open=model.is_open,
374-
# well_status=model.well_hole_status,
374+
# well_status=model.well_status,
375375
# notes=well_notes,
376376
# well_purposes=well_purposes,
377377
# monitoring_frequencies=monitoring_frequencies,

core/lexicon.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@
100100
"name": "horizontal_datum",
101101
"description": null
102102
},
103+
{
104+
"name": "level_status",
105+
"description": null
106+
},
103107
{
104108
"name": "limit_type",
105109
"description": null
@@ -2398,6 +2402,27 @@
23982402
"term": "Observed (required for F, N, and W water level status)",
23992403
"definition": "Observed (required for F, N, and W water level status)"
24002404
},
2405+
{
2406+
"categories": [
2407+
"level_status"
2408+
],
2409+
"term": "stable",
2410+
"definition": "Water level is stable."
2411+
},
2412+
{
2413+
"categories": [
2414+
"level_status"
2415+
],
2416+
"term": "rising",
2417+
"definition": "Water level is rising."
2418+
},
2419+
{
2420+
"categories": [
2421+
"level_status"
2422+
],
2423+
"term": "falling",
2424+
"definition": "Water level is falling."
2425+
},
24012426
{
24022427
"categories": [
24032428
"sample_method"

schemas/well_inventory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ class WellInventoryRow(BaseModel):
249249
measuring_point_description: Optional[str] = None
250250
well_purpose: WellPurposeField = None
251251
well_purpose_2: WellPurposeField = None
252-
well_hole_status: Optional[str] = None
252+
well_status: Optional[str] = None
253253
monitoring_frequency: MonitoringFrequencyField = None
254254

255255
result_communication_preference: Optional[str] = None

services/well_inventory_csv.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user)
529529
well_pump_depth=model.well_pump_depth_ft,
530530
is_suitable_for_datalogger=model.datalogger_possible,
531531
is_open=model.is_open,
532-
well_status=model.well_hole_status,
532+
well_status=model.well_status,
533533
notes=well_notes,
534534
well_purposes=well_purposes,
535535
monitoring_frequencies=monitoring_frequencies,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
project,well_name_point_id,site_name,date_time,field_staff,utm_easting,utm_northing,utm_zone,elevation_ft,elevation_method,measuring_point_height_ft,field_staff_2,field_staff_3,contact_1_name,contact_1_organization,contact_1_role,contact_1_type,contact_1_phone_1,contact_1_phone_1_type,contact_1_phone_2,contact_1_phone_2_type,contact_1_email_1,contact_1_email_1_type,contact_1_email_2,contact_1_email_2_type,contact_1_address_1_line_1,contact_1_address_1_line_2,contact_1_address_1_type,contact_1_address_1_state,contact_1_address_1_city,contact_1_address_1_postal_code,contact_1_address_2_line_1,contact_1_address_2_line_2,contact_1_address_2_type,contact_1_address_2_state,contact_1_address_2_city,contact_1_address_2_postal_code,contact_2_name,contact_2_organization,contact_2_role,contact_2_type,contact_2_phone_1,contact_2_phone_1_type,contact_2_phone_2,contact_2_phone_2_type,contact_2_email_1,contact_2_email_1_type,contact_2_email_2,contact_2_email_2_type,contact_2_address_1_line_1,contact_2_address_1_line_2,contact_2_address_1_type,contact_2_address_1_state,contact_2_address_1_city,contact_2_address_1_postal_code,contact_2_address_2_line_1,contact_2_address_2_line_2,contact_2_address_2_type,contact_2_address_2_state,contact_2_address_2_city,contact_2_address_2_postal_code,directions_to_site,specific_location_of_well,repeat_measurement_permission,sampling_permission,datalogger_installation_permission,public_availability_acknowledgement,result_communication_preference,contact_special_requests_notes,ose_well_record_id,date_drilled,completion_source,total_well_depth_ft,historic_depth_to_water_ft,depth_source,well_pump_type,well_pump_depth_ft,is_open,datalogger_possible,casing_diameter_ft,measuring_point_description,well_purpose,well_purpose_2,well_hole_status,monitoring_frequency,sampling_scenario_notes,well_measuring_notes,sample_possible,contact_1_email_1
1+
project,well_name_point_id,site_name,date_time,field_staff,utm_easting,utm_northing,utm_zone,elevation_ft,elevation_method,measuring_point_height_ft,field_staff_2,field_staff_3,contact_1_name,contact_1_organization,contact_1_role,contact_1_type,contact_1_phone_1,contact_1_phone_1_type,contact_1_phone_2,contact_1_phone_2_type,contact_1_email_1,contact_1_email_1_type,contact_1_email_2,contact_1_email_2_type,contact_1_address_1_line_1,contact_1_address_1_line_2,contact_1_address_1_type,contact_1_address_1_state,contact_1_address_1_city,contact_1_address_1_postal_code,contact_1_address_2_line_1,contact_1_address_2_line_2,contact_1_address_2_type,contact_1_address_2_state,contact_1_address_2_city,contact_1_address_2_postal_code,contact_2_name,contact_2_organization,contact_2_role,contact_2_type,contact_2_phone_1,contact_2_phone_1_type,contact_2_phone_2,contact_2_phone_2_type,contact_2_email_1,contact_2_email_1_type,contact_2_email_2,contact_2_email_2_type,contact_2_address_1_line_1,contact_2_address_1_line_2,contact_2_address_1_type,contact_2_address_1_state,contact_2_address_1_city,contact_2_address_1_postal_code,contact_2_address_2_line_1,contact_2_address_2_line_2,contact_2_address_2_type,contact_2_address_2_state,contact_2_address_2_city,contact_2_address_2_postal_code,directions_to_site,specific_location_of_well,repeat_measurement_permission,sampling_permission,datalogger_installation_permission,public_availability_acknowledgement,result_communication_preference,contact_special_requests_notes,ose_well_record_id,date_drilled,completion_source,total_well_depth_ft,historic_depth_to_water_ft,depth_source,well_pump_type,well_pump_depth_ft,is_open,datalogger_possible,casing_diameter_ft,measuring_point_description,well_purpose,well_purpose_2,well_status,monitoring_frequency,sampling_scenario_notes,well_measuring_notes,sample_possible,contact_1_email_1
22
Middle Rio Grande Groundwater Monitoring,MRG-001_MP1,Smith Farm Domestic Well,2025-02-15T10:30:00,A Lopez,250000,4000000,13N,5250,Survey-grade GPS,1.5,B Chen,,John Smith,NMBGMR,Owner,Primary,505-555-0101,Primary,,,john.smith@example.com,Primary,,,123 County Rd 7,,Mailing,NM,Los Lunas,87031,,,,,,,Maria Garcia,NMBGMR,Principal Investigator,Secondary,505-555-0123,Home,,,maria.garcia@mrgcd.nm.gov,Work,,,1931 2nd St SW,Suite 200,Mailing,NM,Albuquerque,87102,,,,,,,Gate off County Rd 7 0.4 miles south of canal crossing,Domestic well in pump house east of residence,True,True,True,True,email,Call before visits during irrigation season,OSE-123456,2010-06-15,OSE well record,280,45,owner estimate,submersible,200,True,True,0.5,Top of steel casing inside pump house marked with orange paint,Domestic,,active,Biannual,Sample only when pump has been off more than 12 hours,Measure before owner starts irrigation,True,john.smith@example.com
33
Middle Rio Grande Groundwater Monitoring,MRG-003_MP1,Old Orchard Well,2025-01-20T09:00:00,B Chen,250000,4000000,13N,5320,Global positioning system (GPS),1.8,,,Emily Davis,NMBGMR,Biologist,Primary,505-555-0303,Work,,,emily.davis@example.org,Work,,,78 Orchard Ln,,Mailing,NM,Los Lunas,87031,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,From Main St turn east on Orchard Ln well house at dead end,Abandoned irrigation well in small cinderblock building,False,False,False,True,phone,Owner prefers weekday visits,,1965-04-10,historic log scanned,350,60,historic log,vertical turbine inactive,280,False,False,0.75,Top of steel casing under removable hatch use fixed reference mark,Irrigation,,abandoned,Annual,Sampling not permitted water level only when owner present,Well house can be locked coordinate ahead,False,emily.davis@example.org

0 commit comments

Comments
 (0)