Metadata-Version: 2.4
Name: mgi_prepall
Version: 0.0.5
Summary: Python SDK for compiling protocols for the MGI PrepALL workstation.
Author-email: Yang Meng <yangmeng1@mgi-tech.com>
Maintainer-email: Yang Meng <yangmeng1@mgi-tech.com>
License: Apache-2.0
Keywords: pipette,lab,automation,liquid handler,pcr,protocols,workstation,mgi,prepall
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering
Classifier: Intended Audience :: Science/Research
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic>=2.7.1
Dynamic: license-file

# mgi_prepall Protocol API

`mgi_prepall` is a Python SDK that compiles a lab procedure (liquid handling, module control, gripper moves) into a `ProtocolDefinition` JSON for the PrepALL workstation.

A minimal protocol script:

```python
from mgi_prepall import Context

metadata = {
    "protocolName": "hello",
    "author": "me",
    "description": "",
}

def run(ctx: Context) -> None:
    ctx.init(
        lane1="plateRack", lane2="plateRack",
        lane3="plateRack", lane4="trashAndPCR",
        useTrashBin=True, trashBinType="B",
    )
    pipette = ctx.load_pipette("p1000_flex8")
    tips = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-1") \
              .load_labware("mgi_96_filtertiprack_200ul", code="T1")
    src  = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-2") \
              .load_labware("mgi_96_well_plate", code="P1")
    dst  = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-3") \
              .load_labware("mgi_96_well_plate", code="P2")

    pipette.transfer(50.0, (src, "A1"), (dst, "A1"), tiprack=tips)
```

Compile to JSON:

```bash
python -m mgi_prepall my_protocol.py -o my_protocol.json
```

---

# Getting Started

This page walks through a complete protocol from deck layout to compilation. A runnable version lives in `examples/transfer_demo.py`.

## 1. Protocol script structure

Every protocol is a standalone `.py` file that exports two things:

- a module-level `metadata` dict (optional but recommended)
- a top-level `run(ctx)` function — the runner injects a fresh `Context` instance

```python
from mgi_prepall import Context

metadata = {
    "protocolName": "transfer demo",
    "author": "me",
    "description": "",
}

def run(ctx: Context) -> None:
    ...
```

## 2. Configure the deck: `ctx.init`

The PrepAll deck is a 4-lane × 4-slot grid plus a few gripper-reachable stacks. Each lane's base type is declared with a `LaneType`:

```python
ctx.init(
    lane1="plateRack",       # plate carriers
    lane2="activeModule",    # active modules
    lane3="plateRack",
    lane4="trashAndPCR",     # trash + PCR
    useTrashBin=True,
    trashBinType="B",
)
```

Constraints (any violation raises `ValueError`):

- `lane4 == "trashAndPCR"` ⇔ `useTrashBin=True` ⇔ `trashBinType is not None`
- Some module models are pinned to a specific slot (e.g. the thermocycler must sit at `4-1`, see `MODULE_MODEL_REQUIRED_SLOT`)
- `temperature` / `heaterShaker` modules can only go into a lane whose type is `activeModule`

See `Context.init` for the full signature including `stacks=...` and `ingredients=...`.

## 3. Load instruments and labware

```python
pipette = ctx.load_pipette("p1000_flex8")
gripper = ctx.load_gripper("gripper")

# Only deck adapters (plate carriers) can sit directly in a slot
slot11 = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-1")
slot12 = ctx.load_labware("mgi_prepall_plate_carrier_low_adapter", "1-2")

# Plates, tip racks, etc. are stacked onto adapters / modules via `.load_labware`
tiprack = slot11.load_labware("mgi_96_filtertiprack_200ul", code="T1")
src     = slot12.load_labware("mgi_96_well_plate", code="P1")
```

Four `load_*` factories return the four resource kinds:

| API | Returns |
|---|---|
| `ctx.load_pipette(name)` | `Pipette` |
| `ctx.load_gripper(name)` | `Gripper` |
| `ctx.load_module(model, slot)` | `Module` subclass (Temperature / HeaterShaker / Thermocycler) |
| `ctx.load_labware(loadName, slot)` | `Labware` — only deck adapters; see `DeckAdapterLoadName` |

Anything other than a deck adapter (plates, tip racks, tube racks, …) must be stacked onto an adapter / module via `labware.load_labware(...)` or `module.load_labware(...)`.

## 4. Move liquid

```python
pipette.transfer(
    100.0,
    (src, "A1"),    # 8-channel column anchor: A1 expands to A1..H1
    (dst, "A1"),
    tiprack=tiprack,
)
```

`source` and `dest` accept two shapes:

- `(labware, "A1")` — column anchor, automatically expanded by `pipette.channel` (8 by default) into a full column
- `(labware, ["A1", "B1", ...])` — explicit well list whose length must be a multiple of `channels`

Many options are available — see `Pipette.transfer` for the full reference. Common patterns:

| Need | How |
|---|---|
| Don't change tip every cycle | `new_tip="once"` or `"never"` |
| Mix before / mix after | `mix_before=(5, 100)` |
| One source, many destinations | `path="multiDispense"` |
| Many sources, one destination | `path="multiAspirate"` |
| Liquid-level detection | `aspirate_detect=AspirateDetect(...)` |
| Drop tip back into source well | `drop_location="sourceWell"` |

