diff --git a/core/lexicon.json b/core/lexicon.json index 90ead61b9..d25eae897 100644 --- a/core/lexicon.json +++ b/core/lexicon.json @@ -335,12 +335,18 @@ {"categories": ["status_type"], "term": "Well Status", "definition": "Defines the well's operational condition as reported by the owner"}, {"categories": ["status_type"], "term": "Monitoring Status", "definition": "Defines the well's current monitoring status by NMBGMR."}, {"categories": ["status_type"], "term": "Access Status", "definition": "Defines the well's access status for field personnel."}, + {"categories": ["status_type"], "term": "Open Status", "definition": "Defines if the well is open or closed"}, + {"categories": ["status_type"], "term": "Datalogger Suitability Status", "definition": "Defines if a datalogger can or cannot be installed at the well"}, {"categories": ["status_value"], "term": "Abandoned", "definition": "The well has been properly decommissioned."}, {"categories": ["status_value"], "term": "Active, pumping well", "definition": "This well is in use."}, {"categories": ["status_value"], "term": "Destroyed, exists but not usable", "definition": "The well structure is physically present but is damaged, collapsed, or otherwise compromised to the point that it is non-functional."}, {"categories": ["status_value"], "term": "Inactive, exists but not used", "definition": "The well is not currently in use but is believed to be in a usable condition; it has not been permanently decommissioned/abandoned."}, {"categories": ["status_value"], "term": "Currently monitored", "definition": "The well is currently being monitored by AMMP."}, {"categories": ["status_value"], "term": "Not currently monitored", "definition": "The well is not currently being monitored by AMMP."}, + {"categories": ["status_value"], "term": "Open", "definition": "The well is open."}, + {"categories": ["status_value"], "term": "Closed", "definition": "The well is closed."}, + {"categories": ["status_value"], "term": "Datalogger can be installed", "definition": "A datalogger can be installed at the well"}, + {"categories": ["status_value"], "term": "Datalogger cannot be installed", "definition": "A datalogger cannot be installed at the well"}, {"categories": ["sample_method"], "term": "Airline measurement", "definition": "Airline measurement"}, {"categories": ["sample_method"], "term": "Analog or graphic recorder", "definition": "Analog or graphic recorder"}, {"categories": ["sample_method"], "term": "Calibrated airline measurement", "definition": "Calibrated airline measurement"}, diff --git a/db/thing.py b/db/thing.py index 35d7482ba..0c2754d6f 100644 --- a/db/thing.py +++ b/db/thing.py @@ -394,6 +394,32 @@ def monitoring_status(self) -> str | None: ) return latest_status.status_value if latest_status else None + @property + def open_status(self) -> str | None: + """ + Returns the open status from the most recent status history entry + where status_type is "Open Status". + + Since status_history is eagerly loaded, this should not introduce N+1 query issues. + """ + latest_status = retrieve_latest_polymorphic_history_table_record( + self, "status_history", "Open Status" + ) + return latest_status.status_value if latest_status else None + + @property + def datalogger_suitability_status(self) -> str | None: + """ + Returns the datalogger installation status from the most recent status history entry + where status_type is "Datalogger Suitability Status". + + Since status_history is eagerly loaded, this should not introduce N+1 query issues. + """ + latest_status = retrieve_latest_polymorphic_history_table_record( + self, "status_history", "Datalogger Suitability Status" + ) + return latest_status.status_value if latest_status else None + @property def measuring_point_height(self) -> int | None: """ diff --git a/docker-compose.yml b/docker-compose.yml index 1c6dec4ef..30d22b9d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,7 @@ services: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} - POSTGRES_HOST=db + - POSTGRES_PORT=5432 - MODE=${MODE} - AUTHENTIK_DISABLE_AUTHENTICATION=${AUTHENTIK_DISABLE_AUTHENTICATION} ports: diff --git a/schemas/thing.py b/schemas/thing.py index 9f2a084e3..a2b089089 100644 --- a/schemas/thing.py +++ b/schemas/thing.py @@ -138,7 +138,6 @@ class CreateWell(CreateBaseThing, ValidateWell): well_construction_method: WellConstructionMethod | None = None well_construction_method_source: str | None = None well_pump_type: WellPumpType | None = None - is_suitable_for_datalogger: bool | None formation_completion_code: FormationCode | None = None @@ -238,8 +237,9 @@ class WellResponse(BaseThingResponse): well_pump_type: WellPumpType | None well_pump_depth: float | None well_pump_depth_unit: str = "ft" - is_suitable_for_datalogger: bool | None well_status: str | None + open_status: str | None + datalogger_suitability_status: str | None measuring_point_height: float measuring_point_height_unit: str = "ft" measuring_point_description: str | None diff --git a/tests/features/environment.py b/tests/features/environment.py index 123bc588f..64645d1c1 100644 --- a/tests/features/environment.py +++ b/tests/features/environment.py @@ -501,9 +501,9 @@ def add_geologic_formation(context, session, formation_code, well): def before_all(context): context.objects = {} - rebuild = False + rebuild = True # rebuild = True - erase_data = True + erase_data = False if rebuild: erase_and_rebuild_db() elif erase_data: @@ -581,14 +581,31 @@ def before_all(context): target_table="thing", ) - for value, start, end in ( - ("Currently monitored", datetime(2020, 1, 1), datetime(2021, 1, 1)), - ("Not currently monitored", datetime(2021, 1, 1), None), + for value, status_type, start, end in ( + ( + "Currently monitored", + "Monitoring Status", + datetime(2020, 1, 1), + datetime(2021, 1, 1), + ), + ( + "Not currently monitored", + "Monitoring Status", + datetime(2021, 1, 1), + None, + ), + ("Open", "Open Status", datetime(2020, 1, 1), None), + ( + "Datalogger can be installed", + "Datalogger Suitability Status", + datetime(2020, 1, 1), + None, + ), ): add_status_history( context, session, - status_type="Monitoring Status", + status_type=status_type, status_value=value, start_date=start, end_date=end, diff --git a/tests/features/steps/well-additional-information.py b/tests/features/steps/well-additional-information.py index 8b00f7eb7..8eecef159 100644 --- a/tests/features/steps/well-additional-information.py +++ b/tests/features/steps/well-additional-information.py @@ -221,10 +221,15 @@ def step_impl(context): "the response should include whether the well is open and suitable for a datalogger" ) def step_impl(context): - assert "is_suitable_for_datalogger" in context.water_well_data + assert "datalogger_installation_status" in context.water_well_data + assert "open_status" in context.water_well_data assert ( - context.water_well_data["is_suitable_for_datalogger"] - == context.objects["wells"][0].is_suitable_for_datalogger + context.water_well_data["datalogger_installation_status"] + == context.objects["wells"][0].datalogger_installation_status + ) + assert ( + context.water_well_data["open_status"] + == context.objects["wells"][0].open_status ) diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index 02d6b1c69..c1105d8b1 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -279,10 +279,6 @@ def _step(self, session: Session, df: pd.DataFrame, i: int, row: pd.Series): row, f"LU_ConstructionMethod:{row.ConstructionMethod}", "Unknown" ) - is_suitable_for_datalogger = False - if notna(row.OpenWellLoggerOK): - is_suitable_for_datalogger = bool(row.OpenWellLoggerOK) - mpheight = row.MPHeight mpheight_description = row.MeasuringPoint if mpheight is None: @@ -321,7 +317,6 @@ def _step(self, session: Session, df: pd.DataFrame, i: int, row: pd.Series): well_driller_name=row.DrillerName, well_construction_method=wcm, well_pump_type=well_pump_type, - is_suitable_for_datalogger=is_suitable_for_datalogger, ) CreateWell.model_validate(data) @@ -425,6 +420,9 @@ def _extract_well_purposes(self, row) -> list[str]: else: purposes = [] for cui in cu: + if cui == "A": + # skip "Open, unequipped well" as that gets mapped to the status_history table + continue p = self._get_lexicon_value(row, f"LU_CurrentUse:{cui}") if p is not None: purposes.append(p) @@ -819,7 +817,6 @@ def _after_hook_chunk(self, well, formations): ) if notna(row.Status): - status_value = self._get_lexicon_value(row, f"LU_Status:{row.Status}") if status_value is not None: status_history = StatusHistory( @@ -835,6 +832,39 @@ def _after_hook_chunk(self, well, formations): logger.info( f" Added well status for well {well.name}: {status_value}" ) + + if notna(row.OpenWellLoggerOK): + if bool(row.OpenWellLoggerOK): + status_value = "Datalogger can be installed" + else: + status_value = "Datalogger cannot be installed" + status_history = StatusHistory( + status_type="Datalogger Suitability Status", + status_value=status_value, + reason=None, + start_date=datetime.now(tz=UTC), + target_id=target_id, + target_table=target_table, + ) + objs.append(status_history) + if self.verbose: + logger.info( + f" Added datalogger suitability status for well {well.name}: {status_value}" + ) + + if notna(row.CurrentUse) and "A" in row.CurrentUse: + status_history = StatusHistory( + status_type="Open Status", + status_value="Open", + reason=None, + start_date=datetime.now(tz=UTC), + target_id=target_id, + target_table=target_table, + ) + objs.append(status_history) + if self.verbose: + logger.info(f" Added open open status for well {well.name}") + return objs