Skip to content

Convert plan trims to each patch coordinate's own units - #838

Merged
d-chambers merged 2 commits into
devfrom
plan-trim-units
Aug 10, 2026
Merged

Convert plan trims to each patch coordinate's own units#838
d-chambers merged 2 commits into
devfrom
plan-trim-units

Conversation

@d-chambers

@d-chambers d-chambers commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Description

Spool.select and Spool.chunk disagree about what a plan trim means on a coordinate whose units are not the canonical SI unit. select is correct; chunk silently drops data.

The index stores coordinate summaries in canonical SI, but never rewrites the patch — the patch keeps its native units. docs/notes/spool_selection.qmd makes that the contract:

Numeric coordinate summaries are stored in canonical SI units, so bare numeric range bounds are interpreted as canonical SI regardless of a patch's native coordinate units […] The exact per-patch trim defers its representation until each patch is known.

select honors the second sentence through _CanonicalRange.for_patch_coord, which converts the range once the patch's own unit is known. The plan path had no equivalent step: it handed SI magnitudes straight to patch.select(...) as bare numbers, so a trim of 20 to 60 cut 20–60 feet from a coordinate stored in feet rather than the 20–60 metres the plan described. Each piece covered the wrong physical interval and the samples between pieces were dropped.

import dascore as dc

spool = dc.spool([dc.get_example_patch().convert_units(distance="ft")])  # 300 channels
out = spool.chunk(distance=100, keep_partial=True)
sum(x.shape[x.get_axis("distance")] for x in out)
# before: 61      after: 300

This adds that deferred conversion to the assembler, reusing _CanonicalRange rather than introducing a second implementation. Unitless coordinates keep their bare canonical magnitudes, which is the documented policy for them.

Scope of exposure

No IO reader produces non-SI coordinate units, so this is not reachable by reading a file. It requires an explicit convert_units / set_units, which is public and documented (docs/tutorial/concepts.qmd), and which the selection spec already uses as its own worked example (docs/notes/spool_selection.qmd, plus the select fixture in tests/test_core/test_spool_select_spec.py). So: a real contract violation and silent data loss, but on a path a user has to opt into rather than one every archive hits.

Found while building Spool.unselect, which inherits the same plan machinery. Split out so it lands on its own merits and chunk gets it independently.

Tests

TestNonSIUnitTrim in tests/test_core/test_patch_chunk.py. Three of the five fail without the change (sample count, per-piece agreement with the plan, physical span of a piece); the other two are SI and unitless controls that must keep passing either way.

Changelog

none

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes.
  • documented the new feature with docstrings and/or appropriate doc page.
  • included tests. See testing guidelines.
  • added the "ready_for_review" tag once the PR is ready to be reviewed.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed chunking for coordinates using non-SI units, such as feet.
    • Preserved samples and ensured chunk boundaries align correctly across unit systems.
    • Improved numeric range selection for unit-bearing and unitless coordinates.
  • Documentation

    • Added an unreleased changelog entry describing the coordinate-unit chunking fix.

@d-chambers d-chambers added the ready_for_review PR is ready for review label Aug 8, 2026
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92c35927-2bf6-4b82-b155-1e657e9b1856

📥 Commits

Reviewing files that changed from the base of the PR and between 017bb54 and 2e7ca84.

📒 Files selected for processing (7)
  • dascore/io/index/catalog.py
  • dascore/utils/misc.py
  • dascore/utils/patch_assembly.py
  • docs/changelog.qmd
  • tests/test_core/test_patch_chunk.py
  • tests/test_io/test_index/test_catalog.py
  • tests/test_io/test_index/test_index_edge_cases.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/changelog.qmd
  • dascore/utils/patch_assembly.py
  • tests/test_core/test_patch_chunk.py

📝 Walkthrough

Walkthrough

The patch loader converts canonical SI trim bounds into each coordinate’s native units before selection. Shared range utilities now support this conversion for catalog residual selection. Tests cover feet, SI, and unitless coordinates.

Changes

Non-SI coordinate trimming

