Skip to content

Conversation

@ALITTLELZ
Copy link
Contributor

@ALITTLELZ ALITTLELZ commented Nov 20, 2025

Add step mode feature to 9320:

  • Supported pick_up_tips, aspirate, dispense, drop_tips, discard_tips, and mix
  • Add running example
    Add feature to obtain matrices and materials list
    Updated host for 9320

Summary by Sourcery

Introduce step_mode for per-action protocol execution, add utility to export matrices and materials as JSON, include usage examples, and update the device host address.

New Features:

  • Add step_mode to perform pick_up_tips, aspirate, dispense, drop_tips, discard_tips, and mix as individual protocol steps
  • Add command-line feature to fetch deck matrices and materials list and save them to JSON

Enhancements:

  • Include runnable examples demonstrating step_mode, normal mode, and mixed usage

Chores:

  • Update default host IP for PRCXI9320 device to 192.168.1.201

@sourcery-ai
Copy link

sourcery-ai bot commented Nov 20, 2025

Reviewer's Guide

Implements a step_mode feature enabling immediate execution per liquid handling method in PRCXI9320, adds matrices/materials export logic and structured example scripts, and updates the default host address to 192.168.1.201.

Sequence diagram for step_mode immediate execution in PRCXI9320

sequenceDiagram
    participant User
    participant Handler
    participant Backend
    User->>Handler: Call pick_up_tips()
    alt step_mode = True
        Handler->>Handler: create_protocol()
        Handler->>Backend: pick_up_tips()
        Handler->>Handler: run_protocol()
    else step_mode = False
        Handler->>Backend: pick_up_tips()
    end
Loading

Class diagram for PRCXI9300Handler step_mode changes

classDiagram
    class PRCXI9300Handler {
      +bool step_mode
      +async pick_up_tips(...)
      +async aspirate(...)
      +async dispense(...)
      +async drop_tips(...)
      +async discard_tips(...)
      +async mix(...)
      +async create_protocol(...)
      +async run_protocol(...)
    }
    PRCXI9300Handler --|> SuperHandler
    PRCXI9300Handler o-- "1" PRCXI9300Deck
    PRCXI9300Handler o-- "1" Backend
Loading

Flow diagram for exporting matrices and materials to JSON

flowchart TD
    A["Create PRCXI9300Api"] --> B["Fetch matrices"]
    A --> C["Fetch materials"]
    B --> D["Combine matrices and materials"]
    C --> D
    D --> E["Write to prcxi_layout_and_materials.json"]
Loading

File-Level Changes

Change Details Files
Add step_mode branching to major liquid handling methods
  • Wrap mix calls with create_protocol/run_protocol when step_mode is enabled
  • Apply the same step_mode check logic to pick_up_tips, aspirate, dispense, drop_tips, and discard_tips
unilabos/devices/liquid_handling/prcxi/prcxi.py
Introduce matrices and materials export functionality
  • Add FETCH_LAYOUT_AND_MATERIALS_ONLY flag branch in the main entry
  • Call list_matrices() and get_all_materials() and serialize results to JSON
  • Write output to prcxi_layout_and_materials.json with proper formatting
unilabos/devices/liquid_handling/prcxi/prcxi.py
Provide running examples for step_mode and normal workflows
  • Add example_step_mode() showcasing per-operation execution
  • Add example_normal_mode() demonstrating batch protocol creation and execution
  • Add example_mixed_usage() guidance for combining modes
unilabos/devices/liquid_handling/prcxi/prcxi.py
Update default host address for PRCXI9320
  • Change host from 192.168.0.121 to 192.168.1.201 in get_tip_rack defaults
  • Align host update in the main handler constructor for consistency
unilabos/devices/liquid_handling/prcxi/prcxi.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The step_mode logic is repeated verbatim in multiple methods; consider refactoring it into a shared decorator or helper function to reduce boilerplate.
  • In step_mode branches you drop the return values of backend calls, causing inconsistent return types between modes—ensure both branches return the same result.
  • The large demo and JSON‐export code in the main block could bloat the library; move examples and one‐off scripts into a separate file or CLI entrypoint instead of a hardcoded flag.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The step_mode logic is repeated verbatim in multiple methods; consider refactoring it into a shared decorator or helper function to reduce boilerplate.
- In step_mode branches you drop the return values of backend calls, causing inconsistent return types between modes—ensure both branches return the same result.
- The large demo and JSON‐export code in the __main__ block could bloat the library; move examples and one‐off scripts into a separate file or CLI entrypoint instead of a hardcoded flag.

## Individual Comments

