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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions services/well_inventory_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,22 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user)
}
)

if (
model.mp_height is not None
and model.measuring_point_height_ft is not None
and model.mp_height != model.measuring_point_height_ft
):
raise ValueError(
"Conflicting values for measuring point height: mp_height and measuring_point_height_ft"
)

if model.measuring_point_height_ft is not None:
universal_mp_height = model.measuring_point_height_ft
elif model.mp_height is not None:
universal_mp_height = model.mp_height
else:
universal_mp_height = None

data = CreateWell(
location_id=loc.id,
group_id=group.id,
Expand All @@ -689,7 +705,7 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user)
well_depth=model.total_well_depth_ft,
well_depth_source=model.depth_source,
well_casing_diameter=model.casing_diameter_ft,
measuring_point_height=model.measuring_point_height_ft,
measuring_point_height=universal_mp_height,
measuring_point_description=model.measuring_point_description,
well_completion_date=model.date_drilled,
well_completion_date_source=model.completion_source,
Expand Down Expand Up @@ -821,6 +837,11 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user)
session.add(sample)
session.flush()

if model.depth_to_water_ft is not None and universal_mp_height is None:
raise ValueError(
"measuring_point_height_ft or mp_height is required when depth_to_water_ft is provided for a non-null observation"
)

# create Observation
# TODO: groundwater_level_reason may be conditionally required for null depth_to_water_ft - handle accordingly
observation = Observation(
Expand All @@ -829,7 +850,7 @@ def _add_csv_row(session: Session, group: Group, model: WellInventoryRow, user)
value=model.depth_to_water_ft,
unit="ft",
observation_datetime=model.measurement_date_time,
measuring_point_height=model.mp_height,
measuring_point_height=universal_mp_height,
groundwater_level_reason=(
model.level_status.value
if hasattr(model.level_status, "value")
Expand Down
160 changes: 157 additions & 3 deletions tests/test_well_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ def test_well_inventory_db_contents_with_waterlevels(tmp_path):
"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,
"mp_height_ft": 3.5,
"level_status": "Water level not affected",
}
)
Expand Down Expand Up @@ -529,6 +529,160 @@ def test_well_inventory_db_contents_with_waterlevels(tmp_path):
assert observation.sample == sample


def test_measuring_point_height_ft_used_for_thing_and_observation(tmp_path):
"""When measuring_point_height_ft is provided it is used for the thing's (MeasuringPointHistory) and observation's measuring_point_height values."""
row = _minimal_valid_well_inventory_row()
row.update(
{
"measuring_point_height_ft": 3.5,
"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",
}
)

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:
things = session.query(Thing).all()
observations = session.query(Observation).all()

assert len(things) == 1
assert things[0].measuring_point_height == 3.5
assert len(observations) == 1
assert observations[0].measuring_point_height == 3.5


def test_mp_height_used_for_thing_and_observation_when_measuring_point_height_ft_blank(
tmp_path,
):
"""When depth to water is provided and measuring_point_height_ft is blank the mp_height value should be used for the thing's (MeasuringPointHistory) and observation's measuring_point_height."""
row = _minimal_valid_well_inventory_row()
row.update(
{
"measuring_point_height_ft": "",
"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": 4.0,
}
)

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:
things = session.query(Thing).all()
observations = session.query(Observation).all()

assert len(things) == 1
assert things[0].measuring_point_height == 4.0
assert len(observations) == 1
assert observations[0].measuring_point_height == 4.0


def test_null_observation_allows_blank_mp_height(tmp_path):
"""When depth to water is not provided (ie null), blank measuring_point_height_ft and mp_height fields should be allowed and result in a null measuring_point_height for the observation and no associated measuring point height (MeasuringPointHistory) for the well."""
row = _minimal_valid_well_inventory_row()
row.update(
{
"measuring_point_height_ft": "",
"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",
}
)

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:
things = session.query(Thing).all()
observations = session.query(Observation).all()

assert len(things) == 1
assert things[0].measuring_point_height is None
assert len(observations) == 1
assert observations[0].measuring_point_height is None


def test_conflicting_mp_heights_raises_error(tmp_path):
"""
When both measuring_point_height_ft and mp_height are provided, an inequality (conflict) should raise an error.
"""
row = _minimal_valid_well_inventory_row()

row.update(
{
"measuring_point_height_ft": 3.5,
"mp_height": 4.0,
}
)

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 == 1, result.stderr
assert (
result.payload["validation_errors"][0]["error"]
== "Conflicting values for measuring point height: mp_height and measuring_point_height_ft"
)


def test_no_mp_height_raises_error_when_depth_to_water_provided(tmp_path):
row = _minimal_valid_well_inventory_row()
row.update(
{
"water_level_date_time": "2025-02-15T10:30:00",
"measuring_point_height_ft": "",
"mp_height": "",
"depth_to_water_ft": "8",
}
)

file_path = tmp_path / "well-inventory-no-mp-height.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
assert (
result.payload["validation_errors"][0]["error"]
== "measuring_point_height_ft or mp_height is required when depth_to_water_ft is provided for a non-null observation"
)


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()
Expand All @@ -539,7 +693,7 @@ def test_blank_depth_to_water_still_creates_water_level_records(tmp_path):
"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,
"mp_height_ft": 3.5,
}
)

Expand All @@ -563,7 +717,7 @@ def test_blank_depth_to_water_still_creates_water_level_records(tmp_path):
"2025-02-15T10:30:00Z"
)
assert observations[0].value is None
assert observations[0].measuring_point_height == 2.5
assert observations[0].measuring_point_height == 3.5


def test_rerunning_same_well_inventory_csv_is_idempotent():
Expand Down
Loading