Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c65920e
refactor: update observation model
jacob-a-brown Aug 18, 2025
a29cf20
feat: udpate observation schemas
jacob-a-brown Aug 18, 2025
14a5351
refactor: simplify adding observations
jacob-a-brown Aug 18, 2025
eefc32d
fix: remove field_sample_id from observation
jacob-a-brown Aug 18, 2025
de3ad5c
refactor: make only the id the primary key
jacob-a-brown Aug 18, 2025
3560028
refactor: inherit from AutBaseMixin
jacob-a-brown Aug 18, 2025
544b910
feat: add temperature and Calcius to lexicon
jacob-a-brown Aug 18, 2025
9b697a4
refactor: update C to deg C for clarity
jacob-a-brown Aug 18, 2025
79ae468
refactor: update observation POST tests
jacob-a-brown Aug 18, 2025
153fc11
note: noting thoughts on query parameters
jacob-a-brown Aug 19, 2025
c86e2ba
fix: fix observation fixtures
jacob-a-brown Aug 19, 2025
d9f58bc
refactor: order observed properties by observation type
jacob-a-brown Aug 19, 2025
d0fe51e
feat: implement get groundwater level observations
jacob-a-brown Aug 19, 2025
bd450f4
refactor: observation responses should inherit from ORMBaseModel
jacob-a-brown Aug 19, 2025
c39d120
feat: implement GET water chemistry observations
jacob-a-brown Aug 19, 2025
3ded376
refactor: specify URL for 404 wrong observation class
jacob-a-brown Aug 19, 2025
dc4f5a1
feat: implement get observations for observation classes
jacob-a-brown Aug 19, 2025
4db3473
feat: implement general GET for /observation
jacob-a-brown Aug 19, 2025
75a54c4
feat: add auth to GET endpoints
jacob-a-brown Aug 19, 2025
edda50b
feat: implement PATCH for groundwater level observations
jacob-a-brown Aug 19, 2025
1ab5c49
feat: create observation helpers for sub tables in observation table
jacob-a-brown Aug 19, 2025
4944871
refactor: update error msg for wrong observation class ok id
jacob-a-brown Aug 19, 2025
c626ea3
refactor: update observation fixtures dt for testing order
jacob-a-brown Aug 19, 2025
202a63e
feat: implement PATCH for water chemistry observations
jacob-a-brown Aug 19, 2025
caf541a
feat: implement PATCH for geothermal observations
jacob-a-brown Aug 19, 2025
39be19f
feat: implement DELETE observation by ID
jacob-a-brown Aug 19, 2025
11e9205
Merge branch 'jab-api-coverage-contact' into jab-api-coverage-observa…
jacob-a-brown Aug 20, 2025
2ed59d1
Merge branch 'pre-production' into jab-api-coverage-observation
jacob-a-brown Aug 20, 2025
e357b0e
refactor: simplify method to specify observation class
jacob-a-brown Aug 21, 2025
48684d4
Merge branch 'pre-production' into jab-api-coverage-observation
jacob-a-brown Aug 21, 2025
0df0815
Merge branch 'pre-production' into jab-api-coverage-observation
jacob-a-brown Aug 21, 2025
4f46bb7
refactor: use <observation class>:<observed_property> in observed pro…
jacob-a-brown Aug 21, 2025
600d3bf
refactor: get observation class from path
jacob-a-brown Aug 21, 2025
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
304 changes: 255 additions & 49 deletions api/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,37 @@
# limitations under the License.
# ===============================================================================
from datetime import datetime

from fastapi import APIRouter, Query
from fastapi_pagination.ext.sqlalchemy import paginate
from sqlalchemy import select
from starlette.status import HTTP_201_CREATED
from fastapi import APIRouter, Query, Request
from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT

from api.pagination import CustomPage
from core.dependencies import session_dependency
from db import Sample
from db.observation import Observation
from core.dependencies import (
session_dependency,
amp_admin_dependency,
admin_dependency,
amp_viewer_dependency,
viewer_dependency,
)
from db import Observation, adder
from schemas.observation import (
CreateGroundwaterLevelObservation,
GroundwaterLevelObservationResponse,
CreateWaterChemistryObservation,
WaterChemistryObservationResponse,
CreateGeothermalObservation,
GeothermalObservationResponse,
ObservationResponse,
UpdateGroundwaterLevelObservation,
UpdateWaterChemistryObservation,
UpdateGeothermalObservation,
)
from services.crud_helper import model_deleter
from services.query_helper import simple_get_by_id
from services.observation_helper import (
get_observations,
observation_model_patcher,
get_observation_of_an_observation_class_by_id,
)
from services.observation_helper import add_observation
from services.query_helper import order_sort_filter

router = APIRouter(prefix="/observation", tags=["observation"])

