Add IsovarReadPhasing adapter for RNA-read variant phasing - #183
Merged
Conversation
Exposes IsovarResult.phased_variants_in_supporting_reads through a narrow has_evidence/partners_in_cis interface, suitable as a read- phasing source for varcode's germline-aware effect prediction. Distinct from VarcodeAdapter (which implements the full IsovarAssemblyProvider protocol): IsovarReadPhasing answers only the phasing question and so does not require assembled contigs or mutant transcripts. Closes #182.
…epr__ - Replace the broken docstring example (referenced undefined names) with a doctest-runnable one built on SimpleNamespace, executed via a dedicated `test_docstring_examples_run` test. - Add a module-level "Contract" section spelling out the three attributes the adapter reads from each input result. This is intended as the basis for the matching `ReadPhasingSource` protocol on the varcode side. - Note the symmetry guarantee inherited from `compute_phasing_counts` / `threshold_phased_variant_counts`. - Add `__repr__` for friendlier debugging.
Closed
8 tasks
- Add four integration tests that exercise IsovarReadPhasing through a full run_isovar invocation on the b16 RNA fixture (same data the varcode_adapter tests use). Cover: has_evidence matches num_alt_fragments; partners are a subset of the input set; partners exactly match phased_variants_in_supporting_reads; partners are symmetric. The b16 fixture's partner sets may be empty -- the assertions are structural and pass either way while still catching drop/fabricate/asymmetry regressions in the adapter. - Remove the "last entry wins" sentence from `__init__`'s docstring. It pinned an accidental dict-comprehension detail as a contract; not worth promising.
The b16 fixture has four variants on four different chromosomes, so
`run_isovar` against it never produces non-empty
`phased_variants_in_supporting_reads` -- the b16 tests verify wiring
but not phased-partner behavior end-to-end.
Add two integration tests that drive
`isovar.phasing.annotate_phased_variants` directly with hand-rolled
results sharing alt-read names, then exercise `IsovarReadPhasing`
against the annotated output:
* test_adapter_consistent_with_annotate_phased_variants verifies
the adapter exposes exactly what the upstream pipeline populates,
on a v1<-(r12a,r12b)->v2<-(r23)->v3 topology that v1/v3 don't
directly bridge.
* test_adapter_respects_min_shared_fragments_threshold verifies a
single shared read below threshold yields no partners.
Also relabel the b16 block as a pipeline smoke test and point readers
at the new integration tests for phased-partner coverage.
Builds a synthetic mini-BAM (via MockAlignmentFile + make_pysam_read) with reads spanning an imagined somatic SNV at chr1:4 (T>G) and a nearby germline SNP at chr1:8 (T>A): three reads carry both alts, two carry the somatic alt only, two carry the germline alt only. Walks the full ReadCollector -> annotate_phased_variants -> IsovarReadPhasing stack and verifies the somatic and germline calls are reported as in-cis partners. This is the canonical motivating scenario for openvax/varcode#268's germline-aware effect prediction: a somatic mutation whose codon overlaps a germline polymorphism, where the protein outcome depends on whether the two are in cis -- and only RNA-read co-occurrence disambiguates.
…nterface * Rename test_adapter_respects_min_shared_fragments_threshold to test_threshold_filter_propagates_through_adapter and explain that the adapter is passive here -- the test asserts the threshold propagates rather than being silently undone. * Document in the module Contract that partners_in_cis and has_evidence answer independent questions and are not cross-checked. Note that outputs from run_isovar / annotate_phased_variants never exhibit a has_evidence=False with a non-empty partners_in_cis, since the upstream pipeline only populates partners for variants with shared supporting reads.
This was referenced May 14, 2026
Varcode is dropping Isovar-named identifiers from its public surface (openvax/varcode#378, openvax/varcode#379). Replace the docstring reference to the old `IsovarAssemblyProvider` Protocol with the new `ReadPhasingSource` + `MutantTranscriptSource` split, name the matching consumer (`ReadPhaseResolver`), and point at #184 for the companion heavy-adapter rework that will split `VarcodeAdapter` into two narrower classes. No code change; the adapter's method names (`has_evidence`, `partners_in_cis`) already match the new `ReadPhasingSource` Protocol.
Phrase the relationship as "implements the shape that varcode adopts in #378/#379" rather than "satisfies varcode's ReadPhasingSource Protocol", so a reader who lands on master between isovar#183 merging and varcode#379 merging doesn't see a docstring claiming a varcode symbol that doesn't yet resolve. No behavior change.
iskandr
added a commit
that referenced
this pull request
May 14, 2026
Bump version to 1.6.0 for IsovarReadPhasing (#183)
iskandr
added a commit
to openvax/varcode
that referenced
this pull request
May 14, 2026
* Generalize phasing API; remove Isovar-named identifiers (bump 4.24.0) Closes #378. The phasing Protocol bundled two unrelated concerns (read-phasing + mutant-transcript construction) and named them after Isovar, even though varcode imports nothing isovar-shaped. That made the dependency direction look circular (isovar already declares varcode>=4.15) and forced any non-Isovar source to either lie about its identity or build a wrapper. Split into two narrow Protocols, both fully generic: - ReadPhasingSource: has_evidence(variant) + partners_in_cis(variant). - MutantTranscriptSource: mutant_transcript(variant, transcript). IsovarPhaseResolver renamed to ReadPhaseResolver. Accepts any ReadPhasingSource; routes mutant_transcript() to the wrapped source when it also satisfies MutantTranscriptSource (otherwise returns None silently rather than raising). Source tag flipped from "isovar" to "read_phasing". The actual implementation classes (Isovar's IsovarReadPhasing in openvax/isovar#183; the heavier mutant-transcript adapter tracked in openvax/isovar#184) stay in isovar, where the dependency direction already supports them. Varcode no longer mentions Isovar by name on any public symbol. Hard rename, no deprecation alias. Test suite: tests/test_isovar_phase_resolver.py renamed to tests/test_read_phase_resolver.py with the stub updated to satisfy both new Protocols. test_haplotype_effect.py + test_vcf_phase_resolver.py updated to match. 1188/1190 pass; 2 pre-existing skips. * Rename resolver .source -> .phase_source to disambiguate from Outcome.source; trim Isovar-named examples from docstrings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
isovar/read_phasing.pywithIsovarReadPhasing, a narrow adapter that exposesIsovarResult.phased_variants_in_supporting_readsvia two methods:has_evidence(variant)andpartners_in_cis(variant).ReadPhasingSourceProtocol (Make phasing API generic: drop Isovar-specific names from public surface varcode#378, Generalize phasing API; remove Isovar-named identifiers varcode#379) — no method renames needed.isovar.__init__.Addresses #182. Full closure depends on the companion isovar follow-up (#184), which moves the mutant-transcript half of the existing
VarcodeAdapterinto a separateIsovarMutantTranscriptclass implementing varcode'sMutantTranscriptSourceProtocol. Both halves then plug intovarcode.ReadPhaseResolverdirectly; the issue #182 acceptance test will run end-to-end after that.Design notes
IsovarResult-- no new variant-identification logic.has_evidenceis true iff the variant is in the input set and hasnum_alt_fragments > 0.partners_in_cisreturns a deterministically-ordered tuple (sorted by contig, start, ref, alt) so callers can rely on ordering in snapshot tests.variant,num_alt_fragments,phased_variants_in_supporting_reads), documented in the module's "Contract" section so non-Isovar producers (long-read RNA tools, hand-rolled stubs) can target the same shape.Related upstream work
IsovarAssemblyProviderintoReadPhasingSource+MutantTranscriptSource; renamesIsovarPhaseResolver→ReadPhaseResolver.IsovarReadPhasingplugs straight into the newReadPhaseResolveronce that lands.VarcodeAdapterintoIsovarReadPhasing(this PR's class, unchanged) + newIsovarMutantTranscript. Blocks on varcode#379 merging.Test plan
pytest tests/test_read_phasing.py-- 16 unit + integration tests:IsovarResults: alt-evidence true/false, unknown variants, partners returned/empty, deterministic ordering,__repr__, and adoctest.testmodrunner that executes the module's documented example.run_isovar(the b16 fixture has 4 variants on 4 different chromosomes, so no actual co-phasing — these verify the adapter consumes real Isovar output without errors).isovar.phasing.annotate_phased_variantsdirectly with hand-rolled inputs to exercise the non-empty-partner path: one for a 3-nodev1-v2-v3topology where v1↔v3 isn't directly bridged, one for the upstream threshold propagating through.MockAlignmentFile/make_pysam_readcovering a somatic SNV at chr1:4 and a nearby germline SNP at chr1:8, walks them through the fullReadCollector→annotate_phased_variants→IsovarReadPhasingstack, and asserts the somatic and germline calls are reported as in-cis partners. Canonical motivating scenario for Germline-aware effect prediction (umbrella) varcode#268.pytest tests/test_phasing.py tests/test_varcode_adapter.py-- no regressions in adjacent phasing code.