From c095cb484e4312d97a1a72a5e06afa0687e3f74d Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Mon, 7 Sep 2026 23:58:44 +0200 Subject: [PATCH] fix: transmission is a fraction of full beam everywhere DataCollectionParameters.transmission was the outlier: int percent 0-100 while BeamlineStatus / SimpleScanParameters / FluorescenceSpectrum already used a 0-1 fraction. Per the DAQ/DB consensus it is now a strict Annotated[float, Field(ge=0.0, le=1.0)] fraction (1.0 = full beam); percent-scale values in (1.0, 100.0] fail validation by design instead of being silently reinterpreted. RotationScanRequest / RasterGridRequest gain the same constraint (production data is already fraction on those paths). Human-facing surfaces (user spreadsheets, AareDB public API) stay percent; their converters live in AareDB at the boundary. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HiGzkkuiZXei894cnJfgSg --- src/aarecommon/models/models.py | 6 +++++- src/aarecommon/models/raster_grid.py | 3 ++- src/aarecommon/models/rotation_scan.py | 7 +++++-- tests/test_data_collection_parameters.py | 23 +++++++++++++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) 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) -- 2.54.0