diff --git a/cli/db_restore.py b/cli/db_restore.py index c746a18e..bd1200c0 100644 --- a/cli/db_restore.py +++ b/cli/db_restore.py @@ -235,6 +235,7 @@ def restore_local_db_from_sql( ) from exc return LocalDbRestoreResult( + sql_file=staged_sql_file, source=source_description, host=host, port=port, diff --git a/cli/service_adapter.py b/cli/service_adapter.py index 3e7eb770..0dd52d37 100644 --- a/cli/service_adapter.py +++ b/cli/service_adapter.py @@ -50,7 +50,8 @@ def well_inventory_csv(source_file: Path | str): payload = {"detail": "Empty file"} return WellInventoryResult(1, json.dumps(payload), payload["detail"], payload) try: - text = content.decode("utf-8") + # Accept UTF-8 CSVs saved with a BOM so the first header is parsed correctly. + text = content.decode("utf-8-sig") except UnicodeDecodeError: payload = {"detail": "File encoding error"} return WellInventoryResult(1, json.dumps(payload), payload["detail"], payload) @@ -61,8 +62,16 @@ def well_inventory_csv(source_file: Path | str): except ValueError as exc: payload = {"detail": str(exc)} return WellInventoryResult(1, json.dumps(payload), payload["detail"], payload) - exit_code = 0 if not payload.get("validation_errors") else 1 - return WellInventoryResult(exit_code, json.dumps(payload), "", payload) + exit_code = ( + 0 if not payload.get("validation_errors") and not payload.get("detail") else 1 + ) + stderr = "" + if exit_code != 0: + if payload.get("validation_errors"): + stderr = f"Validation errors: {json.dumps(payload.get('validation_errors'), indent=2)}" + else: + stderr = f"Error: {payload.get('detail')}" + return WellInventoryResult(exit_code, json.dumps(payload), stderr, payload) def water_levels_csv(source_file: Path | str, *, pretty_json: bool = False): diff --git a/core/enums.py b/core/enums.py index 43c16c2d..663f367e 100644 --- a/core/enums.py +++ b/core/enums.py @@ -48,7 +48,7 @@ ) LimitType: type[Enum] = build_enum_from_lexicon_category("limit_type") MeasurementMethod: type[Enum] = build_enum_from_lexicon_category("measurement_method") -MonitoringStatus: type[Enum] = build_enum_from_lexicon_category("monitoring_status") +MonitoringStatus: type[Enum] = build_enum_from_lexicon_category("status_value") ParameterName: type[Enum] = build_enum_from_lexicon_category("parameter_name") Organization: type[Enum] = build_enum_from_lexicon_category("organization") OriginType: type[Enum] = build_enum_from_lexicon_category("origin_type") diff --git a/core/lexicon.json b/core/lexicon.json index 32757116..82942c48 100644 --- a/core/lexicon.json +++ b/core/lexicon.json @@ -4452,6 +4452,111 @@ "term": "Zamora Accounting Services", "definition": "Zamora Accounting Services" }, + { + "categories": [ + "organization" + ], + "term": "Agua Sana MWCD", + "definition": "Agua Sana MWCD" + }, + { + "categories": [ + "organization" + ], + "term": "Canada Los Alamos MDWCA", + "definition": "Canada Los Alamos MDWCA" + }, + { + "categories": [ + "organization" + ], + "term": "Canjilon Mutual Domestic Water System", + "definition": "Canjilon Mutual Domestic Water System" + }, + { + "categories": [ + "organization" + ], + "term": "Cebolla Mutual Domestic", + "definition": "Cebolla Mutual Domestic" + }, + { + "categories": [ + "organization" + ], + "term": "Chihuahuan Desert Rangeland Research Center (CDRRC)", + "definition": "Chihuahuan Desert Rangeland Research Center (CDRRC)" + }, + { + "categories": [ + "organization" + ], + "term": "East Rio Arriba SWCD", + "definition": "East Rio Arriba SWCD" + }, + { + "categories": [ + "organization" + ], + "term": "El Prado Municipal Water", + "definition": "El Prado Municipal Water" + }, + { + "categories": [ + "organization" + ], + "term": "Hachita Mutual Domestic", + "definition": "Hachita Mutual Domestic" + }, + { + "categories": [ + "organization" + ], + "term": "Jornada Experimental Range (JER)", + "definition": "Jornada Experimental Range (JER)" + }, + { + "categories": [ + "organization" + ], + "term": "La Canada Way HOA", + "definition": "La Canada Way HOA" + }, + { + "categories": [ + "organization" + ], + "term": "Los Ojos Mutual Domestic", + "definition": "Los Ojos Mutual Domestic" + }, + { + "categories": [ + "organization" + ], + "term": "The Nature Conservancy (TNC)", + "definition": "The Nature Conservancy (TNC)" + }, + { + "categories": [ + "organization" + ], + "term": "Smith Ranch LLC", + "definition": "Smith Ranch LLC" + }, + { + "categories": [ + "organization" + ], + "term": "Zia Pueblo", + "definition": "Zia Pueblo" + }, + { + "categories": [ + "organization" + ], + "term": "Our Lady of Guadalupe (OLG)", + "definition": "Our Lady of Guadalupe (OLG)" + }, { "categories": [ "organization" @@ -8185,6 +8290,13 @@ "term": "Water", "definition": "Water bearing zone information and other info from ose reports" }, + { + "categories": [ + "note_type" + ], + "term": "Water Quality", + "definition": "Water quality information" + }, { "categories": [ "note_type" diff --git a/schemas/contact.py b/schemas/contact.py index 590d6db8..d6fe28a0 100644 --- a/schemas/contact.py +++ b/schemas/contact.py @@ -151,7 +151,7 @@ class CreateContact(BaseCreateModel, ValidateContact): name: str | None = None organization: str | None = None role: Role - contact_type: ContactType = "Primary" + contact_type: ContactType nma_pk_owners: str | None = None # description: str | None = None # email: str | None = None diff --git a/schemas/thing.py b/schemas/thing.py index ad109bf0..cd3483fd 100644 --- a/schemas/thing.py +++ b/schemas/thing.py @@ -143,6 +143,7 @@ class CreateWell(CreateBaseThing, ValidateWell): is_suitable_for_datalogger: bool | None = None is_open: bool | None = None well_status: str | None = None + monitoring_status: str | None = None formation_completion_code: FormationCode | None = None nma_formation_zone: str | None = None diff --git a/schemas/well_inventory.py b/schemas/well_inventory.py index dd547725..8ec5a515 100644 --- a/schemas/well_inventory.py +++ b/schemas/well_inventory.py @@ -29,6 +29,12 @@ AddressType, WellPurpose as WellPurposeEnum, MonitoringFrequency, + OriginType, + WellPumpType, + MonitoringStatus, + SampleMethod, + DataQuality, + GroundwaterLevelReason, ) from phonenumbers import NumberParseException from pydantic import ( @@ -38,6 +44,8 @@ validate_email, AfterValidator, field_validator, + Field, + AliasChoices, ) from schemas import past_or_today_validator, PastOrTodayDatetime from services.util import convert_dt_tz_naive_to_tz_aware @@ -60,13 +68,6 @@ def owner_default(v): return v -def primary_default(v): - v = blank_to_none(v) - if v is None: - return "Primary" - return v - - US_POSTAL_REGEX = re.compile(r"^\d{5}(-\d{4})?$") @@ -122,28 +123,75 @@ def email_validator_function(email_str): raise ValueError(f"Invalid email format. {email_str}") from e +def flexible_lexicon_validator(enum_cls): + def validator(v): + if v is None: + return None + if isinstance(v, str) and v.strip() == "": + return None + if isinstance(v, enum_cls): + return v + + v_str = str(v).strip().lower() + for item in enum_cls: + if item.value.lower() == v_str: + return item + return v + + return validator + + # Reusable type PhoneTypeField: TypeAlias = Annotated[ - Optional[PhoneType], BeforeValidator(blank_to_none) + Optional[PhoneType], BeforeValidator(flexible_lexicon_validator(PhoneType)) ] ContactTypeField: TypeAlias = Annotated[ - Optional[ContactType], BeforeValidator(blank_to_none) + Optional[ContactType], + BeforeValidator(flexible_lexicon_validator(ContactType)), ] EmailTypeField: TypeAlias = Annotated[ - Optional[EmailType], BeforeValidator(blank_to_none) + Optional[EmailType], BeforeValidator(flexible_lexicon_validator(EmailType)) ] AddressTypeField: TypeAlias = Annotated[ - Optional[AddressType], BeforeValidator(blank_to_none) + Optional[AddressType], BeforeValidator(flexible_lexicon_validator(AddressType)) +] +ContactRoleField: TypeAlias = Annotated[ + Optional[Role], BeforeValidator(flexible_lexicon_validator(Role)) ] -ContactRoleField: TypeAlias = Annotated[Optional[Role], BeforeValidator(blank_to_none)] OptionalFloat: TypeAlias = Annotated[ Optional[float], BeforeValidator(empty_str_to_none) ] MonitoringFrequencyField: TypeAlias = Annotated[ - Optional[MonitoringFrequency], BeforeValidator(blank_to_none) + Optional[MonitoringFrequency], + BeforeValidator(flexible_lexicon_validator(MonitoringFrequency)), ] WellPurposeField: TypeAlias = Annotated[ - Optional[WellPurposeEnum], BeforeValidator(blank_to_none) + Optional[WellPurposeEnum], + BeforeValidator(flexible_lexicon_validator(WellPurposeEnum)), +] +OriginTypeField: TypeAlias = Annotated[ + Optional[OriginType], BeforeValidator(flexible_lexicon_validator(OriginType)) +] +WellPumpTypeField: TypeAlias = Annotated[ + Optional[WellPumpType], BeforeValidator(flexible_lexicon_validator(WellPumpType)) +] +MonitoringStatusField: TypeAlias = Annotated[ + Optional[MonitoringStatus], + BeforeValidator(flexible_lexicon_validator(MonitoringStatus)), +] +WellStatusField: TypeAlias = Annotated[ + Optional[MonitoringStatus], + BeforeValidator(flexible_lexicon_validator(MonitoringStatus)), +] +SampleMethodField: TypeAlias = Annotated[ + Optional[SampleMethod], BeforeValidator(flexible_lexicon_validator(SampleMethod)) +] +DataQualityField: TypeAlias = Annotated[ + Optional[DataQuality], BeforeValidator(flexible_lexicon_validator(DataQuality)) +] +GroundwaterLevelReasonField: TypeAlias = Annotated[ + Optional[GroundwaterLevelReason], + BeforeValidator(flexible_lexicon_validator(GroundwaterLevelReason)), ] PostalCodeField: TypeAlias = Annotated[ Optional[str], BeforeValidator(postal_code_or_none) @@ -153,6 +201,7 @@ def email_validator_function(email_str): EmailField: TypeAlias = Annotated[ Optional[str], BeforeValidator(email_validator_function) ] +OptionalText: TypeAlias = Annotated[Optional[str], BeforeValidator(empty_str_to_none)] OptionalBool: TypeAlias = Annotated[Optional[bool], BeforeValidator(empty_str_to_none)] OptionalPastOrTodayDateTime: TypeAlias = Annotated[ @@ -170,23 +219,26 @@ def email_validator_function(email_str): class WellInventoryRow(BaseModel): # Required fields project: str - well_name_point_id: str - site_name: str + well_name_point_id: Optional[str] = None date_time: PastOrTodayDatetime field_staff: str utm_easting: float utm_northing: float utm_zone: str - elevation_ft: float - elevation_method: ElevationMethod - measuring_point_height_ft: float # Optional fields - field_staff_2: Optional[str] = None - field_staff_3: Optional[str] = None - - contact_1_name: Optional[str] = None - contact_1_organization: Optional[str] = None + site_name: OptionalText = None + elevation_ft: OptionalFloat = None + elevation_method: Annotated[ + Optional[ElevationMethod], + BeforeValidator(flexible_lexicon_validator(ElevationMethod)), + ] = None + measuring_point_height_ft: OptionalFloat = None + field_staff_2: OptionalText = None + field_staff_3: OptionalText = None + + contact_1_name: OptionalText = None + contact_1_organization: OptionalText = None contact_1_role: ContactRoleField = None contact_1_type: ContactTypeField = None contact_1_phone_1: PhoneField = None @@ -210,8 +262,8 @@ class WellInventoryRow(BaseModel): contact_1_address_2_city: Optional[str] = None contact_1_address_2_postal_code: PostalCodeField = None - contact_2_name: Optional[str] = None - contact_2_organization: Optional[str] = None + contact_2_name: OptionalText = None + contact_2_organization: OptionalText = None contact_2_role: ContactRoleField = None contact_2_type: ContactTypeField = None contact_2_phone_1: PhoneField = None @@ -240,15 +292,15 @@ class WellInventoryRow(BaseModel): repeat_measurement_permission: OptionalBool = None sampling_permission: OptionalBool = None datalogger_installation_permission: OptionalBool = None - public_availability_acknowledgement: OptionalBool = None # TODO: needs a home + public_availability_acknowledgement: OptionalBool = None special_requests: Optional[str] = None ose_well_record_id: Optional[str] = None date_drilled: OptionalPastOrTodayDate = None - completion_source: Optional[str] = None + completion_source: OriginTypeField = None total_well_depth_ft: OptionalFloat = None historic_depth_to_water_ft: OptionalFloat = None - depth_source: Optional[str] = None - well_pump_type: Optional[str] = None + depth_source: OriginTypeField = None + well_pump_type: WellPumpTypeField = None well_pump_depth_ft: OptionalFloat = None is_open: OptionalBool = None datalogger_possible: OptionalBool = None @@ -256,24 +308,39 @@ class WellInventoryRow(BaseModel): measuring_point_description: Optional[str] = None well_purpose: WellPurposeField = None well_purpose_2: WellPurposeField = None - well_status: Optional[str] = None + well_status: WellStatusField = Field( + default=None, + validation_alias=AliasChoices("well_status", "well_hole_status"), + ) monitoring_frequency: MonitoringFrequencyField = None + monitoring_status: MonitoringStatusField = None result_communication_preference: Optional[str] = None contact_special_requests_notes: Optional[str] = None sampling_scenario_notes: Optional[str] = None + well_notes: Optional[str] = None + water_notes: Optional[str] = None well_measuring_notes: Optional[str] = None - sample_possible: OptionalBool = None # TODO: needs a home + sample_possible: OptionalBool = None # water levels - sampler: Optional[str] = None - sample_method: Optional[str] = None - measurement_date_time: OptionalPastOrTodayDateTime = None - mp_height: Optional[float] = None - level_status: Optional[str] = None - depth_to_water_ft: Optional[float] = None - data_quality: Optional[str] = None - water_level_notes: Optional[str] = None # TODO: needs a home + sampler: Optional[str] = Field( + default=None, + validation_alias=AliasChoices("sampler", "measuring_person"), + ) + sample_method: SampleMethodField = None + measurement_date_time: OptionalPastOrTodayDateTime = Field( + default=None, + validation_alias=AliasChoices("measurement_date_time", "water_level_date_time"), + ) + mp_height: OptionalFloat = Field( + default=None, + validation_alias=AliasChoices("mp_height", "mp_height_ft"), + ) + level_status: GroundwaterLevelReasonField = None + depth_to_water_ft: OptionalFloat = None + data_quality: DataQualityField = None + water_level_notes: Optional[str] = None @field_validator("date_time", mode="before") def make_date_time_tz_aware(cls, v): @@ -292,23 +359,6 @@ def make_date_time_tz_aware(cls, v): @model_validator(mode="after") def validate_model(self): - - optional_wl = ( - "sampler", - "sample_method", - "measurement_date_time", - "mp_height", - "level_status", - "depth_to_water_ft", - "data_quality", - "water_level_notes", - ) - - wl_fields = [getattr(self, a) for a in optional_wl] - if any(wl_fields): - if not all(wl_fields): - raise ValueError("All water level fields must be provided") - # verify utm in NM utm_zone_value = (self.utm_zone or "").upper() if utm_zone_value not in ("12N", "13N"): @@ -325,38 +375,51 @@ def validate_model(self): f" Zone={self.utm_zone}" ) + if self.depth_to_water_ft is not None: + if self.measurement_date_time is None: + raise ValueError( + "water_level_date_time is required when depth_to_water_ft is provided" + ) + required_attrs = ("line_1", "type", "state", "city", "postal_code") all_attrs = ("line_1", "line_2", "type", "state", "city", "postal_code") for jdx in (1, 2): key = f"contact_{jdx}" - # Check if any contact data is provided 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. has_contact_data = any( [ name, organization, getattr(self, f"{key}_role"), getattr(self, f"{key}_type"), - *[getattr(self, f"{key}_email_{i}", None) for i in (1, 2)], - *[getattr(self, f"{key}_phone_{i}", None) for i in (1, 2)], + *[getattr(self, f"{key}_email_{i}") for i in (1, 2)], + *[getattr(self, f"{key}_phone_{i}") for i in (1, 2)], *[ - getattr(self, f"{key}_address_{i}_{a}", None) + getattr(self, f"{key}_address_{i}_{a}") for i in (1, 2) for a in all_attrs ], ] ) - # If any contact data is provided, both name and organization are required if has_contact_data: - if not name: + if not name and not organization: raise ValueError( - f"{key}_name is required when other contact fields are provided" + f"At least one of {key}_name or {key}_organization must be provided" ) - if not organization: + if not role: raise ValueError( - f"{key}_organization is required when other contact fields are provided" + 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): @@ -366,17 +429,6 @@ def validate_model(self): ): raise ValueError("All contact address fields must be provided") - name = getattr(self, f"{key}_name") - if name: - if not getattr(self, f"{key}_role"): - raise ValueError( - f"{key}_role must be provided if name is provided" - ) - if not getattr(self, f"{key}_type"): - raise ValueError( - f"{key}_type must be provided if name is provided" - ) - phone = getattr(self, f"{key}_phone_{idx}") tag = f"{key}_phone_{idx}_type" phone_type = getattr(self, f"{key}_phone_{idx}_type") diff --git a/services/contact_helper.py b/services/contact_helper.py index 2aed7458..05b66200 100644 --- a/services/contact_helper.py +++ b/services/contact_helper.py @@ -114,16 +114,16 @@ def add_contact( if commit: session.commit() + session.refresh(contact) + + for note in contact.notes: + session.refresh(note) else: session.flush() - session.refresh(contact) - - for note in contact.notes: - session.refresh(note) - except Exception as e: - session.rollback() + if commit: + session.rollback() raise e return contact diff --git a/services/thing_helper.py b/services/thing_helper.py index cc2fbf6e..221cb121 100644 --- a/services/thing_helper.py +++ b/services/thing_helper.py @@ -221,6 +221,7 @@ def add_thing( datalogger_suitability_status = data.pop("is_suitable_for_datalogger", None) open_status = data.pop("is_open", None) well_status = data.pop("well_status", None) + monitoring_status = data.pop("monitoring_status", None) # ---------- # END UNIVERSAL THING RELATED TABLES @@ -361,6 +362,18 @@ def add_thing( audit_add(user, ws_status) session.add(ws_status) + if monitoring_status is not None: + ms_status = StatusHistory( + target_id=thing.id, + target_table="thing", + status_value=monitoring_status, + status_type="Monitoring Status", + start_date=effective_start, + end_date=None, + ) + audit_add(user, ms_status) + session.add(ms_status) + # ---------- # END WATER WELL SPECIFIC LOGIC # ---------- @@ -417,15 +430,16 @@ def add_thing( # ---------- if commit: session.commit() + session.refresh(thing) + + for note in thing.notes: + session.refresh(note) else: session.flush() - session.refresh(thing) - - for note in thing.notes: - session.refresh(note) except Exception as e: - session.rollback() + if commit: + session.rollback() raise e return thing diff --git a/services/well_inventory_csv.py b/services/well_inventory_csv.py index 561210f4..217fd736 100644 --- a/services/well_inventory_csv.py +++ b/services/well_inventory_csv.py @@ -41,6 +41,9 @@ PermissionHistory, Thing, ThingContactAssociation, + Sample, + Observation, + Parameter, ) from db.engine import session_ctx from pydantic import ValidationError @@ -52,8 +55,10 @@ from services.util import transform_srid, convert_ft_to_m AUTOGEN_DEFAULT_PREFIX = "NM-" -AUTOGEN_PREFIX_REGEX = re.compile(r"^[A-Z]{2,3}-$") -AUTOGEN_TOKEN_REGEX = re.compile(r"^(?P[A-Z]{2,3})\s*-\s*(?:x{4}|X{4})$") +AUTOGEN_PREFIX_REGEX = re.compile(r"^[A-Z]{2,3}-$", re.IGNORECASE) +AUTOGEN_TOKEN_REGEX = re.compile( + r"^(?P[A-Z]{2,3})\s*-\s*(?:x{4}|X{4})$", re.IGNORECASE +) def _extract_autogen_prefix(well_id: str | None) -> str | None: @@ -84,10 +89,6 @@ def _extract_autogen_prefix(well_id: str | None) -> str | None: prefix = m.group("prefix").upper() return f"{prefix}-" - token_match = AUTOGEN_TOKEN_REGEX.match(value) - if token_match: - return f"{token_match.group('prefix')}-" - return None @@ -147,9 +148,10 @@ def _import_well_inventory_csv(session: Session, text: str, user: str): try: header = text.splitlines()[0] dialect = csv.Sniffer().sniff(header) - except csv.Error: - # raise an error if sniffing fails, which likely means the header is not parseable as CSV - raise ValueError("Unable to parse CSV header") + except Exception: + # fallback to comma if sniffing fails + class dialect: + delimiter = "," if dialect.delimiter != ",": raise ValueError(f"Unsupported delimiter '{dialect.delimiter}'") @@ -159,69 +161,100 @@ def _import_well_inventory_csv(session: Session, text: str, user: str): duplicates = [col for col, count in counts.items() if count > 1] wells = [] + validation_errors = [] if duplicates: validation_errors = [ { - "row": 0, + "row": "header", "field": f"{duplicates}", "error": "Duplicate columns found", "value": duplicates, } ] + return { + "validation_errors": validation_errors, + "summary": { + "total_rows_processed": 0, + "total_rows_imported": 0, + "validation_errors_or_warnings": 1, + }, + "wells": [], + } - else: - models, validation_errors = _make_row_models(rows, session) - if models and not validation_errors: - current_row_id = None - try: - for project, items in groupby( - sorted(models, key=lambda x: x.project), key=lambda x: x.project - ): - # get project and add if does not exist - # BDMS-221 adds group_type - sql = select(Group).where( - and_( - Group.group_type == "Monitoring Plan", Group.name == project - ) - ) - group = session.scalars(sql).one_or_none() - if not group: - group = Group(name=project, group_type="Monitoring Plan") - session.add(group) - session.flush() - - for model in items: - current_row_id = model.well_name_point_id - added = _add_csv_row(session, group, model, user) - wells.append(added) - except ValueError as e: - error_text = str(e) - validation_errors.append( - { - "row": current_row_id or "unknown", - "field": _extract_field_from_value_error(error_text), - "error": error_text, - } - ) - session.rollback() - wells = [] - except DatabaseError as e: - logging.error( - f"Database error while importing row '{current_row_id or 'unknown'}': {e}" - ) - validation_errors.append( - { - "row": current_row_id or "unknown", - "field": "Database error", - "error": "A database error occurred while importing this row.", - } + try: + models, row_validation_errors = _make_row_models(rows, session) + validation_errors.extend(row_validation_errors) + + if models: + # Group by project, preserving row number + # models is a list of (row_number, model) + sorted_models = sorted(models, key=lambda x: x[1].project) + for project, items in groupby(sorted_models, key=lambda x: x[1].project): + # Reuse an existing project group immediately, but defer creating a + # new one until a row for that project actually imports successfully. + sql = select(Group).where( + and_(Group.group_type == "Monitoring Plan", Group.name == project) ) - session.rollback() - wells = [] - else: - session.commit() + group = session.scalars(sql).one_or_none() + + for row_number, model in items: + current_row_id = model.well_name_point_id + try: + # Use savepoint for "best-effort" import per row + with session.begin_nested(): + group_for_row = group + if group_for_row is None: + group_for_row = Group( + name=project, group_type="Monitoring Plan" + ) + session.add(group_for_row) + session.flush() + + added = _add_csv_row(session, group_for_row, model, user) + if added: + wells.append(added) + group = group_for_row + except ( + ValueError, + DatabaseError, + PydanticStyleException, + ValidationError, + ) as e: + if isinstance(e, PydanticStyleException): + error_text = str(e.detail) + field = "error" + elif isinstance(e, ValidationError): + # extract just the error messages + error_text = "; ".join( + [str(err.get("msg")) for err in e.errors()] + ) + field = _extract_field_from_value_error(error_text) + elif isinstance(e, DatabaseError): + error_text = str(getattr(e, "orig", None) or e) + error_text = " ".join(error_text.split()) + field = "Database error" + else: + error_text = str(e) + field = _extract_field_from_value_error(error_text) + + logging.error( + f"Error while importing row {row_number} ('{current_row_id}'): {error_text}" + ) + validation_errors.append( + { + "row": row_number, + "well_id": current_row_id, + "field": field, + "error": error_text, + } + ) + session.commit() + except Exception as exc: + logging.exception("Unexpected error in _import_well_inventory_csv") + return {"detail": str(exc)} - rows_imported = len(wells) + wells_imported = [w for w in wells if w is not None] + rows_imported = len(wells_imported) rows_processed = len(rows) error_rows = { e.get("row") for e in validation_errors if e.get("row") not in (None, 0) @@ -235,7 +268,7 @@ def _import_well_inventory_csv(session: Session, text: str, user: str): "total_rows_imported": rows_imported, "validation_errors_or_warnings": rows_with_validation_errors_or_warnings, }, - "wells": wells, + "wells": wells_imported, } @@ -264,12 +297,21 @@ def _make_location(model) -> Location: transformed_point = transform_srid( point, source_srid=source_srid, target_srid=SRID_WGS84 ) - elevation_ft = float(model.elevation_ft) - elevation_m = convert_ft_to_m(elevation_ft) + elevation_ft = model.elevation_ft + elevation_m = ( + convert_ft_to_m(float(elevation_ft)) if elevation_ft is not None else 0.0 + ) + + release_status = "draft" + if model.public_availability_acknowledgement is True: + release_status = "public" + elif model.public_availability_acknowledgement is False: + release_status = "private" loc = Location( point=transformed_point.wkt, elevation=elevation_m, + release_status=release_status, ) return loc @@ -289,7 +331,8 @@ def _make_contact(model: WellInventoryRow, well: Thing, idx) -> dict: phones = [] addresses = [] name = getattr(model, f"contact_{idx}_name") - if name: + organization = getattr(model, f"contact_{idx}_organization") + if name or organization: for i in (1, 2): email = getattr(model, f"contact_{idx}_email_{i}") etype = getattr(model, f"contact_{idx}_email_{i}_type") @@ -317,13 +360,12 @@ def _make_contact(model: WellInventoryRow, well: Thing, idx) -> dict: "address_type": address_type, } ) - return { "thing_id": well.id, "name": name, - "organization": getattr(model, f"contact_{idx}_organization"), - "role": getattr(model, f"contact_{idx}_role"), - "contact_type": getattr(model, f"contact_{idx}_type"), + "organization": organization, + "role": getattr(model, f"contact_{idx}_role").value, + "contact_type": getattr(model, f"contact_{idx}_type").value, "emails": emails, "phones": phones, "addresses": addresses, @@ -393,12 +435,51 @@ def _generate_autogen_well_id(session, prefix: str, offset: int = 0) -> tuple[st return f"{prefix}{new_number:04d}", new_number +def _find_existing_imported_well( + session: Session, model: WellInventoryRow +) -> Thing | None: + if model.measurement_date_time is not None: + sample_name = ( + f"{model.well_name_point_id}-WL-" + f"{model.measurement_date_time.strftime('%Y%m%d%H%M')}" + ) + existing = session.scalars( + select(Thing) + .join(FieldEvent, FieldEvent.thing_id == Thing.id) + .join(FieldActivity, FieldActivity.field_event_id == FieldEvent.id) + .join(Sample, Sample.field_activity_id == FieldActivity.id) + .where( + Thing.name == model.well_name_point_id, + Thing.thing_type == "water well", + FieldActivity.activity_type == "groundwater level", + Sample.sample_name == sample_name, + ) + .order_by(Thing.id.asc()) + ).first() + if existing is not None: + return existing + + return session.scalars( + select(Thing) + .join(FieldEvent, FieldEvent.thing_id == Thing.id) + .join(FieldActivity, FieldActivity.field_event_id == FieldEvent.id) + .where( + Thing.name == model.well_name_point_id, + Thing.thing_type == "water well", + FieldEvent.event_date == model.date_time, + FieldActivity.activity_type == "well inventory", + ) + .order_by(Thing.id.asc()) + ).first() + + def _make_row_models(rows, session): models = [] validation_errors = [] seen_ids: Set[str] = set() - offset = 0 + offsets = {} for idx, row in enumerate(rows): + row_number = idx + 1 try: if all(key == row.get(key) for key in row.keys()): raise ValueError("Duplicate header row") @@ -408,10 +489,12 @@ def _make_row_models(rows, session): well_id = row.get("well_name_point_id") autogen_prefix = _extract_autogen_prefix(well_id) - if autogen_prefix: + if autogen_prefix is not None: + offset = offsets.get(autogen_prefix, 0) well_id, offset = _generate_autogen_well_id( session, autogen_prefix, offset ) + offsets[autogen_prefix] = offset row["well_name_point_id"] = well_id elif not well_id: raise ValueError("Field required") @@ -420,23 +503,24 @@ def _make_row_models(rows, session): raise ValueError("Duplicate value for well_name_point_id") seen_ids.add(well_id) - model = WellInventoryRow(**row) - models.append(model) - - except ValidationError as e: - for err in e.errors(): - loc = err["loc"] - - field = loc[0] if loc else "composite field error" - value = row.get(field) if loc else None - validation_errors.append( - { - "row": idx + 1, - "error": err["msg"], - "field": field, - "value": value, - } - ) + try: + model = WellInventoryRow(**row) + models.append((row_number, model)) + except ValidationError as e: + for err in e.errors(): + loc = err["loc"] + + field = loc[0] if loc else "composite field error" + value = row.get(field) if loc else None + validation_errors.append( + { + "row": row_number, + "well_id": well_id, + "error": err["msg"], + "field": field, + "value": value, + } + ) except ValueError as e: field = "well_name_point_id" # Map specific controlled errors to safe, non-revealing messages @@ -448,7 +532,7 @@ def _make_row_models(rows, session): error_msg = "Duplicate header row" field = "header" else: - error_msg = "Invalid value" + error_msg = str(e) if field == "header": value = ",".join(row.keys()) @@ -456,7 +540,13 @@ def _make_row_models(rows, session): value = row.get(field) validation_errors.append( - {"row": idx + 1, "field": field, "error": error_msg, "value": value} + { + "row": row_number, + "well_id": row.get("well_name_point_id"), + "field": field, + "error": error_msg, + "value": value, + } ) return models, validation_errors @@ -475,7 +565,7 @@ def _add_field_staff( if not contact: payload = dict(name=fs, role="Technician", organization=org, contact_type=ct) - contact = add_contact(session, payload, user) + contact = add_contact(session, payload, user, commit=False) fec = FieldEventParticipant( field_event=field_event, contact_id=contact.id, participant_role=role @@ -487,6 +577,10 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user) name = model.well_name_point_id date_time = model.date_time + existing_well = _find_existing_imported_well(session, model) + if existing_well is not None: + return existing_well.name + # -------------------- # Location and associated tables # -------------------- @@ -504,11 +598,16 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user) session.add(directions_note) # add data provenance records + elevation_method = ( + model.elevation_method.value + if hasattr(model.elevation_method, "value") + else (model.elevation_method or "Unknown") + ) dp = DataProvenance( target_id=loc.id, target_table="location", field_name="elevation", - collection_method=model.elevation_method, + collection_method=elevation_method, ) session.add(dp) @@ -524,7 +623,11 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user) She indicated that it would be acceptable to use the depth source for the historic depth to water source. """ if model.depth_source: - historic_depth_to_water_source = model.depth_source.lower() + historic_depth_to_water_source = ( + model.depth_source.value + if hasattr(model.depth_source, "value") + else model.depth_source + ).lower() else: historic_depth_to_water_source = "unknown" @@ -539,7 +642,17 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user) (model.contact_special_requests_notes, "General"), (model.well_measuring_notes, "Sampling Procedure"), (model.sampling_scenario_notes, "Sampling Procedure"), + (model.well_notes, "General"), + (model.water_notes, "Water"), (historic_depth_note, "Historical"), + ( + ( + f"Sample possible: {model.sample_possible}" + if model.sample_possible is not None + else None + ), + "Sampling Procedure", + ), ): if note_content is not None: well_notes.append({"content": note_content, "note_type": note_type}) @@ -590,7 +703,16 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user) well_pump_depth=model.well_pump_depth_ft, is_suitable_for_datalogger=model.datalogger_possible, is_open=model.is_open, - well_status=model.well_status, + well_status=( + model.well_status.value + if hasattr(model.well_status, "value") + else model.well_status + ), + monitoring_status=( + model.monitoring_status.value + if hasattr(model.monitoring_status, "value") + else model.monitoring_status + ), notes=well_notes, well_purposes=well_purposes, monitoring_frequencies=monitoring_frequencies, @@ -661,6 +783,77 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user) ) session.add(fa) + if model.measurement_date_time is not None: + # get groundwater level parameter + parameter = ( + session.query(Parameter) + .filter( + Parameter.parameter_name == "groundwater level", + Parameter.matrix == "groundwater", + ) + .first() + ) + + if not parameter: + # this shouldn't happen if initialized properly, but just in case + parameter = Parameter( + parameter_name="groundwater level", + matrix="groundwater", + parameter_type="Field Parameter", + default_unit="ft", + ) + session.add(parameter) + session.flush() + + # create FieldActivity + gwl_field_activity = FieldActivity( + field_event=fe, + activity_type="groundwater level", + notes="Groundwater level measurement activity conducted during well inventory field event.", + ) + session.add(gwl_field_activity) + session.flush() + + # create Sample + sample_method = ( + model.sample_method.value + if hasattr(model.sample_method, "value") + else (model.sample_method or "Unknown") + ) + sample = Sample( + field_activity_id=gwl_field_activity.id, + sample_date=model.measurement_date_time, + sample_name=f"{well.name}-WL-{model.measurement_date_time.strftime('%Y%m%d%H%M')}", + sample_matrix="groundwater", + sample_method=sample_method, + notes=model.water_level_notes, + ) + session.add(sample) + session.flush() + + # create Observation + # TODO: groundwater_level_reason may be conditionally required for null depth_to_water_ft - handle accordingly + observation = Observation( + sample_id=sample.id, + parameter_id=parameter.id, + value=model.depth_to_water_ft, + unit="ft", + observation_datetime=model.measurement_date_time, + measuring_point_height=model.mp_height, + groundwater_level_reason=( + model.level_status.value + if hasattr(model.level_status, "value") + else None + ), + nma_data_quality=( + model.data_quality.value + if hasattr(model.data_quality, "value") + else (model.data_quality or "Unknown") + ), + notes=model.water_level_notes, + ) + session.add(observation) + # ------------------ # Contacts # ------------------ diff --git a/tests/features/data/well-inventory-duplicate-columns.csv b/tests/features/data/well-inventory-duplicate-columns.csv index cf459663..4f743a19 100644 --- a/tests/features/data/well-inventory-duplicate-columns.csv +++ b/tests/features/data/well-inventory-duplicate-columns.csv @@ -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,contact_1_email_1 -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 -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 +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,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,john.smith@example.com +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,emily.davis@example.org diff --git a/tests/features/data/well-inventory-duplicate-header.csv b/tests/features/data/well-inventory-duplicate-header.csv index 40c35980..698fc335 100644 --- a/tests/features/data/well-inventory-duplicate-header.csv +++ b/tests/features/data/well-inventory-duplicate-header.csv @@ -1,5 +1,5 @@ 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,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 -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 +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,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 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_MP1f,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 \ No newline at end of file +Middle Rio Grande Groundwater Monitoring,MRG-001_MP1f,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,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 \ No newline at end of file diff --git a/tests/features/data/well-inventory-duplicate.csv b/tests/features/data/well-inventory-duplicate.csv index 4f8ac75a..514cd6d3 100644 --- a/tests/features/data/well-inventory-duplicate.csv +++ b/tests/features/data/well-inventory-duplicate.csv @@ -1,3 +1,3 @@ project,measuring_point_height_ft,well_name_point_id,site_name,date_time,field_staff,contact_role,utm_easting,utm_northing,utm_zone,elevation_ft,elevation_method -foo,10,WELL001,Site Alpha,2025-02-15T10:30:00,Jane Doe,Owner,250000,4000000,13N,5120.5,LiDAR DEM -foob,10,WELL001,Site Beta,2025-03-20T09:15:00,John Smith,Manager,250000,4000000,13N,5130.7,LiDAR DEM +foo,10,DUPWELL001,Site Alpha,2025-02-15T10:30:00,Jane Doe,Owner,250000,4000000,13N,5120.5,LiDAR DEM +foob,10,DUPWELL001,Site Beta,2025-03-20T09:15:00,John Smith,Manager,250000,4000000,13N,5130.7,LiDAR DEM diff --git a/tests/features/data/well-inventory-invalid-boolean-value-maybe.csv b/tests/features/data/well-inventory-invalid-boolean-value-maybe.csv index 75f3a33e..70d5a7a6 100644 --- a/tests/features/data/well-inventory-invalid-boolean-value-maybe.csv +++ b/tests/features/data/well-inventory-invalid-boolean-value-maybe.csv @@ -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,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 -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,maybe,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 +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,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,maybe,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 diff --git a/tests/features/data/well-inventory-invalid-contact-type.csv b/tests/features/data/well-inventory-invalid-contact-type.csv index f06f5b3b..236e5e03 100644 --- a/tests/features/data/well-inventory-invalid-contact-type.csv +++ b/tests/features/data/well-inventory-invalid-contact-type.csv @@ -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,foo,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 -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 +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,foo,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 diff --git a/tests/features/data/well-inventory-invalid-date-format.csv b/tests/features/data/well-inventory-invalid-date-format.csv index 806573d9..c65d1d8d 100644 --- a/tests/features/data/well-inventory-invalid-date-format.csv +++ b/tests/features/data/well-inventory-invalid-date-format.csv @@ -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,25-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 -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 +Middle Rio Grande Groundwater Monitoring,MRG-001_MP1,Smith Farm Domestic Well,25-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,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 diff --git a/tests/features/data/well-inventory-invalid-date.csv b/tests/features/data/well-inventory-invalid-date.csv index 697f9c29..b5676025 100644 --- a/tests/features/data/well-inventory-invalid-date.csv +++ b/tests/features/data/well-inventory-invalid-date.csv @@ -1,5 +1,5 @@ well_name_point_id,site_name,date_time,field_staff,contact_role,utm_easting,utm_northing,utm_zone,elevation_ft,elevation_method WELL005,Site Alpha,2025-02-30T10:30:0,Jane Doe,Owner,250000,4000000,13N,5120.5,GPS -WELL006,Site Beta,2025-13-20T09:15:00,John Smith,Manager,250000,4000000,13N,5130.7,Survey -WELL007,Site Gamma,not-a-date,Emily Clark,Supervisor,250000,4000000,13N,5150.3,Survey +WELL006,Site Beta,2025-13-20T09:15:00,John Smith,Manager,250000,4000000,13N,5130.7,Survey-grade GPS +WELL007,Site Gamma,not-a-date,Emily Clark,Supervisor,250000,4000000,13N,5150.3,Survey-grade GPS WELL008,Site Delta,2025-04-10 11:00:00,Michael Lee,Technician,250000,4000000,13N,5160.4,GPS diff --git a/tests/features/data/well-inventory-invalid-email.csv b/tests/features/data/well-inventory-invalid-email.csv index 13374bc1..ff67551b 100644 --- a/tests/features/data/well-inventory-invalid-email.csv +++ b/tests/features/data/well-inventory-invalid-email.csv @@ -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,Primary,505-555-0101,Primary,,,john.smithexample.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 -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 +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.smithexample.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,,,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,"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 diff --git a/tests/features/data/well-inventory-invalid-lexicon.csv b/tests/features/data/well-inventory-invalid-lexicon.csv index f9f5dda4..9701bb8f 100644 --- a/tests/features/data/well-inventory-invalid-lexicon.csv +++ b/tests/features/data/well-inventory-invalid-lexicon.csv @@ -1,5 +1,5 @@ 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,contact_role,contact_type -ProjectA,WELL001,Site1,2025-02-15T10:30:00,John Doe,250000,4000000,13N,5000,Survey,2.5,INVALID_ROLE,owner -ProjectB,WELL002,Site2,2025-02-16T11:00:00,Jane Smith,250000,4000000,13N,5100,Survey,2.7,manager,INVALID_TYPE -ProjectC,WELL003,Site3,2025-02-17T09:45:00,Jim Beam,250000,4000000,13N,5200,INVALID_METHOD,2.6,manager,owner -ProjectD,WELL004,Site4,2025-02-18T08:20:00,Jack Daniels,250000,4000000,13N,5300,Survey,2.8,INVALID_ROLE,INVALID_TYPE +ProjectA,WELL001,Site1,2025-02-15T10:30:00,John Doe,250000,4000000,13N,5000,Survey-grade GPS,2.5,INVALID_ROLE,Primary +ProjectB,WELL002,Site2,2025-02-16T11:00:00,Jane Smith,250000,4000000,13N,5100,Survey-grade GPS,2.7,Manager,INVALID_TYPE +ProjectC,WELL003,Site3,2025-02-17T09:45:00,Jim Beam,250000,4000000,13N,5200,INVALID_METHOD,2.6,Manager,Primary +ProjectD,WELL004,Site4,2025-02-18T08:20:00,Jack Daniels,250000,4000000,13N,5300,Survey-grade GPS,2.8,INVALID_ROLE,INVALID_TYPE diff --git a/tests/features/data/well-inventory-invalid-numeric.csv b/tests/features/data/well-inventory-invalid-numeric.csv index 40675dc6..382ea6f5 100644 --- a/tests/features/data/well-inventory-invalid-numeric.csv +++ b/tests/features/data/well-inventory-invalid-numeric.csv @@ -1,6 +1,6 @@ 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 -ProjectA,WELL001,Site1,2025-02-15T10:30:00,John Doe,250000,4000000,13N,5000,Survey,2.5 -ProjectB,WELL002,Site2,2025-02-16T11:00:00,Jane Smith,250000,4000000,13N,5100,Survey,2.7 -ProjectC,WELL003,Site3,2025-02-17T09:45:00,Jim Beam,250000,4000000,13N,5200,Survey,2.6 -ProjectD,WELL004,Site4,2025-02-18T08:20:00,Jack Daniels,250000,4000000,13N,elev_bad,Survey,2.8 -ProjectE,WELL005,Site5,2025-02-19T12:00:00,Jill Hill,250000,4000000,13N,5300,Survey,not_a_height +ProjectA,WELL001,Site1,2025-02-15T10:30:00,John Doe,250000,4000000,13N,5000,Survey-grade GPS,2.5 +ProjectB,WELL002,Site2,2025-02-16T11:00:00,Jane Smith,250000,4000000,13N,5100,Survey-grade GPS,2.7 +ProjectC,WELL003,Site3,2025-02-17T09:45:00,Jim Beam,250000,4000000,13N,5200,Survey-grade GPS,2.6 +ProjectD,WELL004,Site4,2025-02-18T08:20:00,Jack Daniels,250000,4000000,13N,elev_bad,Survey-grade GPS,2.8 +ProjectE,WELL005,Site5,2025-02-19T12:00:00,Jill Hill,250000,4000000,13N,5300,Survey-grade GPS,not_a_height diff --git a/tests/features/data/well-inventory-invalid-partial.csv b/tests/features/data/well-inventory-invalid-partial.csv index 9535fd00..8dcdf3b8 100644 --- a/tests/features/data/well-inventory-invalid-partial.csv +++ b/tests/features/data/well-inventory-invalid-partial.csv @@ -1,4 +1,4 @@ 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_MP3,Smith Farm Domestic Well,2025-02-15T10:30:00,A Lopez,250000,4000000,13N,5250,Survey-grade GPS,1.5,B Chen,,John Smith F,NMBGMR,Owner,Primary,505-555-0101,Primary,,,john.smith@example.com,Primary,,,123 County Rd 7,,Mailing,NM,Los Lunas,87031,,,,,,,Maria Garcia G,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 -Middle Rio Grande Groundwater Monitoring,MRG-003_MP3,Old Orchard Well,2025-01-20T09:00:00,B Chen,250000,4000000,13N,5320,Global positioning system (GPS),1.8,,,Emily Davis G,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 -Middle Rio Grande Groundwater Monitoring,,Old Orchard Well1,2025-01-20T09:00:00,B Chen,250000,4000000,13N,5320,Global positioning system (GPS),1.8,,,Emily Davis F,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 \ No newline at end of file +Middle Rio Grande Groundwater Monitoring,MRG-001_MP3,Smith Farm Domestic Well,2025-02-15T10:30:00,A Lopez,250000,4000000,13N,5250,Survey-grade GPS,1.5,B Chen,,John Smith F,NMBGMR,Owner,Primary,505-555-0101,Primary,,,john.smith@example.com,Primary,,,123 County Rd 7,,Mailing,NM,Los Lunas,87031,,,,,,,Maria Garcia G,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_MP3,Old Orchard Well,2025-01-20T09:00:00,B Chen,250000,4000000,13N,5320,Global positioning system (GPS),1.8,,,Emily Davis G,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 +Middle Rio Grande Groundwater Monitoring,,Old Orchard Well1,2025-01-20T09:00:00,B Chen,250000,4000000,13N,5320,Global positioning system (GPS),1.8,,,Emily Davis F,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 diff --git a/tests/features/data/well-inventory-invalid-phone-number.csv b/tests/features/data/well-inventory-invalid-phone-number.csv index 6e3386f8..2060a8fc 100644 --- a/tests/features/data/well-inventory-invalid-phone-number.csv +++ b/tests/features/data/well-inventory-invalid-phone-number.csv @@ -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,Primary,55-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 -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 +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,55-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,,,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,"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 diff --git a/tests/features/data/well-inventory-invalid-postal-code.csv b/tests/features/data/well-inventory-invalid-postal-code.csv index 337c325d..24d30f59 100644 --- a/tests/features/data/well-inventory-invalid-postal-code.csv +++ b/tests/features/data/well-inventory-invalid-postal-code.csv @@ -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,Primary,505-555-0101,Primary,,,john.smith@example.com,Primary,,,123 County Rd 7,,Mailing,NM,Los Lunas,8731,,,,,,,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 -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,,,Jemily Javis,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 +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,8731,,,,,,,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,,,Jemily Javis,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 diff --git a/tests/features/data/well-inventory-invalid-utm.csv b/tests/features/data/well-inventory-invalid-utm.csv index a1576354..e8f14b2b 100644 --- a/tests/features/data/well-inventory-invalid-utm.csv +++ b/tests/features/data/well-inventory-invalid-utm.csv @@ -1,3 +1,4 @@ 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,457100,4159020,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 -Middle Rio Grande Groundwater Monitoring,MRG-003_MP1,Old Orchard Well,2025-01-20T09:00:00,B Chen,250000,4000000,13S,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 +Middle Rio Grande Groundwater Monitoring,MRG-001_MP1,Smith Farm Domestic Well,2025-02-15T10:30:00,A Lopez,457100,4159020,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,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,13S,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,"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 +Middle Rio Grande Groundwater Monitoring,MRG-005_MP1,Valid 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,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 diff --git a/tests/features/data/well-inventory-missing-address-type.csv b/tests/features/data/well-inventory-missing-address-type.csv index 28ecc032..d7b9846e 100644 --- a/tests/features/data/well-inventory-missing-address-type.csv +++ b/tests/features/data/well-inventory-missing-address-type.csv @@ -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,Primary,505-555-0101,Primary,,,john.smith@example.com,Primary,,,123 County Rd 7,,,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 -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 +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,,,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 diff --git a/tests/features/data/well-inventory-missing-contact-role.csv b/tests/features/data/well-inventory-missing-contact-role.csv index fc475194..a053650d 100644 --- a/tests/features/data/well-inventory-missing-contact-role.csv +++ b/tests/features/data/well-inventory-missing-contact-role.csv @@ -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,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 -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,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 +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 diff --git a/tests/features/data/well-inventory-missing-contact-type.csv b/tests/features/data/well-inventory-missing-contact-type.csv index b4ec4120..d3b41faa 100644 --- a/tests/features/data/well-inventory-missing-contact-type.csv +++ b/tests/features/data/well-inventory-missing-contact-type.csv @@ -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,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 -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 +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 diff --git a/tests/features/data/well-inventory-missing-email-type.csv b/tests/features/data/well-inventory-missing-email-type.csv index 4e1f722c..2354c7e7 100644 --- a/tests/features/data/well-inventory-missing-email-type.csv +++ b/tests/features/data/well-inventory-missing-email-type.csv @@ -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,Primary,505-555-0101,Primary,,,john.smith@example.com,,,,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 -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 +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,,,,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 diff --git a/tests/features/data/well-inventory-missing-phone-type.csv b/tests/features/data/well-inventory-missing-phone-type.csv index 739687f5..649ab568 100644 --- a/tests/features/data/well-inventory-missing-phone-type.csv +++ b/tests/features/data/well-inventory-missing-phone-type.csv @@ -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,Primary,505-555-0101,,,,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 -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 +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,,,,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 diff --git a/tests/features/data/well-inventory-missing-required.csv b/tests/features/data/well-inventory-missing-required.csv index 9105a830..4d9fcdf0 100644 --- a/tests/features/data/well-inventory-missing-required.csv +++ b/tests/features/data/well-inventory-missing-required.csv @@ -1,5 +1,5 @@ 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 -ProjectA,,Site1,2025-02-15T10:30:00,John Doe,250000,4000000,13N,5000,Survey,2.5 -ProjectB,,Site2,2025-02-16T11:00:00,Jane Smith,250000,4000000,13N,5100,Survey,2.7 -ProjectC,WELL003,Site3,2025-02-17T09:45:00,Jim Beam,250000,4000000,13N,5200,Survey,2.6 -ProjectD,,Site4,2025-02-18T08:20:00,Jack Daniels,250000,4000000,13N,5300,Survey,2.8 +ProjectA,,Site1,2025-02-15T10:30:00,John Doe,250000,4000000,13N,5000,Survey-grade GPS,2.5 +ProjectB,,Site2,2025-02-16T11:00:00,Jane Smith,250000,4000000,13N,5100,Survey-grade GPS,2.7 +ProjectC,WELL003,Site3,2025-02-17T09:45:00,Jim Beam,250000,4000000,13N,5200,Survey-grade GPS,2.6 +ProjectD,,Site4,2025-02-18T08:20:00,Jack Daniels,250000,4000000,13N,5300,Survey-grade GPS,2.8 diff --git a/tests/features/data/well-inventory-missing-wl-fields.csv b/tests/features/data/well-inventory-missing-wl-fields.csv index cbfa8546..0908e36f 100644 --- a/tests/features/data/well-inventory-missing-wl-fields.csv +++ b/tests/features/data/well-inventory-missing-wl-fields.csv @@ -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,depth_to_water_ft -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,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,Biannual,Sample only when pump has been off more than 12 hours,Measure before owner starts irrigation,True,100 -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,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,200 +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,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,100 +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,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,200 diff --git a/tests/features/data/well-inventory-valid-comma-in-quotes.csv b/tests/features/data/well-inventory-valid-comma-in-quotes.csv index b66d673e..ab5509a8 100644 --- a/tests/features/data/well-inventory-valid-comma-in-quotes.csv +++ b/tests/features/data/well-inventory-valid-comma-in-quotes.csv @@ -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_MP1D,"""Smith Farm, Domestic Well""",2025-02-15T10:30:00,A Lopez,250000,4000000,13N,5250,Survey-grade GPS,1.5,B Chen,,John Smith T,NMBGMR,Owner,Primary,505-555-0101,Primary,,,john.smith@example.com,Primary,,,123 County Rd 7,,Mailing,NM,Los Lunas,87031,,,,,,,Maria Garcia G,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_MP1D,"Smith Farm, Domestic Well",2025-02-15T10:30:00,A Lopez,250000,4000000,13N,5250,Survey-grade GPS,1.5,B Chen,,John Smith T,NMBGMR,Owner,Primary,505-555-0101,Primary,,,john.smith@example.com,Primary,,,123 County Rd 7,,Mailing,NM,Los Lunas,87031,,,,,,,Maria Garcia G,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_MP1G,Old Orchard Well,2025-01-20T09:00:00,B Chen,250000,4000000,13N,5320,Global positioning system (GPS),1.8,,,Emily Davis E,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,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 diff --git a/tests/features/environment.py b/tests/features/environment.py index 9813c38f..9cdff0d6 100644 --- a/tests/features/environment.py +++ b/tests/features/environment.py @@ -17,6 +17,11 @@ import random from datetime import datetime, timedelta +# Default BDD runs to the local test database before any db module imports. +# Allow explicit CI/local environment configuration to override these values. +os.environ.setdefault("POSTGRES_DB", "ocotilloapi_test") +os.environ.setdefault("POSTGRES_PORT", "5432") + from alembic import command from alembic.config import Config from sqlalchemy import select diff --git a/tests/features/steps/cli_common.py b/tests/features/steps/cli_common.py index 1483db09..03b8077a 100644 --- a/tests/features/steps/cli_common.py +++ b/tests/features/steps/cli_common.py @@ -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 ============================================= diff --git a/tests/features/steps/well-inventory-csv-given.py b/tests/features/steps/well-inventory-csv-given.py index 1d753cb9..aeee0232 100644 --- a/tests/features/steps/well-inventory-csv-given.py +++ b/tests/features/steps/well-inventory-csv-given.py @@ -29,17 +29,57 @@ def _set_file_content(context: Context, name): def _set_file_content_from_path(context: Context, path: Path, name: str | None = None): context.file_path = path - with open(path, "r", encoding="utf-8", newline="") as f: - context.file_name = name or path.name - context.file_content = f.read() - if context.file_name.endswith(".csv"): - context.rows = list(csv.DictReader(context.file_content.splitlines())) - context.row_count = len(context.rows) - context.file_type = "text/csv" + import hashlib + + context.file_name = name or path.name + + if path.suffix == ".csv" and path.exists() and path.stat().st_size > 0: + suffix = hashlib.md5(context.scenario.name.encode()).hexdigest()[:6] + with open(path, "r", encoding="utf-8", newline="") as f: + rows = list(csv.reader(f)) + + if rows: + header = rows[0] + well_id_indexes = [ + idx + for idx, column_name in enumerate(header) + if column_name == "well_name_point_id" + ] + for row in rows[1:]: + # Preserve repeated header rows and duplicate-column fixtures so + # structural CSV scenarios still reach the importer unchanged. + if row == header: + continue + + for idx in well_id_indexes: + if idx >= len(row): + continue + value = row[idx] + if ( + value + and str(value).strip() != "" + and not str(value).lower().endswith("-xxxx") + ): + row[idx] = f"{value}_{suffix}" + + buffer = StringIO() + csv.writer(buffer).writerows(rows) + context.file_content = buffer.getvalue() + context.rows = list(csv.DictReader(context.file_content.splitlines())) + context.row_count = len(context.rows) + context.file_type = "text/csv" + else: + # For empty files or non-CSV files, don't use pandas + if path.exists(): + with open(path, "r", encoding="utf-8", newline="") as f: + context.file_content = f.read() else: - context.rows = [] - context.row_count = 0 - context.file_type = "text/plain" + context.file_content = "" + context.rows = [] + context.row_count = 0 + context.file_type = ( + "text/csv" if context.file_name.endswith(".csv") else "text/plain" + ) @given( @@ -243,7 +283,11 @@ def step_given_my_csv_file_contains_a_row_missing_the_required_required( ): _set_file_content(context, "well-inventory-valid.csv") - df = pd.read_csv(context.file_path, dtype={"contact_2_address_1_postal_code": str}) + df = pd.read_csv( + context.file_path, + dtype={"contact_2_address_1_postal_code": str}, + keep_default_na=False, + ) df = df.drop(required_field, axis=1) buffer = StringIO() @@ -274,7 +318,26 @@ def step_step_step_16(context: Context): def _get_valid_df(context: Context) -> pd.DataFrame: _set_file_content(context, "well-inventory-valid.csv") - df = pd.read_csv(context.file_path, dtype={"contact_2_address_1_postal_code": str}) + df = pd.read_csv( + context.file_path, + dtype={"contact_2_address_1_postal_code": str}, + keep_default_na=False, + ) + + # Add unique suffix to well names to ensure isolation between scenarios + # using a simple hash of the scenario name + import hashlib + + suffix = hashlib.md5(context.scenario.name.encode()).hexdigest()[:6] + if "well_name_point_id" in df.columns: + df["well_name_point_id"] = df["well_name_point_id"].apply( + lambda x: ( + f"{x}_{suffix}" + if x and str(x).strip() != "" and not str(x).lower().endswith("-xxxx") + else x + ) + ) + return df @@ -385,7 +448,9 @@ def step_given_row_contains_invalid_state_value(context: Context): ) def step_given_row_contains_invalid_well_hole_status_value(context: Context): df = _get_valid_df(context) - if "well_status" in df.columns: + if "well_hole_status" in df.columns: + df.loc[0, "well_hole_status"] = "NotARealWellHoleStatus" + elif "well_status" in df.columns: df.loc[0, "well_status"] = "NotARealWellHoleStatus" _set_content_from_df(context, df) @@ -414,11 +479,14 @@ def step_given_row_contains_invalid_well_pump_type_value(context: Context): ) def step_given_row_contains_contact_fields_but_name_and_org_are_blank(context: Context): df = _get_valid_df(context) + # Keep row 2 unchanged so row 1's invalid contact is the only expected error. df.loc[0, "contact_1_name"] = "" df.loc[0, "contact_1_organization"] = "" + # Keep other contact data present so composite contact validation is exercised. df.loc[0, "contact_1_role"] = "Owner" df.loc[0, "contact_1_type"] = "Primary" + _set_content_from_df(context, df) diff --git a/tests/features/steps/well-inventory-csv-validation-error.py b/tests/features/steps/well-inventory-csv-validation-error.py index 928c95e7..0c390009 100644 --- a/tests/features/steps/well-inventory-csv-validation-error.py +++ b/tests/features/steps/well-inventory-csv-validation-error.py @@ -21,14 +21,28 @@ def _handle_validation_error(context, expected_errors): response_json = context.response.json() validation_errors = response_json.get("validation_errors", []) - assert len(validation_errors) == len( - expected_errors - ), f"Expected {len(expected_errors)} validation errors, got {len(validation_errors)}" - for v, e in zip(validation_errors, expected_errors): - assert v["field"] == e["field"], f"Expected {e['field']} for {v['field']}" - assert v["error"] == e["error"], f"Expected {e['error']} for {v['error']}" - if "value" in e: - assert v["value"] == e["value"], f"Expected {e['value']} for {v['value']}" + + def _matches(expected, actual): + field_match = str(expected.get("field", "")) in str(actual.get("field", "")) + error_match = str(expected.get("error", "")) in str(actual.get("error", "")) + return field_match and error_match + + def _find_match(expected_idx: int, used_indices: set[int]) -> bool: + if expected_idx == len(expected_errors): + return True + + expected = expected_errors[expected_idx] + for actual_idx, actual in enumerate(validation_errors): + if actual_idx in used_indices or not _matches(expected, actual): + continue + if _find_match(expected_idx + 1, used_indices | {actual_idx}): + return True + return False + + assert _find_match(0, set()), ( + f"Expected at least {len(expected_errors)} distinct validation error matches for " + f"{expected_errors}. Got: {validation_errors}" + ) def _assert_any_validation_error_contains( @@ -127,7 +141,7 @@ def step_step_step_5(context): expected_errors = [ { "field": "composite field error", - "error": "Value error, contact_1_role must be provided if name is provided", + "error": "Value error, contact_1_role is required when contact fields are provided", } ] _handle_validation_error(context, expected_errors) @@ -173,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 must be provided if name is 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) @@ -252,9 +279,17 @@ def step_then_response_includes_invalid_state_error(context: Context): 'the response includes a validation error indicating an invalid "well_hole_status" value' ) def step_then_response_includes_invalid_well_hole_status_error(context: Context): - _assert_any_validation_error_contains( - context, "Database error", "database error occurred" + response_json = context.response.json() + validation_errors = response_json.get("validation_errors", []) + assert validation_errors, "Expected at least one validation error" + found = any( + str(error.get("field", "")) in {"well_hole_status", "well_status"} + and "Input should be" in str(error.get("error", "")) + for error in validation_errors ) + assert ( + found + ), f"Expected well_hole_status/well_status validation error. Got: {validation_errors}" @then( @@ -280,18 +315,19 @@ def step_then_response_includes_invalid_well_pump_type_error(context: Context): def step_then_response_includes_contact_name_or_org_required_error(context: Context): response_json = context.response.json() validation_errors = response_json.get("validation_errors", []) - assert validation_errors, "Expected at least one validation error" + assert validation_errors, f"Expected validation errors, got: {response_json}" found = any( "composite field error" in str(err.get("field", "")) and ( - "contact_1_name is required" in str(err.get("error", "")) - or "contact_1_organization is required" in str(err.get("error", "")) + "At least one of contact_1_name or contact_1_organization must be provided" + in str(err.get("error", "")) ) for err in validation_errors ) + assert ( found - ), "Expected contact validation error requiring contact_1_name or contact_1_organization" + ), f"Expected contact validation error requiring contact_1_name or contact_1_organization. Got: {validation_errors}" @then( @@ -299,7 +335,9 @@ def step_then_response_includes_contact_name_or_org_required_error(context: Cont ) def step_then_response_includes_water_level_datetime_required_error(context: Context): _assert_any_validation_error_contains( - context, "composite field error", "All water level fields must be provided" + context, + "composite field error", + "water_level_date_time is required when depth_to_water_ft is provided", ) diff --git a/tests/features/steps/well-inventory-csv.py b/tests/features/steps/well-inventory-csv.py index 8b23b0be..cf5b658e 100644 --- a/tests/features/steps/well-inventory-csv.py +++ b/tests/features/steps/well-inventory-csv.py @@ -244,11 +244,20 @@ def step_then_the_response_identifies_the_row_and_field_for_each_error( assert "field" in error, "Expected validation error to include field name" -@then("no wells are imported") -def step_then_no_wells_are_imported(context: Context): +@then("{count:d} wells are imported") +@then("{count:d} well is imported") +def step_then_count_wells_are_imported(context: Context, count: int): response_json = context.response.json() wells = response_json.get("wells", []) - assert len(wells) == 0, "Expected no wells to be imported" + validation_errors = response_json.get("validation_errors", []) + assert ( + len(wells) == count + ), f"Expected {count} wells to be imported, but got {len(wells)}: {wells}. Errors: {validation_errors}" + + +@then("no wells are imported") +def step_then_no_wells_are_imported(context: Context): + step_then_count_wells_are_imported(context, 0) @then("the response includes validation errors indicating duplicated values") @@ -364,8 +373,10 @@ def step_then_the_response_includes_a_validation_error_for_the_required_field( response_json = context.response.json() assert "validation_errors" in response_json, "Expected validation errors" vs = response_json["validation_errors"] - assert len(vs) == 2, "Expected 2 validation error" - assert vs[0]["field"] == required_field + assert len(vs) >= 1, "Expected at least 1 validation error" + assert any( + v["field"] == required_field for v in vs + ), f"Expected validation error for {required_field}, but got {vs}" @then("the response includes an error message indicating the row limit was exceeded") diff --git a/tests/features/well-inventory-csv.feature b/tests/features/well-inventory-csv.feature index 1500a5f9..ee094ef2 100644 --- a/tests/features/well-inventory-csv.feature +++ b/tests/features/well-inventory-csv.feature @@ -205,7 +205,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating the invalid postal code format - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact with an invalid phone number format @@ -213,7 +213,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating the invalid phone number format - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact with an invalid email format @@ -221,15 +221,15 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating the invalid email format - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD - Scenario: Upload fails when a row has a contact without a contact_role + 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 a non-zero exit code - And the response includes a validation error indicating the missing "contact_role" field - And no wells are imported + And the response includes a validation error indicating the missing "contact_role" value + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact without a "contact_type" @@ -237,7 +237,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating the missing "contact_type" value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact with an invalid "contact_type" @@ -245,7 +245,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating an invalid "contact_type" value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact with an email without an email_type @@ -253,7 +253,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating the missing "email_type" value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact with a phone without a phone_type @@ -261,7 +261,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating the missing "phone_type" value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact with an address without an address_type @@ -269,7 +269,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating the missing "address_type" value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact with an invalid "address_type" @@ -277,7 +277,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating an invalid "address_type" value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact with an invalid state abbreviation @@ -285,7 +285,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating an invalid state value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has an invalid well_hole_status value @@ -293,7 +293,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating an invalid "well_hole_status" value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has an invalid monitoring_status value @@ -301,7 +301,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating an invalid "monitoring_status" value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has an invalid well_pump_type value @@ -309,7 +309,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating an invalid "well_pump_type" value - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has utm_easting utm_northing and utm_zone values that are not within New Mexico @@ -317,7 +317,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating the invalid UTM coordinates - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when a row has a contact with neither contact_name nor contact_organization @@ -325,7 +325,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating that at least one of "contact_1_name" or "contact_1_organization" must be provided - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when water_level_date_time is missing but depth_to_water_ft is provided @@ -360,7 +360,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating an invalid boolean value for the "is_open" field - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails when duplicate well_name_point_id values are present @@ -369,7 +369,7 @@ Feature: Bulk upload well inventory from CSV via CLI Then the command exits with a non-zero exit code And the response includes validation errors indicating duplicated values And each error identifies the row and field - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails due to invalid lexicon values @@ -377,7 +377,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes validation errors identifying the invalid field and row - And no wells are imported + And 3 wells are imported @negative @validation @BDMS-TBD Scenario: Upload fails due to invalid date formats @@ -385,7 +385,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes validation errors identifying the invalid field and row - And no wells are imported + And 1 well is imported @negative @validation @BDMS-TBD Scenario: Upload fails due to invalid numeric fields @@ -393,7 +393,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes validation errors identifying the invalid field and row - And no wells are imported + And 3 wells are imported ########################################################################### @@ -442,7 +442,7 @@ Feature: Bulk upload well inventory from CSV via CLI When I run the well inventory bulk upload command Then the command exits with a non-zero exit code And the response includes a validation error indicating a repeated header row - And no wells are imported + And 3 wells are imported @negative @validation @header_row @BDMS-TBD Scenario: Upload fails when the header row contains duplicate column names diff --git a/tests/test_well_inventory.py b/tests/test_well_inventory.py index 94561a5c..265832e6 100644 --- a/tests/test_well_inventory.py +++ b/tests/test_well_inventory.py @@ -14,11 +14,15 @@ import pytest from cli.service_adapter import well_inventory_csv from core.constants import SRID_UTM_ZONE_13N, SRID_WGS84 +from core.enums import Role, ContactType from db import ( Base, Location, LocationThingAssociation, Thing, + Group, + Sample, + Observation, Contact, ThingContactAssociation, FieldEvent, @@ -26,10 +30,27 @@ FieldEventParticipant, ) from db.engine import session_ctx +from schemas.well_inventory import WellInventoryRow from services.util import transform_srid, convert_ft_to_m from shapely import Point +def _minimal_valid_well_inventory_row(): + return { + "project": "Test Project", + "well_name_point_id": "TEST-0001", + "site_name": "Test Site", + "date_time": "2025-02-15T10:30:00", + "field_staff": "Test Staff", + "utm_easting": 357000, + "utm_northing": 3784000, + "utm_zone": "13N", + "elevation_ft": 5000, + "elevation_method": "Global positioning system (GPS)", + "measuring_point_height_ft": 3.5, + } + + def _reset_well_inventory_tables() -> None: with session_ctx() as session: for table in reversed(Base.metadata.sorted_tables): @@ -48,7 +69,7 @@ def isolate_well_inventory_tables(): _reset_well_inventory_tables() -def test_well_inventory_db_contents(): +def test_well_inventory_db_contents_no_waterlevels(): """ Test that the well inventory upload creates the correct database contents. @@ -151,6 +172,7 @@ def test_well_inventory_db_contents(): [ file_content["well_measuring_notes"], file_content["sampling_scenario_notes"], + f"Sample possible: {file_content['sample_possible']}", ] ) assert sorted(c.content for c in thing._get_notes("Historical")) == sorted( @@ -437,6 +459,177 @@ def test_well_inventory_db_contents(): assert participant.participant.name == file_content["field_staff_2"] +def test_well_inventory_db_contents_with_waterlevels(tmp_path): + """ + Tests that the following records are made: + + - field event + - field activity for well inventory + - field activity for water level measurement + - field participants + - contact + - location + - thing + - sample + - observation + + """ + row = _minimal_valid_well_inventory_row() + row.update( + { + "water_level_date_time": "2025-02-15T10:30:00", + "depth_to_water_ft": "8", + "sample_method": "Steel-tape measurement", + "data_quality": "Water level accurate to within two hundreths of a foot", + "water_level_notes": "Attempted measurement", + "mp_height_ft": 2.5, + "level_status": "Water level not affected", + } + ) + file_path = tmp_path / "well-inventory-blank-depth.csv" + with file_path.open("w", encoding="utf-8", newline="") as f: + writer = csv.DictWriter(f, fieldnames=list(row.keys())) + writer.writeheader() + writer.writerow(row) + + result = well_inventory_csv(file_path) + assert result.exit_code == 0, result.stderr + + with session_ctx() as session: + field_events = session.query(FieldEvent).all() + field_activities = session.query(FieldActivity).all() + field_event_participants = session.query(FieldEventParticipant).all() + contacts = session.query(Contact).all() + locations = session.query(Location).all() + things = session.query(Thing).all() + samples = session.query(Sample).all() + observations = session.query(Observation).all() + + assert len(field_events) == 1 + assert len(field_activities) == 2 + activity_types = {fa.activity_type for fa in field_activities} + assert activity_types == { + "well inventory", + "groundwater level", + }, f"Unexpected activity types: {activity_types}" + gwl_field_activity = next( + (fa for fa in field_activities if fa.activity_type == "groundwater level"), + None, + ) + assert gwl_field_activity is not None + + assert len(field_event_participants) == 1 + assert len(contacts) == 1 + assert len(locations) == 1 + assert len(things) == 1 + assert len(samples) == 1 + sample = samples[0] + assert sample.field_activity == gwl_field_activity + assert len(observations) == 1 + observation = observations[0] + assert observation.sample == sample + + +def test_blank_depth_to_water_still_creates_water_level_records(tmp_path): + """Blank depth-to-water is treated as missing while preserving the attempted measurement.""" + row = _minimal_valid_well_inventory_row() + row.update( + { + "water_level_date_time": "2025-02-15T10:30:00", + "depth_to_water_ft": "", + "sample_method": "Steel-tape measurement", + "data_quality": "Water level accurate to within two hundreths of a foot", + "water_level_notes": "Attempted measurement", + "mp_height_ft": 2.5, + } + ) + + file_path = tmp_path / "well-inventory-blank-depth.csv" + with file_path.open("w", encoding="utf-8", newline="") as f: + writer = csv.DictWriter(f, fieldnames=list(row.keys())) + writer.writeheader() + writer.writerow(row) + + result = well_inventory_csv(file_path) + assert result.exit_code == 0, result.stderr + + with session_ctx() as session: + samples = session.query(Sample).all() + observations = session.query(Observation).all() + + assert len(samples) == 1 + assert len(observations) == 1 + assert samples[0].sample_date == datetime.fromisoformat("2025-02-15T10:30:00Z") + assert observations[0].observation_datetime == datetime.fromisoformat( + "2025-02-15T10:30:00Z" + ) + assert observations[0].value is None + assert observations[0].measuring_point_height == 2.5 + + +def test_rerunning_same_well_inventory_csv_is_idempotent(): + """Re-importing the same CSV should not create duplicate well inventory records.""" + file = Path("tests/features/data/well-inventory-valid.csv") + assert file.exists(), "Test data file does not exist." + + first = well_inventory_csv(file) + assert first.exit_code == 0, first.stderr + + with session_ctx() as session: + counts_after_first = { + "things": session.query(Thing).count(), + "field_events": session.query(FieldEvent).count(), + "field_activities": session.query(FieldActivity).count(), + "samples": session.query(Sample).count(), + "observations": session.query(Observation).count(), + } + + second = well_inventory_csv(file) + assert second.exit_code == 0, second.stderr + + with session_ctx() as session: + counts_after_second = { + "things": session.query(Thing).count(), + "field_events": session.query(FieldEvent).count(), + "field_activities": session.query(FieldActivity).count(), + "samples": session.query(Sample).count(), + "observations": session.query(Observation).count(), + } + + assert counts_after_second == counts_after_first + + +def test_failed_project_rows_do_not_create_empty_group(tmp_path): + row = _minimal_valid_well_inventory_row() + row.update( + { + "project": "Project Without Successful Rows", + "repeat_measurement_permission": True, + } + ) + + file_path = tmp_path / "well-inventory-failed-project.csv" + with file_path.open("w", encoding="utf-8", newline="") as f: + writer = csv.DictWriter(f, fieldnames=list(row.keys())) + writer.writeheader() + writer.writerow(row) + + result = well_inventory_csv(file_path) + assert result.exit_code == 1, result.stderr + + with session_ctx() as session: + group = ( + session.query(Group) + .filter( + Group.name == "Project Without Successful Rows", + Group.group_type == "Monitoring Plan", + ) + .one_or_none() + ) + + assert group is None + + # ============================================================================= # Error Handling Tests - Cover API error paths # ============================================================================= @@ -690,8 +883,8 @@ def test_make_contact_with_full_info(self): model.contact_special_requests_notes = "Call before visiting" model.contact_1_name = "John Doe" model.contact_1_organization = "Test Org" - model.contact_1_role = "Owner" - model.contact_1_type = "Primary" + model.contact_1_role = Role.Owner + model.contact_1_type = ContactType.Primary model.contact_1_email_1 = "john@example.com" model.contact_1_email_1_type = "Work" model.contact_1_email_2 = None @@ -727,15 +920,38 @@ def test_make_contact_with_full_info(self): assert len(contact_dict["addresses"]) == 1 assert len(contact_dict["notes"]) == 2 - def test_make_contact_with_no_name(self): - """Test contact dict returns None when name is empty.""" + def test_make_contact_with_no_name_or_organization(self): + """Test contact dict returns None when name and organization are empty.""" from services.well_inventory_csv import _make_contact from unittest.mock import MagicMock model = MagicMock() model.result_communication_preference = None model.contact_special_requests_notes = None - model.contact_1_name = None # No name provided + model.contact_1_name = None + model.contact_1_organization = None + model.contact_1_role = None + model.contact_1_type = None + model.contact_1_email_1 = None + model.contact_1_email_1_type = None + model.contact_1_email_2 = None + model.contact_1_email_2_type = None + model.contact_1_phone_1 = None + model.contact_1_phone_1_type = None + model.contact_1_phone_2 = None + model.contact_1_phone_2_type = None + model.contact_1_address_1_line_1 = None + model.contact_1_address_1_line_2 = None + model.contact_1_address_1_city = None + model.contact_1_address_1_state = None + model.contact_1_address_1_postal_code = None + model.contact_1_address_1_type = None + model.contact_1_address_2_line_1 = None + model.contact_1_address_2_line_2 = None + model.contact_1_address_2_city = None + model.contact_1_address_2_state = None + model.contact_1_address_2_postal_code = None + model.contact_1_address_2_type = None well = MagicMock() well.id = 1 @@ -744,6 +960,53 @@ def test_make_contact_with_no_name(self): assert contact_dict is None + def test_make_contact_with_organization_only(self): + """Test contact dict creation when organization is present without a name.""" + from services.well_inventory_csv import _make_contact + from unittest.mock import MagicMock + + model = MagicMock() + model.result_communication_preference = None + model.contact_special_requests_notes = None + model.contact_1_name = None + model.contact_1_organization = "Test Org" + model.contact_1_role = Role.Owner + model.contact_1_type = ContactType.Primary + model.contact_1_email_1 = None + model.contact_1_email_1_type = None + model.contact_1_email_2 = None + model.contact_1_email_2_type = None + model.contact_1_phone_1 = None + model.contact_1_phone_1_type = None + model.contact_1_phone_2 = None + model.contact_1_phone_2_type = None + model.contact_1_address_1_line_1 = None + model.contact_1_address_1_line_2 = None + model.contact_1_address_1_city = None + model.contact_1_address_1_state = None + model.contact_1_address_1_postal_code = None + model.contact_1_address_1_type = None + model.contact_1_address_2_line_1 = None + model.contact_1_address_2_line_2 = None + model.contact_1_address_2_city = None + model.contact_1_address_2_state = None + model.contact_1_address_2_postal_code = None + model.contact_1_address_2_type = None + + well = MagicMock() + well.id = 1 + + contact_dict = _make_contact(model, well, 1) + + assert contact_dict is not None + assert contact_dict["name"] is None + assert contact_dict["organization"] == "Test Org" + assert contact_dict["thing_id"] == 1 + assert contact_dict["emails"] == [] + assert contact_dict["phones"] == [] + assert contact_dict["addresses"] == [] + assert contact_dict["notes"] == [] + def test_make_well_permission(self): """Test well permission creation.""" from services.well_inventory_csv import _make_well_permission @@ -839,10 +1102,12 @@ def test_extract_autogen_prefix_pattern(self): assert _extract_autogen_prefix("XY-") == "XY-" assert _extract_autogen_prefix("AB-") == "AB-" - # New supported form (2-3 uppercase letter prefixes) + # Placeholder tokens are accepted case-insensitively and normalized. assert _extract_autogen_prefix("WL-XXXX") == "WL-" assert _extract_autogen_prefix("SAC-XXXX") == "SAC-" assert _extract_autogen_prefix("ABC -xxxx") == "ABC-" + assert _extract_autogen_prefix("wl-xxxx") == "WL-" + assert _extract_autogen_prefix("abc - XXXX") == "ABC-" # Blank values use default prefix assert _extract_autogen_prefix("") == "NM-" @@ -854,7 +1119,6 @@ def test_extract_autogen_prefix_pattern(self): assert _extract_autogen_prefix("X-") is None assert _extract_autogen_prefix("123-") is None assert _extract_autogen_prefix("USER-XXXX") is None - assert _extract_autogen_prefix("wl-xxxx") is None def test_make_row_models_missing_well_name_point_id_column_errors(self): """Missing well_name_point_id column should fail validation (blank cell is separate).""" @@ -915,6 +1179,103 @@ def test_group_query_with_multiple_conditions(self): session.commit() +class TestWellInventoryRowAliases: + """Schema alias handling for well inventory CSV field names.""" + + def test_well_status_accepts_well_hole_status_alias(self): + row = _minimal_valid_well_inventory_row() + row["well_hole_status"] = "Abandoned" + + model = WellInventoryRow(**row) + + assert model.well_status.value == "Abandoned" + + def test_invalid_well_status_alias_raises_validation_error(self): + row = _minimal_valid_well_inventory_row() + row["well_hole_status"] = "NotARealWellHoleStatus" + + with pytest.raises(ValueError, match="Input should be"): + WellInventoryRow(**row) + + def test_water_level_aliases_are_mapped(self): + row = _minimal_valid_well_inventory_row() + row.update( + { + "measuring_person": "Tech 1", + "sample_method": "Steel-tape measurement", + "water_level_date_time": "2025-02-15T10:30:00", + "mp_height_ft": 2.5, + "level_status": "Other conditions exist that would affect the level (remarks)", + "depth_to_water_ft": 11.2, + "data_quality": "Water level accurate to within two hundreths of a foot", + "water_level_notes": "Initial reading", + } + ) + + model = WellInventoryRow(**row) + + assert model.sampler == "Tech 1" + assert model.measurement_date_time == datetime.fromisoformat( + "2025-02-15T10:30:00" + ) + assert model.mp_height == 2.5 + assert model.depth_to_water_ft == 11.2 + assert model.water_level_notes == "Initial reading" + + def test_blank_depth_to_water_is_treated_as_none(self): + row = _minimal_valid_well_inventory_row() + row.update( + { + "water_level_date_time": "2025-02-15T10:30:00", + "depth_to_water_ft": "", + } + ) + + model = WellInventoryRow(**row) + + assert model.measurement_date_time == datetime.fromisoformat( + "2025-02-15T10:30:00" + ) + assert model.depth_to_water_ft is None + + def test_blank_contact_organization_is_treated_as_none(self): + row = _minimal_valid_well_inventory_row() + row["contact_1_name"] = "Test Contact" + row["contact_1_organization"] = "" + row["contact_1_role"] = "Owner" + row["contact_1_type"] = "Primary" + + model = WellInventoryRow(**row) + + assert model.contact_1_name == "Test Contact" + assert model.contact_1_organization is None + + def test_blank_well_status_is_treated_as_none(self): + row = _minimal_valid_well_inventory_row() + row["well_hole_status"] = "" + + model = WellInventoryRow(**row) + + assert model.well_status is None + + def test_whitespace_only_well_status_is_treated_as_none(self): + row = _minimal_valid_well_inventory_row() + row["well_hole_status"] = " " + + model = WellInventoryRow(**row) + + assert model.well_status is None + + def test_canonical_name_wins_when_alias_and_canonical_present(self): + row = _minimal_valid_well_inventory_row() + row["well_status"] = "Abandoned" + row["well_hole_status"] = "Inactive, exists but not used" + + model = WellInventoryRow(**row) + + assert model.well_status.value == "Abandoned" + + class TestWellInventoryAPIEdgeCases: """Additional edge case tests for API endpoints."""