### Comment 1
<location> `unilabos/devices/liquid_handling/prcxi/prcxi.py:342-347` </location>
<code_context>
-        return await self._unilabos_backend.mix(
-            targets, mix_time, mix_vol, height_to_bottom, offsets, mix_rate, none_keys
-        )
+        if self.step_mode:
+            await self.create_protocol(f"单点动作{time.time()}")
+            await self._unilabos_backend.mix(
+                targets, mix_time, mix_vol, height_to_bottom, offsets, mix_rate, none_keys
+            )
+            await self.run_protocol()
+        else:
+            return await self._unilabos_backend.mix(
</code_context>

<issue_to_address>
**issue:** Mix method now has different return behavior in step_mode.

Returning a value in both branches will help prevent unexpected behavior for callers relying on a consistent method interface.
</issue_to_address>

### Comment 2
<location> `unilabos/devices/liquid_handling/prcxi/prcxi.py:364-368` </location>
<code_context>
    async def pick_up_tips(
        self,
        tip_spots: List[TipSpot],
        use_channels: Optional[List[int]] = None,
        offsets: Optional[List[Coordinate]] = None,
        **backend_kwargs,
    ):
        if self.step_mode:
            await self.create_protocol(f"单点动作{time.time()}")
            await super().pick_up_tips(tip_spots, use_channels, offsets, **backend_kwargs)
            await self.run_protocol()
        else:
            return await super().pick_up_tips(tip_spots, use_channels, offsets, **backend_kwargs)

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Swap if/else branches ([`swap-if-else-branches`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/swap-if-else-branches/))
- Remove unnecessary else after guard condition ([`remove-unnecessary-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-else/))
</issue_to_address>

### Comment 3
<location> `unilabos/devices/liquid_handling/prcxi/prcxi.py:382` </location>
<code_context>
    async def aspirate(
        self,
        resources: Sequence[Container],
        vols: List[float],
        use_channels: Optional[List[int]] = None,
        flow_rates: Optional[List[Optional[float]]] = None,
        offsets: Optional[List[Coordinate]] = None,
        liquid_height: Optional[List[Optional[float]]] = None,
        blow_out_air_volume: Optional[List[Optional[float]]] = None,
        spread: Literal["wide", "tight", "custom"] = "wide",
        **backend_kwargs,
    ):
        if self.step_mode:
            await self.create_protocol(f"单点动作{time.time()}")
            await super().aspirate(
                resources,
                vols,
                use_channels,
                flow_rates,
                offsets,
                liquid_height,
                blow_out_air_volume,
                spread,
                **backend_kwargs,
            )
            await self.run_protocol()
        else:
            return await super().aspirate(
                resources,
                vols,
                use_channels,
                flow_rates,
                offsets,
                liquid_height,
                blow_out_air_volume,
                spread,
                **backend_kwargs,
            )

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Swap if/else branches ([`swap-if-else-branches`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/swap-if-else-branches/))
- Remove unnecessary else after guard condition ([`remove-unnecessary-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-else/))
</issue_to_address>

### Comment 4
<location> `unilabos/devices/liquid_handling/prcxi/prcxi.py:417-422` </location>
<code_context>
    async def drop_tips(
        self,
        tip_spots: Sequence[Union[TipSpot, Trash]],
        use_channels: Optional[List[int]] = None,
        offsets: Optional[List[Coordinate]] = None,
        allow_nonzero_volume: bool = False,
        **backend_kwargs,
    ):
        if self.step_mode:
            await self.create_protocol(f"单点动作{time.time()}")
            await super().drop_tips(tip_spots, use_channels, offsets, allow_nonzero_volume, **backend_kwargs)
            await self.run_protocol()
        else:
            return await super().drop_tips(tip_spots, use_channels, offsets, allow_nonzero_volume, **backend_kwargs)

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Swap if/else branches ([`swap-if-else-branches`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/swap-if-else-branches/))
- Remove unnecessary else after guard condition ([`remove-unnecessary-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-else/))
</issue_to_address>

### Comment 5
<location> `unilabos/devices/liquid_handling/prcxi/prcxi.py:436` </location>
<code_context>
    async def dispense(
        self,
        resources: Sequence[Container],
        vols: List[float],
        use_channels: Optional[List[int]] = None,
        flow_rates: Optional[List[Optional[float]]] = None,
        offsets: Optional[List[Coordinate]] = None,
        liquid_height: Optional[List[Optional[float]]] = None,
        blow_out_air_volume: Optional[List[Optional[float]]] = None,
        spread: Literal["wide", "tight", "custom"] = "wide",
        **backend_kwargs,
    ):
        if self.step_mode:
            await self.create_protocol(f"单点动作{time.time()}")
            await super().dispense(
                resources,
                vols,
                use_channels,
                flow_rates,
                offsets,
                liquid_height,
                blow_out_air_volume,
                spread,
                **backend_kwargs,
            )
            await self.run_protocol()
        else:
            return await super().dispense(
                resources,
                vols,
                use_channels,
                flow_rates,
                offsets,
                liquid_height,
                blow_out_air_volume,
                spread,
                **backend_kwargs,
            )

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Swap if/else branches ([`swap-if-else-branches`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/swap-if-else-branches/))
- Remove unnecessary else after guard condition ([`remove-unnecessary-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-else/))
</issue_to_address>

### Comment 6
<location> `unilabos/devices/liquid_handling/prcxi/prcxi.py:470-475` </location>
<code_context>
    async def discard_tips(
        self,
        use_channels: Optional[List[int]] = None,
        allow_nonzero_volume: bool = True,
        offsets: Optional[List[Coordinate]] = None,
        **backend_kwargs,
    ):
        if self.step_mode:
            await self.create_protocol(f"单点动作{time.time()}")
            await super().discard_tips(use_channels, allow_nonzero_volume, offsets, **backend_kwargs)
            await self.run_protocol()
        else:
            return await super().discard_tips(use_channels, allow_nonzero_volume, offsets, **backend_kwargs)

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Swap if/else branches ([`swap-if-else-branches`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/swap-if-else-branches/))
- Remove unnecessary else after guard condition ([`remove-unnecessary-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-else/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +342 to +347
if self.step_mode:
await self.create_protocol(f"单点动作{time.time()}")
await self._unilabos_backend.mix(
targets, mix_time, mix_vol, height_to_bottom, offsets, mix_rate, none_keys
)
await self.run_protocol()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Mix method now has different return behavior in step_mode.

Returning a value in both branches will help prevent unexpected behavior for callers relying on a consistent method interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant