Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fe3625a
fix: deprecate groundwater level sample matrix
jacob-a-brown Dec 22, 2025
35985cf
feat: test water level bulk upload
jacob-a-brown Dec 22, 2025
70153aa
refactor: use contact fixture for test bulk water level upload
jacob-a-brown Dec 22, 2025
2198e8b
fix: cleanup transfer tests
jacob-a-brown Dec 22, 2025
6a1395e
refactor: update water level bulk upload
jacob-a-brown Dec 22, 2025
53e6a10
Merge branch 'water-level-csv' into water-level-csv-refactor
jacob-a-brown Jan 5, 2026
70d0e31
refactor: remove redundant water level test in CLI
jacob-a-brown Jan 5, 2026
19d597d
feat: test file not found scenario in water level CSV upload
jacob-a-brown Jan 5, 2026
fad9f98
feat: implement water level csv unit test for nonexistent well
jacob-a-brown Jan 5, 2026
08e7a60
feat: implement water level csv unit test for bad dtw bgs
jacob-a-brown Jan 5, 2026
f0b6b2f
feat: implement water level csv unit test for bad dtw bgs
jacob-a-brown Jan 5, 2026
797dee4
feat: enable all errors in a row to be captured
jacob-a-brown Jan 5, 2026
d7f38e8
feat: test invalid field staff names
jacob-a-brown Jan 5, 2026
ededb61
fix: remove outdated TODO note
jacob-a-brown Jan 5, 2026
c4ae569
refactor: update positive givens for water level csv feature tests
jacob-a-brown Jan 5, 2026
d8d0aa3
fix: only process DB validations after Pydantic validation has passed
jacob-a-brown Jan 5, 2026
ba27820
feat: add water_level_date_time to water level csv upload response
jacob-a-brown Jan 5, 2026
03f0b3e
fix: update seeding data
jacob-a-brown Jan 6, 2026
64f5ea3
fix: remove measuring_person from scenario since that is not a lexico…
jacob-a-brown Jan 6, 2026
50cfa8d
fix: update test steps to reflect changes in field names
jacob-a-brown Jan 6, 2026
0ac590f
Merge branch 'water-level-csv' into water-level-csv-refactor
jacob-a-brown Jan 6, 2026
4ec2635
Merge branch 'water-level-csv' into water-level-csv-refactor
jacob-a-brown Jan 6, 2026
19306c9
refactor: move common CSV gives to common.py
jacob-a-brown Jan 6, 2026
493f027
Merge branch 'water-level-csv' into water-level-csv-refactor
jacob-a-brown Feb 4, 2026
55ca16f
fix: fix cli test for refactored/updated water level csv handling
jacob-a-brown Feb 4, 2026
64bace0
Formatting changes
jacob-a-brown Feb 4, 2026
5ef609e
Merge branch 'water-level-csv' into water-level-csv-refactor
jacob-a-brown Feb 4, 2026
d4acffe
formatting changes
jacob-a-brown Feb 5, 2026
74a06a4
fix: move datetime naive-to-aware test to common.py
jacob-a-brown Feb 5, 2026
a1052a4
feat: test invalid "measuring_person" and standardize test verbiage
jacob-a-brown Feb 5, 2026
6aced6c
fix: remove commented-out code
jacob-a-brown Feb 5, 2026
360495a
fix: remove print debugging statement
jacob-a-brown Feb 5, 2026
7debe73
fix: fix spelling typo
jacob-a-brown Feb 5, 2026
a8bd3ef
Merge branch 'water-level-csv' into water-level-csv-refactor
jacob-a-brown Feb 5, 2026
cfa825c
Merge branch 'water-level-csv' into water-level-csv-refactor
jacob-a-brown Feb 5, 2026
336e741
Merge branch 'water-level-csv' into water-level-csv-refactor
jacob-a-brown Feb 5, 2026
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
10 changes: 6 additions & 4 deletions api/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
UpdateWaterChemistryObservation,
)
from schemas.transducer import TransducerObservationWithBlockResponse
from schemas.water_level_csv import WaterLevelBulkUploadResponse
from schemas.water_level_csv import WaterLevelBulkUploadPayload
from services.crud_helper import model_deleter, model_adder
from services.observation_helper import (
get_observations,
Expand Down Expand Up @@ -90,8 +90,8 @@ async def add_water_chemistry_observation(

@router.post(
"/groundwater-level/bulk-upload",
response_model=WaterLevelBulkUploadResponse,
status_code=HTTP_200_OK,
response_model=WaterLevelBulkUploadPayload,
status_code=HTTP_201_CREATED,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming can change as we see fit 😅 . I tried to mimic the nomenclature from when I refactored/updated the water level csv implementation. I think originally we were planning to have it work as an API endpoint but now it's being implemented by the CLI, whose "response" we need to define manually.

)
async def bulk_upload_groundwater_levels(
user: amp_admin_dependency,
Expand All @@ -107,7 +107,9 @@ async def bulk_upload_groundwater_levels(
result = bulk_upload_water_levels(contents)

if result.exit_code != 0:
raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=result.payload)
raise HTTPException(
status_code=HTTP_400_BAD_REQUEST, detail=result.payload.model_dump()
)

return result.payload

Expand Down
1 change: 0 additions & 1 deletion cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ def water_levels_bulk_upload(file_path: str, output_format: str | None):
"""
parse and upload a csv
"""
# TODO: use the same helper function used by api to parse and upload a WL csv
from cli.service_adapter import water_levels_csv

pretty_json = (output_format or "").lower() == "json"
Expand Down
4 changes: 2 additions & 2 deletions core/parameter.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[
{
"parameter_name": "groundwater level",
"matrix": "groundwater",
"matrix": "water",
"parameter_type": "Field Parameter",
"cas_number": null,
"default_unit": "ft"
},
{
"parameter_name": "pH",
"matrix": "groundwater",
"matrix": "water",
"parameter_type": "Field Parameter",
"cas_number": null,
"default_unit": "dimensionless"
Expand Down
132 changes: 121 additions & 11 deletions schemas/water_level_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,140 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# ===============================================================================
from pydantic import BaseModel
from datetime import datetime
from pydantic import BaseModel, ConfigDict, field_validator, Field
from typing import Any

from core.enums import SampleMethod, GroundwaterLevelReason, GroundwaterLevelAccuracy

class WaterLevelBulkUploadSummary(BaseModel):
total_rows_processed: int
total_rows_imported: int
validation_errors_or_warnings: int

class WaterLevelCsvRow(BaseModel):
"""
This class defines the schema for a single row in the water level CSV upload.
"""

model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)

well_name_point_id: str = Field(
description="Name/PointID of the well where the measurement was taken."
)
field_event_date_time: datetime = Field(
description="Date and time when the field event occurred."
)
field_staff: str = Field(description="Name of the person who led the field event.")
field_staff_2: str | None = Field(
description="Name of the second person who participated in the field event.",
default=None,
)
field_staff_3: str | None = Field(
description="Name of the third person who participated in the field event.",
default=None,
)
water_level_date_time: datetime = Field(
description="Date and time when the water level measurement was taken."
)
measuring_person: str = Field(
description="Person who took the water level measurement. They must be one of the field staff"
)
sample_method: SampleMethod = Field(
description="Method used to measure the water level."
)
mp_height: float = Field(
description="Measuring point height relative to the ground surface in feet."
)
level_status: GroundwaterLevelReason = Field(
description="Status of the water level."
)
depth_to_water_ft: float = Field(description="Depth to water in feet.")
data_quality: GroundwaterLevelAccuracy = Field(
description="A description of the accuracy of the data."
)
water_level_notes: str | None = Field(
description="Additional notes about the water level measurement.", default=None
)

@field_validator("water_level_notes", mode="before")
@classmethod
def _empty_to_none(cls, value: str | None) -> str | None:
if value is None:
return None
if isinstance(value, str) and value.strip() == "":
return None
return value

@field_validator("measuring_person")
@classmethod
def ensure_measuring_person_is_field_staff(
cls, value: str, values: dict[str, Any]
) -> str:
data = values.data
field_staffs = [
data.get("field_staff"),
data.get("field_staff_2"),
data.get("field_staff_3"),
]
if value not in field_staffs:
raise ValueError("measuring_person must be one of the field staff")
return value


class WaterLevelBulkUploadRow(WaterLevelCsvRow):
"""
This class extends WaterLevelCsvRow to include resolved database objects
for easier processing during bulk upload.
"""

well: Any = Field(description="The Thing object representing the well.")
field_staff_contact: Any = Field(
description="The Contact object for the field staff."
)
field_staff_2_contact: Any | None = Field(
description="The Contact object for the second field staff."
)
field_staff_3_contact: Any | None = Field(
description="The Contact object for the third field staff."
)
measuring_person_field_staff_index: int = Field(
description="The index of the field staff who is the measuring person: 1, 2, or 3."
)


class WaterLevelBulkUploadRow(BaseModel):
class WaterLevelCreatedRow(BaseModel):
"""
This class defines the structure of a successfully created water level row
during bulk upload.
"""

well_name_point_id: str
field_event_id: int
field_activity_id: int
field_event_participant_1_id: int
field_event_participant_2_id: int | None
field_event_participant_3_id: int | None
sample_id: int
observation_id: int
measurement_date_time: str
level_status: str
data_quality: str
water_level_date_time: str
groundwater_level_reason: str
groundwater_level_accuracy: str


class WaterLevelBulkUploadResponse(BaseModel):
class WaterLevelBulkUploadSummary(BaseModel):
total_rows_processed: int
total_rows_imported: int
total_validation_errors_or_warnings: int


class WaterLevelBulkUploadPayload(BaseModel):
summary: WaterLevelBulkUploadSummary
water_levels: list[WaterLevelBulkUploadRow]
water_levels: list[WaterLevelCreatedRow]
validation_errors: list[str]


class WaterLevelBulkUploadResponse(BaseModel):
exit_code: int
stdout: str
stderr: str
payload: WaterLevelBulkUploadPayload


# ============= EOF =============================================
Loading
Loading