Skip to content

fix: Tecan Magellan - support compact Magellan Pro 7.5 exports - #1258

Merged
tamargrey merged 3 commits into
mainfrom
tamar-fix-magellan-pro-signed
Aug 25, 2026
Merged

fix: Tecan Magellan - support compact Magellan Pro 7.5 exports#1258
tamargrey merged 3 commits into
mainfrom
tamar-fix-magellan-pro-signed

Conversation

@tamargrey

Copy link
Copy Markdown
Collaborator

Summary

Magellan Pro v7.5 can produce a compact measurement metadata block that omits the Unit:, Number of flashes, and trailing Meas. temperature fields expected by the Tecan Magellan parser. The parser consequently consumed the remaining metadata and raised Expected non-null value.

This change:

  • Supports the compact v7.5 metadata layout without changing the existing parsing path.
  • Infers Absorbance mode from the measurement section.
  • Maps the measurement label and wavelength normally.
  • Recovers measurement temperature from the table’s pre-data temperature row.
  • Uses the bracketed plate description as the plate identifier.
  • Omits number of averages when it is unavailable.
  • Preserves plate description, range, and action as custom information.
  • Adds a sanitized regression workbook and expected ASM output.

Testing

  • Confirmed the original customer workbook converts all 96 wells successfully.
  • Ran hatch run test tests/parsers/tecan_magellan/to_allotrope_test.py.
  • Ran hatch run lint.

@tamargrey
tamargrey merged commit 82a79a1 into main Aug 25, 2026
9 checks passed
@tamargrey
tamargrey deleted the tamar-fix-magellan-pro-signed branch August 25, 2026 17:01
nathan-stender added a commit that referenced this pull request Aug 25, 2026
## Problem

The OpenLab CDS help docs tell users they may compress their `.rslt`
result set before dropping it in the File Watched directory:

> (Optional) Users can additionally compress their RSLT file to a zipped
file within their File Watched directory if they choose. The Agilent
OpenLabs CDS connector is able to process this format along with the
standard RSLT file export.

That was never true in allotropy. `SUPPORTED_EXTENSIONS = "rslt"` meant
a `.zip` upload was rejected before the parser saw any bytes:

```
EXPLICIT .zip FAIL: AllotropeConversionError Unsupported file extension 'zip' for parser 'Agilent OpenLab CDS', expected one of '['rslt']'.
DISCOVER .zip FAIL: AllotropeVendorNotFoundError No vendor could be identified for file with extension '.zip'.
```

The `.rslt` files we already accept *are* zip archives, so the contents
were never the problem — only the extension gate and, for one shape, an
extra layer of nesting.

## Changes

- **Accept the `zip` extension** in addition to `rslt`.
- **Replace the unconditional `sniff()` with a content check.** `return
True` was only safe while `.rslt` was exclusive to this parser. With
`zip` added, the old sniff would have claimed every `.zip` and stolen
files from Cytiva Unicorn (`"zip"`) and AppBio Absolute Q (`"csv,zip"`).
It now looks for an ACAML member.
- **Unwrap extra layers of compression** (`open_result_set`).
Compressing the `.rslt` *folder* yields an archive whose members are the
result set; compressing the `.rslt` *file* yields an archive whose only
member is another archive. Both now work.
- **Clearer errors.** A non-result-set archive raised bare
`StopIteration`, and an uncompressed file raised `BadZipFile`. Both now
raise `AllotropeConversionError` with a message, since malformed
archives are newly reachable input.

## Verification

Tested against archives produced by the real macOS Finder "Compress"
path (`ditto -c -k --sequesterRsrc --keepParent`, which adds the
`__MACOSX` resource forks that hand-built zips lack):

```
finder_folder.zip           parse=IDENTICAL  discover=AGILENT_OPENLAB_CDS
finder_folder_renamed.rslt  parse=IDENTICAL  discover=AGILENT_OPENLAB_CDS
finder_file.zip             parse=IDENTICAL  discover=AGILENT_OPENLAB_CDS
finder_file_renamed.rslt    parse=IDENTICAL  discover=AGILENT_OPENLAB_CDS
```

IDENTICAL = byte-equal ASM against the existing fixture (~192KB of
output, data cubes included), ignoring `file name` / `UNC path`, which
legitimately differ.

- Full suite: **1496 passed**, `hatch run lint` clean.
- `tests/discover_vendor_test.py` passes, confirming no cross-vendor
sniff regressions.

## Notes for reviewers

- The new test **synthesizes** each compression shape from the existing
`.rslt` fixture rather than checking in new files, avoiding ~3MB of
duplicate test data.
- Mutation-tested the new test (`MAX_ARCHIVE_NESTING` 3 → 1): exactly
the two nested cases fail, so the assertions aren't vacuous.
- Nesting is bounded at 3 archive levels. Beyond that a user would have
to compress three or more times, which the docs don't describe.
- **Still open, outside this repo:** if the connector zips the watched
folder itself, a pre-zipped upload may not reach allotropy to benefit
from this. The `Unsupported file extension 'zip'` error above is the
signal that it did.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

## Update: rebased on main (2026-08-25)

Merged latest `main`, which now contains #1254 (OpenLab CDS memory +
optional ACAML metadata) and #1258. Resolving the overlap in
`agilent_openlab_cds_decoder.py` changed two things in this PR:

- **`.sqx` stays optional.** This PR originally routed the sequence-file
lookup through a new `_get_first_matching_filename` helper that raised
when `.sqx` was absent. #1254 made `.sqx` optional (falling back to the
ACAML method documents), and the `Sirius` fixture it added has no
`.sqx`, so main's behaviour wins. The helper became dead code and was
removed — the "clearer errors" bullet above now applies only to the
ACAML and non-archive cases, both still covered by tests.
- **`sniff` no longer copies the whole file into memory.** It now calls
`get_seekable_bytes_stream()` instead of `get_bytes_stream()`. Result
sets reach ~1GB, and `get_bytes_stream()` buffers the entire upload —
using it in `sniff` would have silently undone #1254's memory fix on
every discovery pass.

Also fixed the `Quality Checks` failure that predated the merge:
`from_file` returns `Mapping[str, Any]`, not `dict[str, Any]`, so the
`expected` fixture's annotation failed mypy.

Re-verified after the merge: **1498 passed** (up from 1496 — main added
the two `Sirius` fixture cases), `hatch run lint` clean,
`tests/discover_vendor_test.py` green.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
nathan-stender pushed a commit that referenced this pull request Aug 25, 2026
### Fixed

- Agilent OpenLab CDS - accept zipped result sets (#1252)
- Tecan Magellan - support compact Magellan Pro 7.5 exports (#1258)
- Cut OpenLab CDS peak memory 4x and handle optional ACAML metadata
(#1254)
- Report custom information keys in a stable order (#1249)
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.

2 participants