Expand All @@ -40,51 +54,96 @@
def add_groundwater_level_observation(
obs_data: CreateGroundwaterLevelObservation,
session: session_dependency,
):
user: amp_admin_dependency,
) -> GroundwaterLevelObservationResponse:
"""
Add a new groundwater observation to the database.
"""
return add_observation(session, obs_data)
return adder(session, Observation, obs_data, user=user)


@router.post("/water-chemistry", status_code=HTTP_201_CREATED)
def add_water_chemistry_observation(
obs_data: CreateWaterChemistryObservation,
session: session_dependency,
):
user: amp_admin_dependency,
) -> WaterChemistryObservationResponse:
"""
Add a new water chemistry observation to the database.
This endpoint is currently a placeholder and does not implement any functionality.
"""
return add_observation(session, obs_data)
return adder(session, Observation, obs_data, user=user)


#
# @router.post("/geothermal", status_code=HTTP_201_CREATED)
# def add_geothermal_observation(
# obs_data: CreateGeothermalObservation | CreateGeothermalObservationDirect,
# session: session_dependency,
# ):
# """
# Add a new geothermal observation to the database.
# This endpoint is currently a placeholder and does not implement any functionality.
# """
# if isinstance(obs_data, CreateGeothermalObservationDirect):
# return direct_adder(session, GeothermalObservation, obs_data)
# else:
# return adder(session, GeothermalObservation, obs_data)
@router.post("/geothermal", status_code=HTTP_201_CREATED)
def add_geothermal_observation(
obs_data: CreateGeothermalObservation,
session: session_dependency,
user: admin_dependency,
) -> GeothermalObservationResponse:
"""
Add a new geothermal observation to the database.
This endpoint is currently a placeholder and does not implement any functionality.
"""
return adder(session, Observation, obs_data, user=user)


# PATCH ========================================================================


@router.patch("/groundwater-level/{observation_id}", status_code=HTTP_200_OK)
def update_groundwater_level_observation(
observation_id: int,
obs_data: UpdateGroundwaterLevelObservation,
session: session_dependency,
user: amp_admin_dependency,
request: Request,
) -> GroundwaterLevelObservationResponse:
"""
Update an existing groundwater level observation in the database.
"""
return observation_model_patcher(session, request, observation_id, obs_data, user)


@router.patch("/water-chemistry/{observation_id}", status_code=HTTP_200_OK)
def update_water_chemistry_observation(
observation_id: int,
obs_data: UpdateWaterChemistryObservation,
session: session_dependency,
user: amp_admin_dependency,
request: Request,
) -> WaterChemistryObservationResponse:
"""
Update an existing water chemistry observation in the database.
"""
return observation_model_patcher(session, request, observation_id, obs_data, user)


@router.patch("/geothermal/{observation_id}", status_code=HTTP_200_OK)
def update_geothermal_observation(
observation_id: int,
obs_data: UpdateGeothermalObservation,
session: session_dependency,
user: admin_dependency,
request: Request,
) -> GeothermalObservationResponse:
"""
Update an existing geothermal observation in the database.
"""
return observation_model_patcher(session, request, observation_id, obs_data, user)


# ============= Get ==============================================
@router.get(
"/groundwater-level",
)


