labcode 0.0.1__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {labcode-0.0.1 → labcode-0.1.1}/PKG-INFO +26 -9
  2. {labcode-0.0.1 → labcode-0.1.1}/README.md +22 -5
  3. labcode-0.1.1/SPECIFICATIONS.md +188 -0
  4. labcode-0.1.1/examples/README.md +85 -0
  5. labcode-0.1.1/examples/outputs/plate_line.boundary.yaml +13 -0
  6. labcode-0.1.1/examples/outputs/plate_line.observation.yaml +202 -0
  7. labcode-0.1.1/examples/outputs/plate_line.plan.yaml +144 -0
  8. labcode-0.1.1/examples/outputs/plate_line.svg +2 -0
  9. labcode-0.1.1/examples/plate_line.boundary.yaml +18 -0
  10. labcode-0.1.1/examples/plate_line.env.yaml +146 -0
  11. labcode-0.1.1/examples/plate_line.workflow.yaml +104 -0
  12. labcode-0.1.1/examples/render_plate_line.py +105 -0
  13. labcode-0.1.1/labcode/_child.py +62 -0
  14. labcode-0.1.1/labcode/backend.py +238 -0
  15. {labcode-0.0.1 → labcode-0.1.1}/labcode/cli.py +7 -5
  16. labcode-0.1.1/labcode/dialect.py +151 -0
  17. labcode-0.1.1/labcode/idgen.py +63 -0
  18. labcode-0.1.1/labcode/objectid.py +231 -0
  19. labcode-0.1.1/labcode/run_cli.py +189 -0
  20. labcode-0.1.1/labcode/runner.py +123 -0
  21. {labcode-0.0.1 → labcode-0.1.1}/labcode.egg-info/PKG-INFO +26 -9
  22. labcode-0.1.1/labcode.egg-info/SOURCES.txt +44 -0
  23. labcode-0.1.1/labcode.egg-info/requires.txt +11 -0
  24. labcode-0.1.1/labcode.egg-info/scm_file_list.json +40 -0
  25. labcode-0.1.1/labcode.egg-info/scm_version.json +8 -0
  26. {labcode-0.0.1 → labcode-0.1.1}/pyproject.toml +10 -7
  27. labcode-0.1.1/tests/fixtures/device_script.env.yaml +28 -0
  28. labcode-0.1.1/tests/fixtures/device_script.workflow.yaml +26 -0
  29. labcode-0.1.1/tests/fixtures/transport.env.yaml +42 -0
  30. labcode-0.1.1/tests/fixtures/transport.workflow.yaml +41 -0
  31. labcode-0.1.1/tests/test_backend.py +201 -0
  32. labcode-0.1.1/tests/test_dialect.py +104 -0
  33. labcode-0.1.1/tests/test_objectid.py +137 -0
  34. labcode-0.1.1/tests/test_run_cli.py +192 -0
  35. labcode-0.0.1/labcode.egg-info/SOURCES.txt +0 -19
  36. labcode-0.0.1/labcode.egg-info/requires.txt +0 -11
  37. labcode-0.0.1/labcode.egg-info/scm_file_list.json +0 -15
  38. labcode-0.0.1/labcode.egg-info/scm_version.json +0 -8
  39. {labcode-0.0.1 → labcode-0.1.1}/.github/workflows/ci.yml +0 -0
  40. {labcode-0.0.1 → labcode-0.1.1}/.github/workflows/publish.yml +0 -0
  41. {labcode-0.0.1 → labcode-0.1.1}/.gitignore +0 -0
  42. {labcode-0.0.1 → labcode-0.1.1}/LICENSE +0 -0
  43. {labcode-0.0.1 → labcode-0.1.1}/labcode/__init__.py +0 -0
  44. {labcode-0.0.1 → labcode-0.1.1}/labcode/__main__.py +0 -0
  45. {labcode-0.0.1 → labcode-0.1.1}/labcode/py.typed +0 -0
  46. {labcode-0.0.1 → labcode-0.1.1}/labcode.egg-info/dependency_links.txt +0 -0
  47. {labcode-0.0.1 → labcode-0.1.1}/labcode.egg-info/entry_points.txt +0 -0
  48. {labcode-0.0.1 → labcode-0.1.1}/labcode.egg-info/top_level.txt +0 -0
  49. {labcode-0.0.1 → labcode-0.1.1}/setup.cfg +0 -0
  50. {labcode-0.0.1 → labcode-0.1.1}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labcode
