labcode 0.1.0__tar.gz → 0.1.2__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.1.0 → labcode-0.1.2}/PKG-INFO +4 -4
  2. {labcode-0.1.0 → labcode-0.1.2}/SPECIFICATIONS.md +54 -1
  3. labcode-0.1.2/examples/README.md +85 -0
  4. labcode-0.1.2/examples/outputs/plate_line.boundary.yaml +13 -0
  5. labcode-0.1.2/examples/outputs/plate_line.observation.yaml +202 -0
  6. labcode-0.1.2/examples/outputs/plate_line.plan.yaml +144 -0
  7. labcode-0.1.2/examples/outputs/plate_line.svg +2 -0
  8. labcode-0.1.2/examples/plate_line.boundary.yaml +18 -0
  9. {labcode-0.1.0 → labcode-0.1.2}/examples/plate_line.env.yaml +66 -6
  10. labcode-0.1.2/examples/plate_line.workflow.yaml +104 -0
  11. labcode-0.1.2/examples/render_plate_line.py +105 -0
  12. {labcode-0.1.0 → labcode-0.1.2}/labcode/backend.py +38 -11
  13. {labcode-0.1.0 → labcode-0.1.2}/labcode/dialect.py +11 -0
  14. labcode-0.1.2/labcode/idgen.py +63 -0
  15. labcode-0.1.2/labcode/objectid.py +231 -0
  16. {labcode-0.1.0 → labcode-0.1.2}/labcode/run_cli.py +16 -13
  17. labcode-0.1.2/labcode/runner.py +123 -0
  18. {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/PKG-INFO +4 -4
  19. {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/SOURCES.txt +10 -0
  20. labcode-0.1.2/labcode.egg-info/requires.txt +11 -0
  21. {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/scm_file_list.json +10 -0
  22. labcode-0.1.2/labcode.egg-info/scm_version.json +8 -0
  23. {labcode-0.1.0 → labcode-0.1.2}/pyproject.toml +9 -8
  24. {labcode-0.1.0 → labcode-0.1.2}/tests/test_backend.py +14 -9
  25. {labcode-0.1.0 → labcode-0.1.2}/tests/test_dialect.py +11 -0
  26. labcode-0.1.2/tests/test_objectid.py +137 -0
  27. {labcode-0.1.0 → labcode-0.1.2}/tests/test_run_cli.py +88 -21
  28. labcode-0.1.0/examples/README.md +0 -40
  29. labcode-0.1.0/examples/plate_line.workflow.yaml +0 -71
  30. labcode-0.1.0/labcode.egg-info/requires.txt +0 -11
  31. labcode-0.1.0/labcode.egg-info/scm_version.json +0 -8
  32. {labcode-0.1.0 → labcode-0.1.2}/.github/workflows/ci.yml +0 -0
  33. {labcode-0.1.0 → labcode-0.1.2}/.github/workflows/publish.yml +0 -0
  34. {labcode-0.1.0 → labcode-0.1.2}/.gitignore +0 -0
  35. {labcode-0.1.0 → labcode-0.1.2}/LICENSE +0 -0
  36. {labcode-0.1.0 → labcode-0.1.2}/README.md +0 -0
  37. {labcode-0.1.0 → labcode-0.1.2}/labcode/__init__.py +0 -0
  38. {labcode-0.1.0 → labcode-0.1.2}/labcode/__main__.py +0 -0
  39. {labcode-0.1.0 → labcode-0.1.2}/labcode/_child.py +0 -0
  40. {labcode-0.1.0 → labcode-0.1.2}/labcode/cli.py +0 -0
  41. {labcode-0.1.0 → labcode-0.1.2}/labcode/py.typed +0 -0
  42. {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/dependency_links.txt +0 -0
  43. {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/entry_points.txt +0 -0
  44. {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/top_level.txt +0 -0
  45. {labcode-0.1.0 → labcode-0.1.2}/setup.cfg +0 -0
  46. {labcode-0.1.0 → labcode-0.1.2}/tests/fixtures/device_script.env.yaml +0 -0
  47. {labcode-0.1.0 → labcode-0.1.2}/tests/fixtures/device_script.workflow.yaml +0 -0
  48. {labcode-0.1.0 → labcode-0.1.2}/tests/fixtures/transport.env.yaml +0 -0
  49. {labcode-0.1.0 → labcode-0.1.2}/tests/fixtures/transport.workflow.yaml +0 -0
  50. {labcode-0.1.0 → labcode-0.1.2}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labcode
3
- Version: 0.1.0
3
+ Version: 0.1.2
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.2
25
- Requires-Dist: ofplang-run>=0.1.6
23
+ Requires-Dist: ofplang-validate>=0.1.3
24
+ Requires-Dist: ofplang-schedule>=0.1.3
25
+ Requires-Dist: ofplang-run>=0.1.10
26
26
  Provides-Extra: test
27
27
  Requires-Dist: pytest>=7.0; extra == "test"
28
28
  Provides-Extra: dev
@@ -128,7 +128,60 @@ Cadence: the effective poll period is `poll_interval × seconds_per_tick`. labco
128
128
  sub-second, which would flood the replan loop); `lc run --seconds-per-tick/--speed/
129
129
  --poll-interval/--margin` override it.
130
130
 
131
- ## 4. Not yet in this version (roadmap)
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)
132
185
 
133
186
  - **Device / transporter `x-labcode`** — connection and availability information
134
187
  (e.g. SiLA2 address) consolidated on `devices[]` / `transporters[]`, used for a
@@ -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
@@ -0,0 +1,144 @@
1
+ time:
2
+ unit: second
3
+ now: 14
4
+ interface:
5
+ inputs:
6
+ tube: rack.slot
7
+ outputs:
8
+ tube: rack.slot
9
+ activities:
10
+ - kind: transport
11
+ start: 0
12
+ end: 1
13
+ from_spot: rack.slot
14
+ to_spot: dispenser.tube
15
+ transporter: arm
16
+ arc:
17
+ from:
18
+ node: []
19
+ port: tube
20
+ to:
21
+ node:
22
+ - Dispense
23
+ port: tube
24
+ status: completed
25
+ - kind: processing
26
+ start: 0
27
+ end: 1
28
+ process: load
29
+ mode: m0
30
+ node:
31
+ - Load
32
+ devices:
33
+ - loader
34
+ output_spots:
35
+ plate: loader.stage
36
+ status: completed
37
+ - kind: transport
38
+ start: 1
39
+ end: 3
40
+ from_spot: loader.stage
41
+ to_spot: dispenser.deck
42
+ transporter: arm
43
+ arc:
44
+ from:
45
+ node:
46
+ - Load
47
+ port: plate
48
+ to:
49
+ node:
50
+ - Dispense
51
+ port: plate
52
+ status: completed
53
+ - kind: processing
54
+ start: 3
55
+ end: 5
56
+ process: dispense
57
+ mode: m0
58
+ node:
59
+ - Dispense
60
+ devices:
61
+ - dispenser
62
+ input_spots:
63
+ plate: dispenser.deck
64
+ tube: dispenser.tube
65
+ output_spots:
66
+ plate: dispenser.deck
67
+ tube: dispenser.tube
68
+ status: completed
69
+ - kind: transport
70
+ start: 5
71
+ end: 7
72
+ from_spot: dispenser.deck
73
+ to_spot: reader.stage
74
+ transporter: arm
75
+ arc:
76
+ from:
77
+ node:
78
+ - Dispense
79
+ port: plate
80
+ to:
81
+ node:
82
+ - Read
83
+ port: plate
84
+ status: completed
85
+ - kind: transport
86
+ start: 7
87
+ end: 9
88
+ from_spot: dispenser.tube
89
+ to_spot: rack.slot
90
+ transporter: arm
91
+ arc:
92
+ from:
93
+ node:
94
+ - Dispense
95
+ port: tube
96
+ to:
97
+ node: []
98
+ port: tube
99
+ status: completed
100
+ - kind: processing
101
+ start: 7
102
+ end: 10
103
+ process: read
104
+ mode: m0
105
+ node:
106
+ - Read
107
+ devices:
108
+ - reader
109
+ input_spots:
110
+ plate: reader.stage
111
+ output_spots:
112
+ plate: reader.stage
113
+ status: completed
114
+ - kind: transport
115
+ start: 10
116
+ end: 12
117
+ from_spot: reader.stage
118
+ to_spot: sealer.stage
119
+ transporter: arm
120
+ arc:
121
+ from:
122
+ node:
123
+ - Read
124
+ port: plate
125
+ to:
126
+ node:
127
+ - Store
128
+ port: plate
129
+ status: completed
130
+ - kind: processing
131
+ start: 12
132
+ end: 14
133
+ process: store
134
+ mode: m0
135
+ node:
136
+ - Store
137
+ devices:
138
+ - sealer
139
+ input_spots:
140
+ plate: sealer.stage
141
+ status: completed
142
+ objective:
143
+ kind: makespan
144
+ value: 14
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 784 296" width="784" height="296" font-family="system-ui, sans-serif" font-size="12"><text fill="#1a1a1a" font-size="15" font-weight="600" x="16" y="24">Schedule &#8212; device view</text><text fill="#666666" x="16" y="40">makespan 14 second</text><defs><marker id="ah" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#9333ea"/></marker></defs><g transform="translate(0,48)"><line stroke="#b3b3b3" stroke-width="1" x1="200.0" y1="34.0" x2="200.0" y2="220.0"/><text fill="#666666" x="200.0" y="28.0" text-anchor="middle">0</text><line stroke="#b3b3b3" stroke-width="1" x1="280.0" y1="34.0" x2="280.0" y2="220.0"/><text fill="#666666" x="280.0" y="28.0" text-anchor="middle">2</text><line stroke="#b3b3b3" stroke-width="1" x1="360.0" y1="34.0" x2="360.0" y2="220.0"/><text fill="#666666" x="360.0" y="28.0" text-anchor="middle">4</text><line stroke="#b3b3b3" stroke-width="1" x1="440.0" y1="34.0" x2="440.0" y2="220.0"/><text fill="#666666" x="440.0" y="28.0" text-anchor="middle">6</text><line stroke="#b3b3b3" stroke-width="1" x1="520.0" y1="34.0" x2="520.0" y2="220.0"/><text fill="#666666" x="520.0" y="28.0" text-anchor="middle">8</text><line stroke="#b3b3b3" stroke-width="1" x1="600.0" y1="34.0" x2="600.0" y2="220.0"/><text fill="#666666" x="600.0" y="28.0" text-anchor="middle">10</text><line stroke="#b3b3b3" stroke-width="1" x1="680.0" y1="34.0" x2="680.0" y2="220.0"/><text fill="#666666" x="680.0" y="28.0" text-anchor="middle">12</text><line stroke="#b3b3b3" stroke-width="1" x1="760.0" y1="34.0" x2="760.0" y2="220.0"/><text fill="#666666" x="760.0" y="28.0" text-anchor="middle">14</text><text fill="#666666" x="200.0" y="240.0">time (second)</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="70.0" x2="784.0" y2="70.0"/><text fill="#1a1a1a" x="190.0" y="59.0" text-anchor="end">dispenser</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="100.0" x2="784.0" y2="100.0"/><text fill="#1a1a1a" x="190.0" y="89.0" text-anchor="end">loader</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="130.0" x2="784.0" y2="130.0"/><text fill="#1a1a1a" x="190.0" y="119.0" text-anchor="end">rack</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="160.0" x2="784.0" y2="160.0"/><text fill="#1a1a1a" x="190.0" y="149.0" text-anchor="end">reader</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="190.0" x2="784.0" y2="190.0"/><text fill="#1a1a1a" x="190.0" y="179.0" text-anchor="end">sealer</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="220.0" x2="784.0" y2="220.0"/><text fill="#1a1a1a" x="190.0" y="209.0" text-anchor="end">arm (transporter)</text><line stroke="#dc2626" stroke-width="1.5" stroke-dasharray="4 3" x1="760.0" y1="34.0" x2="760.0" y2="220.0"/><text fill="#dc2626" x="763.0" y="28.0">now=14</text><rect fill="#f59e0b" x="200.0" y="195.0" width="40.0" height="20" rx="3"/><text fill="#3a2a00" x="204.0" y="209.0">&gt; D…</text><rect fill="#f59e0b" fill-opacity="0.2" x="200.0" y="105.0" width="40.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="200.0" y="45.0" width="40.0" height="20" rx="3"/><rect fill="#3b82f6" x="200.0" y="75.0" width="40.0" height="20" rx="3"/><text fill="#ffffff" x="204.0" y="89.0">Load</text><rect fill="#f59e0b" x="240.0" y="195.0" width="80.0" height="20" rx="3"/><text fill="#3a2a00" x="244.0" y="209.0">&gt; Dispense</text><rect fill="#f59e0b" fill-opacity="0.2" x="240.0" y="75.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="240.0" y="45.0" width="80.0" height="20" rx="3"/><rect fill="#3b82f6" x="320.0" y="45.0" width="80.0" height="20" rx="3"/><text fill="#ffffff" x="324.0" y="59.0">Dispense</text><rect fill="#f59e0b" x="400.0" y="195.0" width="80.0" height="20" rx="3"/><text fill="#3a2a00" x="404.0" y="209.0">&gt; Read</text><rect fill="#f59e0b" fill-opacity="0.2" x="400.0" y="45.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="400.0" y="135.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" x="480.0" y="195.0" width="80.0" height="20" rx="3"/><text fill="#3a2a00" x="484.0" y="209.0">&gt; transport</text><rect fill="#f59e0b" fill-opacity="0.2" x="480.0" y="45.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="480.0" y="105.0" width="80.0" height="20" rx="3"/><rect fill="#3b82f6" x="480.0" y="135.0" width="120.0" height="20" rx="3"/><text fill="#ffffff" x="484.0" y="149.0">Read</text><rect fill="#f59e0b" x="600.0" y="195.0" width="80.0" height="20" rx="3"/><text fill="#3a2a00" x="604.0" y="209.0">&gt; Store</text><rect fill="#f59e0b" fill-opacity="0.2" x="600.0" y="135.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="600.0" y="165.0" width="80.0" height="20" rx="3"/><rect fill="#3b82f6" x="680.0" y="165.0" width="80.0" height="20" rx="3"/><text fill="#ffffff" x="684.0" y="179.0">Store</text></g></svg>
@@ -0,0 +1,18 @@
1
+ # Run boundary (dev-notes D28) for plate_line.workflow.yaml.
2
+ #
3
+ # The whole workflow takes one `Tube` in and passes the same Tube out (plus the Pure
4
+ # Data measurement `od`). A boundary port has two halves: `spot` (where an Object sits)
5
+ # and `view` (its observable value).
6
+ #
7
+ # - `tube` is Object-bearing, so it MUST name a spot: it starts in the standalone tube
8
+ # rack (`rack.slot`, bound to no process). The arm carries it to the dispenser for the
9
+ # dispense and back. `Tube` has no view fields, so no `view` is supplied.
10
+ # - `od` is Pure Data (no spot); its produced view is filled in at run end.
11
+ # - the `tube` output is pinned back to its rack slot, so it round-trips (its view is
12
+ # filled in at run end).
13
+ boundary:
14
+ inputs:
15
+ tube: { spot: rack.slot }
16
+ outputs:
17
+ od: {}
18
+ tube: { spot: rack.slot }
@@ -3,16 +3,27 @@
3
3
  # The workflow stays portable v0 (`ofplang-schedule` just tolerates the x- keys).
4
4
  #
5
5
  # Every process mode AND every transport route carries a script here. The process
6
- # scripts are Object-bearing: `load` returns the new Plate's view; `read` returns the
7
- # *same* plate it was given plus the measured `od`; `store` takes it (side-effect only).
6
+ # scripts are Object-bearing: `load` returns the new Plate's view; `dispense` takes the
7
+ # Plate and the Tube and passes *both* through (objects.map), so its script returns
8
+ # nothing; `read` returns the measured `od` (the Plate is carried); `store` takes it.
8
9
  # The transport scripts move the plate and may read its view (the barcode).
10
+ #
11
+ # Physical layout: the Plate is loaded on `loader`, dispensed into on the `dispenser`
12
+ # deck, read on `reader`, sealed on `sealer`; the `arm` moves it along. The source Tube
13
+ # starts in a standalone `rack` slot -- a plain holding location bound to no process --
14
+ # enters and leaves the run there (see plate_line.boundary.yaml), and the `arm` carries
15
+ # it to the dispenser's `tube` holder for the dispense and back to the rack afterwards.
9
16
  time:
10
17
  unit: second
11
18
 
12
19
  devices:
13
- - { id: loader, spots: [stage] }
14
- - { id: reader, spots: [stage] }
15
- - { id: sealer, spots: [stage] }
20
+ - { id: loader, spots: [stage] }
21
+ - { id: dispenser, spots: [deck, tube] }
22
+ - { id: reader, spots: [stage] }
23
+ - { id: sealer, spots: [stage] }
24
+ # A tube rack: a plain holding location, used by no process. The source Tube lives
25
+ # here at the run boundary; the arm ferries it to/from the dispenser.
26
+ - { id: rack, spots: [slot] }
16
27
 
17
28
  transporters:
18
29
  - { id: arm }
@@ -20,7 +31,7 @@ transporters:
20
31
  transports:
21
32
  - transporter: arm
22
33
  from: loader.stage
23
- to: reader.stage
34
+ to: dispenser.deck
24
35
  duration: 2
25
36
  x-labcode:
26
37
  script:
@@ -31,6 +42,16 @@ transports:
31
42
  barcode = (view or {}).get("barcode")
32
43
  # a real arm would move the plate here; e.g. robot.move(from_spot, to_spot)
33
44
 
45
+ - transporter: arm
46
+ from: dispenser.deck
47
+ to: reader.stage
48
+ duration: 2
49
+ x-labcode:
50
+ script:
51
+ language: python
52
+ code: |
53
+ barcode = (view or {}).get("barcode")
54
+
34
55
  - transporter: arm
35
56
  from: reader.stage
36
57
  to: sealer.stage
@@ -41,6 +62,30 @@ transports:
41
62
  code: |
42
63
  barcode = (view or {}).get("barcode")
43
64
 
65
+ # The source Tube's round trip: rack -> dispenser for the dispense, then back.
66
+ - transporter: arm
67
+ from: rack.slot
68
+ to: dispenser.tube
69
+ duration: 2
70
+ x-labcode:
71
+ script:
72
+ language: python
73
+ code: |
74
+ # Move the source tube to the dispenser. `view` is the Tube's view; it has no
75
+ # fields here. A real arm would grip and move the tube; e.g. robot.move(...).
76
+ pass
77
+
78
+ - transporter: arm
79
+ from: dispenser.tube
80
+ to: rack.slot
81
+ duration: 2
82
+ x-labcode:
83
+ script:
84
+ language: python
85
+ code: |
86
+ # Return the tube to its rack slot.
87
+ pass
88
+
44
89
  processes:
45
90
 
46
91
  load:
@@ -55,6 +100,21 @@ processes:
55
100
  # Create the Plate's view (its observable data). Returns the one output port.
56
101
  return {"plate": {"barcode": "P001"}}
57
102
 
103
+ dispense:
104
+ modes:
105
+ - devices: [dispenser]
106
+ duration: 3
107
+ input_spots: { plate: dispenser.deck, tube: dispenser.tube }
108
+ output_spots: { plate: dispenser.deck, tube: dispenser.tube } # both stay in place
109
+ x-labcode:
110
+ script:
111
+ language: python
112
+ code: |
113
+ # `plate` and `tube` are the incoming Objects' views. A real dispenser would
114
+ # aspirate from the tube and dispense into the plate here. Both Objects are
115
+ # carried through by objects.map, so the script returns nothing.
116
+ return {}
117
+
58
118
  read:
59
119
  modes:
60
120
  - devices: [reader]