Skip to content

Commit

Permalink
Add observations service/endpoints (#432)
Browse files Browse the repository at this point in the history
  • Loading branch information
HansKallekleiv authored Oct 24, 2023
1 parent 63e1180 commit 38ddee3
Show file tree
Hide file tree
Showing 13 changed files with 396 additions and 0 deletions.
2 changes: 2 additions & 0 deletions backend/src/backend/primary/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .routers.seismic.router import router as seismic_router
from .routers.surface_polygons.router import router as surface_polygons_router
from .routers.graph.router import router as graph_router
from .routers.observations.router import router as observations_router

logging.basicConfig(
level=logging.WARNING,
Expand Down Expand Up @@ -62,6 +63,7 @@ def custom_generate_unique_id(route: APIRoute) -> str:
app.include_router(seismic_router, prefix="/seismic", tags=["seismic"])
app.include_router(surface_polygons_router, prefix="/surface_polygons", tags=["surface_polygons"])
app.include_router(graph_router, prefix="/graph", tags=["graph"])
app.include_router(observations_router, prefix="/observations", tags=["observations"])

authHelper = AuthHelper()
app.include_router(authHelper.router)
Expand Down
30 changes: 30 additions & 0 deletions backend/src/backend/primary/routers/observations/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import logging
from typing import List, Optional, Literal

from fastapi import APIRouter, Depends, Query

from src.backend.auth.auth_helper import AuthHelper
from src.services.sumo_access.observation_access import ObservationAccess
from src.services.utils.authenticated_user import AuthenticatedUser

from . import schemas

LOGGER = logging.getLogger(__name__)

router = APIRouter()


@router.get("/observations/")
async def get_observations(
# fmt:off
authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user),
case_uuid: str = Query(description="Sumo case uuid"),
ensemble_name: str = Query(description="Ensemble name"),
# fmt:on
) -> schemas.Observations:
"""Retrieve all observations found in sumo case"""
access = await ObservationAccess.from_case_uuid(
authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name
)
observations = await access.get_observations()
return observations
76 changes: 76 additions & 0 deletions backend/src/backend/primary/routers/observations/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from typing import List, Optional
from enum import Enum

from pydantic import BaseModel


class ObservationType(str, Enum):
"""The observation file in Sumo is a dictionary with these datatypes as keys."""

SUMMARY = "smry"
RFT = "rft"


class SummaryVectorDateObservation(BaseModel):
"""A single observation of a summary vector at a specific date."""

date: str
comment: Optional[str] = None
value: float
error: float
label: str


class SummaryVectorObservations(BaseModel):
"""A collection of observations of a summary vector."""

vector_name: str
comment: Optional[str] = None
observations: List[SummaryVectorDateObservation]


class RftObservation(BaseModel):
"""A specific RFT (Repeat Formation Tester) observation.
Attributes:
value (float): The measured value of the observation.
comment (Optional[str]): An optional comment associated with the observation.
error (float): The measurement error associated with the observation.
zone (str): The zone or region associated with the observation.
md_msl (float): Measured depth from mean sea level.
x (float): X utm coordinate of the observation.
y (float): Y utm coordinate of the observation.
z (float): Z utm coordinate of the observation.
"""

value: float
comment: Optional[str] = None
error: float
zone: str
md_msl: float
x: float
y: float
z: float


class RftObservations(BaseModel):
"""A collection of RFT (Repeat Formation Tester) observations for a specific well at a specific date.
Attributes:
well (str): Unique well identifier
date (str): Observation date
comment (Optional[str]): An optional comment associated with the collection of observations.
observations (List[RftObservation]): A list of RFT observations associated with this collection.
"""

well: str
date: str
comment: Optional[str] = None
observations: List[RftObservation]


class Observations(BaseModel):
"""A collection of observations associated with a field/case/ensemble"""

summary: List[SummaryVectorObservations] = []
rft: List[RftObservations] = []
70 changes: 70 additions & 0 deletions backend/src/services/sumo_access/observation_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import logging
from typing import List

import json
from fmu.sumo.explorer.objects.dictionary import Dictionary

from ._helpers import SumoEnsemble
from .observation_types import (
Observations,
SummaryVectorDateObservation,
SummaryVectorObservations,
ObservationType,
RftObservations,
)

LOGGER = logging.getLogger(__name__)


class ObservationAccess(SumoEnsemble):
async def get_observations(self) -> Observations:
"""Retrieve all observations found in sumo case"""
observations_collection = self._case.dictionaries.filter(
stage="case",
name="observations",
tagname="all",
)
if await observations_collection.length_async() == 0:
return Observations()
if await observations_collection.length_async() > 1:
raise ValueError(f"More than one observations dictionary found. {observations_collection.names}")

observations_handle: Dictionary = await observations_collection.getitem_async(0)
observations_byteio = await observations_handle.blob_async
observations_dict = json.loads(observations_byteio.getvalue().decode())

return Observations(
summary=_create_summary_observations(observations_dict), rft=_create_rft_observations(observations_dict)
)


def _create_summary_observations(observations_dict: dict) -> List[SummaryVectorObservations]:
"""Create summary observations from the observations dictionary"""
summary_observations: List[SummaryVectorObservations] = []
if ObservationType.SUMMARY not in observations_dict:
return summary_observations
for smry_obs in observations_dict[ObservationType.SUMMARY]:
summary_observations.append(
SummaryVectorObservations(
vector_name=smry_obs["key"],
observations=[
SummaryVectorDateObservation(
date=obs["date"],
value=obs["value"],
error=obs["error"],
label=obs["label"],
)
for obs in smry_obs["observations"]
],
)
)
return summary_observations


def _create_rft_observations(observations_dict: dict) -> List[RftObservations]:
"""Create RFT observations from the observations dictionary"""
rft_observations: List[RftObservations] = []
if ObservationType.RFT not in observations_dict:
return rft_observations
LOGGER.debug("RFT observations found. This is not yet implemented.")
return rft_observations
76 changes: 76 additions & 0 deletions backend/src/services/sumo_access/observation_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from typing import List, Optional
from enum import Enum

from pydantic import BaseModel


class ObservationType(str, Enum):
"""The observation file in Sumo is a dictionary with these datatypes as keys."""

SUMMARY = "smry"
RFT = "rft"


class SummaryVectorDateObservation(BaseModel):
"""A single observation of a summary vector at a specific date."""

date: str
comment: Optional[str] = None
value: float
error: float
label: str


class SummaryVectorObservations(BaseModel):
"""A collection of observations of a summary vector."""

vector_name: str
comment: Optional[str] = None
observations: List[SummaryVectorDateObservation]


class RftObservation(BaseModel):
"""A specific RFT (Repeat Formation Tester) observation.
Attributes:
value (float): The measured value of the observation.
comment (Optional[str]): An optional comment associated with the observation.
error (float): The measurement error associated with the observation.
zone (str): The zone or region associated with the observation.
md_msl (float): Measured depth from mean sea level.
x (float): X utm coordinate of the observation.
y (float): Y utm coordinate of the observation.
z (float): Z utm coordinate of the observation.
"""

value: float
comment: Optional[str] = None
error: float
zone: str
md_msl: float
x: float
y: float
z: float


class RftObservations(BaseModel):
"""A collection of RFT (Repeat Formation Tester) observations for a specific well at a specific date.
Attributes:
well (str): Unique well identifier
date (str): Observation date
comment (Optional[str]): An optional comment associated with the collection of observations.
observations (List[RftObservation]): A list of RFT observations associated with this collection.
"""

well: str
date: str
comment: Optional[str] = None
observations: List[RftObservation]


class Observations(BaseModel):
"""A collection of observations associated with a field/case/ensemble"""

summary: List[SummaryVectorObservations] = []
rft: List[RftObservations] = []
3 changes: 3 additions & 0 deletions frontend/src/api/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ExploreService } from './services/ExploreService';
import { GraphService } from './services/GraphService';
import { GridService } from './services/GridService';
import { InplaceVolumetricsService } from './services/InplaceVolumetricsService';
import { ObservationsService } from './services/ObservationsService';
import { ParametersService } from './services/ParametersService';
import { PvtService } from './services/PvtService';
import { SeismicService } from './services/SeismicService';
Expand All @@ -28,6 +29,7 @@ export class ApiService {
public readonly graph: GraphService;
public readonly grid: GridService;
public readonly inplaceVolumetrics: InplaceVolumetricsService;
public readonly observations: ObservationsService;
public readonly parameters: ParametersService;
public readonly pvt: PvtService;
public readonly seismic: SeismicService;
Expand Down Expand Up @@ -57,6 +59,7 @@ export class ApiService {
this.graph = new GraphService(this.request);
this.grid = new GridService(this.request);
this.inplaceVolumetrics = new InplaceVolumetricsService(this.request);
this.observations = new ObservationsService(this.request);
this.parameters = new ParametersService(this.request);
this.pvt = new PvtService(this.request);
this.seismic = new SeismicService(this.request);
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ export type { GridSurface as GridSurface_api } from './models/GridSurface';
export type { HTTPValidationError as HTTPValidationError_api } from './models/HTTPValidationError';
export type { InplaceVolumetricsCategoricalMetaData as InplaceVolumetricsCategoricalMetaData_api } from './models/InplaceVolumetricsCategoricalMetaData';
export type { InplaceVolumetricsTableMetaData as InplaceVolumetricsTableMetaData_api } from './models/InplaceVolumetricsTableMetaData';
export type { Observations as Observations_api } from './models/Observations';
export type { PolygonData as PolygonData_api } from './models/PolygonData';
export type { PvtData as PvtData_api } from './models/PvtData';
export type { RftObservation as RftObservation_api } from './models/RftObservation';
export type { RftObservations as RftObservations_api } from './models/RftObservations';
export type { SeismicCubeMeta as SeismicCubeMeta_api } from './models/SeismicCubeMeta';
export { SensitivityType as SensitivityType_api } from './models/SensitivityType';
export { StatisticFunction as StatisticFunction_api } from './models/StatisticFunction';
export type { StatisticValueObject as StatisticValueObject_api } from './models/StatisticValueObject';
export { StratigraphicFeature as StratigraphicFeature_api } from './models/StratigraphicFeature';
export type { SummaryVectorDateObservation as SummaryVectorDateObservation_api } from './models/SummaryVectorDateObservation';
export type { SummaryVectorObservations as SummaryVectorObservations_api } from './models/SummaryVectorObservations';
export { SurfaceAttributeType as SurfaceAttributeType_api } from './models/SurfaceAttributeType';
export type { SurfaceData as SurfaceData_api } from './models/SurfaceData';
export type { SurfaceMeta as SurfaceMeta_api } from './models/SurfaceMeta';
Expand All @@ -61,6 +66,7 @@ export { ExploreService } from './services/ExploreService';
export { GraphService } from './services/GraphService';
export { GridService } from './services/GridService';
export { InplaceVolumetricsService } from './services/InplaceVolumetricsService';
export { ObservationsService } from './services/ObservationsService';
export { ParametersService } from './services/ParametersService';
export { PvtService } from './services/PvtService';
export { SeismicService } from './services/SeismicService';
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/api/models/Observations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

import type { RftObservations } from './RftObservations';
import type { SummaryVectorObservations } from './SummaryVectorObservations';

/**
* A collection of observations associated with a field/case/ensemble
*/
export type Observations = {
summary: Array<SummaryVectorObservations>;
rft: Array<RftObservations>;
};

28 changes: 28 additions & 0 deletions frontend/src/api/models/RftObservation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

/**
* A specific RFT (Repeat Formation Tester) observation.
*
* Attributes:
* value (float): The measured value of the observation.
* comment (Optional[str]): An optional comment associated with the observation.
* error (float): The measurement error associated with the observation.
* zone (str): The zone or region associated with the observation.
* md_msl (float): Measured depth from mean sea level.
* x (float): X utm coordinate of the observation.
* y (float): Y utm coordinate of the observation.
* z (float): Z utm coordinate of the observation.
*/
export type RftObservation = {
value: number;
comment: (string | null);
error: number;
zone: string;
md_msl: number;
'x': number;
'y': number;
'z': number;
};

22 changes: 22 additions & 0 deletions frontend/src/api/models/RftObservations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

import type { RftObservation } from './RftObservation';

/**
* A collection of RFT (Repeat Formation Tester) observations for a specific well at a specific date.
*
* Attributes:
* well (str): Unique well identifier
* date (str): Observation date
* comment (Optional[str]): An optional comment associated with the collection of observations.
* observations (List[RftObservation]): A list of RFT observations associated with this collection.
*/
export type RftObservations = {
well: string;
date: string;
comment: (string | null);
observations: Array<RftObservation>;
};

Loading

0 comments on commit 38ddee3

Please sign in to comment.