Files
csaxs_bec/tests/tests_devices/test_omny_sample_storage.py
Mirko HollerandClaude Opus 5.5 0dd14f6226
CI for csaxs_bec / test (pull_request) Successful in 3m20s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 3s
CI for csaxs_bec / test (push) Successful in 3m33s
feat(omny, flomni): MEAS_ACCOUNT per sample record; OMNY status 'screened'
- set_sample_measured(..., account=...) stores the e-account of the status change in
  <slot>:MEAS_ACCOUNT; OWNER is no longer filled with the account (owner is free
  text now). Callers in omny.py/flomni.py pass bec.active_account. The account moves
  with the sample (transfer_sample) and is reset on load/clear.
- OMNY: status 'screened' when a sample leaves the sample stage (stage -> gripper)
  with status 'none', i.e. no tomoscan was started on it (Mirko 2026-10-27).
- widget: shows 'screened'; the P-touch label prints MEAS_ACCOUNT (owner as fallback
  for older records); CLI views show the account.
- IOCs: OMNY ioc_omny.py / flOMNI ioc_flomni.py got the MEAS_ACCOUNT field (and
  'screened' appended to the OMNY status enum), deployed on mpc3217 / mpc2844.
- tests/e2e harness updated (owner_if_empty replaced by account).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDvfTY6VYunNtWse9VZ
2026-10-27 21:02:58 +01:00

109 lines
4.6 KiB
Python

"""OMNYSampleStorage on the OMNY soft IOC records (OMNY-SAMPLE:<slot>:USED/NAME/OWNER/STATUS/MEAS_DATE/MEAS_ACCOUNT)."""
import pytest
from csaxs_bec.devices.omny.omny_sample_storage import (
SAMPLE_SLOTS,
OMNYSampleStorage,
OMNYSampleStorageError,
slot_name,
)
from csaxs_bec.devices.sim.sim_omny import SimOMNYSampleStorage
@pytest.fixture
def storage():
dev = SimOMNYSampleStorage(name="omny_samples", sim_samples={})
try:
yield dev
finally:
dev.destroy()
@pytest.mark.parametrize(
"container, slot_nr, expected",
[("A", 1, "A1"), ("C", 6, "C6"), ("O", 10, "P10"), ("O", 101, "P101"), ("STAGE", 0, "STAGE"),
("gripper", 0, "GRIPPER"), ("B4", 0, "B4"), ("P32", 0, "P32")],
)
def test_slot_name(container, slot_nr, expected):
assert slot_name(container, slot_nr) == expected
@pytest.mark.parametrize("container, slot_nr", [("A", 0), ("A", 7), ("O", 15), ("O", 1), ("D", 1)])
def test_slot_name_rejects_unknown_slots(container, slot_nr):
with pytest.raises(OMNYSampleStorageError):
slot_name(container, slot_nr)
def test_pv_names_match_the_ioc():
assert OMNYSampleStorage.sample_used.defn["A1"][1] == "OMNY-SAMPLE:A1:USED"
assert OMNYSampleStorage.sample_names.defn["P100"][1] == "OMNY-SAMPLE:P100:NAME"
assert OMNYSampleStorage.sample_status.defn["STAGE"][1] == "OMNY-SAMPLE:STAGE:STATUS"
assert OMNYSampleStorage.sample_meas_date.defn["GRIPPER"][1] == "OMNY-SAMPLE:GRIPPER:MEAS_DATE"
assert OMNYSampleStorage.sample_meas_account.defn["A3"][1] == "OMNY-SAMPLE:A3:MEAS_ACCOUNT"
assert OMNYSampleStorage.parking_names.defn["parking3"][1] == "OMNY-SAMPLE:PARK3:NAME"
assert len(SAMPLE_SLOTS) == 18 + 10 + 2
def test_set_and_unset_slot(storage):
storage.set_sample_slot("O", 32, "pin1", "e12345")
assert storage.get_sample_record("O", 32) == {
"used": True, "name": "pin1", "owner": "e12345", "status": "none", "meas_date": "",
"meas_account": ""}
storage.unset_sample_slot("O", 32)
rec = storage.get_sample_record("O", 32)
assert rec["used"] is False and rec["name"] == "-" and rec["owner"] == ""
def test_status_travels_with_the_sample(storage):
storage.set_sample_slot("B", 2, "pin2", "e11111")
storage.set_sample_measured("B", 2, "completed", "2026-10", account="e20001")
storage.transfer_sample(("B", 2), "GRIPPER")
storage.transfer_sample("GRIPPER", "STAGE")
assert not storage.is_sample_slot_used("B", 2)
assert not storage.is_sample_in_gripper()
assert storage.get_sample_record("STAGE") == {
"used": True, "name": "pin2", "owner": "e11111", "status": "completed", "meas_date": "2026-10",
"meas_account": "e20001"}
def test_set_sample_measured_stores_the_account_not_the_owner(storage):
# owner (free text) and measurement account are separate fields (2026-10-27)
storage.set_sample_in_samplestage("pin3", "Group XY")
storage.set_sample_measured("STAGE", 0, "started", "2026-10", account="e22222")
assert storage.get_sample_owner_in_samplestage() == "Group XY"
assert storage.get_sample_status_in_samplestage() == "started"
assert storage.get_sample_meas_account("STAGE") == "e22222"
# the account of the latest status change is stored
storage.set_sample_measured("STAGE", 0, "completed", "2026-10", account="e33333")
assert storage.get_sample_meas_account("STAGE") == "e33333"
# no account given: the stored one is kept
storage.set_sample_measured("STAGE", 0, "completed", "2026-11")
assert storage.get_sample_meas_account("STAGE") == "e33333"
# loading a new sample resets status, date and account
storage.set_sample_in_samplestage("pin3b", "Group XY")
assert storage.get_sample_record("STAGE")["meas_account"] == ""
def test_screened_status(storage):
storage.set_sample_slot("C", 4, "pin5", "someone")
storage.set_sample_measured("C", 4, "screened", "2026-10", account="e44444")
assert storage.get_sample_status("C", 4) == "screened"
assert storage.get_sample_meas_account("C", 4) == "e44444"
def test_invalid_status_raises(storage):
storage.set_sample_slot("A", 1, "pin4")
with pytest.raises(OMNYSampleStorageError):
storage.set_sample_measured("A", 1, "bogus", "")
def test_parking(storage):
storage.set_shuttle_slot("C", 2)
assert storage.is_shuttle_slot_used(2) and storage.get_shuttle_name_slot(2) == "C"
assert storage.search_shuttle_in_slot("C") == 2
storage.unset_shuttle_slot(2)
assert not storage.is_shuttle_slot_used(2) and storage.get_shuttle_name_slot(2) == "none"
with pytest.raises(OMNYSampleStorageError):
storage.set_shuttle_slot("A", 7)