Skip to content

Release 6.3.0 - #84

Merged
rhshah merged 5 commits into
mainfrom
release/6.3.0
Aug 7, 2026
Merged

Release 6.3.0#84
rhshah merged 5 commits into
mainfrom
release/6.3.0

Conversation

@rhshah

@rhshah rhshah commented Aug 7, 2026

Copy link
Copy Markdown
Member

Release PR for 6.3.0. Merge to main, then tag 6.3.0 — bare, no v prefix.

What's in it

observe_molecules() takes the settings it actually uses

It reads seven settings; GbcmsDnaConfig requires four, and they do not overlap at all. Worse, variant_file and reference_fasta must name files that exist on disk, bam_files is a dict, and output is an OutputConfig — so changing one read filter meant fabricating paths that read as load-bearing and are not.

New keyword arguments filters, quality, alignment, umi_tag, threads, apply_baq, library_type. config= keeps working; an individual argument overrides the matching field.

umi_tag uses a sentinel because None is a real choice there — "group by read pair, not UMI family" — so passing it overrides config rather than being read as an omission. Without that, a caller could silently change what counts as one molecule.

🐛 --filter-secondary / --filter-supplementary now do something

Both previously could not change any output: an unconditional skip discarded those records before fragment evidence, regardless of the flags. Audited across all six read filters on real MSK-ACCESS data — these were the only two that were inert (duplicates, improper_pair, indel all worked).

The fix draws the line at read-level vs fragment-level, not on/off:

default (on) opted out (off)
read-level dp/rd/ad excluded still excluded — a supplementary is the same physical read as its primary
fragment dpf/rdf/adf excluded admitted
observation export excluded admitted

That split is what the existing tests demand: test_supplementary_shared_qname_not_double_counted builds a primary and its supplementary over the same locus and asserts dp == 1. Fragments are immune either way — hash_molecule keys on QNAME, so the two collapse into one.

Fixes concretely: a locus reached only by a supplementary segment reported dpf=0 — not a filtered read but a wrong answer. That is what made a molecule spanning a large deletion invisible to cross-locus phasing.

Compatibility

Default runs are byte-identical. Both filters still default to on. Verified on real data: total depth unchanged at 1202 across 40 loci, and binned↔legacy parity green (18 tests).

New behavior to be aware of when opting out: an admitted supplementary raises dpf while leaving dp unchanged, so the two stop moving together. Documented in the read-filters page, the observations page, and the CLI help.

Minor rather than patch: new public arguments, and a filter that did nothing now does something.

Gate

Check Result
pytest 421 passed
cargo test 215 passed
cargo clippy --all-targets -D warnings clean
mypy / ruff check . / black --check clean
mkdocs build --strict 1 warning, unchanged from develop (pre-existing)
Runtime version after release rebuild 6.3.0
Stale 6.2.0 references none (outside historical prose in the release guide)

🤖 Generated with Claude Code

rhshah and others added 5 commits August 6, 2026 22:28
The CI Release Pipeline section claimed wheels for Linux x86_64 + aarch64,
macOS x86_64 + arm64, and Windows. release.yml has only `linux` and `sdist`
jobs, and 6.2.0 published exactly one wheel --
gbcms-6.2.0-cp311-cp311-manylinux_2_34_x86_64.whl -- plus the sdist.

This matters beyond tidiness: everyone not on cp311 manylinux x86_64 installs
from the sdist and therefore needs a Rust toolchain. That is all macOS users
(Intel and Apple Silicon) and every Python other than 3.11. The old text would
have you tell them `pip install gbcms` just works.

Broadening the matrix is a change to release.yml, not a docs fix. Until
someone makes that call, this section should say what actually ships.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…uses

The only way to change a setting was to build a whole GbcmsDnaConfig, whose
four required fields -- variant_file, bam_files, reference_fasta, output --
have ZERO overlap with the seven this entry point reads. Two of them must name
files that exist on disk, bam_files is a dict, and output is an OutputConfig.
So adjusting one read filter meant fabricating an output directory and a
variant path that are never touched: paths that read as load-bearing and are
not, which is how the next person gets misled.

Adds filters/quality/alignment/umi_tag/threads/apply_baq/library_type as
keyword arguments. config= keeps working; an individual argument overrides the
matching config field, so a pipeline config can be reused and adjusted.

Found by the first real consumer (mulligan) needing a non-default read filter.
Tests written inside gbcms could not have found it -- they never had to
construct the config a caller does.

umi_tag is the one argument where None is a choice rather than an absence: it
means group by read pair, not UMI family. It uses a sentinel so that passing
umi_tag=None alongside a config that sets one OVERRIDES it. Treating None as
"not supplied" there would silently inherit the config's tag and change what
counts as a molecule, with nothing to indicate it.

Also corrects advice this module previously gave. The filters docstring told
callers to set supplementary=False for cross-locus phasing. That does nothing:
supplementary and secondary alignments are dropped before fragment evidence is
built, so they never produce an observation. Measured across all six read
filters on real data -- duplicates, improper_pair and indel work as documented;
those two cannot change the result at all (#82).

The new tests use `duplicates` as the discriminating filter for exactly that
reason: `supplementary` would have been the intuitive choice and would have
made the test pass vacuously.

Tests: 420 passed (+11). ruff, black, mypy, clippy -D warnings, cargo test
(215) all clean. Docs build warnings unchanged (1, pre-existing).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n turned off

Closes #82.

Both flags previously could not change ANY output. An unconditional skip in
the counting loop discarded those records before fragment evidence, "regardless
of the user filter flags" -- redundant when the flags were on (such records
never reach the cache) and defeating them when off. Measured across all six
read filters on real data, these were the only two that did nothing;
duplicates, improper_pair and indel all worked.

Default runs are byte-identical. Both still default to on, so the records never
reach the cache: total depth unchanged at 1202 across 40 real loci, and
binned<->legacy parity holds.

Turning a filter off now admits those records to FRAGMENT-level evidence
(dpf/rdf/adf) and to the observation export, while read-level dp/rd/ad keeps
its promise and excludes them.

That split is not a compromise, it is the correct line, and the existing tests
say so. A supplementary shares a QNAME with its primary and is the same
physical read, so counting both toward dp reports depth 2 where one read
exists -- test_supplementary_shared_qname_not_double_counted pins exactly that
case, and two more tests assert the same read-level contract. Honoring the flag
naively broke all three. Fragments are immune either way: hash_molecule keys on
QNAME, so a primary and its supplementary collapse into one FragmentEvidence,
which is why admitting them there cannot double-count.

What this fixes concretely: a locus reached ONLY by a supplementary segment
reported dpf=0 -- not a filtered read but a wrong answer, since a molecule
demonstrably covers it. That is what made a molecule spanning a large deletion
invisible to cross-locus phasing. A new test covers that arrangement; it is the
other half of the double-count test's contract.

New consequence, documented in the read-filters page, the observations page and
the CLI help: with the filter off an admitted supplementary raises dpf while
leaving dp unchanged, so the two stop moving together. The observation
reconciliation invariant (rows == dpf) is verified under both settings.

Also documents the improper-pair foot-gun found while auditing: MSK-ACCESS v1
alignments are 100% paired but 0% PROPER_PAIR, so enabling that filter there
discards every read.

Tests: 421 passed (+1). clippy -D warnings, cargo test (215), mypy, ruff, black,
mkdocs --strict all clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(observations): configurable observe_molecules; fix the secondary/supplementary filters (#82)
Bumps all 11 version references and dates the CHANGELOG entry.

Contents:
  - observe_molecules() takes the settings it actually uses, instead of
    requiring a GbcmsDnaConfig whose four required fields it never reads.
  - --filter-secondary / --filter-supplementary now change the result when
    turned off. They previously could not change any output. Default runs are
    byte-identical; opting out admits those records to fragment-level evidence
    and the observation export, while read-level dp/rd/ad still excludes them.

Minor rather than patch: new public arguments, and a filter that did nothing
now does something. Default behavior is unchanged either way -- verified on
real data (total depth 1202 across 40 loci, unmoved) and by the parity suite.

Gate: 421 python tests, 215 rust tests, clippy -D warnings, mypy, ruff, black,
mkdocs --strict all clean. Runtime version confirmed 6.3.0 after a release
rebuild. No stale 6.2.0 references outside historical prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rhshah
rhshah merged commit 3ca2787 into main Aug 7, 2026
11 checks passed
@rhshah
rhshah deleted the release/6.3.0 branch August 7, 2026 18:52
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