## 5. Step-by-step liquid handling

When you need finer control than `transfer` — e.g. inspecting tip state mid-sequence, splitting an aspirate and dispense across non-trivial logic, or building a custom protocol fragment — use the four standalone step APIs. Each one emits its own step form (visible in the designer / saved JSON) plus the matching runtime command(s). A runnable example covering single-rack and cross-rack scenarios lives at `examples/single_step_demo.py`.

```python
# 1. Pick up tips. With a single rack you get one pickUpTip step;
#    with a TipRackGroup you may get multiple steps when the pickup spans racks.
pipette.pick_up_tip(tiprack)

# 2. Aspirate — supports the full transfer-aligned option set
pipette.aspirate(
    50, (src, "A1"),
    flow_rate=80,
    offset=Offset(z=2),
    pre_aspirate=10,                           # front air buffer at well top
    air_gap=5,                                 # rear air seal at well top
    aspirate_detect=AspirateDetect(endZOffset=1.2, follow=True),
)

# 3. Dispense
pipette.dispense(
    65, (dst, "A1"),                           # 50 main + 10 pre + 5 air_gap
    flow_rate=120,
    delay_seconds=1,
)

# 4. Drop tip — "trash" (default) or "sourceWell"
pipette.drop_tip()
```

Both `aspirate` / `dispense` require a tip to be held (raise `RuntimeError` otherwise). They mirror the per-cycle behavior inside `transfer`, so the resulting commands match what an equivalent `transfer` call would have emitted.

| API | Step form | Runtime commands per call |
|---|---|---|
| `pick_up_tip` | `pickUpTip` (one per source rack) | `pickUpTip` |
| `aspirate` | `aspirate` | `aspirate` (+ optional `pre_aspirate at top` / `air_gap at top` / `moveToWell + waitForDuration`) |
| `dispense` | `dispense` | `dispense` (+ optional `moveToWell + waitForDuration`) |
| `drop_tip` | `dropTip` (one for trash; one per source rack for sourceWell) | `moveToAddressableAreaForDropTip + dropTipInPlace` (trash) or `dropTip` per source rack (sourceWell) |

### Cross-rack pickup (TipRackGroup)

If `tiprack` is a `TipRackGroup` and the first rack runs short, the SDK keeps scanning into the next rack(s) until `channels` tips are collected. Each `(rack, column)` worth of tips becomes its **own** `pickUpTip` step — so an 8-channel pickup of `5 + 3` (rack A + rack B) produces two consecutive `pickUpTip` steps:

```python
tips = ctx.create_tip_rack_group([rack_a, rack_b], name="tips")
# ... rack A is now mostly used: only D1..H1 are left ...
pipette.pick_up_tip(tips)
# emits:
#   pickUpTip step  rack=A  channels=5  channelKeys=["1","2","3","4","5"]  wells=[D1..H1]
#   pickUpTip step  rack=B  channels=3  channelKeys=["6","7","8"]          wells=[A1..C1]
```

A subsequent `drop_tip("sourceWell")` mirrors the split — one `dropTip` step per source rack, returning each tip to its origin. `drop_tip("trash")` always emits a single step regardless of how many racks the tips came from.

Inside `transfer` / `mix` the same cross-rack pickup happens automatically, but those multiple `pickUpTip` runtime commands stay bundled inside the **single** `moveLiquid` / `mix` step — `transfer` is one logical operation. `RuntimeError("Tip racks exhausted")` is raised only when the racks together can't supply `channels` tips.

## 6. Modules, gripper, pauses

```python
temp_mod = ctx.load_module("prepAllTemperatureModuleV1", "2-1")
temp_mod.set_temperature(4)
temp_mod.wait_for_temperature(4, timeout_seconds=60)

thermo = ctx.load_module("prepAllThermocyclerModuleV1", "4-1")
thermo.run_method("FastFS")    # `wait=True` blocks until completion

ctx.pause("Operator: please verify reagents")    # untilResume
ctx.delay(minutes=5)                              # untilTime
```

The gripper carries plates / tip racks between stacks and deck adapters:

```python
fresh_tips = gripper.load_labware_to(           # 出料: feed off the stack
    ctx.find_labware_by_code("T2"),
    target=adapter_slot,
)
gripper.store_labware_to(used_tips)              # 回料: put back onto a stack
gripper.move_labware(plate, target_module)       # carry to a module
```

See `Gripper` for details.

## 7. Compile

```bash
# write to file
python -m mgi_prepall my_protocol.py -o my_protocol.json

# write to stdout
python -m mgi_prepall my_protocol.py

# or via the installed console script
mgi_prepall my_protocol.py -o my_protocol.json
```

Or from Python:

```python
from mgi_prepall.runner import compile_script_to_json

json_text = compile_script_to_json("my_protocol.py", indent=2)
```

See `runner` for details.

## Next steps

- Browse the API Reference for any specific method
- Read the Examples closest to your scenario
