Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9a272cb
feat: add transducer observation model and water levels pressure tran…
jirhiker Oct 10, 2025
56237f7
feat: enhance TransducerObservation model with relationships and upda…
jirhiker Oct 10, 2025
bd25003
feat: add transducer observation models and endpoints for groundwater…
jirhiker Oct 13, 2025
e8388c0
feat: update schemas to use enums for release status, contact types, …
jirhiker Oct 13, 2025
e2995dd
feat: add ScreenType and WellCasingMaterial enums, update related sch…
jirhiker Oct 13, 2025
956c9b2
feat: refactor enums and update transducer observation model with new…
jirhiker Oct 13, 2025
1463935
feat: rename WellCasingMaterial enum to CasingMaterial in enums and u…
jirhiker Oct 13, 2025
67a57d5
feat: refactor transducer observation handling and add pressure level…
jirhiker Oct 14, 2025
4cca32f
feat: add qc_status to lexicon, update transducer observation model, …
jirhiker Oct 14, 2025
0677412
feat: refactor water levels transfer functions and update logger output
jirhiker Oct 14, 2025
5a350f0
feat: replace cfgv with pydantic for validation in water levels trans…
jirhiker Oct 14, 2025
a123b7f
feat: update BaseResponseModel to serialize release_status as string …
jirhiker Oct 14, 2025
b99aa59
feat: add actions permission and trigger BDD tests in CD_staging.yml
jirhiker Oct 16, 2025
d579790
feat: add actions permission and trigger BDD tests in CD_staging.yml
jirhiker Oct 16, 2025
b5ba0e9
feat: add actions permission and trigger BDD tests in CD_staging.yml
jirhiker Oct 16, 2025
7b4aa51
feat: update BDD test feature path to use API directory
jirhiker Oct 16, 2025
3ba932a
feat: add BDD dependencies and update test feature paths
jirhiker Oct 16, 2025
bbc3c77
feat: update test command to use 'uv run' for BDD tests
jirhiker Oct 16, 2025
70072db
feat: enhance well and sensor models with new relationships and valid…
jirhiker Oct 17, 2025
70b2f32
feat: add well-location feature with BDD steps for retrieving well lo…
jirhiker Oct 18, 2025
16a8bed
feat: add API fixture for testing and override authentication for tes…
jirhiker Oct 19, 2025
e24c7a8
feat: use nma_csv_cache folder
jirhiker Oct 20, 2025
95c7047
feat: add option to disable authentication for development and enforc…
jirhiker Oct 20, 2025
8e406ae
feat: update test command to include approved tags for backend tests
jirhiker Oct 20, 2025
7b92b55
feat: refactor API fixture initialization and authentication override…
jirhiker Oct 20, 2025
cd39ae4
feat: add review status to transducer observations and implement rele…
jirhiker Oct 21, 2025
237e555
feat: specify types for session and permission dependencies in depend…
jirhiker Oct 21, 2025
0572e14
feat: enhance water levels transfer by filtering valid point IDs and …
jirhiker Oct 21, 2025
ea1b03b
feat: enhance transducer observation responses with block details and…
jirhiker Oct 21, 2025
83b642c
feat: update transducer observation retrieval to include parameter ID…
jirhiker Oct 21, 2025
ffd2868
feat: import schemas for contact and transducer observations in trans…
jirhiker Oct 21, 2025
3441b2b
feat: enhance API testing setup with location and thing creation, and…
jirhiker Oct 22, 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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ POSTGRES_DB=
GCS_BUCKET_NAME=
GOOGLE_APPLICATION_CREDENTIALS=/path/to/gcs_credentials.json



# disable authentication (for development only)
AUTHENTIK_DISABLE_AUTHENTICATION=1

# authentik
AUTHENTIK_URL=
AUTHENTIK_CLIENT_ID=
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/CD_staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,4 @@ jobs:
- name: Tag commit
run: |
git tag -a "staging-deploy-$(date -u +%Y-%m-%d)T$(date -u +%H-%M-%S%z)" -m "staging gcloud deployment: $(date -u +%Y-%m-%d)T$(date -u +%H:%M:%S%z)"
git push origin --tags
git push origin --tags
17 changes: 17 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ jobs:

run: uv run pytest -vv --durations=20 --cov --cov-report=xml --junitxml=junit.xml