3
- Version: 0.0.1
3
+ Version: 0.1.1
4
4
  Summary: labcode -- a dialect wrapper over the Object-Flow Programming Language toolchain
5
5
  Author-email: Kazunari Kaizu <kwaizu@gmail.com>
6
6
  License-Expression: MIT
@@ -20,9 +20,9 @@ Classifier: Typing :: Typed
20
20
  Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: ofplang-validate>=0.1.0
24
- Requires-Dist: ofplang-schedule>=0.1.0
25
- Requires-Dist: ofplang-run>=0.1.0
23
+ Requires-Dist: ofplang-validate>=0.1.2
24
+ Requires-Dist: ofplang-schedule>=0.1.3
25
+ Requires-Dist: ofplang-run>=0.1.9
26
26
  Provides-Extra: test
27
27
  Requires-Dist: pytest>=7.0; extra == "test"
28
28
  Provides-Extra: dev
@@ -43,14 +43,31 @@ ofplang toolchain and exposes it under a single command:
43
43
  ```sh
44
44
  lc validate ... # check a workflow is well-formed portable v0
45
45
  lc schedule ... # compute a schedule for a workflow
46
- lc run ... # execute a workflow (rolling-horizon runner / simulator)
46
+ lc run ... # execute a workflow on the labcode backend
47
47
  ```
48
48
 
49
49
  labcode is where a site-specific dialect and a custom runner (real lab hardware)
50
- are developed on top of the ofplang toolchain. At this version `lc` is a thin
51
- dispatcher that forwards each subcommand to the ofplang siblings **unchanged**;
52
- the dialect and custom-runner behavior will land in later versions, replacing
53
- individual subcommands behind the same interface.
50
+ are developed on top of the ofplang toolchain. `lc validate` and `lc schedule`
51
+ forward to the ofplang siblings unchanged; **`lc run` is the labcode dialect's own
52
+ runner**: it drives the workflow on the labcode backend, running each device
53
+ operation's script supplied in the environment as an `x-labcode.script` extension
54
+ on a process mode — out-of-process on a wall clock, so a long-running real operation
55
+ never blocks the replan loop. See [`SPECIFICATIONS.md`](SPECIFICATIONS.md) for the
56
+ `x-labcode` extension.
57
+
58
+ ```yaml
59
+ # in the execution environment: how a (process, mode) is carried out
60
+ processes:
61
+ measure_od:
62
+ modes:
63
+ - id: v0
64
+ duration: 45
65
+ x-labcode:
66
+ script:
67
+ language: python
68
+ code: |
69
+ return {"od": read_plate(plate)}
70
+ ```
54
71
 
55
72
  ## Install
56
73
 
@@ -10,14 +10,31 @@ ofplang toolchain and exposes it under a single command:
10
10
  ```sh
11
11
  lc validate ... # check a workflow is well-formed portable v0
12
12
  lc schedule ... # compute a schedule for a workflow
13
- lc run ... # execute a workflow (rolling-horizon runner / simulator)
13
+ lc run ... # execute a workflow on the labcode backend
14
14
  ```
15
15
 
16
16
  labcode is where a site-specific dialect and a custom runner (real lab hardware)
17
- are developed on top of the ofplang toolchain. At this version `lc` is a thin
18
- dispatcher that forwards each subcommand to the ofplang siblings **unchanged**;
19
- the dialect and custom-runner behavior will land in later versions, replacing
20
- individual subcommands behind the same interface.
17
+ are developed on top of the ofplang toolchain. `lc validate` and `lc schedule`
18
+ forward to the ofplang siblings unchanged; **`lc run` is the labcode dialect's own
19
+ runner**: it drives the workflow on the labcode backend, running each device
20
+ operation's script supplied in the environment as an `x-labcode.script` extension
21
+ on a process mode — out-of-process on a wall clock, so a long-running real operation
22
+ never blocks the replan loop. See [`SPECIFICATIONS.md`](SPECIFICATIONS.md) for the
23
+ `x-labcode` extension.
24
+
25
+ ```yaml
26
+ # in the execution environment: how a (process, mode) is carried out
27
+ processes:
28
+ measure_od:
29
+ modes:
30
+ - id: v0
31
+ duration: 45
32
+ x-labcode:
33
+ script:
34
+ language: python
35
+ code: |
36
+ return {"od": read_plate(plate)}
37
+ ```
21
38
 
