Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5d8bdfe
reorder unit types so more commonly used are at top.
jamie-spyker-sqnt Aug 26, 2026
792a959
use enum type for units
jamie-spyker-sqnt Aug 26, 2026
bd118d5
remove module reference that does not exist
jamie-spyker-sqnt Aug 26, 2026
aed80d1
update order to match preferred user-shown order
jamie-spyker-sqnt Aug 26, 2026
a0fe52e
add new units/types
jamie-spyker-sqnt Aug 26, 2026
ec6d1e4
remove volume per volume, not a type that has any associated units
jamie-spyker-sqnt Aug 26, 2026
ec59a34
order units alphabetically by group
jamie-spyker-sqnt Aug 26, 2026
58bfe75
use str enum to maintain compatability with sdk usage
jamie-spyker-sqnt Aug 27, 2026
6086d3d
add category unit
jamie-spyker-sqnt Aug 27, 2026
afae110
remove volume per volume from other unit type definition
jamie-spyker-sqnt Aug 27, 2026
d57fe77
fix package imports
jamie-spyker-sqnt Aug 27, 2026
7e19397
Update comment to match code (#340)
AdamFreemanSeequent Aug 26, 2026
a67aa3c
Expose groups, add test coverage. (#329)
tim-hamblin-seequent Aug 27, 2026
a4be6f1
Blockmodel updates aug 2026 (#339)
davidknight-seequent Aug 27, 2026
0307089
fix html widget (#342)
geostats-BSY Aug 27, 2026
e321107
Add batch download tables (#331)
jamesz3ngsq Aug 31, 2026
afc160e
remove percentage unit
jamie-spyker-sqnt Aug 31, 2026
3549f35
use generic dimensionless unit to match BMS
jamie-spyker-sqnt Sep 2, 2026
fb7c233
update to use generic dimensionless another location too
jamie-spyker-sqnt Sep 2, 2026
f83f171
resolve unknown unit types to unknown, in case the service changes be…
jamie-spyker-sqnt Sep 2, 2026
4dd5b4f
Merge remote-tracking branch 'origin/main' into blockmodel-support-ad…
jamie-spyker-sqnt Sep 3, 2026
456e06f
bump version number
jamie-spyker-sqnt Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/evo-blockmodels/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "evo-blockmodels"
description = "Python SDK for using the Seequent Evo Geoscience Block Model API"
version = "0.6.0"
version = "0.6.1"
requires-python = ">=3.10"
license-files = ["LICENSE.md"]
dynamic = ["readme"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -944,14 +944,23 @@ class SizeOptionsRegular(CustomBaseModel):


class UnitType(Enum):
LENGTH = "LENGTH"
UNKNOWN = "UNKNOWN" # For unit types introduced after this SDK version
MASS_PER_MASS = "MASS_PER_MASS"
MASS_PER_VOLUME = "MASS_PER_VOLUME"
DIMENSIONLESS = "GENERIC_DIMENSIONLESS"
MASS = "MASS"
LENGTH = "LENGTH"
VOLUME = "VOLUME"
VALUE = "VALUE"
MASS_PER_VOLUME = "MASS_PER_VOLUME"
MASS_PER_MASS = "MASS_PER_MASS"
VOLUME_PER_VOLUME = "VOLUME_PER_VOLUME"
VALUE_PER_MASS = "VALUE_PER_MASS"
VALUE = "VALUE"
CATEGORY = "CATEGORY"
ANGLE = "ANGLE"

@classmethod
def _missing_(cls, value: object) -> UnitType | None:
if isinstance(value, str):
return cls.UNKNOWN
return None


class UpdateMetadataValues(CustomBaseModel):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

"""Typed access for block models with pandas DataFrame support."""

from evo.common.typed import BoundingBox, Point3, Size3d, Size3i

from .base import BaseTypedBlockModel
from .regular_block_model import RegularBlockModel, RegularBlockModelData
from .report import (
Expand All @@ -24,6 +22,7 @@
ReportResult,
ReportSpecificationData,
)
from .types import BoundingBox, Point3, Size3d, Size3i
from .units import UnitInfo, Units, UnitType, get_available_units

__all__ = [
Expand Down
109 changes: 65 additions & 44 deletions packages/evo-blockmodels/src/evo/blockmodels/typed/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,23 @@
class UnitType(Enum):
"""Types of units supported by the Block Model Service."""

LENGTH = "LENGTH"
UNKNOWN = "UNKNOWN" # For unit types introduced after this SDK version
MASS_PER_MASS = "MASS_PER_MASS"
MASS_PER_VOLUME = "MASS_PER_VOLUME"
DIMENSIONLESS = "GENERIC_DIMENSIONLESS"
MASS = "MASS"
LENGTH = "LENGTH"
VOLUME = "VOLUME"
VALUE = "VALUE"
MASS_PER_VOLUME = "MASS_PER_VOLUME"
MASS_PER_MASS = "MASS_PER_MASS"
VOLUME_PER_VOLUME = "VOLUME_PER_VOLUME"
VALUE_PER_MASS = "VALUE_PER_MASS"
VALUE = "VALUE"
CATEGORY = "CATEGORY"
ANGLE = "ANGLE"

@classmethod
def _missing_(cls, value: object) -> UnitType | None:
if isinstance(value, str):
return cls.UNKNOWN
return None


@dataclass(frozen=True)
Expand All @@ -64,7 +73,7 @@ class UnitInfo:
"""Conversion factor to the reference unit for this unit type."""


class Units:
class Units(str, Enum):
"""Common unit IDs for block model attributes.

These are the most commonly used unit IDs. For a complete list,
Expand All @@ -86,60 +95,72 @@ class Units:
await bm_ref.add_attribute(df, "metal_content", unit=Units.KILOS_PER_CUBIC_METRE)
"""

# Length units
METRES = "m"
FEET = "ft"
CENTIMETRES = "cm"

# Mass units
CARATS = "ct"
GRAMS = "g"
POUNDS = "lbm"
TROY_OUNCES = "ozm[troy]"
TONNES = "t"
KILOTONNES = "kt"
MEGATONNES = "Mt"
SHORT_TONS = "ton[US]"
THOUSAND_SHORT_TONS = "kton[US]"
MILLION_SHORT_TONS = "Mton[US]"
KILOGRAMS = "kg"
THOUSAND_POUNDS = "klbm"
MILLIGRAMS = "mg"
MICROGRAMS = "ug"
THOUSAND_CARATS = "1000 ct"
THOUSAND_TROY_OUNCES = "1000 ozm[troy]"
MILLION_TROY_OUNCES = "1000000 ozm[troy]"
MILLION_POUNDS = "Mlbm"

# Mass per mass (grade) units
PERCENT = "%[mass]"
PARTS_PER_MILLION = "ppm[mass]"
CARATS_PER_HUNDRED_TONNE = "0.01 ct/t"
CARATS_PER_TONNE = "ct/t"
GRAMS_PER_TONNE = "g/t"
MICROGRAMS_PER_GRAM = "ug/g"
MICROGRAMS_PER_KILOGRAM = "ug/kg"
MILLIGRAMS_PER_GRAM = "mg/g"
MILLIGRAMS_PER_KILOGRAM = "mg/kg"
MICROGRAMS_PER_GRAM = "ug/g"
CARATS_PER_HUNDRED_TONNE = "0.01 ct/t"
PARTS_PER_BILLION = "ppb[mass]"
PARTS_PER_MILLION = "ppm[mass]"
PERCENT = "%[mass]"
TROY_OUNCES_PER_SHORT_TON = "oz t/ton[US]"
CARATS_PER_TONNE = "ct/t"
MICROGRAMS_PER_KILOGRAM = "ug/kg"

# Mass per volume (density) units
KILOS_PER_CUBIC_METRE = "kg/m3"
GRAMS_PER_CUBIC_CENTIMETRE = "g/cm3"
KILOS_PER_CUBIC_METRE = "kg/m3"
POUNDS_PER_CUBIC_FOOT = "lbm/ft3"
TONNES_PER_CUBIC_METRE = "t/m3"
SHORT_TON_PER_CUBIC_FOOT = "ton[US]/ft3"
TONNES_PER_CUBIC_METRE = "t/m3"

# Value units
DOLLARS_PER_TONNE = "$/t"
DOLLARS_PER_SHORT_TON = "$/ton[US]"
DOLLARS = "$"
# Dimensionless units
COUNT = "count"
RATIO = "ratio"

# Mass units
CARATS = "ct"
GRAMS = "g"
KILOGRAMS = "kg"
KILOTONNES = "kt"
MEGATONNES = "Mt"
MICROGRAMS = "ug"
MILLIGRAMS = "mg"
MILLION_POUNDS = "Mlbm"
MILLION_SHORT_TONS = "Mton[US]"
MILLION_TROY_OUNCES = "1000000 ozm[troy]"
POUNDS = "lbm"
SHORT_TONS = "ton[US]"
THOUSAND_CARATS = "1000 ct"
THOUSAND_POUNDS = "klbm"
THOUSAND_SHORT_TONS = "kton[US]"
THOUSAND_TROY_OUNCES = "1000 ozm[troy]"
TONNES = "t"
TROY_OUNCES = "ozm[troy]"

# Length units
CENTIMETRES = "cm"
FEET = "ft"
METRES = "m"

# Volume units
CUBIC_CENTIMETRES = "cm3"
CUBIC_METRES = "m3"
CUBIC_FEET = "ft3"
CUBIC_METRES = "m3"

# Value units
DOLLARS = "$"
DOLLARS_PER_SHORT_TON = "$/ton[US]"
DOLLARS_PER_TONNE = "$/t"

# Category units
NUMERIC_CATEGORY = "numeric_category"

# Angle units
DEGREES = "dega"
RADIANS = "rad"
REVOLUTIONS = "rev"


async def get_available_units(context: IContext) -> list[UnitInfo]:
Expand Down
48 changes: 48 additions & 0 deletions packages/evo-blockmodels/tests/test_list_block_models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import json
import uuid
from datetime import datetime, timezone
from unittest import mock
from uuid import uuid4

from evo.blockmodels import BlockModelAPIClient
from evo.blockmodels.data import ListingVersion, RegularGridDefinition
from evo.blockmodels.endpoints.models import MissingColumnPolicy
from evo.blockmodels.endpoints.models import UnitType as EndpointUnitType
from evo.common import Environment
from evo.common.test_tools import (
BASE_URL,
Expand Down Expand Up @@ -160,6 +162,52 @@ async def test_list_versions_returns_versions(self) -> None:
self.assertEqual(result[0].version_id, 2)
self.assertEqual(result[1].version_id, 1)

async def test_list_versions_accepts_unknown_referenced_unit_type(self) -> None:
bm_id = uuid.uuid4()
version = self.make_version(1, str(uuid.uuid4()))
referenced_units = [
{
"conversion_factor": 1.0,
"description": "A future unit",
"symbol": "future",
"unit_id": "future",
"unit_type": "FUTURE_UNIT_TYPE",
},
]
captured_units = []
list_block_model_versions = self.client._versions_api.list_block_model_versions

async def capture_referenced_units(*args, **kwargs):
response = await list_block_model_versions(*args, **kwargs)
captured_units.extend(response.referenced_units)
return response

with (
self.transport.set_http_response(
200,
json.dumps(
{
"count": 1,
"limit": 100,
"offset": 0,
"results": [version],
"total": 1,
"referenced_units": referenced_units,
}
),
headers={"Content-Type": "application/json"},
),
mock.patch.object(
self.client._versions_api,
"list_block_model_versions",
side_effect=capture_referenced_units,
),
):
result = await self.client.list_versions(bm_id)

self.assertEqual(len(result), 1)
self.assertIs(captured_units[0].unit_type, EndpointUnitType.UNKNOWN)

async def test_list_versions_with_preview_sends_header(self) -> None:
bm_id = uuid.uuid4()
v1 = self.make_version(1, str(uuid.uuid4()))
Expand Down
Loading