-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
resume procedure endpoint #253
Conversation
@@ -135,7 +140,7 @@ def dispatch_calibrator_action(request: Request, hardware_name: str = Path(...), | |||
|
|||
payload = action.get("payload", {}) | |||
|
|||
return {**calibration_procedure.dispatch(action_to_dispatch, payload), "started": True} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored "started" atrribute to be common state for all procedures. It's needed as the procedure controls (UI) are distinct for an uninitialized (not started) and initialized (started) procedure.
Codecov ReportAttention: Patch coverage is
📢 Thoughts on this report? Let us know! |
@@ -44,7 +44,7 @@ class TestCalibration: | |||
def test_get_calibration_status(self): | |||
_, client = setup_evolver_with_calibrator(NoOpCalibrator) | |||
|
|||
response = client.post("/hardware/test/calibrator/procedure/start") | |||
response = client.post("/hardware/test/calibrator/procedure/start", params={"resume": False}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now the user must be explicit about their intention to attempt to resume a procedure or start a new / fresh one.
|
||
raw_dispatch_response = dispatch_action(client, "test", "read_vial_0_raw_output") | ||
|
||
assert raw_dispatch_response.status_code == 200 | ||
assert raw_dispatch_response.json() == { | ||
"0": {"raw": [1.23], "reference": []}, | ||
"completed_actions": ["read_vial_0_raw_output"], | ||
"history": [{"completed_actions": [], "history": []}], | ||
"history": [{"completed_actions": [], "history": [], "started": True}], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only downside to adding "started" to common procedure state is it appears in history.
…ns and calibration data
evolver/calibration/interface.py
Outdated
class CalibrationData(Transformer.Config): | ||
"""Stores calibration data, including the procedure_state from the calibration_procedure.""" | ||
|
||
procedure_state: CalibrationStateModel = Field( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CalibrationStateModel is now common model shared across the Procedure, Actions and CalibrationData
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the common model, but given that we now expect the measure data to be under CalibrationData.procedure_state.measured
, I'm not sure what else would ever need to go in this structure at the base level, so is there even a need to have the nesting? In other words, do we need both CalibrationData
and CalibrationStateModel
or are they really the same thing?
@@ -75,29 +55,31 @@ def undo(self): | |||
""" | |||
Undo the last action that was dispatched in the calibration procedure. | |||
""" | |||
if len(self.state["history"]) > 0: | |||
self.state = self.state["history"].pop() | |||
if len(self.state.history) > 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since state remains a pydantic model now across the system, we can get typed property access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall looks good. Just minor comments and maybe opportunity to simplify
evolver/calibration/interface.py
Outdated
class CalibrationData(Transformer.Config): | ||
"""Stores calibration data, including the procedure_state from the calibration_procedure.""" | ||
|
||
procedure_state: CalibrationStateModel = Field( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the common model, but given that we now expect the measure data to be under CalibrationData.procedure_state.measured
, I'm not sure what else would ever need to go in this structure at the base level, so is there even a need to have the nesting? In other words, do we need both CalibrationData
and CalibrationStateModel
or are they really the same thing?
evolver/calibration/procedure.py
Outdated
@@ -54,7 +33,8 @@ def __init__(self, hardware, state=None, *args, **kwargs): | |||
""" | |||
super().__init__(*args, **kwargs) | |||
self.actions = [] | |||
self.state = CalibrationStateModel(**(state or {})).model_dump() | |||
self.state = CalibrationStateModel(**(state or {})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not wrong, but since we are working with an object, might it be cleaner to
CalibrationStateModel.model_validate(state or {})
And I didn't really catch earlier, but since the init takes state as a parameter we could default state={}
there, and can pass just state
to the above - unless of course we specifically need to support None being passed in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep will fix
class CalibrationData(Calibrator.CalibrationData): | ||
measured: Dict[int, Dict[str, List[float]]] = {} # {vial_index: {"reference": [], "raw": []}} | ||
procedure_state: CalibrationStateModel = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this case we actually don't even need to overload anymore, so this whole class can (and should) be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep will fix
@@ -212,6 +275,22 @@ def test_dispatch_temperature_calibration_calculate_fit_action(): | |||
assert output_transformer_response.json()["0"]["parameters"] == [0.6149999999999997, 0.024599999999999997] | |||
|
|||
|
|||
def get_stable_state_subset(state): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having to do this reveals an area for improvement in the history data structure. Since history is part of the state persisted to the history, the entire history is actually already available in the history prop of the last added item to the history stack...
Soooo...
To fix, update the history stack data-structure update methods (push and pop) to only need the last state in the stack, and update the undo method, to essentially walk the history attr of the single state that's now in the history stack.
See issue: #256
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I think it can go, but a couple comments for your perusal
evolver/calibration/interface.py
Outdated
if self.calibration_file: | ||
self.calibration_data = CalibrationStateModel.load(file_path=self.calibration_file) | ||
else: | ||
self.calibration_data = CalibrationStateModel() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not quite right. Since this is init and we are passed the calibration_file to input, we shouldn't have the self.calibration_file
unless set here, so the above condition (if calibration_file
) should be sufficient? Meaning here is just to create a default calibration state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah makes sense. Fixed.
if not state.measured: | ||
state.measured = {} | ||
if self.vial_idx not in state.measured: | ||
state.measured[self.vial_idx] = {"reference": [], "raw": []} | ||
state.measured[self.vial_idx]["reference"].append(payload.temperature) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totally fine, but in case you like: collections.defaultdict
and/or setdefault
is useful here:
state.measured = state.measured or defaultdict(lambda: {"referece": [], "raw": []})
state.measured[self.vial_idx]["reference"].append(payload.temperature)
where all elements are same kind, no need to check and initialize yourself.
feature - when a calibration procedure state has been saved (persisted to fs), it can be 'resumed' by passing the "resume" flag to the /start endpoint.