Skill Tracer
Skills › Scientific Agent Skills › pylabrobot › generate_simulation_plan.py

generate_simulation_plan.py

140 lines · skills/pylabrobot/scripts/generate_simulation_plan.py at 5f2bc03

1#!/usr/bin/env python3
2"""Generate a non-executable, hardware-blocked simulation plan as JSON."""
3
4from __future__ import annotations
5
6import argparse
7from typing import Any, Sequence
8
9if __package__:
10 from ._common import (
11 PYLABROBOT_VERSION,
12 ValidationError,
13 emit_error,
14 emit_json,
15 geometry_report,
16 load_csv,
17 load_json,
18 plan_transfers,
19 validate_manifest,
20 validate_transfers,
21 )
22else:
23 from _common import (
24 PYLABROBOT_VERSION,
25 ValidationError,
26 emit_error,
27 emit_json,
28 geometry_report,
29 load_csv,
30 load_json,
31 plan_transfers,
32 validate_manifest,
33 validate_transfers,
34 )
37def build_parser() -> argparse.ArgumentParser:
38 parser = argparse.ArgumentParser(
39 description=(
40 "Generate a deterministic JSON plan for offline review. The output is data, "
41 "not executable Python; this command cannot select or connect to live hardware."
42 )
43 )
44 parser.add_argument("--manifest", required=True, help="Local UTF-8 .json manifest")
45 parser.add_argument("--transfers", required=True, help="Local UTF-8 .csv transfer table")
46 return parser
49def _steps(operations: list[dict[str, Any]]) -> list[dict[str, Any]]:
50 steps: list[dict[str, Any]] = []
51 for operation in operations:
52 common = {
53 "transfer_id": operation["transfer_id"],
54 "channel": operation["channel"],
55 }
56 steps.extend(
57 [
58 {
59 **common,
60 "action": "pick_up_tip",
61 "tip": operation["assigned_tip"],
62 "tip_type": operation["tip_type"],
63 },
64 {
65 **common,
66 "action": "aspirate",
67 "resource": operation["source"],
68 "volume_uL": operation["volume_uL"],
69 "height_mm": operation["aspiration_height_mm"],
70 "rate_uL_s": operation["aspiration_rate_uL_s"],
71 "liquid_class_review_label": operation["liquid_class"],
72 },
73 {
74 **common,
75 "action": "dispense",
76 "resource": operation["destination"],
77 "volume_uL": operation["volume_uL"],
78 "height_mm": operation["dispense_height_mm"],
79 "rate_uL_s": operation["dispense_rate_uL_s"],
80 "liquid_class_review_label": operation["liquid_class"],
81 },
82 {
83 **common,
84 "action": "drop_tip",
85 "resource": operation["drop_tip_at"],
86 },
87 ]
88 )
89 return steps
92def main(argv: Sequence[str] | None = None) -> int:
93 args = build_parser().parse_args(argv)
94 try:
95 manifest = validate_manifest(load_json(args.manifest))
96 geometry = geometry_report(manifest)
97 if not geometry["ok"]:
98 raise ValidationError("deck geometry must pass before a simulation plan is generated")
99 transfers = validate_transfers(load_csv(args.transfers))
100 transfer_plan = plan_transfers(manifest, transfers)
101 except (OSError, ValidationError) as error:
102 return emit_error(error)
104 emit_json(
105 {
106 "ok": True,
107 "plan_kind": "offline_review_only",
108 "protocol_id": manifest["protocol_id"],
109 "pylabrobot_target": PYLABROBOT_VERSION,
110 "backend": (
111 "pylabrobot.liquid_handling.backends.chatterbox."
112 "LiquidHandlerChatterboxBackend"
113 ),
114 "backend_setup_permitted": True,
115 "live_backend_permitted": False,
116 "connection_attempted": False,
117 "serial_usb_network_access": False,
118 "geometry": geometry,
119 "steps": _steps(transfer_plan["operations"]),
120 "final_volume_ledger": transfer_plan["final_volume_ledger"],
121 "tip_summary": transfer_plan["tip_summary"],
122 "mandatory_human_review": [
123 "physically reconcile deck and labware identities with this manifest",
124 "verify calibration, collision envelopes, coordinates, channels, and units",
125 "verify source volumes, dead volumes, destination capacity, and physical liquid",
126 "verify tips, contamination controls, heights, rates, and vendor liquid classes",
127 "confirm guards and emergency stop are ready before any separately authorized live run",
128 ],
129 "limitations": [
130 "the chatterbox backend reports planned commands but does not model robot physics",
131 "the visualizer renders tracker state but does not perform simulation logic",
132 "bookkeeping cannot detect physical liquid, tips, seals, lids, tubing, or obstacles",
133 ],
134 }
135 )
136 return 0
139if __name__ == "__main__":
140 raise SystemExit(main())