diff --git a/src/aarecommon/models/models.py b/src/aarecommon/models/models.py index 493f922..47e3a38 100644 --- a/src/aarecommon/models/models.py +++ b/src/aarecommon/models/models.py @@ -61,7 +61,11 @@ class DataCollectionParameters(BaseModel): totalangle: int | None = Field( # was totalrange default=None, validation_alias=AliasChoices("totalangle", "totalrange") ) # Only accept positive integers between 0 and 360 - transmission: int | None = None # Only accept positive integers between 0 and 100 + # Fraction of full beam (1.0 = full transmission), NOT a percentage. + # Values in (1.0, 100.0] fail validation by design: they are percent-scale + # leftovers and must be divided by 100 at whatever boundary produced them + # (user spreadsheets stay percent for humans; converters live in AareDB). + transmission: Annotated[float, Field(ge=0.0, le=1.0)] | None = None targetresolution: float | None = None # Only accept positive float beamsize: str | None = None aperture: int | None = None # Optional string field diff --git a/src/aarecommon/models/raster_grid.py b/src/aarecommon/models/raster_grid.py index db37e4a..fd0c02d 100644 --- a/src/aarecommon/models/raster_grid.py +++ b/src/aarecommon/models/raster_grid.py @@ -11,7 +11,8 @@ from aarecommon.models.gridscan_decision import GridScanDecision class RasterGridRequest(BaseModel): dtz: float | None = None - transmission: float | None = None + # Fraction of full beam (1.0 = full transmission), NOT a percentage. + transmission: Annotated[float, Field(ge=0.0, le=1.0)] | None = None file_prefix: str | None = None exp_time_s: float diff --git a/src/aarecommon/models/rotation_scan.py b/src/aarecommon/models/rotation_scan.py index 008ced3..8936ecf 100644 --- a/src/aarecommon/models/rotation_scan.py +++ b/src/aarecommon/models/rotation_scan.py @@ -1,12 +1,15 @@ +from typing import Annotated + from jfjoch_client.models.scan_result import ScanResult -from pydantic import BaseModel +from pydantic import BaseModel, Field from aarecommon.math.coordinate import SmargonCoordinate class RotationScanRequest(BaseModel): dtz: float | None = None - transmission: float | None = None + # Fraction of full beam (1.0 = full transmission), NOT a percentage. + transmission: Annotated[float, Field(ge=0.0, le=1.0)] | None = None file_prefix: str | None = None exp_time_s: float diff --git a/tests/test_data_collection_parameters.py b/tests/test_data_collection_parameters.py index 20a0b90..56c274a 100644 --- a/tests/test_data_collection_parameters.py +++ b/tests/test_data_collection_parameters.py @@ -71,3 +71,26 @@ def test_datacollectionparameters_accepts_new_fields(): assert params.processingresolution == 1.2 assert params.pdbmodel == "model.pdb" assert params.cloud is False + + +def test_transmission_is_a_fraction_of_full_beam(): + params = DataCollectionParameters(transmission=0.35) + assert params.transmission == 0.35 + + +def test_transmission_boundaries_are_valid(): + assert DataCollectionParameters(transmission=0.0).transmission == 0.0 + assert DataCollectionParameters(transmission=1.0).transmission == 1.0 + + +def test_transmission_rejects_percent_scale_values(): + # Percent-scale leftovers (1.0, 100.0] must fail loudly, never be + # silently reinterpreted — converters live at the producing boundary. + for percent in (1.5, 35, 100): + with pytest.raises(ValidationError): + DataCollectionParameters(transmission=percent) + + +def test_transmission_rejects_negative_values(): + with pytest.raises(ValidationError): + DataCollectionParameters(transmission=-0.1)