# - name: Checkout BDD repo (features only)
# uses: actions/checkout@v4
# with:
# repository: DataIntegrationGroup/OcotilloBDD
# path: bdd
#
# - name: Copy BDD features into backend test directory
# run: |
# mkdir -p tests/features
# cp -r bdd/features/backend/* tests/features/
#
# - name: Run BDD tests
# env:
# BASE_URL: ${{ secrets.BACKEND_URL }}
# run: |
# uv run behave tests/features --tags=@backend,@approved --no-capture

- name: Upload results to Codecov
uses: codecov/codecov-action@v4
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ gcs_credentials.json
transfers/data/assets*
transfers/transfer*.log
transfer*.log
tests/features/*.feature

# deployment files
app.yaml
23 changes: 22 additions & 1 deletion api/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# limitations under the License.
# ===============================================================================
from datetime import datetime

from fastapi import APIRouter, Query, Request
from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT

Expand All @@ -34,13 +35,15 @@
UpdateGroundwaterLevelObservation,
UpdateWaterChemistryObservation,
)
from schemas.transducer import TransducerObservationWithBlockResponse
from services.crud_helper import model_deleter, model_adder
from services.query_helper import simple_get_by_id
from services.observation_helper import (
get_observations,
observation_model_patcher,
get_observation_of_an_activity_type_by_id,
get_transducer_observations,
)
from services.query_helper import simple_get_by_id

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

Expand Down Expand Up @@ -112,6 +115,24 @@ async def update_water_chemistry_observation(
# ============= Get ==============================================


@router.get(
"/transducer-groundwater-level",
summary="Get transducer groundwater level observations",
)
async def get_transducer_groundwater_level_observations(
request: Request,
session: session_dependency,
user: amp_viewer_dependency,
thing_id: int | None = None,
parameter_id: int | None = None,
start_time: datetime | None = None,
end_time: datetime | None = None,
) -> CustomPage[TransducerObservationWithBlockResponse]:
return get_transducer_observations(
session, thing_id, parameter_id, start_time, end_time
)


@router.get("/groundwater-level", summary="Get groundwater level observations")
async def get_groundwater_level_observations(
request: Request,
Expand Down
7 changes: 3 additions & 4 deletions api/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
# limitations under the License.
# ===============================================================================
from fastapi import APIRouter
from sqlalchemy import select, func, text
from sqlalchemy.orm import Session
from api.pagination import CustomPage
from fastapi_pagination import paginate
from fastapi_pagination.utils import disable_installed_extensions_check
from sqlalchemy import select, func, text
from sqlalchemy.orm import Session

from api.pagination import CustomPage
from core.dependencies import session_dependency, viewer_dependency
from db import (
Contact,
Expand All @@ -34,7 +34,6 @@
search,
)


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

Expand Down
2 changes: 1 addition & 1 deletion api/thing.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
)
from services.crud_helper import model_patcher, model_adder, model_deleter
from services.exceptions_helper import PydanticStyleException
from services.lexicon_helper import get_terms_by_category
from services.query_helper import (
simple_get_by_id,
paginated_all_getter,
Expand All @@ -64,7 +65,6 @@
modify_well_descriptor_tables,
WELL_DESCRIPTOR_MODEL_MAP,
)
from services.lexicon_helper import get_terms_by_category

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

Expand Down
6 changes: 5 additions & 1 deletion core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.openapi.utils import get_openapi
from fastapi_pagination import add_pagination

from .initializers import init_db, init_lexicon, init_parameter
from .initializers import init_db, init_lexicon, init_parameter, register_routes
from .settings import settings


Expand All @@ -37,6 +38,9 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
init_db()
init_lexicon()
init_parameter()

register_routes(app)
add_pagination(app)
yield


Expand Down
22 changes: 12 additions & 10 deletions core/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from core.permissions import authenticated
from db.engine import get_db_session

session_dependency = Annotated[Session, Depends(get_db_session)]
session_dependency: type[Session] = Annotated[Session, Depends(get_db_session)]

"""
Developer Notes
Expand Down Expand Up @@ -61,16 +61,18 @@


# Permissions Dependencies -----------------------------------------------------
admin_dependency = Annotated[dict, Depends(admin_function)]
editor_dependency = Annotated[dict, Depends(editor_function)]
viewer_dependency = Annotated[dict, Depends(viewer_function)]
admin_dependency: type[dict] = Annotated[dict, Depends(admin_function)]
editor_dependency: type[dict] = Annotated[dict, Depends(editor_function)]
viewer_dependency: type[dict] = Annotated[dict, Depends(viewer_function)]

lexicon_admin_dependency = Annotated[dict, Depends(lexicon_admin_function)]
lexicon_editor_dependency = Annotated[dict, Depends(lexicon_editor_function)]
lexicon_admin_dependency: type[dict] = Annotated[dict, Depends(lexicon_admin_function)]
lexicon_editor_dependency: type[dict] = Annotated[
dict, Depends(lexicon_editor_function)
]

amp_admin_dependency = Annotated[dict, Depends(amp_admin_function)]
amp_editor_dependency = Annotated[dict, Depends(amp_editor_function)]
amp_viewer_dependency = Annotated[dict, Depends(amp_viewer_function)]
amp_admin_dependency: type[dict] = Annotated[dict, Depends(amp_admin_function)]
amp_editor_dependency: type[dict] = Annotated[dict, Depends(amp_editor_function)]
amp_viewer_dependency: type[dict] = Annotated[dict, Depends(amp_viewer_function)]

no_permission_dependency = Annotated[dict, Depends(no_permission_function)]
no_permission_dependency: type[dict] = Annotated[dict, Depends(no_permission_function)]
# ============= EOF =============================================
69 changes: 69 additions & 0 deletions core/enums.py
Comment thread
jirhiker marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# ===============================================================================
# Copyright 2025 ross
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===============================================================================
from enum import Enum

from services.lexicon_helper import build_enum_from_lexicon_category

ActivityType: type[Enum] = build_enum_from_lexicon_category("activity_type")
AddressType: type[Enum] = build_enum_from_lexicon_category("address_type")
AnalysisMethodType: type[Enum] = build_enum_from_lexicon_category(
"analysis_method_type"
)
CasingMaterial: type[Enum] = build_enum_from_lexicon_category("casing_material")
CollectionMethod: type[Enum] = build_enum_from_lexicon_category("collection_method")
ConstructionMethod: type[Enum] = build_enum_from_lexicon_category("construction_method")
ContactType: type[Enum] = build_enum_from_lexicon_category("contact_type")
CoordinateMethod: type[Enum] = build_enum_from_lexicon_category("coordinate_method")
WellPurpose: type[Enum] = build_enum_from_lexicon_category("well_purpose")
DataQuality: type[Enum] = build_enum_from_lexicon_category("data_quality")
DataSource: type[Enum] = build_enum_from_lexicon_category("data_source")
DepthCompletionSource: type[Enum] = build_enum_from_lexicon_category(
"depth_completion_source"
)
DischargeSource: type[Enum] = build_enum_from_lexicon_category("discharge_source")
DrillingFluid: type[Enum] = build_enum_from_lexicon_category("drilling_fluid")
ElevationMethod: type[Enum] = build_enum_from_lexicon_category("elevation_method")
EmailType: type[Enum] = build_enum_from_lexicon_category("email_type")
ParticipantRole: type[Enum] = build_enum_from_lexicon_category("participant_role")
Geochronology: type[Enum] = build_enum_from_lexicon_category("geochronology")
HorizontalDatum: type[Enum] = build_enum_from_lexicon_category("horizontal_datum")
GroundwaterLevelReason: type[Enum] = build_enum_from_lexicon_category(
"groundwater_level_reason"
)
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")
ParameterName: type[Enum] = build_enum_from_lexicon_category("parameter_name")
Organization: type[Enum] = build_enum_from_lexicon_category("organization")
ParameterType: type[Enum] = build_enum_from_lexicon_category("parameter_type")
PhoneType: type[Enum] = build_enum_from_lexicon_category("phone_type")
PublicationType: type[Enum] = build_enum_from_lexicon_category("publication_type")
SampleQcType: type[Enum] = build_enum_from_lexicon_category("qc_type")
QualityFlag: type[Enum] = build_enum_from_lexicon_category("quality_flag")
Relation: type[Enum] = build_enum_from_lexicon_category("relation")
ReleaseStatus: type[Enum] = build_enum_from_lexicon_category("release_status")
ReviewStatus: type[Enum] = build_enum_from_lexicon_category("review_status")
Role: type[Enum] = build_enum_from_lexicon_category("role")
SampleMatrix: type[Enum] = build_enum_from_lexicon_category("sample_matrix")
SampleMethod: type[Enum] = build_enum_from_lexicon_category("sample_method")
SampleType: type[Enum] = build_enum_from_lexicon_category("sample_type")
SpringType: type[Enum] = build_enum_from_lexicon_category("spring_type")
Status: type[Enum] = build_enum_from_lexicon_category("status")
ThingType: type[Enum] = build_enum_from_lexicon_category("thing_type")
Unit: type[Enum] = build_enum_from_lexicon_category("unit")
Vertical_datum: type[Enum] = build_enum_from_lexicon_category("vertical_datum")
ScreenType: type[Enum] = build_enum_from_lexicon_category("screen_type")
# ============= EOF =============================================
45 changes: 43 additions & 2 deletions core/initializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,16 @@ def init_db():
This function is called during application startup.
"""

Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)
from sqlalchemy import text

with engine.connect() as conn:
conn.execute(text("DROP SCHEMA public CASCADE"))
conn.execute(text("CREATE SCHEMA public"))
conn.execute(text("CREATE EXTENSION IF NOT EXISTS postgis"))
conn.commit()

Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)


def init_hypertables():
Expand Down Expand Up @@ -117,3 +125,36 @@ def init_lexicon(path: str = None) -> None:
)

session.rollback()


def register_routes(app):
from api.group import router as group_router
from api.contact import router as contact_router
from api.location import router as location_router
from api.thing import router as thing_router
from api.sensor import router as sensor_router

from api.sample import router as sample_router
from api.observation import router as observation_router

from api.lexicon import router as lexicon_router

from api.publication import router as publication_router
from api.author import router as author_router
from api.asset import router as asset_router
from api.search import router as search_router
from api.geospatial import router as geospatial_router

app.include_router(asset_router)
app.include_router(author_router)
app.include_router(contact_router)
app.include_router(geospatial_router)
app.include_router(group_router)
app.include_router(lexicon_router)
app.include_router(location_router)
app.include_router(observation_router)
app.include_router(publication_router)
app.include_router(sample_router)
app.include_router(sensor_router)
app.include_router(search_router)
app.include_router(thing_router)
10 changes: 7 additions & 3 deletions core/lexicon.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@
{"name": "quality_flag", "description": null},
{"name": "relation", "description": null},
{"name": "release_status", "description": null},
{"name": "review_status", "description": null},
{"name": "role", "description": null},
{"name": "sample_matrix", "description": null},
{"name": "sample_method", "description": null},
{"name": "sample_type", "description": null},
{"name": "screen_type", "description": null},
{"name": "sensor_type", "description": null},
{"name": "sensor_status", "description": null},
{"name": "spring_type", "description": null},
Expand All @@ -48,6 +50,8 @@
{"name": "well_status", "description": null}
],
"terms": [
{"categories": ["review_status"], "term": "approved", "definition": "approved"},
{"categories": ["review_status"], "term": "not reviewed", "definition": "raw"},
{"categories": ["qc_type"], "term": "Normal", "definition": "The primary environmental sample collected from the well, spring, or soil boring."},
{"categories": ["qc_type"], "term": "Duplicate", "definition": "A second, independent sample collected at the same location, at the same time, and in the same manner as the normal sample. This sample is sent to the primary laboratory."},
{"categories": ["qc_type"], "term": "Split", "definition": "A subsample of a primary environmental sample that is sent to a separate, independent laboratory for analysis."},
Expand Down Expand Up @@ -448,9 +452,9 @@
{"categories": ["spring_type"], "term": "Perennial", "definition": "perennial spring"},
{"categories": ["spring_type"], "term": "Thermal", "definition": "thermal spring"},
{"categories": ["spring_type"], "term": "Mineral", "definition": "mineral spring"},
{"categories": ["casing_material"], "term": "PVC", "definition": "Polyvinyl Chloride"},
{"categories": ["casing_material"], "term": "Steel", "definition": "Steel"},
{"categories": ["casing_material"], "term": "Concrete", "definition": "Concrete"},
{"categories": ["casing_material", "screen_type"], "term": "PVC", "definition": "Polyvinyl Chloride"},
{"categories": ["casing_material", "screen_type"], "term": "Steel", "definition": "Steel"},
{"categories": ["casing_material", "screen_type"], "term": "Concrete", "definition": "Concrete"},
{"categories": ["quality_flag"], "term": "Good", "definition": "The measurement was collected and analyzed according to standard procedures and passed all QA/QC checks."},
{"categories": ["quality_flag"], "term": "Questionable", "definition": "The measurement is suspect due to a known issue during collection or analysis, but it may still be usable."},
{"categories": ["quality_flag"], "term": "Estimated", "definition": "The value is not a direct measurement but an estimate derived from other data or models."},
Expand Down
Loading
Loading