udpate reset

This commit is contained in:
hanhua@dp.tech
2026-05-18 18:26:16 +08:00
parent 72495bfc74
commit a4678b7aa8
2 changed files with 83 additions and 26 deletions

View File

@@ -11,7 +11,7 @@ import sys
from contextlib import nullcontext from contextlib import nullcontext
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from typing import Annotated, Any, Dict, Iterable, List, Optional, Tuple from typing import Annotated, Any, Dict, Iterable, List, Literal, Optional, Tuple
from uuid import UUID from uuid import UUID
import requests import requests
@@ -743,40 +743,58 @@ class BioyondPeptideStation(BioyondWorkstation):
result["resultTable"] = resultTable or {} result["resultTable"] = resultTable or {}
return result return result
@action(always_free=True, description="复位调度器/订单/库位") @action(
always_free=True,
goal_default={
"reset_operations": ["scheduler_reset", "reset_order_status", "reset_location"],
},
description="复位调度器/订单/库位",
)
def reset( def reset(
self, self,
reset_operations: Optional[List[str]] = None, reset_operations: Optional[
dry_run: bool = False, List[Literal["scheduler_reset", "reset_order_status", "reset_location"]]
order_id: str = "", ] = None,
location_id: str = "",
**kwargs: Any, **kwargs: Any,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
with self._debug_call_session("reset"): with self._debug_call_session("reset"):
operations = self._normalize_reset_operations(reset_operations) operations = self._normalize_reset_operations(reset_operations)
planned = [{"operation": op, "endpoint": self._reset_operation_endpoint(op)} for op in operations] result: Dict[str, Any] = {
result: Dict[str, Any] = {"dry_run": bool(dry_run), "planned_calls": planned, "executed_calls": [], "skipped_operations": []} "selected_operations": operations,
if dry_run: "executed_calls": [],
return result "skipped_operations": [],
}
rpc = self._require_hardware_interface() rpc = self._require_hardware_interface()
for operation in operations: for operation in operations:
if operation == "scheduler_reset": if operation == "scheduler_reset":
code = rpc.scheduler_reset() code = rpc.scheduler_reset()
result["executed_calls"].append({"operation": operation, "result": {"code": code}}) result["executed_calls"].append({"operation": operation, "result": {"code": code}})
elif operation == "reset_order_status": elif operation == "reset_order_status":
resolved = str(kwargs.get("reset_order_id") or order_id or kwargs.get("order_id") or "").strip() resolved = str(
kwargs.get("reset_order_id") or kwargs.get("order_id") or ""
).strip()
if not resolved: if not resolved:
result["skipped_operations"].append({"operation": operation, "reason": "缺少 order_id/reset_order_id"}) result["skipped_operations"].append(
{"operation": operation, "reason": "缺少 order_id/reset_order_id"}
)
continue continue
code = rpc.reset_order_status(resolved) code = rpc.reset_order_status(resolved)
result["executed_calls"].append({"operation": operation, "order_id": resolved, "result": {"code": code}}) result["executed_calls"].append(
{"operation": operation, "order_id": resolved, "result": {"code": code}}
)
elif operation == "reset_location": elif operation == "reset_location":
resolved = str(kwargs.get("reset_location_id") or location_id or kwargs.get("location_id") or "").strip() resolved = str(
kwargs.get("reset_location_id") or kwargs.get("location_id") or ""
).strip()
if not resolved: if not resolved:
result["skipped_operations"].append({"operation": operation, "reason": "缺少 location_id/reset_location_id"}) result["skipped_operations"].append(
{"operation": operation, "reason": "缺少 location_id/reset_location_id"}
)
continue continue
code = rpc.reset_location(resolved) code = rpc.reset_location(resolved)
result["executed_calls"].append({"operation": operation, "location_id": resolved, "result": {"code": code}}) result["executed_calls"].append(
{"operation": operation, "location_id": resolved, "result": {"code": code}}
)
else: else:
raise ValueError(f"未知 reset operation: {operation}") raise ValueError(f"未知 reset operation: {operation}")
return result return result

View File

@@ -616,13 +616,33 @@ def test_start_experiment_starts_when_table_empty() -> None:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_reset_dry_run_default_false_and_planned_calls() -> None: def test_reset_signature_drops_legacy_params_and_uses_literal() -> None:
station = _make_station() """plan 调整:删除 dry_run/order_id/location_idreset_operations 用 Literal 注解。"""
sig = inspect.signature(station.reset) cls = getattr(_import_module(), CLASS_NAME)
assert sig.parameters["dry_run"].default is False sig = inspect.signature(cls.reset)
out = station.reset(reset_operations=["scheduler_reset"], dry_run=True) params = sig.parameters
assert out["dry_run"] is True for legacy in ("dry_run", "order_id", "location_id"):
assert out["planned_calls"][0]["endpoint"] == "/api/lims/scheduler/reset" assert legacy not in params, f"reset 不应再有 {legacy} 入参"
assert "reset_operations" in params
assert any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values()), \
"reset 必须保留 **kwargs 以兜底 reset_order_id/reset_location_id"
annotation = params["reset_operations"].annotation
rendered = annotation if isinstance(annotation, str) else repr(annotation)
for op in ("scheduler_reset", "reset_order_status", "reset_location"):
assert op in rendered, f"reset_operations 的 Literal 必须包含 {op}"
def test_reset_goal_default_contains_all_operations() -> None:
"""像 sirna 一样goal_default 默认勾选全部三个 reset 操作。"""
cls = getattr(_import_module(), CLASS_NAME)
meta = getattr(cls.reset, "_action_registry_meta", {})
goal_default = meta.get("goal_default") or {}
assert goal_default.get("reset_operations") == [
"scheduler_reset",
"reset_order_status",
"reset_location",
]
def test_reset_executes_typed_rpc_calls() -> None: def test_reset_executes_typed_rpc_calls() -> None:
@@ -632,11 +652,30 @@ def test_reset_executes_typed_rpc_calls() -> None:
station.hardware_interface.reset_location.return_value = 1 station.hardware_interface.reset_location.return_value = 1
out = station.reset( out = station.reset(
reset_operations=["scheduler_reset", "reset_order_status", "reset_location"], reset_operations=["scheduler_reset", "reset_order_status", "reset_location"],
dry_run=False, reset_order_id=ORDER_GUID,
order_id=ORDER_GUID, reset_location_id="loc-1",
location_id="loc-1",
) )
station.hardware_interface.scheduler_reset.assert_called_once_with() station.hardware_interface.scheduler_reset.assert_called_once_with()
station.hardware_interface.reset_order_status.assert_called_once_with(ORDER_GUID) station.hardware_interface.reset_order_status.assert_called_once_with(ORDER_GUID)
station.hardware_interface.reset_location.assert_called_once_with("loc-1") station.hardware_interface.reset_location.assert_called_once_with("loc-1")
assert out["selected_operations"] == [
"scheduler_reset",
"reset_order_status",
"reset_location",
]
assert len(out["executed_calls"]) == 3 assert len(out["executed_calls"]) == 3
assert out["skipped_operations"] == []
def test_reset_skips_when_ids_missing() -> None:
"""没有 order_id / location_id 时应该 skip 而不是抛错。"""
station = _make_station()
station.hardware_interface.scheduler_reset.return_value = 1
out = station.reset(
reset_operations=["scheduler_reset", "reset_order_status", "reset_location"],
)
station.hardware_interface.scheduler_reset.assert_called_once_with()
station.hardware_interface.reset_order_status.assert_not_called()
station.hardware_interface.reset_location.assert_not_called()
skipped_ops = {item["operation"] for item in out["skipped_operations"]}
assert skipped_ops == {"reset_order_status", "reset_location"}