interpretation-models 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of interpretation-models might be problematic. Click here for more details.

@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Equinor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: interpretation-models
3
+ Version: 0.1.0
4
+ Summary: Interpretation models maintained by the SID team
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: dsis-schemas>=0.0.6
9
+ Requires-Dist: pydantic<3,>=2
10
+ Dynamic: license-file
11
+
12
+ # Interpretation models
13
+
14
+ This repo is meant to contain the core models for interpretations - horizons, faults, surface grids, polygons, pointsets -
15
+ handled by the SID team in SUB/EXD/DI/SDD
16
+
17
+ Models are contained in the src folder, and are meant to be used as a basis for the interpretations ingested into OSDU and
18
+ the Surfaces API im APIM.
@@ -0,0 +1,7 @@
1
+ # Interpretation models
2
+
3
+ This repo is meant to contain the core models for interpretations - horizons, faults, surface grids, polygons, pointsets -
4
+ handled by the SID team in SUB/EXD/DI/SDD
5
+
6
+ Models are contained in the src folder, and are meant to be used as a basis for the interpretations ingested into OSDU and
7
+ the Surfaces API im APIM.
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "interpretation-models"
3
+ version = "0.1.0"
4
+ description = "Interpretation models maintained by the SID team"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "dsis-schemas>=0.0.6",
9
+ "pydantic>=2, <3",
10
+ ]
11
+
12
+ [build-system]
13
+ requires = ["setuptools>=61.0"]
14
+ build-backend = "setuptools.build_meta"
15
+
16
+ [tool.setuptools.packages.find]
17
+ where = ["src"]
18
+
19
+ [tool.setuptools.package-data]
20
+ "models" = ["py.typed"]
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "pytest>=9.0.2",
25
+ "mypy>=1.19.1",
26
+ "ruff>=0.15.0",
27
+ "ipykernel>=7.2.0",
28
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: interpretation-models
3
+ Version: 0.1.0
4
+ Summary: Interpretation models maintained by the SID team
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: dsis-schemas>=0.0.6
9
+ Requires-Dist: pydantic<3,>=2
10
+ Dynamic: license-file
11
+
12
+ # Interpretation models
13
+
14
+ This repo is meant to contain the core models for interpretations - horizons, faults, surface grids, polygons, pointsets -
15
+ handled by the SID team in SUB/EXD/DI/SDD
16
+
17
+ Models are contained in the src folder, and are meant to be used as a basis for the interpretations ingested into OSDU and
18
+ the Surfaces API im APIM.
@@ -0,0 +1,21 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/interpretation_models.egg-info/PKG-INFO
5
+ src/interpretation_models.egg-info/SOURCES.txt
6
+ src/interpretation_models.egg-info/dependency_links.txt
7
+ src/interpretation_models.egg-info/requires.txt
8
+ src/interpretation_models.egg-info/top_level.txt
9
+ src/mappers/__init__.py
10
+ src/mappers/mappers_dsis.py
11
+ src/mappers/surface_helpers.py
12
+ src/models/__init__.py
13
+ src/models/extent.py
14
+ src/models/interpretation.py
15
+ src/models/origin.py
16
+ src/models/py.typed
17
+ src/models/surface.py
18
+ src/models/surfacegrid_sidv2.py
19
+ tests/test_extent.py
20
+ tests/test_surface.py
21
+ tests/test_surfacegrid_json_validation.py
@@ -0,0 +1,2 @@
1
+ dsis-schemas>=0.0.6
2
+ pydantic<3,>=2
@@ -0,0 +1 @@
1
+ """Mappers for converting between internal models and external schemas."""
@@ -0,0 +1,93 @@
1
+ import datetime
2
+ import math
3
+
4
+ from models.origin import Project
5
+ from models.interpretation import SourceMetadata, OWMetadata, PipelineMetadata
6
+ from models.surface import GridGeometry, Surface
7
+ from dsis_model_sdk.models.common import SurfaceGrid, SurfaceGridProperties
8
+
9
+
10
+ def normalize_surfacegrid_payload(payload: dict) -> dict:
11
+ normalized = dict(payload)
12
+
13
+ rotation_i = normalized.get("rotation_i")
14
+ if rotation_i is not None:
15
+ normalized["rotation_i"] = round(float(rotation_i), 4)
16
+
17
+ rotation_j = normalized.get("rotation_j")
18
+ if rotation_j is not None:
19
+ normalized["rotation_j"] = round(float(rotation_j), 4)
20
+
21
+ data_min = normalized.get("data_min")
22
+ if data_min is not None:
23
+ normalized["data_min"] = round(float(data_min), 3)
24
+
25
+ data_max = normalized.get("data_max")
26
+ if data_max is not None:
27
+ normalized["data_max"] = round(float(data_max), 3)
28
+
29
+ return normalized
30
+
31
+
32
+ def ow_to_sid(
33
+ project: Project,
34
+ ow_surface: SurfaceGrid | SurfaceGridProperties,
35
+ pipeline_metadata: PipelineMetadata | None = None,
36
+ ) -> Surface:
37
+ """Map OW surface schema to the internal Surface model.
38
+
39
+ Args:
40
+ ow_surface: DSIS CommonModel surface object to convert
41
+ pipeline_metadata: metadata calculated within or related to the run of the pipeline
42
+
43
+ Returns:
44
+ Surface instance
45
+ """
46
+
47
+ def convert_date_to_utc(
48
+ date: datetime.datetime | None = None, timezone: str | None = None
49
+ ) -> datetime.datetime | None:
50
+ pass # TODO: implement time conversion to UTC based on the timezone from Project table
51
+
52
+ if ow_surface.crs is None:
53
+ raise ValueError("SurfaceGrid.crs is required to map to SourceMetadata")
54
+
55
+ source_metadata = SourceMetadata(
56
+ project=project,
57
+ native_uid=ow_surface.native_uid,
58
+ name=ow_surface.map_data_set_name,
59
+ crs=ow_surface.crs,
60
+ z_domain=ow_surface.data_domain,
61
+ z_unit=ow_surface.z_unit,
62
+ create_user=ow_surface.create_user_id,
63
+ update_user=ow_surface.update_user_id,
64
+ remark=ow_surface.remark,
65
+ create_date=ow_surface.create_date,
66
+ create_date_utc=convert_date_to_utc(ow_surface.create_date, project.timezone),
67
+ update_date=ow_surface.update_date,
68
+ update_date_utc=convert_date_to_utc(ow_surface.update_date, project.timezone),
69
+ ow=OWMetadata(
70
+ geo_name=ow_surface.geo_name,
71
+ geo_type=ow_surface.geo_type,
72
+ attribute=ow_surface.attribute,
73
+ ),
74
+ )
75
+ geometry = GridGeometry(
76
+ ncol=ow_surface.num_cols,
77
+ nrow=ow_surface.num_rows,
78
+ xori=ow_surface.rotation_origin_x,
79
+ yori=ow_surface.rotation_origin_y,
80
+ xinc=ow_surface.grid_interval_x,
81
+ yinc=ow_surface.grid_interval_y,
82
+ rotation=math.degrees(ow_surface.rotation_i)
83
+ if ow_surface.rotation_i is not None
84
+ else None,
85
+ left_handed=True, # TODO: should this be default for surfaces from OW?
86
+ )
87
+ return Surface(
88
+ source=source_metadata,
89
+ pipeline=pipeline_metadata,
90
+ geometry=geometry,
91
+ extent=None, # TODO: calculate from grid geometry
92
+ collection=[], # TODO
93
+ )
@@ -0,0 +1,12 @@
1
+ from collections.abc import Mapping
2
+
3
+
4
+ def flatten_dict(obj: Mapping, prefix: str = "", sep: str = ".") -> dict[str, object]:
5
+ flat: dict[str, object] = {}
6
+ for key, value in obj.items():
7
+ full_key = f"{prefix}{sep}{key}" if prefix else str(key)
8
+ if isinstance(value, Mapping):
9
+ flat.update(flatten_dict(value, prefix=full_key, sep=sep))
10
+ else:
11
+ flat[full_key] = value
12
+ return flat
File without changes
@@ -0,0 +1,33 @@
1
+ from pydantic import BaseModel, field_validator
2
+
3
+
4
+ class Point(BaseModel):
5
+ """A 2D coordinate point."""
6
+
7
+ x: float
8
+ y: float
9
+
10
+
11
+ class Extent(BaseModel):
12
+ """A polygon extent defined by corner points."""
13
+
14
+ points: list[Point]
15
+
16
+ @field_validator("points")
17
+ @classmethod
18
+ def validate_minimum_points(cls, v):
19
+ if len(v) < 3:
20
+ raise ValueError("Extent must have at least 3 points to form a polygon")
21
+ return v
22
+
23
+ @property
24
+ def is_closed(self) -> bool:
25
+ """Check if the polygon is closed (first and last points are the same)."""
26
+ if len(self.points) < 2:
27
+ return False
28
+ return self.points[0] == self.points[-1]
29
+
30
+ def close(self) -> None:
31
+ """Close the polygon by appending the first point at the end if not already closed."""
32
+ if not self.is_closed and len(self.points) > 0:
33
+ self.points.append(self.points[0])
@@ -0,0 +1,50 @@
1
+ from pydantic import BaseModel
2
+ from datetime import datetime
3
+ from models.extent import Extent
4
+ from models.origin import Collection, Project
5
+
6
+
7
+ class OWMetadata(BaseModel):
8
+ geo_name: str | None = None
9
+ geo_type: str | None = None
10
+ attribute: str | None = None
11
+
12
+
13
+ class PetrelMetadata(BaseModel):
14
+ business_project: str | None = None
15
+ data_status: str | None = None
16
+ confidence_factor: str | None = None
17
+
18
+
19
+ class SourceMetadata(BaseModel):
20
+ project: Project
21
+ native_uid: str | None = None
22
+ name: str
23
+ crs: str
24
+ z_domain: str | None = None
25
+ z_unit: str | None = None
26
+ create_user: str | None = None
27
+ update_user: str | None = None
28
+ remark: str | None = None
29
+ create_date: datetime | None = None
30
+ create_date_utc: datetime | None = None
31
+ update_date: datetime | None = None
32
+ update_date_utc: datetime | None = None
33
+ ow: OWMetadata | None = None
34
+ petrel: PetrelMetadata | None = None
35
+
36
+
37
+ class PipelineMetadata(BaseModel):
38
+ id: str # SID UUID
39
+ create_date: datetime | None = None
40
+ update_date: datetime | None = None
41
+ file_availability: str | None = None
42
+ deleted: bool | None = None
43
+ deleted_date: datetime | None = None
44
+
45
+
46
+ class Interpretation(BaseModel):
47
+ source: SourceMetadata | None = None
48
+ pipeline: PipelineMetadata | None = None
49
+ extent: Extent | None = None
50
+ collection: list[Collection]
@@ -0,0 +1,19 @@
1
+ from pydantic import BaseModel
2
+ from enum import Enum
3
+
4
+
5
+ class SourceSystem(str, Enum):
6
+ OPENWORKS = "OpenWorks R5000" # or 'OpenWorks'?
7
+ PETREL = "Petrel Studio"
8
+
9
+
10
+ class Project(BaseModel):
11
+ source_system: SourceSystem
12
+ database: str
13
+ name: str
14
+ timezone: str
15
+ last_pipeline_run_date: str | None = None
16
+
17
+
18
+ class Collection(BaseModel):
19
+ id: str # SID UUID
File without changes
@@ -0,0 +1,25 @@
1
+ from pydantic import BaseModel
2
+ from models.interpretation import Interpretation
3
+
4
+
5
+ class GridGeometry(BaseModel):
6
+ ncol: int | None = None
7
+ nrow: int | None = None
8
+ xori: float | None = None
9
+ yori: float | None = None
10
+ xinc: float | None = None
11
+ yinc: float | None = None
12
+ rotation: float | None = None
13
+ left_handed: bool | None = True # yflip
14
+
15
+
16
+ class Surface(Interpretation):
17
+ geometry: GridGeometry | None = None
18
+ parent_surface_id: str | None = (
19
+ None # for SurfaceGridProperties objects? source or SID id?
20
+ )
21
+
22
+ # Redundant?
23
+ z_non: float | None = None
24
+ ntotal: int | None = None
25
+ nnan: int | None = None
@@ -0,0 +1,67 @@
1
+ from datetime import datetime
2
+ from pydantic import BaseModel
3
+
4
+ # this class is just here for development purposes, to compare the v2 and v3 models; can be removed later
5
+
6
+
7
+ class SurfaceGrid_SIDv2(BaseModel):
8
+ ssdf_uuid: str | None = None
9
+
10
+ # Source
11
+ source: str | None = None
12
+ source_database: str | None = None
13
+ source_project: str | None = None
14
+ source_grid_id: str | None = None
15
+
16
+ # Surface properties
17
+ surface_name: str | None = None
18
+ geo_name: str | None = None
19
+ geo_type: str | None = None
20
+ z_unit: str | None = None
21
+ z_non: float | None = None
22
+ z_domain: str | None = None
23
+ attribute_source: str | None = None
24
+ interpreter_source: str | None = None
25
+ create_user_source: str | None = None
26
+ update_user_source: str | None = None
27
+ remark_source: str | None = None
28
+ business_project_source: str | None = None
29
+ data_status_source: str | None = None
30
+ confidence_factor_source: str | None = None
31
+
32
+ # Coordinate systems
33
+ object_coordinate_system_name_source: str | None = None # OpenWorks R5000
34
+ object_coordinate_system_unit_source: str | None = None # OpenWorks R5000
35
+ object_coordinate_system_id_source: str | None = None # OpenWorks R5000
36
+ projected_coordinate_system: str | None = None # Petrel Studio
37
+ projected_coordinate_unit: str | None = None # Petrel Studio
38
+ projected_coordinate_uuid: str | None = None # Petrel Studio
39
+
40
+ # Grid geometry and properties
41
+ xinc: float | None = None
42
+ yinc: float | None = None
43
+ xori: float | None = None
44
+ yori: float | None = None
45
+ rotation: float | None = None
46
+ yflip: int | None = None
47
+ ncol: int | None = None
48
+ nrow: int | None = None
49
+ ntotal: int | None = None
50
+ nnan: int | None = None
51
+
52
+ # Timestamps
53
+ create_date: datetime | None = None
54
+ create_date_source: datetime | None = None
55
+ create_date_source_utc: datetime | None = None
56
+ update_date: datetime | None = None
57
+ update_date_source: datetime | None = None
58
+ update_date_source_utc: datetime | None = None
59
+
60
+ # SMDA
61
+ smda_project_uuid: str | None = None
62
+ cs_smda_project_uuid: int | None = None
63
+
64
+ # Bulk file and deletion status
65
+ file_availability: str | None = None
66
+ deleted: bool | None = None
67
+ deleted_date: datetime | None = None
@@ -0,0 +1,34 @@
1
+ import pytest
2
+ from pydantic import ValidationError
3
+ from models.extent import Extent, Point
4
+
5
+
6
+ def test_extent_validate_minimum_points_valid():
7
+ """Test that Extent accepts 3 or more points."""
8
+ # Test with exactly 3 points (minimum valid)
9
+ extent = Extent(points=[Point(x=0, y=0), Point(x=1, y=0), Point(x=0, y=1)])
10
+ assert len(extent.points) == 3
11
+
12
+ # Test with 4 points
13
+ extent = Extent(
14
+ points=[Point(x=0, y=0), Point(x=1, y=0), Point(x=1, y=1), Point(x=0, y=1)]
15
+ )
16
+ assert len(extent.points) == 4
17
+
18
+
19
+ def test_extent_validate_minimum_points_invalid():
20
+ """Test that Extent rejects less than 3 points."""
21
+ # Test with 2 points (invalid)
22
+ with pytest.raises(ValidationError) as exc_info:
23
+ Extent(points=[Point(x=0, y=0), Point(x=1, y=0)])
24
+ assert "at least 3 points" in str(exc_info.value)
25
+
26
+ # Test with 1 point (invalid)
27
+ with pytest.raises(ValidationError) as exc_info:
28
+ Extent(points=[Point(x=0, y=0)])
29
+ assert "at least 3 points" in str(exc_info.value)
30
+
31
+ # Test with 0 points (invalid)
32
+ with pytest.raises(ValidationError) as exc_info:
33
+ Extent(points=[])
34
+ assert "at least 3 points" in str(exc_info.value)
@@ -0,0 +1,13 @@
1
+ import pytest
2
+ from pydantic import ValidationError
3
+ from models.surface import Surface
4
+
5
+
6
+ def test_surface_grid_missing_required_field():
7
+ with pytest.raises(ValidationError):
8
+ Surface(
9
+ id="11111111-1111-1111-1111-111111111111",
10
+ crs="ST_ED50_UTM31N_P23031_T1133",
11
+ rotation=0.0,
12
+ # name omitted on purpose
13
+ )
@@ -0,0 +1,15 @@
1
+ import pytest
2
+ from dsis_model_sdk.models.common import SurfaceGrid
3
+ from pydantic import ValidationError
4
+
5
+
6
+ def test_surfacegrid_raw_json_fails_strict_validation(surfacegrid_payload: dict):
7
+ with pytest.raises(ValidationError):
8
+ SurfaceGrid.model_validate(surfacegrid_payload)
9
+
10
+
11
+ def test_surfacegrid_normalized_json_validates(
12
+ surfacegrid_payload_normalized: dict,
13
+ ):
14
+ obj = SurfaceGrid.model_validate(surfacegrid_payload_normalized)
15
+ assert isinstance(obj, SurfaceGrid)