22
39
  ## Install
23
40
 
@@ -0,0 +1,188 @@
1
+ # labcode dialect specification
2
+
3
+ labcode is a dialect of the Object-Flow Programming Language (ofplang). A labcode
4
+ workflow **is** a portable v0 ofplang workflow; the dialect lives entirely in the
5
+ **execution environment** (§5), as an `x-labcode` extension that says *how* each device
6
+ operation is physically carried out. `lc run` drives the workflow on the labcode backend
7
+ (`ofplang.run.SubprocessBackend`), sourcing each operation's script from `x-labcode` and
8
+ running it out-of-process on a wall clock.
9
+
10
+ This document is the reference for the `x-labcode` extension; `labcode.dialect` is its
11
+ conformance validator, run at the `lc run` front door.
12
+
13
+ ## 1. `x-labcode` on a process mode (P5)
14
+
15
+ An environment process mode (§5) may carry an `x-labcode` mapping. In this version it
16
+ holds a single key, `script`: the Python that carries out that `(process, mode)`.
17
+
18
+ ```yaml
19
+ processes:
20
+ measure_od:
21
+ modes:
22
+ - id: v0
23
+ devices: [reader]
24
+ duration: 45 # the scheduler's estimate; real time is the script's own
25
+ x-labcode:
26
+ script:
27
+ language: python # the only supported language
28
+ code: |
29
+ return {"od": read_plate(plate)}
30
+ ```
31
+
32
+ `x-labcode` is tolerated (and ignored) by `ofplang-schedule` (>= 0.1.2): the environment
33
+ still validates and schedules as plain v0. Only labcode interprets it.
34
+
35
+ ### 1.1 Shape
36
+
37
+ - `x-labcode` MUST be a mapping.
38
+ - `x-labcode.script`, if present, MUST be a mapping with:
39
+ - `language`: MUST be `python`.
40
+ - `code`: MUST be a string (an implementation-provided Python function body).
41
+
42
+ ### 1.2 Calling convention (process)
43
+
44
+ The script runs as the body of a function whose parameters are the operation's **input
45
+ port names**, each bound to that port's view value (Pure Data or an Object's view record)
46
+ — as in a v0 §22 `python_script_processes` script. External `import` is allowed; there is
47
+ no sandbox.
48
+
49
+ **Partial outputs.** Unlike v0 §22.2 (which requires the script to return *every* output
50
+ exactly), a labcode process script `return`s only the outputs it **computes** — a subset.
51
+ The backend fills the rest:
52
+
53
+ - an Object output declared in `objects.map` is **carried from its input** (the same
54
+ Object, its view unchanged) — so a pass-through need not restate it;
55
+ - any other unset output gets a **typed default** for its type.
56
+
57
+ The script's returned values override these. Each returned value must conform to its port's
58
+ type, and **returning a name that is not a declared output is an error** (this catches a
59
+ typo'd output name). A §22.2-strict script — one that returns every output explicitly —
60
+ works unchanged.
61
+
62
+ So for `read` (input `plate`, outputs `plate` via `objects.map` + `od`), all three are
63
+ equivalent to returning `{"plate": plate, "od": 0.42}`… except the defaulted forms:
64
+
65
+ ```python
66
+ return {"plate": plate, "od": 0.42} # explicit
67
+ return {"od": 0.42} # plate carried by objects.map
68
+ return {} # plate carried; od defaults to 0.0
69
+ ```
70
+
71
+ ### 1.3 `x-labcode` on a transport route
72
+
73
+ An environment `transports[]` route may carry an `x-labcode` with a `script`: the Python
74
+ that physically carries out that move (e.g. commanding a robot arm). Same shape as §1.1
75
+ (`language: python`, string `code`).
76
+
77
+ ```yaml
78
+ transports:
79
+ - transporter: arm
80
+ from: reader.stage
81
+ to: sealer.stage
82
+ duration: 3
83
+ x-labcode:
84
+ script:
85
+ language: python
86
+ code: |
87
+ grip = "gentle" if (view or {}).get("fragile") else "firm"
88
+ move_plate(from_spot, to_spot, grip=grip)
89
+ ```
90
+
91
+ **Calling convention (transport).** The script runs as a function body with these locals:
92
+ `from_spot`, `to_spot`, `transporter` (the physical route), and `view` — the view value of
93
+ the moved Object. `view` is **best-effort and MAY be `None`** (the runner resolves it from
94
+ the producing arc; when it cannot, it is `None`), so a script that reads it should tolerate
95
+ `None`. A transport script is **side-effect only**: its return value is ignored and no
96
+ output is verified. Success is "it ran without raising"; an exception is a graceful failure
97
+ (the move ends `failed`, no material is moved — the run stops).
98
+
99
+ A route with no `x-labcode.script` runs as a plain timed move — the runner's material
100
+ bookkeeping only, with no device command (a warned no-op for a real move, from != to).
101
+
102
+ ## 2. Code source resolution and exclusivity
103
+
104
+ For a dispatched `(process, mode)`, labcode resolves the code to run in this order:
105
+
106
+ 1. the mode's `x-labcode.script.code` (2 — the labcode device script), else
107
+ 2. the workflow process's own `script.code` (1 — a v0 §22 script process), else
108
+ 3. none — the operation runs as a **typed-default no-op** (its outputs are typed
109
+ defaults; a device not yet scripted).
110
+
111
+ **Exclusivity (error).** A process MUST NOT carry both a workflow `script` (1) and an env
112
+ `x-labcode.script` (2) on any of its modes; that is ambiguous and is rejected.
113
+
114
+ **Typed-default reachability (warning).** A process with neither (1) nor (2) on any mode
115
+ will run as a typed-default no-op. This is allowed — convenient while mocking a device —
116
+ but `lc run` warns about it, so an unimplemented device is not silently a no-op.
117
+
118
+ ## 3. Execution model
119
+
120
+ Each dispatched operation runs in its own child process (real, wall-clock-paced); the
121
+ runner discovers completion by polling, so a multi-minute computation never blocks it.
122
+ The advisory `duration` is the scheduler's estimate; the real duration is the script's.
123
+ A script error (an exception, a wrong/ missing output name, a non-conformant value) is a
124
+ graceful runtime failure (§22.2): the operation ends `failed` and the run stops.
125
+
126
+ Cadence: the effective poll period is `poll_interval × seconds_per_tick`. labcode defaults
127
+ `seconds_per_tick` to ~20 s (so a real op is polled at an observable cadence, not
128
+ sub-second, which would flood the replan loop); `lc run --seconds-per-tick/--speed/
129
+ --poll-interval/--margin` override it.
130
+
131
+ ## 4. Object identity — the reserved `_id` view key
132
+
133
+ labcode gives every Object a stable, value-layer identity so it can be traced across
134
+ steps and in the observation document. The identity lives in the Object's **view** under
135
+ the reserved key **`_id`** (a `String`). This is a dialect feature layered on portable
136
+ v0: the workflow the user writes carries no `_id`; `lc run` injects and mints it.
137
+
138
+ ### 4.1 Type rewrite
139
+
140
+ Before running, `lc run` rewrites the workflow in memory: it adds `_id: { type: String }`
141
+ to the `view` of **every `domain: object` type** (creating `view` if the type had none).
142
+ `_id` is an ordinary legal v0 view field (a leading-underscore identifier, not reserved
143
+ in core, and a primitive `String`), so the rewritten document validates and schedules
144
+ unchanged, and the runner's closed-shape view conformance treats `_id` as a normal
145
+ declared field. labcode runs this rewritten document directly (no temp file:
146
+ `ofplang.run.run_workflow` accepts an in-memory document).
147
+
148
+ **Reserved (error).** A user type that itself declares a `_id` view field is rejected at
149
+ the dialect front door — labcode owns `_id`, and silently clobbering the field would be
150
+ worse than a clear error.
151
+
152
+ ### 4.2 Where an id comes from
153
+
154
+ An Object's `_id` is set at its two points of origin, then **carried** everywhere else —
155
+ `objects.map` and transport copy the whole view, so `_id` propagates for free:
156
+
157
+ - **`objects.create`** — a newly created Object's `_id` is minted when the operation
158
+ produces it (in the backend's output fill). A device script need not know about `_id`:
159
+ it returns only what it computes, and the fill supplies `_id` (like any other unset
160
+ output, §1.2).
161
+ - **run boundary** — a whole-workflow Object *input* enters at the boundary; `lc run`
162
+ mints its `_id` (filling any other declared view field with a typed default so the
163
+ seeded value conforms), **unless the boundary already carries one** — so a result
164
+ boundary fed back in round-trips its ids.
165
+ - **`objects.map`** — a mapped Object output carries its input's `_id` unchanged
166
+ (identity preserved), even if a §22.2-strict script returned the port explicitly.
167
+
168
+ ### 4.3 Reproducibility
169
+
170
+ Ids come from a swappable generator (`labcode.idgen.IdGenerator`). The default
171
+ (`SeededUuid4Generator`) mints **reproducible** uuid4-shaped ids from a seed and a
172
+ *provenance key* — the node instance + output port for a create, the port name for a
173
+ boundary input — **not** draw order. So the same workflow yields the same ids on every
174
+ run, and the wall-clock backend's jittering completion order cannot change them (which is
175
+ what keeps checked-in example observations stable). A real run wanting globally-unique
176
+ ids per physical Object swaps in `RealUuid4Generator` (via
177
+ `labcode_backend_factory(id_generator=...)`).
178
+
179
+ > The provenance key is the runner's node-instance identity + port. Today each create
180
+ > node runs once, so node-path + port is unique; when dynamic control flow (e.g.
181
+ > `do_while`) is added, that node-instance identity must include the iteration index so
182
+ > ids stay unique and reproducible.
183
+
184
+ ## 5. Not yet in this version (roadmap)
185
+
186
+ - **Device / transporter `x-labcode`** — connection and availability information
187
+ (e.g. SiLA2 address) consolidated on `devices[]` / `transporters[]`, used for a
188
+ connect/command/disconnect wrapper and for `down_devices` availability probing.
@@ -0,0 +1,85 @@
1
+ # labcode examples
2
+
3
+ ## `plate_line` — an Object-bearing line, driven by environment scripts
4
+
5
+ A `Plate` flows down a four-station line, a `Tube` is dispensed into it, and a
6
+ measurement is read off it:
7
+
8
+ ```
9
+ (in) tube ──────────────┐
10
+
11
+ load ──[move]──> dispense ──[move]──> read ──[move]──> store
12
+ │ │
13
+ (out) tube (out) od
14
+ ```
15
+
16
+ - [`plate_line.workflow.yaml`](plate_line.workflow.yaml) is **portable ofplang v0** — it
17
+ says only *what* happens. The whole workflow takes one `Tube` in and passes the *same*
18
+ Tube back out, alongside the Pure Data measurement `od`. `dispense` and `read` are
19
+ **Object-bearing**: their Objects go in and the *same* Objects come out (`objects.map` —
20
+ identity preserved). The Plate is created inside the workflow by `load`; the Tube enters
21
+ at the run boundary.
22
+ - [`plate_line.boundary.yaml`](plate_line.boundary.yaml) is the **run boundary** (dev-notes
23
+ D28). It supplies the `Tube` input — an Object, so it names a `spot`: a standalone tube
24
+ `rack` slot bound to no process, where the Tube starts and returns (`Tube` has no view
25
+ fields, so no `view`). The arm carries it to the dispenser and back. It also names the
26
+ outputs `od` and `tube`, whose produced values are echoed in the result boundary.
27
+ - [`plate_line.env.yaml`](plate_line.env.yaml) is the **labcode environment** — it says
28
+ *how* each step is carried out, as an `x-labcode.script` (see [`../SPECIFICATIONS.md`](../SPECIFICATIONS.md)).
29
+ **Every process mode and every transport route carries a script**: `load` makes the
30
+ Plate, `dispense` dispenses the Tube into the Plate (both Objects are carried through by
31
+ `objects.map`, so its script returns nothing), `read` measures `od` (the Plate is
32
+ carried, so the script returns only what it computes — labcode's *partial outputs*, see
33
+ the spec), `store` takes it; the transport scripts perform the move and may read the
34
+ moved Plate's `view` (its barcode).
35
+
36
+ labcode runs each script **out-of-process** on a wall clock, discovering completion by
37
+ polling — so a real, slow device operation never blocks the runner.
38
+
39
+ ### Run it
40
+
41
+ ```sh
42
+ lc run examples/plate_line.workflow.yaml --env examples/plate_line.env.yaml \
43
+ --boundary examples/plate_line.boundary.yaml --seconds-per-tick 0.2
44
+ ```
45
+
46
+ `--seconds-per-tick` sets the real seconds per environment time tick; it defaults to a
47
+ coarse value suited to real hardware, so a small value here keeps the demo quick. The run
48
+ completes with every activity `completed`; the produced measurement (`od = 0.42`) can be
49
+ written out with `--boundary-out result.yaml`.
50
+
51
+ ### Produce the outputs
52
+
53
+ [`render_plate_line.py`](render_plate_line.py) drives the same run on the labcode backend
54
+ and writes its artifacts under [`outputs/`](outputs/):
55
+
56
+ ```sh
57
+ python examples/render_plate_line.py
58
+ ```
59
+
60
+ - [`outputs/plate_line.plan.yaml`](outputs/plate_line.plan.yaml) — the **final execution
61
+ schedule** (the §6/§7 status document: every activity, `completed`).
62
+ - [`outputs/plate_line.observation.yaml`](outputs/plate_line.observation.yaml) — the
63
+ **observation document** (D38): each completed activity's I/O views, as `lc run
64
+ --observation-out` would stream it.
65
+ - [`outputs/plate_line.svg`](outputs/plate_line.svg) — a **Gantt chart** of that schedule
66
+ (device view), drawn by the scheduler's visualizer. (The ofplang toolchain renders
67
+ SVG/HTML; open it in a browser, or convert to PNG with any SVG rasterizer.)
68
+ - [`outputs/plate_line.boundary.yaml`](outputs/plate_line.boundary.yaml) — the **result
69
+ boundary**, echoing the produced `od` and the returned `tube` (as `--boundary-out`
70
+ writes it).
71
+
72
+ Because the labcode backend runs each op out-of-process on a wall clock, the exact times
73
+ (and makespan) may vary slightly between runs; the sequence and produced values do not.
74
+
75
+ Every Object's view carries a reserved **`_id`** — labcode's implicit, value-layer Object
76
+ identity (see [`../SPECIFICATIONS.md`](../SPECIFICATIONS.md) §4). In the observation you can
77
+ follow the *same* Plate (one `_id`) from `load` through `dispense`/`read` to `store`, and
78
+ the Tube's `_id` round-trips from the input boundary to the output. The ids are
79
+ reproducible (a seeded, provenance-keyed generator), so these outputs are stable to
80
+ re-generate.
81
+
82
+ > The scripts here are mocks (they just return values / reference their locals). Replace a
83
+ > script's body with real device calls — e.g. `robot.move(from_spot, to_spot)` in a
84
+ > transport, or an instrument read in `read` — to drive real hardware; the code may
85
+ > `import` anything the host Python can.
@@ -0,0 +1,13 @@
1
+ boundary:
2
+ inputs:
3
+ tube:
4
+ spot: rack.slot
5
+ view:
6
+ _id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
7
+ outputs:
8
+ od:
9
+ view: 0.42
10
+ tube:
11
+ spot: rack.slot
12
+ view:
13
+ _id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
@@ -0,0 +1,202 @@
1
+ schema: ofplang-observation/v0
2
+ time:
3
+ unit: second
4
+ interface:
5
+ inputs:
6
+ tube: rack.slot
7
+ outputs:
8
+ tube: rack.slot
9
+ ---
10
+ kind: transport
11
+ from_spot: rack.slot
12
+ to_spot: dispenser.tube
13
+ transporter: arm
14
+ arc:
15
+ from:
16
+ node: []
17
+ port: tube
18
+ to:
19
+ node:
20
+ - Dispense
21
+ port: tube
22
+ start: 0
23
+ end: 1
24
+ moved:
25
+ view:
26
+ _id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
27
+ ---
28
+ kind: processing
29
+ process: load
30
+ mode: m0
31
+ node:
32
+ - Load
33
+ devices:
34
+ - loader
35
+ output_spots:
36
+ plate: loader.stage
37
+ start: 0
38
+ end: 1
39
+ inputs: {}
40
+ outputs:
41
+ plate:
42
+ view:
43
+ barcode: P001
44
+ _id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
45
+ ---
46
+ kind: transport
47
+ from_spot: loader.stage
48
+ to_spot: dispenser.deck
49
+ transporter: arm
50
+ arc:
51
+ from:
52
+ node:
53
+ - Load
54
+ port: plate
55
+ to:
56
+ node:
57
+ - Dispense
58
+ port: plate
59
+ start: 1
60
+ end: 3
61
+ moved:
62
+ view:
63
+ barcode: P001
64
+ _id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
65
+ ---
66
+ kind: processing
67
+ process: dispense
68
+ mode: m0
69
+ node:
70
+ - Dispense
71
+ devices:
72
+ - dispenser
73
+ input_spots:
74
+ plate: dispenser.deck
75
+ tube: dispenser.tube
76
+ output_spots:
77
+ plate: dispenser.deck
78
+ tube: dispenser.tube
79
+ start: 3
80
+ end: 5
81
+ inputs:
82
+ plate:
83
+ view:
84
+ barcode: P001
85
+ _id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
86
+ tube:
87
+ view:
88
+ _id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
89
+ outputs:
90
+ plate:
91
+ view:
92
+ barcode: P001
93
+ _id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
94
+ tube:
95
+ view:
96
+ _id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
97
+ ---
98
+ kind: transport
99
+ from_spot: dispenser.deck
100
+ to_spot: reader.stage
101
+ transporter: arm
102
+ arc:
103
+ from:
104
+ node:
105
+ - Dispense
106
+ port: plate
107
+ to:
108
+ node:
109
+ - Read
110
+ port: plate
111
+ start: 5
112
+ end: 7
113
+ moved:
114
+ view:
115
+ barcode: P001
116
+ _id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
117
+ ---
118
+ kind: transport
119
+ from_spot: dispenser.tube
120
+ to_spot: rack.slot
121
+ transporter: arm
122
+ arc:
123
+ from:
124
+ node:
125
+ - Dispense
126
+ port: tube
127
+ to:
128
+ node: []
129
+ port: tube
130
+ start: 7
131
+ end: 9
132
+ moved:
133
+ view:
134
+ _id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
135
+ ---
136
+ kind: processing
137
+ process: read
138
+ mode: m0
139
+ node:
140
+ - Read
141
+ devices:
142
+ - reader
143
+ input_spots:
144
+ plate: reader.stage
145
+ output_spots:
146
+ plate: reader.stage
147
+ start: 7
148
+ end: 10
149
+ inputs:
150
+ plate:
151
+ view:
152
+ barcode: P001
153
+ _id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
154
+ outputs:
155
+ plate:
156
+ view:
157
+ barcode: P001
158
+ _id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
159
+ od:
160
+ view: 0.42
161
+ ---
162
+ kind: transport
163
+ from_spot: reader.stage
164
+ to_spot: sealer.stage
165
+ transporter: arm
166
+ arc:
167
+ from:
168
+ node:
169
+ - Read
170
+ port: plate
171
+ to:
172
+ node:
173
+ - Store
174
+ port: plate
175
+ start: 10
176
+ end: 12
177
+ moved:
178
+ view:
179
+ barcode: P001
180
+ _id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
181
+ ---
182
+ kind: processing
183
+ process: store
184
+ mode: m0
185
+ node:
186
+ - Store
187
+ devices:
188
+ - sealer
189
+ input_spots:
190
+ plate: sealer.stage
191
+ start: 12
192
+ end: 14
193
+ inputs:
194
+ plate:
195
+ view:
196
+ barcode: P001
197
+ _id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
198
+ outputs: {}
199
+ ---
200
+ final: true
201
+ now: 14
202
+ outcome: completed