Layer / File(s) Summary
Canonical range handling and catalog integration
dascore/utils/misc.py, dascore/io/index/catalog.py, tests/test_io/test_index/*
Shared utilities canonicalize numeric ranges and express them for target coordinates. Catalog residual selection and related imports use the shared utilities.
Native-unit trim conversion
dascore/utils/patch_assembly.py
_as_native_units converts coordinate-only trim bounds before patch selection and preserves non-coordinate or unitless values.
Non-SI trim validation
tests/test_core/test_patch_chunk.py, docs/changelog.qmd
Tests verify feet-based chunking, SI-plan alignment, physical spans, and unchanged SI or unitless behavior. The changelog records the fix.

Possibly related PRs

Suggested labels: bug, patch, spool

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: converting plan trims to each patch coordinate's native units.
Description check ✅ Passed The description explains the problem, scope, implementation, tests, documentation, and checklist status in sufficient detail.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch plan-trim-units

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added patch related to Patch class spool related to Spool class labels Aug 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/test_core/test_patch_chunk.py (1)

910-924: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise trimmed SI and unitless coordinates.

chunk(distance=None) keeps the full source envelope. _load_trimmed_patch then skips Patch.select because the member limits are not modified. These tests cannot detect a regression in _as_native_units for SI or unitless coordinates.

Use a finite chunk size such as distance=100 with keep_partial=True in both tests. Assert complete sample coverage after the trim path runs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_core/test_patch_chunk.py` around lines 910 - 924, Update
test_si_coord_unchanged and test_unitless_coord_unchanged to call spool.chunk
with a finite distance=100 and keep_partial=True instead of distance=None.
Retain the existing total sample-coverage assertions so both tests exercise
_load_trimmed_patch and validate complete coverage after trimming.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/test_core/test_patch_chunk.py`:
- Around line 910-924: Update test_si_coord_unchanged and
test_unitless_coord_unchanged to call spool.chunk with a finite distance=100 and
keep_partial=True instead of distance=None. Retain the existing total
sample-coverage assertions so both tests exercise _load_trimmed_patch and
validate complete coverage after trimming.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2efd9957-d695-4569-9127-4b63ca1ea613

📥 Commits

Reviewing files that changed from the base of the PR and between 99b425f and 326d464.

📒 Files selected for processing (3)
  • dascore/utils/patch_assembly.py
  • docs/changelog.qmd
  • tests/test_core/test_patch_chunk.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 326d464387

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# are valid patch selections.
if select_kwargs := _coord_only_kwargs(patch, source_kwargs):
patch = patch.select(**select_kwargs)
patch = patch.select(**_as_native_units(patch, select_kwargs))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid passing unconverted plan bounds to file readers

For file-backed spools with a non-SI numeric coordinate, the conversion here occurs only after load_patch(kwargs), but PlanResolver._load_member forwards the original canonical-SI bounds through FileResolver to dc.read, and readers such as H5Simple and DASDAE apply those bare bounds while loading. A feet-backed file is therefore first truncated as though meter magnitudes were feet; this later native-unit selection cannot recover the discarded samples and can produce short or empty chunks. The plan trim must be withheld from the read hint, or converted before the reader applies it.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (7865baa) to head (2e7ca84).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##               dev      #838    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files          175       176     +1     
  Lines        18475     19372   +897     
==========================================
+ Hits         18475     19372   +897     
Flag Coverage Δ
network 48.36% <18.33%> (-0.70%) ⬇️
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Coordinate envelopes are stored in the index as canonical SI, so a chunk
plan hands its member trims down as SI magnitudes. Those were applied to
the patch coordinate as bare numbers, which means a trim of 20 to 60 on a
coordinate stored in feet cut 20 to 60 feet rather than the 20 to 60
metres the plan described. Every piece covered the wrong physical
interval and the samples between them were dropped: chunking a 300
channel patch whose distance is in feet returned 61 channels.

Convert at load, where the coordinate's own unit is finally known.
Spool.select already did this through _CanonicalRange; the plan path had
no equivalent step, so the two disagreed about the same physical range on
the same spool. Both now go through one helper, express_range_for_coord,
whose bare_is_si flag names the one way they legitimately differ: a plan
trim's plain numbers are SI magnitudes and must convert, while a residual
selection's plain numbers are sample indices and must not.

Move that helper and the _CanonicalRange machinery it needs from
dascore.io.index.catalog to dascore.utils.misc, alongside the other range
helpers. The assembler lives in utils and would otherwise have to import
private names from io, which is backwards.
Resolves conflicts from dev's PLC0415 enablement, the chunk-by-size
schema work, and the test-import cleanup:

- catalog.py keeps dev's SPOOL_PRIVATE_RENAMES import and drops the
  now-unused module-level get_quantity, since _CanonicalRange moved to
  dascore.utils.misc on this branch.
- misc.py's deferred get_quantity import carries an explicit
  noqa: PLC0415 (dascore.units imports misc at import time).
- The index tests take dev's module-level imports and its
  'is not None' narrowing, retargeted at dascore.utils.misc.
- Both changelog entries are kept.
@coderabbitai coderabbitai Bot added the bug Something isn't working label Aug 10, 2026
@d-chambers
d-chambers merged commit c97545e into dev Aug 10, 2026
31 of 32 checks passed
@d-chambers
d-chambers deleted the plan-trim-units branch August 10, 2026 16:54
@d-chambers d-chambers removed the ready_for_review PR is ready for review label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working patch related to Patch class spool related to Spool class

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant