From b3e536a95dee9ed62d58ca5149d536d6baaaddc8 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Thu, 15 Oct 2026 15:23:24 +0100 Subject: [PATCH 1/5] WIP --- .../i15_1/callbacks/analysis_callback.py | 6 +++--- src/crystallography_bluesky/i15_1/plans/centre_sample.py | 4 +++- tests/unit_tests/i15_1/test_centre_sample.py | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/crystallography_bluesky/i15_1/callbacks/analysis_callback.py b/src/crystallography_bluesky/i15_1/callbacks/analysis_callback.py index a81e405..cf309c5 100644 --- a/src/crystallography_bluesky/i15_1/callbacks/analysis_callback.py +++ b/src/crystallography_bluesky/i15_1/callbacks/analysis_callback.py @@ -3,7 +3,7 @@ from bluesky.callbacks import CallbackBase from dodal.log import LOGGER from event_model.documents import RunStart, RunStop -from heliotrapi.client import AnalysisClient +from heliotrapi.client import AnalysisClient, AnalysisResult I15_1_ANALYSIS_URL = "https://i15-1-analysis.diamond.ac.uk" @@ -47,9 +47,9 @@ def stop(self, doc: RunStop) -> RunStop | None: ) LOGGER.info(f"Submitted analysis request {self.request_id}") - def wait_on_and_retrieve_result(self): + def wait_on_and_retrieve_result(self) -> AnalysisResult: if not self.request_id: raise ValueError("Results requested but analysis has not been triggered") result = self._client.get_request_id_result(self.request_id) LOGGER.info(f"Received result from analysis {result} for {self.request_id}") - return result.result + return result diff --git a/src/crystallography_bluesky/i15_1/plans/centre_sample.py b/src/crystallography_bluesky/i15_1/plans/centre_sample.py index 256b9de..b705e8a 100644 --- a/src/crystallography_bluesky/i15_1/plans/centre_sample.py +++ b/src/crystallography_bluesky/i15_1/plans/centre_sample.py @@ -91,7 +91,7 @@ def per_step(): ) analysis_result = analysis_callback.wait_on_and_retrieve_result() - centre = analysis_result["centre"] + centre = analysis_result.result["centre"] assert (start_z < centre < end_z) or (start_z > centre > end_z), ( f"Analysis result {centre} is not within the bounds of the scan: " + f"({start_z, end_z})" @@ -99,3 +99,5 @@ def per_step(): LOGGER.info(f"Got {centre} from analysis, moving there now") yield from bps.mv(hexapod.z, centre) + + return analysis_result diff --git a/tests/unit_tests/i15_1/test_centre_sample.py b/tests/unit_tests/i15_1/test_centre_sample.py index 72f2b05..27fa889 100644 --- a/tests/unit_tests/i15_1/test_centre_sample.py +++ b/tests/unit_tests/i15_1/test_centre_sample.py @@ -36,7 +36,9 @@ def mock_analysis_callback(): "crystallography_bluesky.i15_1.plans.centre_sample.TriggerAnalysisCallback" ) as patch_analysis_callback: instance = patch_analysis_callback.return_value - instance.wait_on_and_retrieve_result.return_value = {"centre": 15} + instance.wait_on_and_retrieve_result.return_value = AnalysisResult( + result={"centre": 15}, status="completed", analysis_name="centring" + ) yield patch_analysis_callback From d385213fccb9a36d8ab36100505ec7e846e3d163 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 20 Oct 2026 13:54:57 +0100 Subject: [PATCH 2/5] Fix test --- tests/unit_tests/i15_1/callbacks/test_analysis_callback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/i15_1/callbacks/test_analysis_callback.py b/tests/unit_tests/i15_1/callbacks/test_analysis_callback.py index 763d15e..d4daa9e 100644 --- a/tests/unit_tests/i15_1/callbacks/test_analysis_callback.py +++ b/tests/unit_tests/i15_1/callbacks/test_analysis_callback.py @@ -105,7 +105,7 @@ def test_wait_on_and_retrieve_result(mock_client_cls): callback = TriggerAnalysisCallback("url", "analysis") callback.request_id = MagicMock() - assert callback.wait_on_and_retrieve_result() == 42 + assert callback.wait_on_and_retrieve_result().result == 42 mock_client.get_request_id_result.assert_called_once() From 14b1730ac90ef4c59024255362f781ea3f809ca1 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 20 Oct 2026 13:57:43 +0100 Subject: [PATCH 3/5] Add hexapod to baseline devices --- src/crystallography_bluesky/i15_1/plans/generic_collection.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/crystallography_bluesky/i15_1/plans/generic_collection.py b/src/crystallography_bluesky/i15_1/plans/generic_collection.py index 0872f76..42215c2 100644 --- a/src/crystallography_bluesky/i15_1/plans/generic_collection.py +++ b/src/crystallography_bluesky/i15_1/plans/generic_collection.py @@ -9,6 +9,7 @@ from dodal.common import inject from dodal.common.beamlines.beamline_utils import get_config_client from dodal.devices.beamlines.i15_1.attenuators import FastAttenuator, SlowAttenuator +from dodal.devices.beamlines.i15_1.hexapod import Hexapod from dodal.devices.beamlines.i15_1.laue import LaueMonochrometer from dodal.devices.beamlines.i15_1.robot import Robot from dodal.devices.synchrotron import Synchrotron @@ -63,6 +64,7 @@ class GenericCollectionDevices: fast_attenuator: FastAttenuator slow_attenuator: SlowAttenuator synchrotron: Synchrotron + hexapod: Hexapod def get_default_baseline_devices(all_devices: GenericCollectionDevices): @@ -72,6 +74,7 @@ def get_default_baseline_devices(all_devices: GenericCollectionDevices): all_devices.tth, all_devices.fast_attenuator, all_devices.slow_attenuator, + all_devices.hexapod, ] From babfc25640158de8a713699f75689597fad746d2 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 20 Oct 2026 14:15:46 +0100 Subject: [PATCH 4/5] Fix tests --- tests/unit_tests/i15_1/conftest.py | 8 +++++--- .../i15_1/test_single_temperature_collection.py | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests/i15_1/conftest.py b/tests/unit_tests/i15_1/conftest.py index c873ce2..1da8db0 100644 --- a/tests/unit_tests/i15_1/conftest.py +++ b/tests/unit_tests/i15_1/conftest.py @@ -13,9 +13,9 @@ from dodal.devices.beamlines.i15_1.attenuators import FastAttenuator, SlowAttenuator from dodal.devices.beamlines.i15_1.beam_health import BeamHealth from dodal.devices.beamlines.i15_1.blower import Blower +from dodal.devices.beamlines.i15_1.hexapod import Hexapod from dodal.devices.beamlines.i15_1.laue import LaueMonochrometer from dodal.devices.beamlines.i15_1.robot import Robot -from dodal.devices.motors import XYZStage from dodal.devices.synchrotron import Synchrotron from dodal.devices.tetramm import SummingTetrammDetector from dodal.devices.zebra.zebra import Zebra, ZebraMapping @@ -113,9 +113,9 @@ async def fast_shutter() -> ZebraFastShutter: @pytest.fixture -async def hexapod() -> XYZStage: +async def hexapod() -> Hexapod: async with init_devices(mock=True): - hexapod = XYZStage("") + hexapod = Hexapod("", "") return hexapod @@ -152,6 +152,7 @@ async def common_collection_devices( slow_attenuator: SlowAttenuator, fast_attenuator: FastAttenuator, synchrotron: Synchrotron, + hexapod: Hexapod, ) -> GenericCollectionDevices: return GenericCollectionDevices( eiger, @@ -164,6 +165,7 @@ async def common_collection_devices( fast_attenuator, slow_attenuator, synchrotron, + hexapod, ) diff --git a/tests/unit_tests/i15_1/test_single_temperature_collection.py b/tests/unit_tests/i15_1/test_single_temperature_collection.py index b37e644..14c4102 100644 --- a/tests/unit_tests/i15_1/test_single_temperature_collection.py +++ b/tests/unit_tests/i15_1/test_single_temperature_collection.py @@ -90,6 +90,7 @@ def fake_setup(*_, **__): common_collection_devices.xtal, common_collection_devices.fast_attenuator, common_collection_devices.slow_attenuator, + common_collection_devices.hexapod, ] + baseline_devices ) From 959f08a1e589103ec744cd2dea31aa451bc276da Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 20 Oct 2026 14:39:39 +0100 Subject: [PATCH 5/5] Update test --- tests/unit_tests/i15_1/test_centre_sample.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/unit_tests/i15_1/test_centre_sample.py b/tests/unit_tests/i15_1/test_centre_sample.py index 27fa889..da66ad8 100644 --- a/tests/unit_tests/i15_1/test_centre_sample.py +++ b/tests/unit_tests/i15_1/test_centre_sample.py @@ -5,7 +5,7 @@ import bluesky.plan_stubs as bps import bluesky.preprocessors as bpp import pytest -from bluesky.run_engine import RunEngine +from bluesky.run_engine import RunEngine, RunEngineResult from bluesky.simulators import RunEngineSimulator, assert_message_and_return_remaining from dodal.devices.motors import XYZStage from heliotrapi.models import AnalysisResult @@ -24,7 +24,7 @@ @pytest.fixture def blueapi_run_engine(): # This adds the info the blueapi will add in prod - RE = RunEngine() + RE = RunEngine(call_returns_result=True) RE.md["data_session_directory"] = "/dls/i15-1/data/2026/cm44163-3" RE.md["scan_file"] = "i15-1-95557" return RE @@ -52,7 +52,7 @@ def mock_analysis_client(): analysis_name="fake_sample_alignment_i15_1", result={"centre": 17}, status="completed", - created_at=datetime.now(), + created_at=datetime(2026, 10, 20, 14, 35, 46, 96041), ) yield mock_client @@ -263,7 +263,7 @@ def test_centre_sample_moved_to_start_before_stage( @patch("crystallography_bluesky.i15_1.plans.centre_sample.generic_per_step_collection") -def test_centre_sample_calls_analysis_and_retrieves_result( +def test_centre_sample_calls_analysis_and_retrieves_and_returns_result( mock_generic_collection: MagicMock, mock_analysis_client: MagicMock, hexapod: XYZStage, @@ -278,12 +278,22 @@ def my_plan(*_, **__): yield from bps.null() mock_generic_collection.side_effect = my_plan - blueapi_run_engine( + result = blueapi_run_engine( centre_sample(10, 20, 10, 0.01, generic_collection_devices, hexapod) ) mock_analysis_client.submit.assert_called_once() mock_analysis_client.get_request_id_result.assert_called_once() + assert isinstance(result, RunEngineResult) and result.plan_result == AnalysisResult( + request_id=None, + status="completed", + analysis_name="fake_sample_alignment_i15_1", + inputs=None, + result={"centre": 17}, + created_at=datetime(2026, 10, 20, 14, 35, 46, 96041), + finished_at=None, + version="0.3.3", + ) @patch("crystallography_bluesky.i15_1.plans.centre_sample.generic_per_step_collection")