Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions alembic/versions/p9c1d2e3f4a5_make_contact_role_nullable.py

This file was deleted.

35 changes: 0 additions & 35 deletions alembic/versions/q0d1e2f3a4b5_make_contact_type_nullable.py

This file was deleted.

6 changes: 3 additions & 3 deletions db/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# ===============================================================================
from typing import List, TYPE_CHECKING, Optional
from typing import List, TYPE_CHECKING

from sqlalchemy import Integer, ForeignKey, String, UniqueConstraint
from sqlalchemy.ext.associationproxy import association_proxy, AssociationProxy
Expand Down Expand Up @@ -49,8 +49,8 @@ class ThingContactAssociation(Base, AutoBaseMixin):
class Contact(Base, AutoBaseMixin, ReleaseMixin, NotesMixin):
name: Mapped[str] = mapped_column(String(100), nullable=True)
organization: Mapped[str] = lexicon_term(nullable=True)
role: Mapped[Optional[str]] = lexicon_term(nullable=True)
contact_type: Mapped[Optional[str]] = lexicon_term(nullable=True)
role: Mapped[str] = lexicon_term(nullable=False)
contact_type: Mapped[str] = lexicon_term(nullable=False)

# primary keys of the nm aquifer tables from which the contacts originate
nma_pk_owners: Mapped[str] = mapped_column(String(100), nullable=True)
Expand Down
8 changes: 4 additions & 4 deletions schemas/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ class CreateContact(BaseCreateModel, ValidateContact):
thing_id: int
name: str | None = None
organization: str | None = None
role: Role | None = None
contact_type: ContactType | None = None
role: Role
contact_type: ContactType
nma_pk_owners: str | None = None
# description: str | None = None
# email: str | None = None
Expand Down Expand Up @@ -218,8 +218,8 @@ class ContactResponse(BaseResponseModel):

name: str | None
organization: str | None
role: Role | None
contact_type: ContactType | None
role: Role
contact_type: ContactType
incomplete_nma_phones: List[str] = []
emails: List[EmailResponse] = []
phones: List[PhoneResponse] = []
Expand Down
10 changes: 10 additions & 0 deletions schemas/well_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ def validate_model(self):
key = f"contact_{jdx}"
name = getattr(self, f"{key}_name")
organization = getattr(self, f"{key}_organization")
role = getattr(self, f"{key}_role")
contact_type = getattr(self, f"{key}_type")

# Treat name or organization as contact data too, so bare contacts
# still go through the same cross-field rules as fully populated ones.
Expand All @@ -399,6 +401,14 @@ def validate_model(self):
raise ValueError(
f"At least one of {key}_name or {key}_organization must be provided"
)
if not role:
raise ValueError(
f"{key}_role is required when contact data is provided"
)
if not contact_type:
raise ValueError(
f"{key}_type is required when contact data is provided"
)
for idx in (1, 2):
if any(getattr(self, f"{key}_address_{idx}_{a}") for a in all_attrs):
if not all(
Expand Down
13 changes: 2 additions & 11 deletions services/well_inventory_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,21 +353,12 @@ def _make_contact(model: WellInventoryRow, well: Thing, idx) -> dict:
"address_type": address_type,
}
)

return {
"thing_id": well.id,
"name": name,
"organization": organization,
"role": (
getattr(model, f"contact_{idx}_role").value
if hasattr(getattr(model, f"contact_{idx}_role"), "value")
else getattr(model, f"contact_{idx}_role")
),
"contact_type": (
getattr(model, f"contact_{idx}_type").value
if hasattr(getattr(model, f"contact_{idx}_type"), "value")
else getattr(model, f"contact_{idx}_type")
),
"role": getattr(model, f"contact_{idx}_role").value,
"contact_type": getattr(model, f"contact_{idx}_type").value,
"emails": emails,
"phones": phones,
"addresses": addresses,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
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
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,,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,Interpreted fr geophys logs by source agency,280,45,"Memory of owner, operator, driller",Submersible,200,True,True,0.5,Top of steel casing inside pump house marked with orange paint,Domestic,,"Active, pumping well",Biannual,Sample only when pump has been off more than 12 hours,Measure before owner starts irrigation,True
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 No Role,NMBGMR,,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,Interpreted fr geophys logs by source agency,280,45,"Memory of owner, operator, driller",Submersible,200,True,True,0.5,Top of steel casing inside pump house marked with orange paint,Domestic,,"Active, pumping well",Biannual,Sample only when pump has been off more than 12 hours,Measure before owner starts irrigation,True
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,,,David Emily,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,From driller's log or well report,350,60,"Reported by person other than driller owner agency",Jet,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
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
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
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,,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,From driller's log or well report,280,45,"Memory of owner, operator, driller",submersible,200,True,True,0.5,Top of steel casing inside pump house marked with orange paint,Domestic,,"Active, pumping well",Biannual,Sample only when pump has been off more than 12 hours,Measure before owner starts irrigation,True
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 No Type,NMBGMR,Owner,,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,From driller's log or well report,280,45,"Memory of owner, operator, driller",submersible,200,True,True,0.5,Top of steel casing inside pump house marked with orange paint,Domestic,,"Active, pumping well",Biannual,Sample only when pump has been off more than 12 hours,Measure before owner starts irrigation,True
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,From driller's log or well report,350,60,"From driller's log or well report",Line Shaft,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
5 changes: 5 additions & 0 deletions tests/features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
import random
from datetime import datetime, timedelta

# Lock test database before any db module imports
# Ensures BDD tests only use ocotilloapi_test, never ocotilloapi_dev
os.environ["POSTGRES_DB"] = "ocotilloapi_test"
os.environ["POSTGRES_PORT"] = "5432"

from alembic import command
from alembic.config import Config
from sqlalchemy import select
Expand Down
2 changes: 1 addition & 1 deletion tests/features/steps/cli_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def step_impl_command_exit_zero(context):

@then("the command exits with a non-zero exit code")
def step_impl_command_exit_nonzero(context):
assert context.cli_result.exit_code != 0
assert context.cli_result.exit_code != 0, context.cli_result.exit_code


# ============= EOF =============================================
17 changes: 15 additions & 2 deletions tests/features/steps/well-inventory-csv-validation-error.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,26 @@ def step_then_the_response_includes_a_validation_error_indicating_the_invalid_em


@then(
'the response includes a validation error indicating the missing "contact_type" value'
'the response includes a validation error indicating the missing "contact_role" value'
)
def step_step_step_8(context):
expected_errors = [
{
"field": "composite field error",
"error": "Value error, contact_1_type is required when contact fields are provided",
"error": "Value error, contact_1_role is required when contact data is provided",
}
]
_handle_validation_error(context, expected_errors)


@then(
'the response includes a validation error indicating the missing "contact_type" value'
)
def step_step_step_9(context):
expected_errors = [
{
"field": "composite field error",
"error": "Value error, contact_1_type is required when contact data is provided",
}
]
_handle_validation_error(context, expected_errors)
Expand Down
18 changes: 10 additions & 8 deletions tests/features/well-inventory-csv.feature
Original file line number Diff line number Diff line change
Expand Up @@ -223,19 +223,21 @@ Feature: Bulk upload well inventory from CSV via CLI
And the response includes a validation error indicating the invalid email format
And 1 well is imported

@positive @validation @BDMS-TBD
Scenario: Upload succeeds when a row has a contact without a contact_role
@negative @validation @BDMS-TBD
Scenario: Upload fails when a row has a contact without a "contact_role"
Given my CSV file contains a row with a contact but is missing the required "contact_role" field for that contact
When I run the well inventory bulk upload command
Then the command exits with code 0
And all wells are imported
Then the command exits with a non-zero exit code
And the response includes a validation error indicating the missing "contact_role" value
And 1 well is imported

@positive @validation @BDMS-TBD
Scenario: Upload succeeds when a row has a contact without a "contact_type"
@negative @validation @BDMS-TBD
Scenario: Upload fails when a row has a contact without a "contact_type"
Given my CSV file contains a row with a contact but is missing the required "contact_type" field for that contact
When I run the well inventory bulk upload command
Then the command exits with code 0
And all wells are imported
Then the command exits with a non-zero exit code
And the response includes a validation error indicating the missing "contact_type" value
And 1 well is imported

@negative @validation @BDMS-TBD
Scenario: Upload fails when a row has a contact with an invalid "contact_type"
Expand Down
8 changes: 4 additions & 4 deletions tests/test_well_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,18 +641,18 @@ def test_upload_invalid_boolean_value(self):
assert result.exit_code == 1

def test_upload_missing_contact_type(self):
"""Upload succeeds when contact is provided without contact_type."""
"""Upload fails when contact is provided without contact_type."""
file_path = Path("tests/features/data/well-inventory-missing-contact-type.csv")
if file_path.exists():
result = well_inventory_csv(file_path)
assert result.exit_code == 0
assert result.exit_code == 1

def test_upload_missing_contact_role(self):
"""Upload succeeds when contact is provided without role."""
"""Upload fails when contact is provided without role."""
file_path = Path("tests/features/data/well-inventory-missing-contact-role.csv")
if file_path.exists():
result = well_inventory_csv(file_path)
assert result.exit_code == 0
assert result.exit_code == 1

def test_upload_partial_water_level_fields(self):
"""Upload fails when only some water level fields are provided."""
Expand Down
Loading