diff --git a/backend_py/primary/primary/routers/explore.py b/backend_py/primary/primary/routers/explore.py index effda0837..4c36a6d06 100644 --- a/backend_py/primary/primary/routers/explore.py +++ b/backend_py/primary/primary/routers/explore.py @@ -30,6 +30,7 @@ class EnsembleInfo(BaseModel): class EnsembleDetails(BaseModel): name: str field_identifier: str + stratigraphic_column_identifier: str case_name: str case_uuid: str realizations: Sequence[int] @@ -91,6 +92,7 @@ async def get_ensemble_details( case_name = await case_inspector.get_case_name_async() realizations = await case_inspector.get_realizations_in_iteration_async(ensemble_name) field_identifiers = await case_inspector.get_field_identifiers_async() + stratigraphic_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async() if len(field_identifiers) != 1: raise NotImplementedError("Multiple field identifiers not supported") @@ -101,4 +103,5 @@ async def get_ensemble_details( case_uuid=case_uuid, realizations=realizations, field_identifier=field_identifiers[0], + stratigraphic_column_identifier=stratigraphic_column_identifier, ) diff --git a/backend_py/primary/primary/routers/polygons/router.py b/backend_py/primary/primary/routers/polygons/router.py index 5883ac4a7..99925d09b 100644 --- a/backend_py/primary/primary/routers/polygons/router.py +++ b/backend_py/primary/primary/routers/polygons/router.py @@ -5,8 +5,8 @@ from webviz_pkg.core_utils.perf_timer import PerfTimer from primary.auth.auth_helper import AuthHelper -from primary.services.smda_access.mocked_drogon_smda_access import _mocked_stratigraphy_access -from primary.services.smda_access.stratigraphy_access import StratigraphyAccess +from primary.services.smda_access.drogon import DrogonSmdaAccess +from primary.services.smda_access import SmdaAccess from primary.services.smda_access.stratigraphy_utils import sort_stratigraphic_names_by_hierarchy from primary.services.sumo_access.case_inspector import CaseInspector from primary.services.sumo_access.polygons_access import PolygonsAccess @@ -34,14 +34,15 @@ async def get_polygons_directory( polygons_dir = await access.get_polygons_directory_async() case_inspector = CaseInspector.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid) + field_identifiers = await case_inspector.get_field_identifiers_async() strat_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async() - strat_access: Union[StratigraphyAccess, _mocked_stratigraphy_access.StratigraphyAccess] + smda_access: Union[SmdaAccess, DrogonSmdaAccess] if strat_column_identifier == "DROGON_HAS_NO_STRATCOLUMN": - strat_access = _mocked_stratigraphy_access.StratigraphyAccess(authenticated_user.get_smda_access_token()) + smda_access = DrogonSmdaAccess() else: - strat_access = StratigraphyAccess(authenticated_user.get_smda_access_token()) - strat_units = await strat_access.get_stratigraphic_units(strat_column_identifier) + smda_access = SmdaAccess(authenticated_user.get_smda_access_token(), field_identifier=field_identifiers[0]) + strat_units = await smda_access.get_stratigraphic_units(strat_column_identifier) sorted_stratigraphic_surfaces = sort_stratigraphic_names_by_hierarchy(strat_units) return converters.to_api_polygons_directory(polygons_dir, sorted_stratigraphic_surfaces) diff --git a/backend_py/primary/primary/routers/polygons/schemas.py b/backend_py/primary/primary/routers/polygons/schemas.py index b63654cbd..4c20dbc7c 100644 --- a/backend_py/primary/primary/routers/polygons/schemas.py +++ b/backend_py/primary/primary/routers/polygons/schemas.py @@ -20,6 +20,7 @@ class PolygonsAttributeType(str, Enum): PINCHOUT = "pinchout" # Values are pinchouts SUBCROP = "subcrop" # Values are subcrops FAULT_LINES = "fault_lines" # Values are fault lines + NAMED_AREA = "named_area" # Values are named areas, e.g. CCS containment polygons class PolygonsMeta(BaseModel): diff --git a/backend_py/primary/primary/routers/seismic/converters.py b/backend_py/primary/primary/routers/seismic/converters.py new file mode 100644 index 000000000..f679df274 --- /dev/null +++ b/backend_py/primary/primary/routers/seismic/converters.py @@ -0,0 +1,50 @@ +from typing import List + +import orjson +import numpy as np +import xtgeo + +from . import schemas + + +def surface_to_float32_array(values: np.ndarray) -> List[float]: + values = values.astype(np.float32) + np.ma.set_fill_value(values, np.nan) + values = np.ma.filled(values) + + # Rotate 90 deg left. + # This will cause the width of to run along the X axis + # and height of along Y axis (starting from bottom.) + values = np.rot90(values) + + return values.flatten().tolist() + + +def to_api_surface_data( + xtgeo_surf: xtgeo.RegularSurface, property_values: np.ndarray +) -> schemas.SurfaceMeshAndProperty: + """ + Create API SurfaceData from xtgeo regular surface + """ + float32_mesh = surface_to_float32_array(xtgeo_surf.values) + float32_property = surface_to_float32_array(property_values) + + return schemas.SurfaceMeshAndProperty( + x_ori=xtgeo_surf.xori, + y_ori=xtgeo_surf.yori, + x_count=xtgeo_surf.ncol, + y_count=xtgeo_surf.nrow, + x_inc=xtgeo_surf.xinc, + y_inc=xtgeo_surf.yinc, + x_min=xtgeo_surf.xmin, + x_max=xtgeo_surf.xmax, + y_min=xtgeo_surf.ymin, + y_max=xtgeo_surf.ymax, + mesh_value_min=xtgeo_surf.values.min(), + mesh_value_max=xtgeo_surf.values.max(), + property_value_min=property_values.min(), + property_value_max=property_values.max(), + rot_deg=xtgeo_surf.rotation, + mesh_data=orjson.dumps(float32_mesh).decode("utf-8"), # pylint: disable=maybe-no-member + property_data=orjson.dumps(float32_property).decode("utf-8"), # pylint: disable=maybe-no-member + ) diff --git a/backend_py/primary/primary/routers/seismic/schemas.py b/backend_py/primary/primary/routers/seismic/schemas.py index dbcf7430c..98c2d1813 100644 --- a/backend_py/primary/primary/routers/seismic/schemas.py +++ b/backend_py/primary/primary/routers/seismic/schemas.py @@ -58,3 +58,23 @@ class SeismicFenceData(BaseModel): num_samples_per_trace: int min_fence_depth: float max_fence_depth: float + + +class SurfaceMeshAndProperty(BaseModel): + x_ori: float + y_ori: float + x_count: int + y_count: int + x_inc: float + y_inc: float + x_min: float + x_max: float + y_min: float + y_max: float + mesh_value_min: float + mesh_value_max: float + property_value_min: float + property_value_max: float + rot_deg: float + mesh_data: str + property_data: str diff --git a/backend_py/primary/primary/routers/surface/converters.py b/backend_py/primary/primary/routers/surface/converters.py index 6580fad97..c10e4c80b 100644 --- a/backend_py/primary/primary/routers/surface/converters.py +++ b/backend_py/primary/primary/routers/surface/converters.py @@ -11,6 +11,7 @@ from primary.services.utils.surface_intersect_with_polyline import XtgeoSurfaceIntersectionResult from primary.services.utils.surface_to_float32 import surface_to_float32_numpy_array from primary.services.utils.surface_to_png import surface_to_png_bytes_optimized +from primary.services.smda_access import StratigraphicUnit from . import schemas @@ -174,3 +175,22 @@ def to_api_surface_intersection( z_points=xtgeo_surface_intersection.zval, cum_lengths=xtgeo_surface_intersection.distance, ) + + +def to_api_stratigraphic_unit( + stratigraphic_unit: StratigraphicUnit, +) -> schemas.StratigraphicUnit: + return schemas.StratigraphicUnit( + identifier=stratigraphic_unit.identifier, + top=stratigraphic_unit.top, + base=stratigraphic_unit.base, + stratUnitLevel=stratigraphic_unit.strat_unit_level, + stratUnitType=stratigraphic_unit.strat_unit_type, + topAge=stratigraphic_unit.top_age, + baseAge=stratigraphic_unit.base_age, + stratUnitParent=stratigraphic_unit.strat_unit_parent, + colorR=stratigraphic_unit.color_r, + colorG=stratigraphic_unit.color_g, + colorB=stratigraphic_unit.color_b, + lithologyType=stratigraphic_unit.lithology_type, + ) diff --git a/backend_py/primary/primary/routers/surface/router.py b/backend_py/primary/primary/routers/surface/router.py index 02d74bab0..4127d212d 100644 --- a/backend_py/primary/primary/routers/surface/router.py +++ b/backend_py/primary/primary/routers/surface/router.py @@ -7,9 +7,9 @@ from primary.services.sumo_access.case_inspector import CaseInspector from primary.services.sumo_access.surface_access import SurfaceAccess -from primary.services.smda_access.stratigraphy_access import StratigraphyAccess, StratigraphicUnit +from primary.services.smda_access import SmdaAccess, StratigraphicUnit from primary.services.smda_access.stratigraphy_utils import sort_stratigraphic_names_by_hierarchy -from primary.services.smda_access.mocked_drogon_smda_access import _mocked_stratigraphy_access +from primary.services.smda_access.drogon import DrogonSmdaAccess from primary.services.utils.statistic_function import StatisticFunction from primary.services.utils.surface_intersect_with_polyline import intersect_surface_with_polyline from primary.services.utils.authenticated_user import AuthenticatedUser @@ -295,22 +295,41 @@ async def get_misfit_surface_data( raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED) +@router.get("/stratigraphic_units") +async def get_stratigraphic_units( + # fmt:off + response: Response, + authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)], + case_uuid: Annotated[str, Query(description="Sumo case uuid")], + # fmt:on +) -> list[schemas.StratigraphicUnit]: + perf_metrics = ResponsePerfMetrics(response) + + strat_units = await _get_stratigraphic_units_for_case_async(authenticated_user, case_uuid) + api_strat_units = [converters.to_api_stratigraphic_unit(strat_unit) for strat_unit in strat_units] + + LOGGER.info(f"Got stratigraphic units in: {perf_metrics.to_string()}") + + return api_strat_units + + async def _get_stratigraphic_units_for_case_async( authenticated_user: AuthenticatedUser, case_uuid: str ) -> list[StratigraphicUnit]: perf_metrics = PerfMetrics() case_inspector = CaseInspector.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid) + field_identifiers = await case_inspector.get_field_identifiers_async() strat_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async() perf_metrics.record_lap("get-strat-ident") - strat_access: StratigraphyAccess | _mocked_stratigraphy_access.StratigraphyAccess + smda_access: SmdaAccess | DrogonSmdaAccess if strat_column_identifier == "DROGON_HAS_NO_STRATCOLUMN": - strat_access = _mocked_stratigraphy_access.StratigraphyAccess(authenticated_user.get_smda_access_token()) + smda_access = DrogonSmdaAccess() else: - strat_access = StratigraphyAccess(authenticated_user.get_smda_access_token()) + smda_access = SmdaAccess(authenticated_user.get_smda_access_token(), field_identifier=field_identifiers[0]) - strat_units = await strat_access.get_stratigraphic_units(strat_column_identifier) + strat_units = await smda_access.get_stratigraphic_units(strat_column_identifier) perf_metrics.record_lap("get-strat-units") LOGGER.info(f"Got stratigraphic units for case in : {perf_metrics.to_string()}") diff --git a/backend_py/primary/primary/routers/surface/schemas.py b/backend_py/primary/primary/routers/surface/schemas.py index 3c5e8be4e..3e6224991 100644 --- a/backend_py/primary/primary/routers/surface/schemas.py +++ b/backend_py/primary/primary/routers/surface/schemas.py @@ -164,3 +164,18 @@ class SurfaceRealizationSampleValues(BaseModel): class PointSetXY(BaseModel): x_points: list[float] y_points: list[float] + + +class StratigraphicUnit(BaseModel): + identifier: str + top: str + base: str + stratUnitLevel: int + stratUnitType: str + topAge: int | float + baseAge: int | float + stratUnitParent: str | None + colorR: int + colorG: int + colorB: int + lithologyType: int | float | str = "unknown" diff --git a/backend_py/primary/primary/routers/well/converters.py b/backend_py/primary/primary/routers/well/converters.py index f6d01f078..fb12f9d40 100644 --- a/backend_py/primary/primary/routers/well/converters.py +++ b/backend_py/primary/primary/routers/well/converters.py @@ -19,6 +19,7 @@ def convert_wellbore_pick_to_schema(wellbore_pick: WellborePick) -> schemas.Well md=wellbore_pick.md, mdMsl=wellbore_pick.md_msl, uniqueWellboreIdentifier=wellbore_pick.unique_wellbore_identifier, + wellboreUuid=wellbore_pick.wellbore_uuid, pickIdentifier=wellbore_pick.pick_identifier, confidence=wellbore_pick.confidence, depthReferencePoint=wellbore_pick.depth_reference_point, @@ -57,6 +58,8 @@ def convert_wellbore_header_to_schema( wellNorthing=drilled_wellbore_header.well_northing, depthReferencePoint=drilled_wellbore_header.depth_reference_point, depthReferenceElevation=drilled_wellbore_header.depth_reference_elevation, + wellborePurpose=(drilled_wellbore_header.wellbore_purpose if drilled_wellbore_header.wellbore_purpose else ""), + wellboreStatus=drilled_wellbore_header.wellbore_status if drilled_wellbore_header.wellbore_status else "", ) diff --git a/backend_py/primary/primary/routers/well/router.py b/backend_py/primary/primary/routers/well/router.py index 9d8e6c415..2ab5baa46 100644 --- a/backend_py/primary/primary/routers/well/router.py +++ b/backend_py/primary/primary/routers/well/router.py @@ -3,15 +3,10 @@ from fastapi import APIRouter, Depends, Query -from primary.services.smda_access.mocked_drogon_smda_access import ( - WellAccess as MockedSmdaWellAccess, - StratigraphyAccess as MockedStratigraphyAccess, -) -from primary.services.smda_access.well_access import WellAccess as SmdaWellAccess -from primary.services.smda_access.stratigraphy_access import StratigraphyAccess +from primary.services.smda_access.drogon import DrogonSmdaAccess +from primary.services.smda_access import SmdaAccess from primary.services.utils.authenticated_user import AuthenticatedUser from primary.auth.auth_helper import AuthHelper -from primary.services.sumo_access.case_inspector import CaseInspector from primary.services.ssdl_access.well_access import WellAccess as SsdlWellAccess @@ -27,42 +22,39 @@ async def get_drilled_wellbore_headers( # fmt:off authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), - field_identifier: str = Query(description="Sumo field identifier"), - # Should be field identifier + field_identifier: str = Query(description="Official field identifier"), # fmt:on ) -> List[schemas.WellboreHeader]: """Get wellbore headers for all wells in the field""" - well_access: Union[SmdaWellAccess, MockedSmdaWellAccess] + well_access: Union[SmdaAccess, DrogonSmdaAccess] if field_identifier == "DROGON": # Handle DROGON - well_access = MockedSmdaWellAccess(authenticated_user.get_smda_access_token()) + well_access = DrogonSmdaAccess() else: - well_access = SmdaWellAccess(authenticated_user.get_smda_access_token()) + well_access = SmdaAccess(authenticated_user.get_smda_access_token(), field_identifier=field_identifier) - wellbore_headers = await well_access.get_wellbore_headers(field_identifier=field_identifier) + wellbore_headers = await well_access.get_wellbore_headers() return [converters.convert_wellbore_header_to_schema(wellbore_header) for wellbore_header in wellbore_headers] -@router.get("/field_well_trajectories/") -async def get_field_well_trajectories( +@router.get("/well_trajectories/") +async def get_well_trajectories( # fmt:off authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), - field_identifier: str = Query(description="Sumo field identifier"), - unique_wellbore_identifiers:List[str] = Query(None, description="Optional subset of well names") + field_identifier: str = Query(description="Official field identifier"), + wellbore_uuids:List[str] = Query(None, description="Optional subset of wellbore uuids") # fmt:on ) -> List[schemas.WellboreTrajectory]: """Get well trajectories for field""" - well_access: Union[SmdaWellAccess, MockedSmdaWellAccess] + well_access: Union[SmdaAccess, DrogonSmdaAccess] if field_identifier == "DROGON": # Handle DROGON - well_access = MockedSmdaWellAccess(authenticated_user.get_smda_access_token()) + well_access = DrogonSmdaAccess() else: - well_access = SmdaWellAccess(authenticated_user.get_smda_access_token()) + well_access = SmdaAccess(authenticated_user.get_smda_access_token(), field_identifier=field_identifier) - wellbore_trajectories = await well_access.get_field_wellbore_trajectories( - field_identifier=field_identifier, unique_wellbore_identifiers=unique_wellbore_identifiers - ) + wellbore_trajectories = await well_access.get_wellbore_trajectories(wellbore_uuids=wellbore_uuids) return [ converters.convert_well_trajectory_to_schema(wellbore_trajectory) @@ -70,65 +62,69 @@ async def get_field_well_trajectories( ] -@router.get("/well_trajectories/") -async def get_well_trajectories( +@router.get("/wellbore_pick_identifiers/") +async def get_wellbore_pick_identifiers( # fmt:off authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), - wellbore_uuids: List[str] = Query(description="Wellbore uuids"), + field_identifier: str = Query(description="Official field identifier"), + strat_column_identifier: str = Query(description="Stratigraphic column identifier") # fmt:on -) -> List[schemas.WellboreTrajectory]: - """Get well trajectories""" - well_access: Union[SmdaWellAccess, MockedSmdaWellAccess] +) -> List[str]: + """Get wellbore pick identifiers for field and stratigraphic column""" + well_access: Union[SmdaAccess, DrogonSmdaAccess] + if field_identifier == "DROGON": + # Handle DROGON + well_access = DrogonSmdaAccess() - # Handle DROGON - if all(x in ["drogon_horizontal", "drogon_vertical"] for x in wellbore_uuids): - well_access = MockedSmdaWellAccess(authenticated_user.get_smda_access_token()) else: - well_access = SmdaWellAccess(authenticated_user.get_smda_access_token()) - - wellbore_trajectories = await well_access.get_wellbore_trajectories(wellbore_uuids=wellbore_uuids) + well_access = SmdaAccess(authenticated_user.get_smda_access_token(), field_identifier=field_identifier) - return [ - converters.convert_well_trajectory_to_schema(wellbore_trajectory) - for wellbore_trajectory in wellbore_trajectories - ] + wellbore_picks = await well_access.get_wellbore_pick_identifiers_in_stratigraphic_column( + strat_column_identifier=strat_column_identifier + ) + return [wellbore_pick.name for wellbore_pick in wellbore_picks] -@router.get("/wellbore_picks_and_stratigraphic_units/") -async def get_wellbore_picks_and_stratigraphic_units( +@router.get("/wellbore_picks_for_pick_identifier/") +async def get_wellbore_picks_for_pick_identifier( # fmt:off authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), - case_uuid: str = Query(description="Sumo case uuid"), # Should be field identifier? - wellbore_uuid: str = Query(description="Wellbore uuid"), + field_identifier: str = Query(description="Official field identifier"), + pick_identifier: str = Query(description="Pick identifier") # fmt:on -) -> schemas.WellborePicksAndStratigraphicUnits: - """Get well bore picks for a single well bore""" - well_access: Union[SmdaWellAccess, MockedSmdaWellAccess] - stratigraphy_access: Union[StratigraphyAccess, MockedStratigraphyAccess] +) -> List[schemas.WellborePick]: + """Get wellbore picks for field and pick identifier""" + well_access: Union[SmdaAccess, DrogonSmdaAccess] + if field_identifier == "DROGON": + # Handle DROGON + well_access = DrogonSmdaAccess() - case_inspector = CaseInspector.from_case_uuid(authenticated_user.get_sumo_access_token(), case_uuid) - stratigraphic_column_identifier = await case_inspector.get_stratigraphic_column_identifier_async() + else: + well_access = SmdaAccess(authenticated_user.get_smda_access_token(), field_identifier=field_identifier) - # Handle DROGON - field_identifiers = await case_inspector.get_field_identifiers_async() - if "DROGON" in field_identifiers: - well_access = MockedSmdaWellAccess(authenticated_user.get_smda_access_token()) - stratigraphy_access = MockedStratigraphyAccess(authenticated_user.get_smda_access_token()) + wellbore_picks = await well_access.get_wellbore_picks_for_pick_identifier(pick_identifier=pick_identifier) + return [converters.convert_wellbore_pick_to_schema(wellbore_pick) for wellbore_pick in wellbore_picks] + + +@router.get("/wellbore_picks_for_wellbore/") +async def get_wellbore_picks_for_wellbore( + # fmt:off + authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), + field_identifier: str = Query(description="Official field identifier"), + wellbore_uuid: str = Query(description="Wellbore uuid") + # fmt:on +) -> List[schemas.WellborePick]: + """Get wellbore picks for field and pick identifier""" + well_access: Union[SmdaAccess, DrogonSmdaAccess] + if field_identifier == "DROGON": + # Handle DROGON + well_access = DrogonSmdaAccess() else: - well_access = SmdaWellAccess(authenticated_user.get_smda_access_token()) - stratigraphy_access = StratigraphyAccess(authenticated_user.get_smda_access_token()) - - stratigraphic_units = await stratigraphy_access.get_stratigraphic_units(stratigraphic_column_identifier) - wellbore_picks = await well_access.get_all_picks_for_wellbore(wellbore_uuid=wellbore_uuid) - - return schemas.WellborePicksAndStratigraphicUnits( - wellbore_picks=[converters.convert_wellbore_pick_to_schema(wellbore_pick) for wellbore_pick in wellbore_picks], - stratigraphic_units=[ - converters.convert_stratigraphic_unit_to_schema(stratigraphic_unit) - for stratigraphic_unit in stratigraphic_units - ], - ) + well_access = SmdaAccess(authenticated_user.get_smda_access_token(), field_identifier=field_identifier) + + wellbore_picks = await well_access.get_wellbore_picks_for_wellbore(wellbore_uuid=wellbore_uuid) + return [converters.convert_wellbore_pick_to_schema(wellbore_pick) for wellbore_pick in wellbore_picks] @router.get("/wellbore_completions/") diff --git a/backend_py/primary/primary/routers/well/schemas.py b/backend_py/primary/primary/routers/well/schemas.py index 8427292d0..5e47a71f8 100644 --- a/backend_py/primary/primary/routers/well/schemas.py +++ b/backend_py/primary/primary/routers/well/schemas.py @@ -32,6 +32,8 @@ class WellboreHeader(BaseModel): wellNorthing: float depthReferencePoint: str depthReferenceElevation: float + wellborePurpose: str + wellboreStatus: str class WellboreTrajectory(BaseModel): @@ -57,6 +59,7 @@ class WellborePick(BaseModel): md: float mdMsl: float uniqueWellboreIdentifier: str + wellboreUuid: str pickIdentifier: str confidence: Optional[str] = None depthReferencePoint: str diff --git a/backend_py/primary/primary/services/smda_access/__init__.py b/backend_py/primary/primary/services/smda_access/__init__.py index e69de29bb..96c7c6ba2 100644 --- a/backend_py/primary/primary/services/smda_access/__init__.py +++ b/backend_py/primary/primary/services/smda_access/__init__.py @@ -0,0 +1,2 @@ +from .smda_access import SmdaAccess +from .types import WellboreHeader, WellborePick, WellboreTrajectory, StratigraphicUnit, StratigraphicSurface diff --git a/backend_py/primary/primary/services/smda_access/_smda_get_request.py b/backend_py/primary/primary/services/smda_access/_smda_get_request.py new file mode 100644 index 000000000..ac824706a --- /dev/null +++ b/backend_py/primary/primary/services/smda_access/_smda_get_request.py @@ -0,0 +1,62 @@ +import logging +from typing import List + +import httpx + +from webviz_pkg.core_utils.perf_timer import PerfTimer + +from primary import config +from primary.services.service_exceptions import ServiceRequestError, Service + +LOGGER = logging.getLogger(__name__) + + +async def smda_get_request(access_token: str, endpoint: str, params: dict) -> List[dict]: + """ + Generic GET request to SMDA API. + Uses `next` pagination to get all results. + https://smda.equinor.com/learn/develop/smda-rest-api/#_next + """ + urlstring = f"https://api.gateway.equinor.com/smda/v2.0/smda-api/{endpoint}?" + params = params if params else {} + params.update({"_items": 10000}) + headers = { + "Content-Type": "application/json", + "authorization": f"Bearer {access_token}", + "Ocp-Apim-Subscription-Key": config.SMDA_SUBSCRIPTION_KEY, + } + timer = PerfTimer() + single_lap_timer = PerfTimer() + + async with httpx.AsyncClient() as client: + results: List[dict] = [] + page: int = 1 + while True: + response = await client.get(urlstring, params=params, headers=headers, timeout=60) + LOGGER.info(f"TIME SMDA fetch '{endpoint}', page {page}, took {single_lap_timer.lap_s():.2f} seconds") + page += 1 + if response.status_code == 200: + result = response.json()["data"]["results"] + if not result: + raise ServiceRequestError(f"No data found for endpoint: '{endpoint}'", Service.SMDA) + + results.extend(result) + + next_request = response.json()["data"]["next"] + if next_request is None: + break + params["_next"] = next_request + elif response.status_code == 404: + LOGGER.error(f"{str(response.status_code) } {endpoint} either does not exists or can not be found") + raise ServiceRequestError( + f"[{str(response.status_code)}] '{endpoint}' either does not exists or can not be found", + Service.SMDA, + ) + else: + raise ServiceRequestError( + f"[{str(response.status_code)}] Cannot fetch data from endpoint: '{endpoint}'", Service.SMDA + ) + + LOGGER.info(f"TIME SMDA fetch '{endpoint}' took {timer.lap_s():.2f} seconds") + + return results diff --git a/backend_py/primary/primary/services/smda_access/drogon/__init__.py b/backend_py/primary/primary/services/smda_access/drogon/__init__.py new file mode 100644 index 000000000..a9df11d78 --- /dev/null +++ b/backend_py/primary/primary/services/smda_access/drogon/__init__.py @@ -0,0 +1 @@ +from .drogon_smda_access import SmdaAccess as DrogonSmdaAccess diff --git a/backend_py/primary/primary/services/smda_access/drogon/_drogon_strat_units.py b/backend_py/primary/primary/services/smda_access/drogon/_drogon_strat_units.py new file mode 100644 index 000000000..10a15595b --- /dev/null +++ b/backend_py/primary/primary/services/smda_access/drogon/_drogon_strat_units.py @@ -0,0 +1,64 @@ +from typing import List + +from ..types import StratigraphicUnit + + +def get_drogon_strat_units() -> List[StratigraphicUnit]: + return [ + StratigraphicUnit( + identifier="Valysar Fm.", + top="Valysar Fm. Top", + base="Valysar Fm. Base", + color_r=255, + color_g=165, + color_b=0, + strat_unit_level=2, + strat_unit_type="formation", + strat_unit_parent="VOLANTIS GP.", + top_age=1, + base_age=2, + lithology_type="unknown", + ), + StratigraphicUnit( + identifier="Therys Fm.", + top="Therys Fm. Top", + base="Therys Fm. Base", + color_r=255, + color_g=255, + color_b=0, + strat_unit_level=2, + strat_unit_type="formation", + strat_unit_parent="VOLANTIS GP.", + top_age=2, + base_age=3, + lithology_type="unknown", + ), + StratigraphicUnit( + identifier="Volon Fm.", + top="Volon Fm. Top", + base="Volon Fm. Base", + color_r=143, + color_g=188, + color_b=143, + strat_unit_level=2, + strat_unit_type="formation", + strat_unit_parent="VOLANTIS GP.", + top_age=3, + base_age=4, + lithology_type="unknown", + ), + StratigraphicUnit( + identifier="VOLANTIS GP.", + top="VOLANTIS GP. Top", + base="VOLANTIS GP. Base", + color_r=255, + color_g=165, + color_b=0, + strat_unit_level=1, + strat_unit_type="group", + strat_unit_parent=None, + top_age=4, + base_age=5, + lithology_type="unknown", + ), + ] diff --git a/backend_py/primary/primary/services/smda_access/drogon/_drogon_well_data.py b/backend_py/primary/primary/services/smda_access/drogon/_drogon_well_data.py new file mode 100644 index 000000000..8bad2c01b --- /dev/null +++ b/backend_py/primary/primary/services/smda_access/drogon/_drogon_well_data.py @@ -0,0 +1,240 @@ +from typing import List + +from ..types import WellboreTrajectory, WellboreHeader, WellborePick + + +def get_drogon_well_headers() -> List[WellboreHeader]: + return [ + WellboreHeader( + wellbore_uuid="drogon_vertical", + unique_wellbore_identifier="55/33-1", + well_uuid="drogon_vertical", + unique_well_identifier="55/33-1", + well_easting=462480.000, + well_northing=5934232.000, + depth_reference_point="RKB", + depth_reference_elevation=25.0, + wellbore_purpose="production", + wellbore_status="active", + ), + WellboreHeader( + wellbore_uuid="drogon_horizontal", + unique_wellbore_identifier="55/33-A-4", + well_uuid="drogon_horizontal", + unique_well_identifier="55/33-A-4", + well_easting=463256.911, + well_northing=5930542.294, + depth_reference_point="RKB", + depth_reference_elevation=49.0, + wellbore_purpose="production", + wellbore_status="active", + ), + ] + + +def get_drogon_well_trajectories() -> List[WellboreTrajectory]: + return [ + WellboreTrajectory( + wellbore_uuid="drogon_vertical", + unique_wellbore_identifier="55/33-1", + tvd_msl_arr=[-25.0, 1774.5], + md_arr=[0.0, 1799.5], + easting_arr=[462480.0, 462480.0], + northing_arr=[5934232.0, 5934232.0], + ), + WellboreTrajectory( + wellbore_uuid="drogon_horizontal", + unique_wellbore_identifier="55/33-A-4", + tvd_msl_arr=[-49.0, 1293.4185, 1536.9384, 1616.4998, 1630.5153, 1656.9874], + md_arr=[0.0, 1477.0, 1761.5, 1899.2601, 2363.9988, 3578.5], + easting_arr=[463256.911, 463564.402, 463637.925, 463690.658, 463910.452, 464465.876], + northing_arr=[5930542.294, 5931057.803, 5931184.235, 5931278.837, 5931688.122, 5932767.761], + ), + ] + + +def get_drogon_well_picks() -> List[WellborePick]: + return [ + WellborePick( + easting=462480.0, + northing=5934232.0, + tvd=1645.44, + tvd_msl=1645.0, + md=1670.0, + md_msl=1670.0, + unique_wellbore_identifier="55/33-1", + wellbore_uuid="drogon_vertical", + pick_identifier="Volantis Fm. Base", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=462480.0, + northing=5934232.0, + tvd=0.0, + tvd_msl=0.0, + md=0.0, + md_msl=0.0, + unique_wellbore_identifier="55/33-1", + wellbore_uuid="drogon_vertical", + pick_identifier="MSL", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=462480.0, + northing=5934232.0, + tvd=1620.08, + tvd_msl=1620.0, + md=1645.0, + md_msl=1645.0, + unique_wellbore_identifier="55/33-1", + wellbore_uuid="drogon_vertical", + pick_identifier="Therys Fm. Top", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=462480.0, + northing=5934232.0, + tvd=1600.57, + tvd_msl=1601.0, + md=1626.0, + md_msl=1626.0, + unique_wellbore_identifier="55/33-1", + wellbore_uuid="drogon_vertical", + pick_identifier="Volantis Fm. Top", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=462480.0, + northing=5934232.0, + tvd=1630.44, + tvd_msl=1630.0, + md=1655.0, + md_msl=1655.0, + unique_wellbore_identifier="55/33-1", + wellbore_uuid="drogon_vertical", + pick_identifier="Volon Fm. Top", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=463256.9114, + northing=5930542.2944, + tvd=0.0, + tvd_msl=0.0, + md=0.0, + md_msl=0.0, + unique_wellbore_identifier="55/33-A-4", + wellbore_uuid="drogon_horizontal", + pick_identifier="MSL", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=464220.0686875976, + northing=5932289.733486914, + tvd=1641.19, + tvd_msl=1641.0, + md=3041.0, + md_msl=3041.0, + unique_wellbore_identifier="55/33-A-4", + wellbore_uuid="drogon_horizontal", + pick_identifier="Therys Fm. Top", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=463849.68880478514, + northing=5931573.151211523, + tvd=1628.2, + tvd_msl=1628.0, + md=2234.0, + md_msl=2234.0, + unique_wellbore_identifier="55/33-A-4", + wellbore_uuid="drogon_horizontal", + pick_identifier="Volantis Fm. Top", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=464220.0686875976, + northing=5932289.733486914, + tvd=1641.19, + tvd_msl=1641.0, + md=3041.0, + md_msl=3041.0, + unique_wellbore_identifier="55/33-A-4", + wellbore_uuid="drogon_horizontal", + pick_identifier="Volon Fm. Top", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=463256.9114, + northing=5930542.2944, + tvd=1828.2, + tvd_msl=1828.0, + md=2434.0, + md_msl=2434.0, + unique_wellbore_identifier="55/33-A-4", + wellbore_uuid="drogon_horizontal", + pick_identifier="VOLANTIS GP. Top", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=464220.0686875976, + northing=5932289.733486914, + tvd=1841.19, + tvd_msl=1841.0, + md=3241.0, + md_msl=3241.0, + unique_wellbore_identifier="55/33-A-4", + wellbore_uuid="drogon_horizontal", + pick_identifier="VOLANTIS GP. Top", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=463849.68880478514, + northing=5931573.151211523, + tvd=1628.2, + tvd_msl=1628.0, + md=2234.0, + md_msl=2234.0, + unique_wellbore_identifier="55/33-A-4", + wellbore_uuid="drogon_horizontal", + pick_identifier="VOLANTIS GP. Base", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + WellborePick( + easting=464220.0686875976, + northing=5932289.733486914, + tvd=1641.19, + tvd_msl=1641.0, + md=3041.0, + md_msl=3041.0, + unique_wellbore_identifier="55/33-A-4", + wellbore_uuid="drogon_horizontal", + pick_identifier="VOLANTIS GP. Base", + confidence=None, + depth_reference_point="RKB", + md_unit="m", + ), + ] diff --git a/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_generate_testdata.py b/backend_py/primary/primary/services/smda_access/drogon/_generate_testdata.py similarity index 100% rename from backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_generate_testdata.py rename to backend_py/primary/primary/services/smda_access/drogon/_generate_testdata.py diff --git a/backend_py/primary/primary/services/smda_access/drogon/drogon_smda_access.py b/backend_py/primary/primary/services/smda_access/drogon/drogon_smda_access.py new file mode 100644 index 000000000..e32f82fc4 --- /dev/null +++ b/backend_py/primary/primary/services/smda_access/drogon/drogon_smda_access.py @@ -0,0 +1,56 @@ +from typing import List, Optional + +from ..types import WellborePick, WellboreTrajectory, WellboreHeader, StratigraphicUnit, StratigraphicSurface +from ..stratigraphy_utils import sort_stratigraphic_names_by_hierarchy + +from ._drogon_strat_units import get_drogon_strat_units +from ._drogon_well_data import get_drogon_well_trajectories, get_drogon_well_headers, get_drogon_well_picks + + +class SmdaAccess: + """Drogon SMDA access class containing methods to get mocked Drogon data + disable-unused-argument needed to keep the same method signature as the SMDA access class""" + + def __init__(self) -> None: + pass + + # pylint: disable=unused-argument + async def get_stratigraphic_units(self, stratigraphic_column_identifier: str) -> List[StratigraphicUnit]: + return get_drogon_strat_units() + + async def get_wellbore_headers(self) -> List[WellboreHeader]: + """Get Drogon wellbore headers""" + return get_drogon_well_headers() + + async def get_wellbore_trajectories(self, wellbore_uuids: Optional[List[str]] = None) -> List[WellboreTrajectory]: + """Get all Drogon trajectories""" + all_well_trajs = get_drogon_well_trajectories() + if wellbore_uuids: + return [traj for traj in all_well_trajs if traj.wellbore_uuid in wellbore_uuids] + return all_well_trajs + + # pylint: disable=unused-argument + async def get_wellbore_picks_for_wellbore( + self, wellbore_uuid: str, obs_no: Optional[int] = None + ) -> List[WellborePick]: + """Get Drogon picks for a wellbore""" + well_picks = [pick for pick in get_drogon_well_picks() if pick.wellbore_uuid == wellbore_uuid] + return well_picks + + # pylint: disable=unused-argument + async def get_wellbore_picks_for_pick_identifier( + self, + pick_identifier: str, + interpreter: str = "STAT", + obs_no: Optional[int] = None, + ) -> List[WellborePick]: + """Get Drogon picks for a pick identifier""" + return [pick for pick in get_drogon_well_picks() if pick.pick_identifier == pick_identifier] + + async def get_wellbore_pick_identifiers_in_stratigraphic_column( + self, strat_column_identifier: str + ) -> List[StratigraphicSurface]: + """Get Drogon pick identifiers for a stratigraphic column""" + strat_units = await self.get_stratigraphic_units(strat_column_identifier) + sorted_stratigraphic_surfaces = sort_stratigraphic_names_by_hierarchy(strat_units) + return sorted_stratigraphic_surfaces diff --git a/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/__init__.py b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/__init__.py deleted file mode 100644 index 0223e8cec..000000000 --- a/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from ._mocked_well_access import WellAccess -from ._mocked_stratigraphy_access import StratigraphyAccess diff --git a/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_stratigraphy_access.py b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_stratigraphy_access.py deleted file mode 100644 index da2b0b59f..000000000 --- a/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_stratigraphy_access.py +++ /dev/null @@ -1,79 +0,0 @@ -from typing import List - - -from ..types import StratigraphicUnit, StratigraphicSurface -from ..stratigraphy_utils import sort_stratigraphic_names_by_hierarchy, sort_stratigraphic_units_by_hierarchy - -DROGON_STRAT_UNITS = [ - StratigraphicUnit( - identifier="Valysar Fm.", - top="Valysar Fm. Top", - base="Valysar Fm. Base", - color_r=255, - color_g=165, - color_b=0, - strat_unit_level=2, - strat_unit_type="formation", - strat_unit_parent="VOLANTIS GP.", - top_age=1, - base_age=2, - lithology_type="unknown", - ), - StratigraphicUnit( - identifier="Therys Fm.", - top="Therys Fm. Top", - base="Therys Fm. Base", - color_r=255, - color_g=255, - color_b=0, - strat_unit_level=2, - strat_unit_type="formation", - strat_unit_parent="VOLANTIS GP.", - top_age=2, - base_age=3, - lithology_type="unknown", - ), - StratigraphicUnit( - identifier="Volon Fm.", - top="Volon Fm. Top", - base="Volon Fm. Base", - color_r=143, - color_g=188, - color_b=143, - strat_unit_level=2, - strat_unit_type="formation", - strat_unit_parent="VOLANTIS GP.", - top_age=3, - base_age=4, - lithology_type="unknown", - ), - StratigraphicUnit( - identifier="VOLANTIS GP.", - top="VOLANTIS GP. Top", - base="VOLANTIS GP. Base", - color_r=255, - color_g=165, - color_b=0, - strat_unit_level=1, - strat_unit_type="group", - strat_unit_parent=None, - top_age=4, - base_age=5, - lithology_type="unknown", - ), -] - - -class StratigraphyAccess: - def __init__(self, access_token: str): - self._smda_token = access_token - - # type: ignore - # pylint: disable=unused-argument - async def get_stratigraphic_units(self, stratigraphic_column_identifier: str) -> List[StratigraphicUnit]: - return sort_stratigraphic_units_by_hierarchy(DROGON_STRAT_UNITS) - - # type: ignore - # pylint: disable=unused-argument - async def get_stratigraphic_surfaces(self, stratigraphic_column_identifier: str) -> List[StratigraphicSurface]: - return sort_stratigraphic_names_by_hierarchy(DROGON_STRAT_UNITS) diff --git a/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_well_access.py b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_well_access.py deleted file mode 100644 index aa028090e..000000000 --- a/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_well_access.py +++ /dev/null @@ -1,99 +0,0 @@ -from typing import List, Optional - -from ..types import WellborePick, WellboreTrajectory, WellboreHeader - -from ._mocked_wellbore_picks import mocked_wellbore_picks - - -class WellAccess: - def __init__(self, access_token: str): - self._smda_token = access_token - - # type: ignore - # pylint: disable=unused-argument - async def get_all_picks_for_wellbore(self, wellbore_uuid: str) -> List[WellborePick]: - """Get Drogon picks""" - if wellbore_uuid == "drogon_horizontal": - well_picks = [pick for pick in mocked_wellbore_picks if pick.unique_wellbore_identifier == "55/33-A-4"] - elif wellbore_uuid == "drogon_vertical": - well_picks = [pick for pick in mocked_wellbore_picks if pick.unique_wellbore_identifier == "55/33-1"] - return well_picks - - # type: ignore - # pylint: disable=unused-argument - async def get_field_wellbore_trajectories( - self, field_identifier: str, unique_wellbore_identifiers: Optional[List[str]] = None - ) -> List[WellboreTrajectory]: - """Get all Drogon trajectories""" - return [ - WellboreTrajectory( - wellbore_uuid="drogon_vertical", - unique_wellbore_identifier="55/33-1", - tvd_msl_arr=[-25.0, 1774.5], - md_arr=[0.0, 1799.5], - easting_arr=[462480.0, 462480.0], - northing_arr=[5934232.0, 5934232.0], - ), - WellboreTrajectory( - wellbore_uuid="drogon_horizontal", - unique_wellbore_identifier="55/33-A-4", - tvd_msl_arr=[-49.0, 1293.4185, 1536.9384, 1616.4998, 1630.5153, 1656.9874], - md_arr=[0.0, 1477.0, 1761.5, 1899.2601, 2363.9988, 3578.5], - easting_arr=[463256.911, 463564.402, 463637.925, 463690.658, 463910.452, 464465.876], - northing_arr=[5930542.294, 5931057.803, 5931184.235, 5931278.837, 5931688.122, 5932767.761], - ), - ] - - async def get_wellbore_trajectories(self, wellbore_uuids: List[str]) -> List[WellboreTrajectory]: - """Get Drogon trajectory""" - trajs: List[WellboreTrajectory] = [] - if "drogon_horizontal" in wellbore_uuids: - trajs.append( - WellboreTrajectory( - wellbore_uuid="drogon_horizontal", - unique_wellbore_identifier="55/33-A-4", - tvd_msl_arr=[-49.0, 1293.4185, 1536.9384, 1616.4998, 1630.5153, 1656.9874], - md_arr=[0.0, 1477.0, 1761.5, 1899.2601, 2363.9988, 3578.5], - easting_arr=[463256.911, 463564.402, 463637.925, 463690.658, 463910.452, 464465.876], - northing_arr=[5930542.294, 5931057.803, 5931184.235, 5931278.837, 5931688.122, 5932767.761], - ) - ) - if "drogon_vertical" in wellbore_uuids: - trajs.append( - WellboreTrajectory( - wellbore_uuid="drogon_vertical", - unique_wellbore_identifier="55/33-1", - tvd_msl_arr=[-25.0, 1774.5], - md_arr=[0.0, 1799.5], - easting_arr=[462480.0, 462480.0], - northing_arr=[5934232.0, 5934232.0], - ) - ) - return trajs - - # type: ignore - # pylint: disable=unused-argument - async def get_wellbore_headers(self, field_identifier: str) -> List[WellboreHeader]: - """Get Drogon wellbore headers""" - return [ - WellboreHeader( - wellbore_uuid="drogon_vertical", - unique_wellbore_identifier="55/33-1", - well_uuid="drogon_vertical", - unique_well_identifier="55/33-1", - well_easting=462480.000, - well_northing=5934232.000, - depth_reference_point="RKB", - depth_reference_elevation=25.0, - ), - WellboreHeader( - wellbore_uuid="drogon_horizontal", - unique_wellbore_identifier="55/33-A-4", - well_uuid="drogon_horizontal", - unique_well_identifier="55/33-A-4", - well_easting=463256.911, - well_northing=5930542.294, - depth_reference_point="RKB", - depth_reference_elevation=49.0, - ), - ] diff --git a/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_wellbore_picks.py b/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_wellbore_picks.py deleted file mode 100644 index 496e33307..000000000 --- a/backend_py/primary/primary/services/smda_access/mocked_drogon_smda_access/_mocked_wellbore_picks.py +++ /dev/null @@ -1,175 +0,0 @@ -from typing import List - -from primary.services.smda_access.types import WellborePick - -mocked_wellbore_picks: List[WellborePick] = [ - WellborePick( - northing=462480.0, - easting=5934232.0, - tvd=1645.44, - tvd_msl=1645.0, - md=1670.0, - md_msl=1670.0, - unique_wellbore_identifier="55/33-1", - pick_identifier="Volantis Fm. Base", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=462480.0, - easting=5934232.0, - tvd=0.0, - tvd_msl=0.0, - md=0.0, - md_msl=0.0, - unique_wellbore_identifier="55/33-1", - pick_identifier="MSL", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=462480.0, - easting=5934232.0, - tvd=1620.08, - tvd_msl=1620.0, - md=1645.0, - md_msl=1645.0, - unique_wellbore_identifier="55/33-1", - pick_identifier="Therys Fm. Top", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=462480.0, - easting=5934232.0, - tvd=1600.57, - tvd_msl=1601.0, - md=1626.0, - md_msl=1626.0, - unique_wellbore_identifier="55/33-1", - pick_identifier="Volantis Fm. Top", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=462480.0, - easting=5934232.0, - tvd=1630.44, - tvd_msl=1630.0, - md=1655.0, - md_msl=1655.0, - unique_wellbore_identifier="55/33-1", - pick_identifier="Volon Fm. Top", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=463256.9114, - easting=5930542.2944, - tvd=0.0, - tvd_msl=0.0, - md=0.0, - md_msl=0.0, - unique_wellbore_identifier="55/33-A-4", - pick_identifier="MSL", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=464220.0686875976, - easting=5932289.733486914, - tvd=1641.19, - tvd_msl=1641.0, - md=3041.0, - md_msl=3041.0, - unique_wellbore_identifier="55/33-A-4", - pick_identifier="Therys Fm. Top", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=463849.68880478514, - easting=5931573.151211523, - tvd=1628.2, - tvd_msl=1628.0, - md=2234.0, - md_msl=2234.0, - unique_wellbore_identifier="55/33-A-4", - pick_identifier="Volantis Fm. Top", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=464220.0686875976, - easting=5932289.733486914, - tvd=1641.19, - tvd_msl=1641.0, - md=3041.0, - md_msl=3041.0, - unique_wellbore_identifier="55/33-A-4", - pick_identifier="Volon Fm. Top", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=463256.9114, - easting=5930542.2944, - tvd=1828.2, - tvd_msl=1828.0, - md=2434.0, - md_msl=2434.0, - unique_wellbore_identifier="55/33-A-4", - pick_identifier="VOLANTIS GP. Top", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=464220.0686875976, - easting=5932289.733486914, - tvd=1841.19, - tvd_msl=1841.0, - md=3241.0, - md_msl=3241.0, - unique_wellbore_identifier="55/33-A-4", - pick_identifier="VOLANTIS GP. Top", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=463849.68880478514, - easting=5931573.151211523, - tvd=1628.2, - tvd_msl=1628.0, - md=2234.0, - md_msl=2234.0, - unique_wellbore_identifier="55/33-A-4", - pick_identifier="VOLANTIS GP. Base", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), - WellborePick( - northing=464220.0686875976, - easting=5932289.733486914, - tvd=1641.19, - tvd_msl=1641.0, - md=3041.0, - md_msl=3041.0, - unique_wellbore_identifier="55/33-A-4", - pick_identifier="VOLANTIS GP. Base", - confidence=None, - depth_reference_point="RKB", - md_unit="m", - ), -] diff --git a/backend_py/primary/primary/services/smda_access/queries/__init__.py b/backend_py/primary/primary/services/smda_access/queries/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend_py/primary/primary/services/smda_access/queries/_get_request.py b/backend_py/primary/primary/services/smda_access/queries/_get_request.py deleted file mode 100644 index 52c14d3fe..000000000 --- a/backend_py/primary/primary/services/smda_access/queries/_get_request.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import List - -import httpx -from dotenv import load_dotenv -from webviz_pkg.core_utils.perf_timer import PerfTimer - -from primary import config - -load_dotenv() - - -async def get(access_token: str, endpoint: str, params: dict) -> List[dict]: - """ - Generic GET request to SMDA API. - Uses `next` pagination to get all results. - https://smda.equinor.com/learn/develop/smda-rest-api/#_next - """ - urlstring = f"https://api.gateway.equinor.com/smda/v2.0/smda-api/{endpoint}?" - params = params if params else {} - params.update({"_items": 10000}) - headers = { - "Content-Type": "application/json", - "authorization": f"Bearer {access_token}", - "Ocp-Apim-Subscription-Key": config.SMDA_SUBSCRIPTION_KEY, - } - timer = PerfTimer() - - async with httpx.AsyncClient() as client: - response = await client.get(urlstring, params=params, headers=headers, timeout=60) - results = [] - if response.status_code == 200: - results = response.json()["data"]["results"] - next_request = response.json()["data"]["next"] - while next_request is not None: - params["_next"] = next_request - response = await client.get(urlstring, params=params, headers=headers, timeout=60) - result = response.json()["data"]["results"] - if result: - results.extend(response.json()["data"]["results"]) - next_request = response.json()["data"]["next"] - elif response.status_code == 404: - print(f"{str(response.status_code) } {endpoint} either does not exists or can not be found") - else: - print(f"[WARNING:] Can not fetch data from endpont {endpoint} ({ str(response.status_code)})") - print(f"TIME SMDA fetch {endpoint} took {timer.lap_s():.2f} seconds") - - return results diff --git a/backend_py/primary/primary/services/smda_access/queries/get_field_wellbore_trajectories.py b/backend_py/primary/primary/services/smda_access/queries/get_field_wellbore_trajectories.py deleted file mode 100644 index 06f9b8313..000000000 --- a/backend_py/primary/primary/services/smda_access/queries/get_field_wellbore_trajectories.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import List, Optional - -import pandas as pd - -from webviz_pkg.core_utils.perf_timer import PerfTimer -from ..types import WellboreTrajectory -from ._get_request import get - - -async def get_field_wellbore_trajectories( - access_token: str, - field_identifier: str, - unique_wellbore_identifiers: Optional[List[str]] = None, -) -> List[WellboreTrajectory]: - endpoint = "wellbore-survey-samples" - params = { - "_projection": "wellbore_uuid, unique_wellbore_identifier,easting,northing,tvd_msl,md", - "_sort": "unique_wellbore_identifier,md", - "field_identifier": field_identifier, - } - if unique_wellbore_identifiers: - params["unique_wellbore_identifiers"] = ", ".join(unique_wellbore_identifiers) - timer = PerfTimer() - result = await get(access_token=access_token, endpoint=endpoint, params=params) - print(f"TIME SMDA fetch wellbore trajectories took {timer.lap_s():.2f} seconds") - resultdf = pd.DataFrame.from_dict(result) - print(f"TIME SMDA wellbore trajectories to dataframe{timer.lap_s():.2f} seconds") - wellbore_trajectories: List[WellboreTrajectory] = [] - for wellbore, df in resultdf.groupby("unique_wellbore_identifier"): - wellbore_trajectories.append( - WellboreTrajectory( - wellbore_uuid=df["wellbore_uuid"].iloc[0], - unique_wellbore_identifier=wellbore, - tvd_msl_arr=df["tvd_msl"].tolist(), - md_arr=df["md"].tolist(), - easting_arr=df["easting"].tolist(), - northing_arr=df["northing"].tolist(), - ) - ) - print(f"TIME SMDA wellbore trajectories to list and validate {timer.lap_s():.2f} seconds") - return wellbore_trajectories diff --git a/backend_py/primary/primary/services/smda_access/queries/get_picks_for_wellbore.py b/backend_py/primary/primary/services/smda_access/queries/get_picks_for_wellbore.py deleted file mode 100644 index 5fcfc61a3..000000000 --- a/backend_py/primary/primary/services/smda_access/queries/get_picks_for_wellbore.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import List, Optional - -from ..types import WellborePick -from ._get_request import get - - -async def get_picks_for_wellbore( - access_token: str, - wellbore_uuid: str, - pick_identifier: Optional[str] = None, - interpreter: str = "STAT", -) -> List[WellborePick]: - """ - Returns wellbore picks for a given wellbore uuid. I.e. picks for each pick identifier (surface) - with the matching wellbore uuid. - - If specific pick identifiers are not provided, all picks for the wellbore uuid are returned. - """ - - endpoint = "wellbore-picks" - params = { - "_sort": "unique_wellbore_identifier,md", - "wellbore_uuid": wellbore_uuid, - "interpreter": interpreter, - } - if pick_identifier: - params["pick_identifier"] = pick_identifier - - results = await get(access_token=access_token, endpoint=endpoint, params=params) - picks = [WellborePick(**result) for result in results] - return picks diff --git a/backend_py/primary/primary/services/smda_access/queries/get_stratigraphic_units.py b/backend_py/primary/primary/services/smda_access/queries/get_stratigraphic_units.py deleted file mode 100644 index 6d5ac97c7..000000000 --- a/backend_py/primary/primary/services/smda_access/queries/get_stratigraphic_units.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import List - -from webviz_pkg.core_utils.perf_timer import PerfTimer -from ..types import StratigraphicUnit -from ._get_request import get - - -async def get_stratigraphic_units(access_token: str, stratigraphic_column_identifier: str) -> List[StratigraphicUnit]: - """Returns a list of all stratigraphic units in a stratigraphic column.""" - endpoint = "strat-units" - params = { - "strat_column_identifier": stratigraphic_column_identifier, - "_sort": "top_age", - "_projection": "top,base,identifier,strat_unit_level,strat_unit_type,strat_unit_parent,top_age,base_age,color_r,color_g,color_b", - } - results = await get(access_token=access_token, endpoint=endpoint, params=params) - timer = PerfTimer() - strat_units = [StratigraphicUnit(**result) for result in results] - print(f"TIME SMDA validate stratigraphic units {timer.lap_s():.2f} seconds") - return strat_units diff --git a/backend_py/primary/primary/services/smda_access/queries/get_wellbore_headers.py b/backend_py/primary/primary/services/smda_access/queries/get_wellbore_headers.py deleted file mode 100644 index 6fd245cb0..000000000 --- a/backend_py/primary/primary/services/smda_access/queries/get_wellbore_headers.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import List - - -from webviz_pkg.core_utils.perf_timer import PerfTimer -from ..types import WellboreHeader -from ._get_request import get - - -async def get_wellbore_headers( - access_token: str, - field_identifier: str, -) -> List[WellboreHeader]: - endpoint = "wellbore-survey-headers" - projection = [ - "wellbore_uuid", - "unique_wellbore_identifier", - "well_uuid", - "unique_well_identifier", - "well_easting", - "well_northing", - "depth_reference_point", - "depth_reference_elevation", - ] - params = { - "_projection": ",".join(projection), - "_sort": "unique_wellbore_identifier", - "field_identifier": field_identifier, - } - - timer = PerfTimer() - result = await get(access_token=access_token, endpoint=endpoint, params=params) - print(f"TIME SMDA fetch well headers took {timer.lap_s():.2f} seconds") - return [WellboreHeader(**result) for result in result] diff --git a/backend_py/primary/primary/services/smda_access/queries/get_wellbore_picks_for_field.py b/backend_py/primary/primary/services/smda_access/queries/get_wellbore_picks_for_field.py deleted file mode 100644 index 0a219daf1..000000000 --- a/backend_py/primary/primary/services/smda_access/queries/get_wellbore_picks_for_field.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import List, Optional - -from webviz_pkg.core_utils.perf_timer import PerfTimer -from ..types import WellborePick -from ._get_request import get - - -async def get_wellbore_picks_for_field( - access_token: str, - field_identifier: str, - pick_identifier: str, - interpreter: str = "STAT", - obs_no: Optional[int] = None, -) -> List[WellborePick]: - """ - Returns wellbore picks for a given identifier(formation top/base) - for all wells in a field - """ - endpoint = "wellbore-picks" - params = { - "_sort": "unique_wellbore_identifier,md", - "field_identifier": field_identifier, - "pick_identifier": pick_identifier, - "interpreter": interpreter, - } - if obs_no: - params["obs_no"] = str(obs_no) - - results = await get(access_token=access_token, endpoint=endpoint, params=params) - timer = PerfTimer() - picks = [WellborePick(**result) for result in results] - print(f"TIME SMDA validate wellbore picks took {timer.lap_s():.2f} seconds") - return picks diff --git a/backend_py/primary/primary/services/smda_access/queries/get_wellbore_trajectory.py b/backend_py/primary/primary/services/smda_access/queries/get_wellbore_trajectory.py deleted file mode 100644 index 48e31f01f..000000000 --- a/backend_py/primary/primary/services/smda_access/queries/get_wellbore_trajectory.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import List - -import pandas as pd - -from webviz_pkg.core_utils.perf_timer import PerfTimer -from ..types import WellboreTrajectory -from ._get_request import get - - -async def get_wellbore_trajectories(access_token: str, wellbore_uuids: List[str]) -> List[WellboreTrajectory]: - endpoint = "wellbore-survey-samples" - params = { - "_projection": "wellbore_uuid, unique_wellbore_identifier,easting,northing,tvd_msl,md", - "_sort": "unique_wellbore_identifier,md", - "wellbore_uuid": ", ".join(wellbore_uuids), - } - - timer = PerfTimer() - result = await get(access_token=access_token, endpoint=endpoint, params=params) - print(f"TIME SMDA fetch wellbore trajectories took {timer.lap_s():.2f} seconds") - resultdf = pd.DataFrame.from_dict(result) - print(f"TIME SMDA wellbore trajectories to dataframe{timer.lap_s():.2f} seconds") - wellbore_trajectories: List[WellboreTrajectory] = [] - for wellbore, df in resultdf.groupby("unique_wellbore_identifier"): - wellbore_trajectories.append( - WellboreTrajectory( - wellbore_uuid=df["wellbore_uuid"].iloc[0], - unique_wellbore_identifier=wellbore, - tvd_msl_arr=df["tvd_msl"].tolist(), - md_arr=df["md"].tolist(), - easting_arr=df["easting"].tolist(), - northing_arr=df["northing"].tolist(), - ) - ) - print(f"TIME SMDA wellbore trajectories to list and validate {timer.lap_s():.2f} seconds") - return wellbore_trajectories diff --git a/backend_py/primary/primary/services/smda_access/smda_access.py b/backend_py/primary/primary/services/smda_access/smda_access.py new file mode 100644 index 000000000..152d53c15 --- /dev/null +++ b/backend_py/primary/primary/services/smda_access/smda_access.py @@ -0,0 +1,243 @@ +import logging +from typing import List, Optional + +import polars as pl + +from primary.services.service_exceptions import ( + Service, + NoDataError, +) + +from .types import WellborePick, WellboreTrajectory, WellboreHeader, StratigraphicUnit, StratigraphicSurface +from .stratigraphy_utils import sort_stratigraphic_names_by_hierarchy +from ._smda_get_request import smda_get_request + +LOGGER = logging.getLogger(__name__) + + +class SmdaEndpoints: + STRAT_UNITS = "strat-units" + WELLBORE_SURVEY_HEADERS = "wellbore-survey-headers" + WELLHEADERS = "wellheaders" + WELLBORE_SURVEY_SAMPLES = "wellbore-survey-samples" + WELLBORE_PICKS = "wellbore-picks" + + +class SmdaAccess: + def __init__(self, access_token: str, field_identifier: str): + self._smda_token = access_token + self._field_identifier = field_identifier + + async def _smda_get_request(self, endpoint: str, params: dict) -> List[dict]: + return await smda_get_request(access_token=self._smda_token, endpoint=endpoint, params=params) + + async def get_stratigraphic_units(self, strat_column_identifier: str) -> List[StratigraphicUnit]: + """ + Get stratigraphic units for a given stratigraphic column + """ + + params = { + "strat_column_identifier": strat_column_identifier, + "_sort": "strat_unit_level,top_age", + } + results = await self._smda_get_request(endpoint=SmdaEndpoints.STRAT_UNITS, params=params) + if not results: + raise NoDataError(f"No stratigraphic units found for {strat_column_identifier=}.", Service.SMDA) + units = [StratigraphicUnit(**result) for result in results] + return units + + async def get_wellbore_headers(self) -> List[WellboreHeader]: + """ + Get wellbore header information for all wellbores in a field. + We need the wellbores with actual survey data, so we must use the wellbore-survey-headers endpoint. + Additionally, we need the wellbore purpose and status, which we only get from the wellheaders endpoint. + """ + projection = [ + "wellbore_uuid", + "unique_wellbore_identifier", + "well_uuid", + "unique_well_identifier", + "well_easting", + "well_northing", + "depth_reference_point", + "depth_reference_elevation", + ] + params = { + "_projection": ",".join(projection), + "_sort": "unique_wellbore_identifier", + "field_identifier": self._field_identifier, + } + + survey_header_results = await self._smda_get_request( + endpoint=SmdaEndpoints.WELLBORE_SURVEY_HEADERS, params=params + ) + + if not survey_header_results: + raise NoDataError(f"No wellbore headers found for {self._field_identifier=}.", Service.SMDA) + + projection = ["unique_wellbore_identifier", "wellbore_purpose", "wellbore_status"] + params = { + "_projection": ",".join(projection), + "_sort": "unique_wellbore_identifier", + "field_identifier": self._field_identifier, + } + + wellbore_headers_results = await self._smda_get_request(endpoint=SmdaEndpoints.WELLHEADERS, params=params) + + # Create a dictionary to map unique wellbore identifiers to wellbore headers for faster lookup + wellbore_headers_dict = { + wellbore_header["unique_wellbore_identifier"]: wellbore_header + for wellbore_header in wellbore_headers_results + } + + # Iterate over the survey headers and update the information from wellbore headers if available + for survey_header in survey_header_results: + unique_id = survey_header["unique_wellbore_identifier"] + + wellbore_header = wellbore_headers_dict.get(unique_id) + if wellbore_header: + survey_header["wellbore_purpose"] = wellbore_header.get("wellbore_purpose") + survey_header["wellbore_status"] = wellbore_header.get("wellbore_status") + + return [WellboreHeader(**result) for result in survey_header_results] + + async def get_wellbore_trajectories(self, wellbore_uuids: Optional[List[str]] = None) -> List[WellboreTrajectory]: + """ + Get wellbore trajectories (survey samples) for all wells in a field, optionally with a subset of wellbores. + """ + params = { + "_projection": "wellbore_uuid, unique_wellbore_identifier,easting,northing,tvd_msl,md", + "_sort": "unique_wellbore_identifier,md", + "field_identifier": self._field_identifier, + } + if wellbore_uuids: + params["wellbore_uuid"] = ", ".join(wellbore_uuids) + + result = await self._smda_get_request(endpoint=SmdaEndpoints.WELLBORE_SURVEY_SAMPLES, params=params) + + if not result: + raise NoDataError( + f"No wellbore surveys found for {self._field_identifier}, {wellbore_uuids=}.", Service.SMDA + ) + + # Convert the result to polars for processing + resultdf = pl.DataFrame(result) + + # Identify wellbores with any null values in survey columns + columns_to_check = ["tvd_msl", "md", "easting", "northing"] + + # Warn of any wellbores with null values in survey columns + wellbores_with_nulls = ( + resultdf.filter(pl.any_horizontal(pl.col(columns_to_check).is_null())) + .get_column("unique_wellbore_identifier") + .unique() + .sort() + .to_list() + ) + if wellbores_with_nulls: + for wellbore in wellbores_with_nulls: + LOGGER.warning(f"Invalid survey samples found for wellbore: {wellbore}. These will be ignored.") + + # Filter out any samples with null values + filtered_df = resultdf.filter(pl.all_horizontal(pl.col(columns_to_check).is_not_null())) + + # Group per wellbore, maintaining order + wellbore_data = filtered_df.group_by("unique_wellbore_identifier", maintain_order=True).agg( + [ + pl.col("wellbore_uuid").first().alias("wellbore_uuid"), + pl.col("tvd_msl").alias("tvd_msl_arr"), + pl.col("md").alias("md_arr"), + pl.col("easting").alias("easting_arr"), + pl.col("northing").alias("northing_arr"), + ] + ) + + wellbore_trajectories: List[WellboreTrajectory] = [ + WellboreTrajectory( + wellbore_uuid=row["wellbore_uuid"], + unique_wellbore_identifier=row["unique_wellbore_identifier"], + tvd_msl_arr=row["tvd_msl_arr"], + md_arr=row["md_arr"], + easting_arr=row["easting_arr"], + northing_arr=row["northing_arr"], + ) + for row in wellbore_data.iter_rows(named=True) + ] + return wellbore_trajectories + + async def get_wellbore_picks_for_wellbore( + self, wellbore_uuid: str, obs_no: Optional[int] = None + ) -> List[WellborePick]: + """ + Get wellbore picks for a given wellbore uuid. I.e. picks for each pick identifier (surface) + with the matching wellbore uuid. + """ + + params = { + "_sort": "unique_wellbore_identifier,md", + "wellbore_uuid": wellbore_uuid, + "interpreter": "STAT", + } + if obs_no: + params["obs_no"] = str(obs_no) + results = await self._smda_get_request(endpoint=SmdaEndpoints.WELLBORE_PICKS, params=params) + + if not results: + raise NoDataError(f"No wellbore picks found for {wellbore_uuid=}.", Service.SMDA) + + picks: List[WellborePick] = [] + for result in results: + # Drop any picks with missing data + if all(result.get(key) for key in ["northing", "easting", "tvd", "tvd_msl"]): + picks.append(WellborePick(**result)) + else: + LOGGER.warning( + f"Invalid pick found for {result.get('pick_identifier')}, {result.get('unique_wellbore_identifier')}. This will be ignored." + ) + return picks + + async def get_wellbore_picks_for_pick_identifier( + self, + pick_identifier: str, + interpreter: str = "STAT", + obs_no: Optional[int] = None, + ) -> List[WellborePick]: + """ + Get wellbore picks for a given pick identifier(formation top/base) + for all wellbores in a field + """ + params = { + "_sort": "unique_wellbore_identifier,md", + "field_identifier": self._field_identifier, + "pick_identifier": pick_identifier, + "interpreter": interpreter, + } + if obs_no: + params["obs_no"] = str(obs_no) + + results = await self._smda_get_request(endpoint=SmdaEndpoints.WELLBORE_PICKS, params=params) + if not results: + raise NoDataError( + f"No wellbore picks found for {self._field_identifier=}, {pick_identifier=}, {interpreter=}, {obs_no=}.", + Service.SMDA, + ) + picks: List[WellborePick] = [] + for result in results: + # Drop any picks with missing data + if all(result.get(key) for key in ["northing", "easting", "tvd", "tvd_msl"]): + picks.append(WellborePick(**result)) + else: + LOGGER.warning( + f"Invalid pick found for {pick_identifier=}, {result.get('unique_wellbore_identifier')}. This will be ignored." + ) + return picks + + async def get_wellbore_pick_identifiers_in_stratigraphic_column( + self, strat_column_identifier: str + ) -> List[StratigraphicSurface]: + """ + Get all potential pick identifiers (formation tops/bases) given a stratigraphic column. + """ + units = await self.get_stratigraphic_units(strat_column_identifier) + sorted_surfaces = sort_stratigraphic_names_by_hierarchy(units, only_include_top_and_base=True) + return sorted_surfaces diff --git a/backend_py/primary/primary/services/smda_access/stratigraphy_access.py b/backend_py/primary/primary/services/smda_access/stratigraphy_access.py deleted file mode 100644 index d0470b123..000000000 --- a/backend_py/primary/primary/services/smda_access/stratigraphy_access.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import List - -from .queries.get_stratigraphic_units import get_stratigraphic_units -from .types import StratigraphicUnit - - -class StratigraphyAccess: - def __init__(self, access_token: str): - self._smda_token = access_token - - async def get_stratigraphic_units(self, stratigraphic_column_identifier: str) -> List[StratigraphicUnit]: - """Get stratigraphic units for a given stratigraphic column""" - return await get_stratigraphic_units(self._smda_token, stratigraphic_column_identifier) diff --git a/backend_py/primary/primary/services/smda_access/stratigraphy_utils.py b/backend_py/primary/primary/services/smda_access/stratigraphy_utils.py index d501364a7..c48dbb46a 100644 --- a/backend_py/primary/primary/services/smda_access/stratigraphy_utils.py +++ b/backend_py/primary/primary/services/smda_access/stratigraphy_utils.py @@ -47,7 +47,7 @@ def flatten_hierarchical_structure(units: List[HierarchicalStratigraphicUnit]) - def flatten_hierarchical_structure_to_surface_name( - units: List[HierarchicalStratigraphicUnit], idx: int = 0 + units: List[HierarchicalStratigraphicUnit], only_include_top_and_base: bool = False, idx: int = 0 ) -> List[StratigraphicSurface]: """Flattens the hierarchy of stratigraphic units to a list of stratigraphic surfaces preserving the order. On Drogon this will result in the following list: @@ -76,16 +76,21 @@ def flatten_hierarchical_structure_to_surface_name( strat_unit_identifier=unit.identifier, ) ) - flattened_list.append( - StratigraphicSurface( - name=unit.identifier, - feature=StratigraphicFeature.ZONE, - strat_unit_parent=unit.strat_unit_parent, - relative_strat_unit_level=idx, - strat_unit_identifier=unit.identifier, + if not only_include_top_and_base: + flattened_list.append( + StratigraphicSurface( + name=unit.identifier, + feature=StratigraphicFeature.ZONE, + strat_unit_parent=unit.strat_unit_parent, + relative_strat_unit_level=idx, + strat_unit_identifier=unit.identifier, + ) + ) + flattened_list.extend( + flatten_hierarchical_structure_to_surface_name( + hierarchical_unit.children, only_include_top_and_base=only_include_top_and_base, idx=idx + 1 ) ) - flattened_list.extend(flatten_hierarchical_structure_to_surface_name(hierarchical_unit.children, idx=idx + 1)) flattened_list.append( StratigraphicSurface( name=unit.base, @@ -100,10 +105,12 @@ def flatten_hierarchical_structure_to_surface_name( return flattened_list -def sort_stratigraphic_names_by_hierarchy(strat_units: List[StratigraphicUnit]) -> List[StratigraphicSurface]: +def sort_stratigraphic_names_by_hierarchy( + strat_units: List[StratigraphicUnit], only_include_top_and_base: bool = False +) -> List[StratigraphicSurface]: """Get a flatten list of top/unit/base surface names in lithostratigraphical order""" hierarchical_units = create_hierarchical_structure(strat_units) - sorted_surfaces = flatten_hierarchical_structure_to_surface_name(hierarchical_units) + sorted_surfaces = flatten_hierarchical_structure_to_surface_name(hierarchical_units, only_include_top_and_base) unique_sorted_surfaces = [] surface_names = set() for surface in sorted_surfaces: diff --git a/backend_py/primary/primary/services/smda_access/types.py b/backend_py/primary/primary/services/smda_access/types.py index fe2903e78..b9bdefa45 100644 --- a/backend_py/primary/primary/services/smda_access/types.py +++ b/backend_py/primary/primary/services/smda_access/types.py @@ -17,6 +17,7 @@ class WellborePick(BaseModel): md: float md_msl: float unique_wellbore_identifier: str + wellbore_uuid: str pick_identifier: str confidence: Optional[str] = None depth_reference_point: str @@ -41,6 +42,8 @@ class WellboreHeader(BaseModel): well_northing: float depth_reference_point: str depth_reference_elevation: float + wellbore_purpose: str | None + wellbore_status: str | None class StratigraphicUnit(BaseModel): diff --git a/backend_py/primary/primary/services/smda_access/well_access.py b/backend_py/primary/primary/services/smda_access/well_access.py deleted file mode 100644 index 6bf14ac51..000000000 --- a/backend_py/primary/primary/services/smda_access/well_access.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import List, Optional - -from .types import WellborePick, WellboreTrajectory, WellboreHeader - -from .queries.get_wellbore_headers import get_wellbore_headers -from .queries.get_wellbore_picks_for_field import get_wellbore_picks_for_field -from .queries.get_field_wellbore_trajectories import get_field_wellbore_trajectories -from .queries.get_wellbore_trajectory import get_wellbore_trajectories -from .queries.get_picks_for_wellbore import get_picks_for_wellbore - - -class WellAccess: - def __init__(self, access_token: str): - self._smda_token = access_token - - async def get_field_wellbore_picks(self, field_identifier: str, pick_identifier: str) -> List[WellborePick]: - wellbore_picks = await get_wellbore_picks_for_field( - access_token=self._smda_token, - field_identifier=field_identifier, - pick_identifier=pick_identifier, - ) - return wellbore_picks - - async def get_field_wellbore_trajectories( - self, field_identifier: str, unique_wellbore_identifiers: Optional[List[str]] = None - ) -> List[WellboreTrajectory]: - wellbore_trajectories = await get_field_wellbore_trajectories( - access_token=self._smda_token, - field_identifier=field_identifier, - unique_wellbore_identifiers=unique_wellbore_identifiers, - ) - return wellbore_trajectories - - async def get_wellbore_trajectories(self, wellbore_uuids: List[str]) -> List[WellboreTrajectory]: - wellbore_trajectories = await get_wellbore_trajectories( - access_token=self._smda_token, wellbore_uuids=wellbore_uuids - ) - return wellbore_trajectories - - async def get_wellbore_headers(self, field_identifier: str) -> List[WellboreHeader]: - return await get_wellbore_headers(access_token=self._smda_token, field_identifier=field_identifier) - - async def get_all_picks_for_wellbore(self, wellbore_uuid: str) -> List[WellborePick]: - return await get_picks_for_wellbore( - access_token=self._smda_token, wellbore_uuid=wellbore_uuid, pick_identifier=None - ) diff --git a/backend_py/primary/primary/services/ssdl_access/_ssdl_get_request.py b/backend_py/primary/primary/services/ssdl_access/_ssdl_get_request.py index 478ae7fff..8e0dffd81 100644 --- a/backend_py/primary/primary/services/ssdl_access/_ssdl_get_request.py +++ b/backend_py/primary/primary/services/ssdl_access/_ssdl_get_request.py @@ -16,7 +16,7 @@ LOGGER = logging.getLogger(__name__) -async def fetch_from_ssdl(access_token: str, endpoint: str, params: Optional[dict] = None) -> List[dict]: +async def ssdl_get_request(access_token: str, endpoint: str, params: Optional[dict] = None) -> List[dict]: """ Generic GET request to SSDL API. Uses `next` pagination to get all results. diff --git a/backend_py/primary/primary/services/ssdl_access/well_access.py b/backend_py/primary/primary/services/ssdl_access/well_access.py index acdb4542f..90ff4c45a 100644 --- a/backend_py/primary/primary/services/ssdl_access/well_access.py +++ b/backend_py/primary/primary/services/ssdl_access/well_access.py @@ -4,7 +4,7 @@ Service, InvalidDataError, ) -from ._ssdl_get_request import fetch_from_ssdl +from ._ssdl_get_request import ssdl_get_request from . import types @@ -17,7 +17,7 @@ async def get_completions_for_wellbore(self, wellbore_uuid: str) -> List[types.W endpoint = f"Wellbores/{wellbore_uuid}/completion" params = {"normalized_data": True} - ssdl_data = await fetch_from_ssdl(access_token=self._ssdl_token, endpoint=endpoint, params=params) + ssdl_data = await ssdl_get_request(access_token=self._ssdl_token, endpoint=endpoint, params=params) try: result = [types.WellboreCompletion.model_validate(casing) for casing in ssdl_data] except ValidationError as error: @@ -29,7 +29,7 @@ async def get_completions_for_wellbore(self, wellbore_uuid: str) -> List[types.W async def get_casings_for_wellbore(self, wellbore_uuid: str) -> List[types.WellboreCasing]: endpoint = f"Wellbores/{wellbore_uuid}/casing" params = {"source": "dbr"} - ssdl_data = await fetch_from_ssdl(access_token=self._ssdl_token, endpoint=endpoint, params=params) + ssdl_data = await ssdl_get_request(access_token=self._ssdl_token, endpoint=endpoint, params=params) try: result = [types.WellboreCasing.model_validate(casing) for casing in ssdl_data] except ValidationError as error: @@ -40,7 +40,7 @@ async def get_perforations_for_wellbore(self, wellbore_uuid: str) -> List[types. endpoint = f"Wellbores/{wellbore_uuid}/perforations" params = {"normalized-data": False, "details": True} - ssdl_data = await fetch_from_ssdl(access_token=self._ssdl_token, endpoint=endpoint, params=params) + ssdl_data = await ssdl_get_request(access_token=self._ssdl_token, endpoint=endpoint, params=params) try: result = [types.WellborePerforation.model_validate(casing) for casing in ssdl_data] except ValidationError as error: @@ -49,7 +49,7 @@ async def get_perforations_for_wellbore(self, wellbore_uuid: str) -> List[types. async def get_log_curve_headers_for_wellbore(self, wellbore_uuid: str) -> List[types.WellboreLogCurveHeader]: endpoint = f"WellLog/{wellbore_uuid}" - ssdl_data = await fetch_from_ssdl(access_token=self._ssdl_token, endpoint=endpoint, params=None) + ssdl_data = await ssdl_get_request(access_token=self._ssdl_token, endpoint=endpoint, params=None) try: result = [types.WellboreLogCurveHeader.model_validate(log_curve) for log_curve in ssdl_data] except ValidationError as error: @@ -58,7 +58,7 @@ async def get_log_curve_headers_for_wellbore(self, wellbore_uuid: str) -> List[t async def get_log_curve_headers_for_field(self, field_uuid: str) -> List[types.WellboreLogCurveHeader]: endpoint = f"WellLog/field/{field_uuid}" - ssdl_data = await fetch_from_ssdl(access_token=self._ssdl_token, endpoint=endpoint, params=None) + ssdl_data = await ssdl_get_request(access_token=self._ssdl_token, endpoint=endpoint, params=None) try: result = [types.WellboreLogCurveHeader.model_validate(log_curve) for log_curve in ssdl_data] except ValidationError as error: @@ -68,7 +68,7 @@ async def get_log_curve_headers_for_field(self, field_uuid: str) -> List[types.W async def get_log_curve_data(self, wellbore_uuid: str, curve_name: str) -> types.WellboreLogCurveData: params = {"normalized_data": False} endpoint = f"WellLog/{wellbore_uuid}/{curve_name}" - ssdl_data = await fetch_from_ssdl(access_token=self._ssdl_token, endpoint=endpoint, params=params) + ssdl_data = await ssdl_get_request(access_token=self._ssdl_token, endpoint=endpoint, params=params) try: result = types.WellboreLogCurveData.model_validate(ssdl_data) except ValidationError as error: diff --git a/backend_py/primary/primary/services/sumo_access/seismic_access.py b/backend_py/primary/primary/services/sumo_access/seismic_access.py index 20e053b41..699e2b208 100644 --- a/backend_py/primary/primary/services/sumo_access/seismic_access.py +++ b/backend_py/primary/primary/services/sumo_access/seismic_access.py @@ -25,7 +25,10 @@ async def from_case_uuid_async(cls, access_token: str, case_uuid: str, iteration return SeismicAccess(case=case, case_uuid=case_uuid, iteration_name=iteration_name) async def get_seismic_cube_meta_list_async(self) -> List[SeismicCubeMeta]: - seismic_cube_collection: CubeCollection = self._case.cubes.filter(iteration=self._iteration_name, realization=0) + first_realization = (await self._case.get_realizations_async())[0] + seismic_cube_collection: CubeCollection = self._case.cubes.filter( + iteration=self._iteration_name, realization=first_realization + ) seismic_cube_meta_list: List[SeismicCubeMeta] = [] async for cube in seismic_cube_collection: t_start = cube["data"].get("time", {}).get("t0", {}).get("value", None) @@ -44,7 +47,7 @@ async def get_seismic_cube_meta_list_async(self) -> List[SeismicCubeMeta]: seismic_attribute=cube["data"].get("tagname"), iso_date_or_interval=iso_string_or_time_interval, is_observation=cube["data"]["is_observation"], - is_depth=cube["data"]["vertical_domain"] == "depth", + is_depth=cube["data"].get("vertical_domain", "depth") == "depth", ) seismic_cube_meta_list.append(seismic_meta) return seismic_cube_meta_list diff --git a/backend_py/primary/tests/unit/services/smda_access/test_stratigraphy_utils.py b/backend_py/primary/tests/unit/services/smda_access/test_stratigraphy_utils.py index 913f50bf9..de48c2a35 100644 --- a/backend_py/primary/tests/unit/services/smda_access/test_stratigraphy_utils.py +++ b/backend_py/primary/tests/unit/services/smda_access/test_stratigraphy_utils.py @@ -6,7 +6,9 @@ sort_stratigraphic_units_by_hierarchy, ) from primary.services.smda_access.types import StratigraphicUnit, StratigraphicSurface, StratigraphicFeature -from primary.services.smda_access.mocked_drogon_smda_access._mocked_stratigraphy_access import DROGON_STRAT_UNITS +from primary.services.smda_access.drogon._drogon_strat_units import get_drogon_strat_units + +DROGON_STRAT_UNITS = get_drogon_strat_units() @pytest.mark.parametrize( diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 883499f0c..fd7cf0531 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -113,7 +113,6 @@ export type { WellboreLogCurveData as WellboreLogCurveData_api } from './models/ export type { WellboreLogCurveHeader as WellboreLogCurveHeader_api } from './models/WellboreLogCurveHeader'; export type { WellborePerforation as WellborePerforation_api } from './models/WellborePerforation'; export type { WellborePick as WellborePick_api } from './models/WellborePick'; -export type { WellborePicksAndStratigraphicUnits as WellborePicksAndStratigraphicUnits_api } from './models/WellborePicksAndStratigraphicUnits'; export type { WellboreTrajectory as WellboreTrajectory_api } from './models/WellboreTrajectory'; export type { WellCompletionsData as WellCompletionsData_api } from './models/WellCompletionsData'; export type { WellCompletionsUnitInfo as WellCompletionsUnitInfo_api } from './models/WellCompletionsUnitInfo'; diff --git a/frontend/src/api/models/EnsembleDetails.ts b/frontend/src/api/models/EnsembleDetails.ts index 43f2c4103..bd0e670e5 100644 --- a/frontend/src/api/models/EnsembleDetails.ts +++ b/frontend/src/api/models/EnsembleDetails.ts @@ -5,6 +5,7 @@ export type EnsembleDetails = { name: string; field_identifier: string; + stratigraphic_column_identifier: string; case_name: string; case_uuid: string; realizations: Array; diff --git a/frontend/src/api/models/PolygonsAttributeType.ts b/frontend/src/api/models/PolygonsAttributeType.ts index 225b58e8b..23a367698 100644 --- a/frontend/src/api/models/PolygonsAttributeType.ts +++ b/frontend/src/api/models/PolygonsAttributeType.ts @@ -17,4 +17,5 @@ export enum PolygonsAttributeType { PINCHOUT = 'pinchout', SUBCROP = 'subcrop', FAULT_LINES = 'fault_lines', + NAMED_AREA = 'named_area', } diff --git a/frontend/src/api/models/StratigraphicUnit.ts b/frontend/src/api/models/StratigraphicUnit.ts index 614eb722c..bb879bac0 100644 --- a/frontend/src/api/models/StratigraphicUnit.ts +++ b/frontend/src/api/models/StratigraphicUnit.ts @@ -2,11 +2,6 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -/** - * Stratigraphic unit from SMDA - * - * Camel case attributes needed for esvIntersection component in front-end - */ export type StratigraphicUnit = { identifier: string; top: string; diff --git a/frontend/src/api/models/WellboreHeader.ts b/frontend/src/api/models/WellboreHeader.ts index adcabe04a..61c60bdc4 100644 --- a/frontend/src/api/models/WellboreHeader.ts +++ b/frontend/src/api/models/WellboreHeader.ts @@ -11,5 +11,7 @@ export type WellboreHeader = { wellNorthing: number; depthReferencePoint: string; depthReferenceElevation: number; + wellborePurpose: string; + wellboreStatus: string; }; diff --git a/frontend/src/api/models/WellborePick.ts b/frontend/src/api/models/WellborePick.ts index 3bd5079c0..a48cf3d62 100644 --- a/frontend/src/api/models/WellborePick.ts +++ b/frontend/src/api/models/WellborePick.ts @@ -15,6 +15,7 @@ export type WellborePick = { md: number; mdMsl: number; uniqueWellboreIdentifier: string; + wellboreUuid: string; pickIdentifier: string; confidence: (string | null); depthReferencePoint: string; diff --git a/frontend/src/api/models/WellborePicksAndStratigraphicUnits.ts b/frontend/src/api/models/WellborePicksAndStratigraphicUnits.ts deleted file mode 100644 index 0d2f4442a..000000000 --- a/frontend/src/api/models/WellborePicksAndStratigraphicUnits.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* generated using openapi-typescript-codegen -- do no edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { StratigraphicUnit } from './StratigraphicUnit'; -import type { WellborePick } from './WellborePick'; -export type WellborePicksAndStratigraphicUnits = { - wellbore_picks: Array; - stratigraphic_units: Array; -}; - diff --git a/frontend/src/api/services/SurfaceService.ts b/frontend/src/api/services/SurfaceService.ts index 5dd76c84f..b4e01dbb7 100644 --- a/frontend/src/api/services/SurfaceService.ts +++ b/frontend/src/api/services/SurfaceService.ts @@ -4,6 +4,7 @@ /* eslint-disable */ import type { Body_post_get_surface_intersection } from '../models/Body_post_get_surface_intersection'; import type { Body_post_sample_surface_in_points } from '../models/Body_post_sample_surface_in_points'; +import type { StratigraphicUnit } from '../models/StratigraphicUnit'; import type { SurfaceDataFloat } from '../models/SurfaceDataFloat'; import type { SurfaceDataPng } from '../models/SurfaceDataPng'; import type { SurfaceIntersectionData } from '../models/SurfaceIntersectionData'; @@ -249,4 +250,24 @@ export class SurfaceService { }, }); } + /** + * Get Stratigraphic Units + * @param caseUuid Sumo case uuid + * @returns StratigraphicUnit Successful Response + * @throws ApiError + */ + public getStratigraphicUnits( + caseUuid: string, + ): CancelablePromise> { + return this.httpRequest.request({ + method: 'GET', + url: '/surface/stratigraphic_units', + query: { + 'case_uuid': caseUuid, + }, + errors: { + 422: `Validation Error`, + }, + }); + } } diff --git a/frontend/src/api/services/WellService.ts b/frontend/src/api/services/WellService.ts index d61482d61..e79a6a572 100644 --- a/frontend/src/api/services/WellService.ts +++ b/frontend/src/api/services/WellService.ts @@ -8,7 +8,7 @@ import type { WellboreHeader } from '../models/WellboreHeader'; import type { WellboreLogCurveData } from '../models/WellboreLogCurveData'; import type { WellboreLogCurveHeader } from '../models/WellboreLogCurveHeader'; import type { WellborePerforation } from '../models/WellborePerforation'; -import type { WellborePicksAndStratigraphicUnits } from '../models/WellborePicksAndStratigraphicUnits'; +import type { WellborePick } from '../models/WellborePick'; import type { WellboreTrajectory } from '../models/WellboreTrajectory'; import type { CancelablePromise } from '../core/CancelablePromise'; import type { BaseHttpRequest } from '../core/BaseHttpRequest'; @@ -17,7 +17,7 @@ export class WellService { /** * Get Drilled Wellbore Headers * Get wellbore headers for all wells in the field - * @param fieldIdentifier Sumo field identifier + * @param fieldIdentifier Official field identifier * @returns WellboreHeader Successful Response * @throws ApiError */ @@ -36,23 +36,23 @@ export class WellService { }); } /** - * Get Field Well Trajectories + * Get Well Trajectories * Get well trajectories for field - * @param fieldIdentifier Sumo field identifier - * @param uniqueWellboreIdentifiers Optional subset of well names + * @param fieldIdentifier Official field identifier + * @param wellboreUuids Optional subset of wellbore uuids * @returns WellboreTrajectory Successful Response * @throws ApiError */ - public getFieldWellTrajectories( + public getWellTrajectories( fieldIdentifier: string, - uniqueWellboreIdentifiers?: Array, + wellboreUuids?: Array, ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', - url: '/well/field_well_trajectories/', + url: '/well/well_trajectories/', query: { 'field_identifier': fieldIdentifier, - 'unique_wellbore_identifiers': uniqueWellboreIdentifiers, + 'wellbore_uuids': wellboreUuids, }, errors: { 422: `Validation Error`, @@ -60,20 +60,47 @@ export class WellService { }); } /** - * Get Well Trajectories - * Get well trajectories - * @param wellboreUuids Wellbore uuids - * @returns WellboreTrajectory Successful Response + * Get Wellbore Pick Identifiers + * Get wellbore pick identifiers for field and stratigraphic column + * @param fieldIdentifier Official field identifier + * @param stratColumnIdentifier Stratigraphic column identifier + * @returns string Successful Response * @throws ApiError */ - public getWellTrajectories( - wellboreUuids: Array, - ): CancelablePromise> { + public getWellborePickIdentifiers( + fieldIdentifier: string, + stratColumnIdentifier: string, + ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', - url: '/well/well_trajectories/', + url: '/well/wellbore_pick_identifiers/', query: { - 'wellbore_uuids': wellboreUuids, + 'field_identifier': fieldIdentifier, + 'strat_column_identifier': stratColumnIdentifier, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Get Wellbore Picks For Pick Identifier + * Get wellbore picks for field and pick identifier + * @param fieldIdentifier Official field identifier + * @param pickIdentifier Pick identifier + * @returns WellborePick Successful Response + * @throws ApiError + */ + public getWellborePicksForPickIdentifier( + fieldIdentifier: string, + pickIdentifier: string, + ): CancelablePromise> { + return this.httpRequest.request({ + method: 'GET', + url: '/well/wellbore_picks_for_pick_identifier/', + query: { + 'field_identifier': fieldIdentifier, + 'pick_identifier': pickIdentifier, }, errors: { 422: `Validation Error`, @@ -81,22 +108,22 @@ export class WellService { }); } /** - * Get Wellbore Picks And Stratigraphic Units - * Get well bore picks for a single well bore - * @param caseUuid Sumo case uuid + * Get Wellbore Picks For Wellbore + * Get wellbore picks for field and pick identifier + * @param fieldIdentifier Official field identifier * @param wellboreUuid Wellbore uuid - * @returns WellborePicksAndStratigraphicUnits Successful Response + * @returns WellborePick Successful Response * @throws ApiError */ - public getWellborePicksAndStratigraphicUnits( - caseUuid: string, + public getWellborePicksForWellbore( + fieldIdentifier: string, wellboreUuid: string, - ): CancelablePromise { + ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', - url: '/well/wellbore_picks_and_stratigraphic_units/', + url: '/well/wellbore_picks_for_wellbore/', query: { - 'case_uuid': caseUuid, + 'field_identifier': fieldIdentifier, 'wellbore_uuid': wellboreUuid, }, errors: { diff --git a/frontend/src/framework/Ensemble.ts b/frontend/src/framework/Ensemble.ts index 7c982a315..cd028fd1c 100644 --- a/frontend/src/framework/Ensemble.ts +++ b/frontend/src/framework/Ensemble.ts @@ -6,6 +6,7 @@ export class Ensemble { private _ensembleIdent: EnsembleIdent; private _fieldIdentifier: string; private _caseName: string; + private _stratigraphic_column_identifier: string; private _realizationsArr: number[]; private _parameters: EnsembleParameters; private _sensitivities: EnsembleSensitivities | null; @@ -17,6 +18,7 @@ export class Ensemble { caseUuid: string, caseName: string, ensembleName: string, + stratigraphic_column_identifier: string, realizationsArr: number[], parameterArr: Parameter[], sensitivityArr: Sensitivity[] | null, @@ -26,6 +28,7 @@ export class Ensemble { this._ensembleIdent = new EnsembleIdent(caseUuid, ensembleName); this._fieldIdentifier = fieldIdentifier; this._caseName = caseName; + this._stratigraphic_column_identifier = stratigraphic_column_identifier; this._realizationsArr = Array.from(realizationsArr).sort((a, b) => a - b); this._parameters = new EnsembleParameters(parameterArr); this._color = color; @@ -45,6 +48,10 @@ export class Ensemble { return this._fieldIdentifier; } + getStratigraphicColumnIdentifier(): string { + return this._stratigraphic_column_identifier; + } + getDisplayName(): string { if (this._customName) { return this._customName; diff --git a/frontend/src/framework/internal/EnsembleSetLoader.ts b/frontend/src/framework/internal/EnsembleSetLoader.ts index d028d652a..9e1faf451 100644 --- a/frontend/src/framework/internal/EnsembleSetLoader.ts +++ b/frontend/src/framework/internal/EnsembleSetLoader.ts @@ -97,6 +97,7 @@ export async function loadEnsembleSetMetadataFromBackend( ensembleDetails.case_uuid, ensembleDetails.case_name, ensembleDetails.name, + ensembleDetails.stratigraphic_column_identifier, ensembleDetails.realizations, parameterArr, sensitivityArr, diff --git a/frontend/src/modules/3DViewer/view/atoms/queryAtoms.ts b/frontend/src/modules/3DViewer/view/atoms/queryAtoms.ts index f7e220120..d6a50ea7a 100644 --- a/frontend/src/modules/3DViewer/view/atoms/queryAtoms.ts +++ b/frontend/src/modules/3DViewer/view/atoms/queryAtoms.ts @@ -22,7 +22,7 @@ export const fieldWellboreTrajectoriesQueryAtom = atomWithQuery((get) => { return { queryKey: ["getFieldWellboreTrajectories", fieldIdentifier ?? ""], - queryFn: () => apiService.well.getFieldWellTrajectories(fieldIdentifier ?? ""), + queryFn: () => apiService.well.getWellTrajectories(fieldIdentifier ?? ""), staleTime: STALE_TIME, gcTime: CACHE_TIME, enabled: Boolean(fieldIdentifier), diff --git a/frontend/src/modules/Intersection/interfaces.ts b/frontend/src/modules/Intersection/interfaces.ts index ccac90da8..b9e976793 100644 --- a/frontend/src/modules/Intersection/interfaces.ts +++ b/frontend/src/modules/Intersection/interfaces.ts @@ -1,4 +1,3 @@ -import { EnsembleIdent } from "@framework/EnsembleIdent"; import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; import { IntersectionType } from "@framework/types/intersection"; import { ColorScale } from "@lib/utils/ColorScale"; @@ -15,13 +14,14 @@ import { import { layerManagerAtom, selectedCustomIntersectionPolylineIdAtom, - selectedEnsembleIdentAtom, + selectedFieldIdentifierAtom, selectedWellboreAtom, } from "./settings/atoms/derivedAtoms"; import { WellboreHeader } from "./typesAndEnums"; import { LayerManager } from "./utils/layers/LayerManager"; export type SettingsToViewInterface = { + fieldIdentifier: string | null; showGridlines: boolean; gridLayer: number; zFactor: number; @@ -29,7 +29,6 @@ export type SettingsToViewInterface = { intersectionType: IntersectionType; seismicColorScale: ColorScale | null; showSeismic: boolean; - ensembleIdent: EnsembleIdent | null; selectedCustomIntersectionPolylineId: string | null; layerManager: LayerManager; wellboreHeader: WellboreHeader | null; @@ -40,6 +39,9 @@ export type Interfaces = { }; export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + fieldIdentifier: (get) => { + return get(selectedFieldIdentifierAtom); + }, showGridlines: (get) => { return get(showGridlinesAtom); }, @@ -61,9 +63,6 @@ export const settingsToViewInterfaceInitialization: InterfaceInitialization { return get(showSeismicAtom); }, - ensembleIdent: (get) => { - return get(selectedEnsembleIdentAtom); - }, selectedCustomIntersectionPolylineId: (get) => { return get(selectedCustomIntersectionPolylineIdAtom); }, diff --git a/frontend/src/modules/Intersection/settings/atoms/baseAtoms.ts b/frontend/src/modules/Intersection/settings/atoms/baseAtoms.ts index 8a8ebb463..fcac52061 100644 --- a/frontend/src/modules/Intersection/settings/atoms/baseAtoms.ts +++ b/frontend/src/modules/Intersection/settings/atoms/baseAtoms.ts @@ -1,4 +1,3 @@ -import { EnsembleIdent } from "@framework/EnsembleIdent"; import { IntersectionType } from "@framework/types/intersection"; import { ColorScale } from "@lib/utils/ColorScale"; @@ -18,6 +17,5 @@ export const seismicColorScaleAtom = atom(null); export const showSeismicAtom = atom(false); export const userSelectedFieldIdentifierAtom = atom(null); -export const userSelectedEnsembleIdentAtom = atom(null); export const userSelectedWellboreUuidAtom = atom(null); export const userSelectedCustomIntersectionPolylineIdAtom = atom(null); diff --git a/frontend/src/modules/Intersection/settings/atoms/derivedAtoms.ts b/frontend/src/modules/Intersection/settings/atoms/derivedAtoms.ts index 0432d8f8b..da6749716 100644 --- a/frontend/src/modules/Intersection/settings/atoms/derivedAtoms.ts +++ b/frontend/src/modules/Intersection/settings/atoms/derivedAtoms.ts @@ -1,4 +1,3 @@ -import { EnsembleIdent } from "@framework/EnsembleIdent"; import { EnsembleSet } from "@framework/EnsembleSet"; import { EnsembleSetAtom } from "@framework/GlobalAtoms"; import { IntersectionPolylinesAtom } from "@framework/userCreatedItems/IntersectionPolylines"; @@ -9,7 +8,6 @@ import { queryClientAtom } from "jotai-tanstack-query"; import { userSelectedCustomIntersectionPolylineIdAtom, - userSelectedEnsembleIdentAtom, userSelectedFieldIdentifierAtom, userSelectedWellboreUuidAtom, } from "./baseAtoms"; @@ -60,17 +58,6 @@ export const selectedCustomIntersectionPolylineIdAtom = atom((get) => { return userSelectedCustomIntersectionPolylineId; }); -export const selectedEnsembleIdentAtom = atom((get) => { - const ensembleSet = get(EnsembleSetAtom); - const userSelectedEnsembleIdent = get(userSelectedEnsembleIdentAtom); - - if (userSelectedEnsembleIdent === null || !ensembleSet.hasEnsemble(userSelectedEnsembleIdent)) { - return ensembleSet.getEnsembleArr()[0]?.getIdent() || null; - } - - return userSelectedEnsembleIdent; -}); - export const selectedWellboreAtom = atom((get) => { const userSelectedWellboreUuid = get(userSelectedWellboreUuidAtom); const wellboreHeaders = get(drilledWellboreHeadersQueryAtom); diff --git a/frontend/src/modules/Intersection/settings/components/layerSettings/wellpicksLayer.tsx b/frontend/src/modules/Intersection/settings/components/layerSettings/wellpicksLayer.tsx index f33b4c373..d497a147d 100644 --- a/frontend/src/modules/Intersection/settings/components/layerSettings/wellpicksLayer.tsx +++ b/frontend/src/modules/Intersection/settings/components/layerSettings/wellpicksLayer.tsx @@ -1,14 +1,20 @@ import React from "react"; +import { EnsembleIdent } from "@framework/EnsembleIdent"; import { EnsembleSet } from "@framework/EnsembleSet"; import { WorkbenchSession } from "@framework/WorkbenchSession"; import { WorkbenchSettings } from "@framework/WorkbenchSettings"; +import { EnsembleDropdown } from "@framework/components/EnsembleDropdown"; import { PendingWrapper } from "@lib/components/PendingWrapper"; import { Select, SelectOption } from "@lib/components/Select"; import { Switch } from "@lib/components/Switch"; import { LayerStatus, useLayerSettings, useLayerStatus } from "@modules/Intersection/utils/layers/BaseLayer"; import { WellpicksLayer, WellpicksLayerSettings } from "@modules/Intersection/utils/layers/WellpicksLayer"; +import { isEqual } from "lodash"; + +import { fixupSetting } from "./utils"; + export type WellpicksLayerSettingsComponentProps = { layer: WellpicksLayer; ensembleSet: EnsembleSet; @@ -22,6 +28,15 @@ export function WellpicksLayerSettingsComponent(props: WellpicksLayerSettingsCom const status = useLayerStatus(props.layer); + const fixupEnsembleIdent = fixupSetting( + "ensembleIdent", + props.ensembleSet.getEnsembleArr().map((el) => el.getIdent()), + newSettings + ); + if (!isEqual(fixupEnsembleIdent, newSettings.ensembleIdent)) { + setNewSettings((prev) => ({ ...prev, ensembleIdent: fixupEnsembleIdent })); + } + React.useEffect( function propagateSettingsChange() { props.layer.maybeUpdateSettings(newSettings); @@ -30,6 +45,10 @@ export function WellpicksLayerSettingsComponent(props: WellpicksLayerSettingsCom [newSettings, props.layer] ); + function handleEnsembleChange(ensembleIdent: EnsembleIdent | null) { + setNewSettings((prev) => ({ ...prev, ensembleIdent })); + } + function handleToggleFilterPicks(e: React.ChangeEvent) { const checked = e.target.checked; setNewSettings((prev) => ({ ...prev, filterPicks: checked })); @@ -63,6 +82,17 @@ export function WellpicksLayerSettingsComponent(props: WellpicksLayerSettingsCom return (
+
+
Stratigraphic column source
+
+ +
+
Filter picks
diff --git a/frontend/src/modules/Intersection/utils/layers/WellpicksLayer.ts b/frontend/src/modules/Intersection/utils/layers/WellpicksLayer.ts index 3da88f844..2ca0eaec8 100644 --- a/frontend/src/modules/Intersection/utils/layers/WellpicksLayer.ts +++ b/frontend/src/modules/Intersection/utils/layers/WellpicksLayer.ts @@ -12,6 +12,7 @@ const CACHE_TIME = 60 * 1000; export type WellpicksLayerSettings = { wellboreUuid: string | null; + fieldIdentifier: string | null; ensembleIdent: EnsembleIdent | null; filterPicks: boolean; selectedUnitPicks: string[]; @@ -23,6 +24,7 @@ export type WellPicksLayerData = ReturnType; export class WellpicksLayer extends BaseLayer { constructor(name: string) { const defaultSettings = { + fieldIdentifier: null, ensembleIdent: null, wellboreUuid: null, filterPicks: false, @@ -33,7 +35,11 @@ export class WellpicksLayer extends BaseLayer { const queryKey = [ "getWellborePicksAndStratigraphicUnits", - this._settings.ensembleIdent?.getCaseUuid(), + this._settings.fieldIdentifier, this._settings.wellboreUuid, ]; this.registerQueryKey(queryKey); - return queryClient - .fetchQuery({ - queryKey, - queryFn: () => - apiService.well.getWellborePicksAndStratigraphicUnits( - this._settings.ensembleIdent?.getCaseUuid() ?? "", - this._settings.wellboreUuid ?? "" - ), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - }) - .then((data) => transformFormationData(data.wellbore_picks, data.stratigraphic_units as any)); + const wellborePicksPromise = queryClient.fetchQuery({ + queryKey, + queryFn: () => + apiService.well.getWellborePicksForWellbore( + this._settings.fieldIdentifier ?? "", + this._settings.wellboreUuid ?? "" + ), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + }); + + const stratigraphicUnitsPromise = queryClient.fetchQuery({ + queryKey: ["getStratigraphicUnits", this._settings.ensembleIdent?.getCaseUuid()], + queryFn: () => apiService.surface.getStratigraphicUnits(this._settings.ensembleIdent?.getCaseUuid() ?? ""), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + }); + + return Promise.all([wellborePicksPromise, stratigraphicUnitsPromise]).then( + ([wellborePicks, stratigraphicUnits]) => transformFormationData(wellborePicks, stratigraphicUnits as any) + ); } } diff --git a/frontend/src/modules/Intersection/view/atoms/baseAtoms.ts b/frontend/src/modules/Intersection/view/atoms/baseAtoms.ts index 0e9b3f426..b381e1a67 100644 --- a/frontend/src/modules/Intersection/view/atoms/baseAtoms.ts +++ b/frontend/src/modules/Intersection/view/atoms/baseAtoms.ts @@ -3,6 +3,7 @@ import { WellboreHeader } from "@modules/Intersection/typesAndEnums"; import { atom } from "jotai"; +export const selectedFieldIdentifierAtom = atom(null); export const selectedCustomIntersectionPolylineIdAtom = atom(null); export const intersectionTypeAtom = atom(IntersectionType.WELLBORE); export const wellboreHeaderAtom = atom(null); diff --git a/frontend/src/modules/Intersection/view/atoms/interfaceEffects.ts b/frontend/src/modules/Intersection/view/atoms/interfaceEffects.ts index 575de6efa..e3e49572a 100644 --- a/frontend/src/modules/Intersection/view/atoms/interfaceEffects.ts +++ b/frontend/src/modules/Intersection/view/atoms/interfaceEffects.ts @@ -5,10 +5,15 @@ import { intersectionExtensionLengthAtom, intersectionTypeAtom, selectedCustomIntersectionPolylineIdAtom, + selectedFieldIdentifierAtom, wellboreHeaderAtom, } from "./baseAtoms"; export const settingsToViewInterfaceEffects: InterfaceEffects = [ + (getInterfaceValue, setAtomValue) => { + const fieldIdentifier = getInterfaceValue("fieldIdentifier"); + setAtomValue(selectedFieldIdentifierAtom, fieldIdentifier); + }, (getInterfaceValue, setAtomValue) => { const selectedCustomIntersectionPolylineId = getInterfaceValue("selectedCustomIntersectionPolylineId"); setAtomValue(selectedCustomIntersectionPolylineIdAtom, selectedCustomIntersectionPolylineId); diff --git a/frontend/src/modules/Intersection/view/atoms/queryAtoms.ts b/frontend/src/modules/Intersection/view/atoms/queryAtoms.ts index ca98144d2..29d0beb6b 100644 --- a/frontend/src/modules/Intersection/view/atoms/queryAtoms.ts +++ b/frontend/src/modules/Intersection/view/atoms/queryAtoms.ts @@ -3,20 +3,22 @@ import { apiService } from "@framework/ApiService"; import { atomWithQuery } from "jotai-tanstack-query"; -import { wellboreHeaderAtom } from "./baseAtoms"; +import { selectedFieldIdentifierAtom, wellboreHeaderAtom } from "./baseAtoms"; const STALE_TIME = 60 * 1000; const CACHE_TIME = 60 * 1000; export const wellboreTrajectoryQueryAtom = atomWithQuery((get) => { const wellbore = get(wellboreHeaderAtom); + const fieldIdentifier = get(selectedFieldIdentifierAtom); return { queryKey: ["getWellboreTrajectory", wellbore?.uuid ?? ""], - queryFn: () => apiService.well.getWellTrajectories(wellbore?.uuid ? [wellbore.uuid] : []), + queryFn: () => + apiService.well.getWellTrajectories(fieldIdentifier ?? "", wellbore?.uuid ? [wellbore.uuid] : []), staleTime: STALE_TIME, gcTime: CACHE_TIME, select: (data: WellboreTrajectory_api[]) => data[0], - enabled: wellbore?.uuid ? true : false, + enabled: Boolean(wellbore?.uuid) && Boolean(fieldIdentifier), }; }); diff --git a/frontend/src/modules/Intersection/view/view.tsx b/frontend/src/modules/Intersection/view/view.tsx index 86161141d..36d73025f 100644 --- a/frontend/src/modules/Intersection/view/view.tsx +++ b/frontend/src/modules/Intersection/view/view.tsx @@ -27,7 +27,7 @@ export function View(props: ModuleViewProps): React.ReactNode { const statusWriter = useViewStatusWriter(props.viewContext); const ensembleSet = useEnsembleSet(props.workbenchSession); - const ensembleIdent = props.viewContext.useSettingsToViewInterfaceValue("ensembleIdent"); + const fieldIdentifier = props.viewContext.useSettingsToViewInterfaceValue("fieldIdentifier"); const intersectionReferenceSystem = useAtomValue(intersectionReferenceSystemAtom); const wellboreHeader = props.viewContext.useSettingsToViewInterfaceValue("wellboreHeader"); const wellboreTrajectoryQuery = useAtomValue(wellboreTrajectoryQueryAtom); @@ -72,30 +72,26 @@ export function View(props: ModuleViewProps): React.ReactNode { for (const layer of layers) { if (isWellpicksLayer(layer)) { layer.maybeUpdateSettings({ - ensembleIdent, + fieldIdentifier, wellboreUuid: intersectionType === IntersectionType.WELLBORE ? wellbore?.uuid : null, }); layer.maybeRefetchData(); } } }, - [layers, wellbore, ensembleIdent, intersectionType] + [layers, wellbore, fieldIdentifier, intersectionType] ); React.useEffect( function handleTitleChange() { - let ensembleName = ""; - if (ensembleIdent) { - const ensemble = ensembleSet.findEnsemble(ensembleIdent); - ensembleName = ensemble?.getDisplayName() ?? ""; - } + const fieldName = fieldIdentifier ?? ""; props.viewContext.setInstanceTitle( `${wellboreHeader?.identifier ?? "Intersection"} - (${ensembleName})` + (${fieldName})` ); }, - [ensembleSet, ensembleIdent, wellboreHeader?.identifier, props.viewContext] + [ensembleSet, fieldIdentifier, wellboreHeader?.identifier, props.viewContext] ); // Status messages diff --git a/frontend/src/modules/_shared/WellBore/index.ts b/frontend/src/modules/_shared/WellBore/index.ts index d2e7ad319..9811263b0 100644 --- a/frontend/src/modules/_shared/WellBore/index.ts +++ b/frontend/src/modules/_shared/WellBore/index.ts @@ -1,5 +1,4 @@ export { useFieldWellboreTrajectoriesQuery as useFieldWellsTrajectoriesQuery, useDrilledWellboreHeadersQuery, - useWellboreTrajectoriesQuery, } from "./queryHooks"; diff --git a/frontend/src/modules/_shared/WellBore/queryHooks.ts b/frontend/src/modules/_shared/WellBore/queryHooks.ts index a110026d5..e3af7fe43 100644 --- a/frontend/src/modules/_shared/WellBore/queryHooks.ts +++ b/frontend/src/modules/_shared/WellBore/queryHooks.ts @@ -1,4 +1,4 @@ -import { WellboreHeader_api, WellborePicksAndStratigraphicUnits_api, WellboreTrajectory_api } from "@api"; +import { WellboreHeader_api, WellboreTrajectory_api } from "@api"; import { apiService } from "@framework/ApiService"; import { UseQueryResult, useQuery } from "@tanstack/react-query"; @@ -20,35 +20,9 @@ export function useFieldWellboreTrajectoriesQuery( ): UseQueryResult { return useQuery({ queryKey: ["getFieldWellsTrajectories", fieldIdentifier], - queryFn: () => apiService.well.getFieldWellTrajectories(fieldIdentifier ?? ""), + queryFn: () => apiService.well.getWellTrajectories(fieldIdentifier ?? ""), staleTime: STALE_TIME, gcTime: CACHE_TIME, enabled: fieldIdentifier ? true : false, }); } - -export function useWellboreTrajectoriesQuery( - wellboreUuids: string[] | undefined -): UseQueryResult { - return useQuery({ - queryKey: ["getWellTrajectories", wellboreUuids], - queryFn: () => apiService.well.getWellTrajectories(wellboreUuids ?? []), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: wellboreUuids !== undefined && wellboreUuids.length > 0, - }); -} - -export function useWellborePicksAndStratigraphicUnitsQuery( - caseUuid: string | undefined, - wellboreUuid: string | undefined, - allowEnable: boolean -): UseQueryResult { - return useQuery({ - queryKey: ["getWellborePicksAndStratigraphicUnits", caseUuid, wellboreUuid], - queryFn: () => apiService.well.getWellborePicksAndStratigraphicUnits(caseUuid ?? "", wellboreUuid ?? ""), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!(allowEnable && caseUuid && wellboreUuid), - }); -} diff --git a/frontend/tests/unit/EnsembleSet.test.ts b/frontend/tests/unit/EnsembleSet.test.ts index d0d229bc9..ec5f26dcc 100644 --- a/frontend/tests/unit/EnsembleSet.test.ts +++ b/frontend/tests/unit/EnsembleSet.test.ts @@ -5,9 +5,39 @@ import { EnsembleSet } from "@framework/EnsembleSet"; import { describe, expect, test } from "vitest"; const ensembleArr = [ - new Ensemble("DROGON", "11111111-aaaa-4444-aaaa-aaaaaaaaaaaa", "case1", "ens1", [], [], null, ""), - new Ensemble("DROGON", "11111111-aaaa-4444-aaaa-aaaaaaaaaaaa", "case1", "ens2", [], [], null, ""), - new Ensemble("DROGON", "22222222-aaaa-4444-aaaa-aaaaaaaaaaaa", "case2", "ens1", [], [], null, ""), + new Ensemble( + "DROGON", + "11111111-aaaa-4444-aaaa-aaaaaaaaaaaa", + "case1", + "ens1", + "DROGON_HAS_NO_STRAT_COLUMN", + [], + [], + null, + "" + ), + new Ensemble( + "DROGON", + "11111111-aaaa-4444-aaaa-aaaaaaaaaaaa", + "case1", + "ens2", + "DROGON_HAS_NO_STRAT_COLUMN", + [], + [], + null, + "" + ), + new Ensemble( + "DROGON", + "22222222-aaaa-4444-aaaa-aaaaaaaaaaaa", + "case2", + "ens1", + "DROGON_HAS_NO_STRAT_COLUMN", + [], + [], + null, + "" + ), ]; describe("EnsembleSet tests", () => { diff --git a/frontend/tests/unit/RealizationFilter.test.ts b/frontend/tests/unit/RealizationFilter.test.ts index 6a07eadcf..8722f5557 100644 --- a/frontend/tests/unit/RealizationFilter.test.ts +++ b/frontend/tests/unit/RealizationFilter.test.ts @@ -46,6 +46,7 @@ const FIRST_ENSEMBLE = new Ensemble( "First ensemble UUID", "First case", "First ensemble", + "DROGON_HAS_NO_STRAT_COLUMN", FIRST_ENSEMBLE_REALIZATIONS, [FIRST_PARAMETER, SECOND_PARAMETER], null,