@router.get("/groundwater-level", summary="Get groundwater level observations")
def get_groundwater_level_observations(
request: Request,
session: session_dependency,
user: amp_viewer_dependency,
thing_id: int | None = None,
sensor_id: int | None = None,
sample_id: int | None = None,
polygon: str | None = None,
start_time: datetime | None = None,
end_time: datetime | None = None,
sort: str | None = None,
Expand All @@ -94,27 +153,174 @@ def get_groundwater_level_observations(
"""
Retrieve all groundwater level observations from the database.
"""
sql = select(Observation)
sql = sql.where(Observation.observed_property == "groundwater level")
if thing_id is not None:
sql = sql.join(Sample)
sql = sql.where(Sample.thing_id == thing_id)
if sample_id is not None:
sql = sql.where(Observation.sample_id == sample_id)
if sensor_id is not None:
sql = sql.where(Observation.sensor_id == sensor_id)
return get_observations(
request=request,
session=session,
thing_id=thing_id,
sensor_id=sensor_id,
sample_id=sample_id,
start_time=start_time,
end_time=end_time,
sort=sort,
order=order,
filter_=filter_,
)


@router.get(
"/groundwater-level/{observation_id}",
summary="Get groundwater level observation by ID",
)
def get_groundwater_level_observation_by_id(
session: session_dependency,
request: Request,
user: amp_viewer_dependency,
observation_id: int,
) -> GroundwaterLevelObservationResponse:
return get_observation_of_an_observation_class_by_id(
session=session,
request=request,
observation_id=observation_id,
)


@router.get("/water-chemistry", summary="Get water chemistry observations")
def get_water_chemistry_observations(
request: Request,
session: session_dependency,
user: amp_viewer_dependency,
thing_id: int | None = None,
sensor_id: int | None = None,
sample_id: int | None = None,
start_time: datetime | None = None,
end_time: datetime | None = None,
sort: str | None = None,
order: str | None = None,
filter_: str = Query(alias="filter", default=None),
) -> CustomPage[WaterChemistryObservationResponse]:
"""
Retrieve all water chemistry observations from the database.
"""
return get_observations(
request=request,
session=session,
thing_id=thing_id,
sensor_id=sensor_id,
sample_id=sample_id,
start_time=start_time,
end_time=end_time,
sort=sort,
order=order,
filter_=filter_,
)

if start_time:
sql = sql.where(Observation.observation_datetime >= start_time)
if end_time:
sql = sql.where(Observation.observation_datetime <= end_time)

sql = order_sort_filter(sql, Observation, sort, order, filter_)
@router.get(
"/water-chemistry/{observation_id}", summary="Get water chemistry observation by ID"
)
def get_water_chemistry_observation_by_id(
session: session_dependency,
request: Request,
user: amp_viewer_dependency,
observation_id: int,
) -> WaterChemistryObservationResponse:
return get_observation_of_an_observation_class_by_id(
session=session,
request=request,
observation_id=observation_id,
)

if not order:
sql = sql.order_by(Observation.observation_datetime.desc())

return paginate(query=sql, conn=session)
@router.get("/geothermal", summary="Get geothermal observations")
def get_geothermal_observations(
request: Request,
session: session_dependency,
user: viewer_dependency,
thing_id: int | None = None,
sensor_id: int | None = None,
sample_id: int | None = None,
start_time: datetime | None = None,
end_time: datetime | None = None,
sort: str | None = None,
order: str | None = None,
filter_: str = Query(alias="filter", default=None),
) -> CustomPage[GeothermalObservationResponse]:
"""
Retrieve all geothermal observations from the database.
"""
return get_observations(
request=request,
session=session,
thing_id=thing_id,
sensor_id=sensor_id,
sample_id=sample_id,
start_time=start_time,
end_time=end_time,
sort=sort,
order=order,
filter_=filter_,
)


@router.get("/geothermal/{observation_id}", summary="Get geothermal observation by ID")
def get_geothermal_observation_by_id(
session: session_dependency,
request: Request,
user: amp_viewer_dependency,
observation_id: int,
) -> GeothermalObservationResponse:
return get_observation_of_an_observation_class_by_id(
session=session, request=request, observation_id=observation_id
)


@router.get("", summary="Get all observations")
def get_all_observations(
request: Request,
session: session_dependency,
user: amp_viewer_dependency,
thing_id: int | None = None,
sensor_id: int | None = None,
sample_id: int | None = None,
start_time: datetime | None = None,
end_time: datetime | None = None,
sort: str | None = None,
order: str | None = None,
filter_: str = Query(alias="filter", default=None),
) -> CustomPage[ObservationResponse]:
return get_observations(
request=request,
session=session,
thing_id=thing_id,
sensor_id=sensor_id,
sample_id=sample_id,
start_time=start_time,
end_time=end_time,
sort=sort,
order=order,
filter_=filter_,
)


@router.get("/{observation_id}", summary="Get an observation by its ID")
def get_observation_by_id(
session: session_dependency, user: amp_viewer_dependency, observation_id: int
) -> ObservationResponse:
return simple_get_by_id(session, Observation, observation_id)


# DELETE =======================================================================


@router.delete(
"/{observation_id}",
summary="Delete an observation",
status_code=HTTP_204_NO_CONTENT,
)
def delete_observation(
session: session_dependency, user: amp_admin_dependency, observation_id: int
) -> None:
return model_deleter(session, Observation, observation_id)

Comment thread
jacob-a-brown marked this conversation as resolved.

# ============= EOF =============================================
12 changes: 9 additions & 3 deletions core/lexicon.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@
{"category": "unit", "term": "W/m²", "definition": "watts per square meter"},
{"category": "unit", "term": "W/m·K", "definition": "watts per meter Kelvin"},
{"category": "unit", "term": "m²/s", "definition": "square meters per second"},
{"category": "unit", "term": "deg C", "definition": "degree Celsius"},

{"category": "observed_property", "term": "groundwater level:groundwater level", "definition": "groundwater level measurement" },

{"category": "observed_property", "term": "geothermal:temperature", "definition": "Temperature measurement"},

{"category": "observed_property", "term": "water chemistry:pH", "definition": "pH"},
{"category": "observed_property", "term": "water chemistry:Alkalinity as CaCO3", "definition": "Alkalinity as CaCO3"},


{"category": "observed_property", "term": "groundwater level", "definition": "groundwater level measurement" },
{"category": "observed_property", "term": "pH", "definition": "pH"},
{"category": "observed_property", "term": "Alkalinity as CaCO3", "definition": "Alkalinity as CaCO3"},

{"category": "release_status", "term": "draft", "definition": "draft version"},
{"category": "release_status", "term": "provisional", "definition": "provisional version"},
Expand Down
Loading
Loading