Test data

Unit tests need small, real sets of reads that anyone can rebuild. Osteosarc makes them three ways: a quick BAM of the reads around some variants; a bundle you make for your library, with a balanced set of reads at each variant, every record pinned; and openvax-v2, the bundle the OpenVax libraries share. A test reads a bundle's member with one call, osteosarc.bundle_file(bundle, member).

A quick test BAM

osteosarc reads rna-seq/reprocessed/BG003082/BG003082.Aligned.sortedByCoord.out.md.bam --variant DYNC1H1-chr14-101980529 --padding 100 --to test-data

This streams just those reads from the public BAM and saves them, with an index, as test-data/BG003082.Aligned.sortedByCoord.out.md.DYNC1H1-chr14-101980529.bam: the BAM's name, then the variant. Give a sample ID instead of a file to do the same for each of that sample's BAMs:

osteosarc reads T0_tumor --assay rna-seq --variant DYNC1H1-chr14-101980529 --padding 100 --to test-data

In Python, data.extract_reads(file, variants=..., to="test-data") does the same. See reads for regions, filters and mates.

Make a bundle

Name the BAMs and the variants (or SVs):

osteosarc test-data make dync1h1 rna-seq/reprocessed/BG003082/BG003082.Aligned.sortedByCoord.out.md.bam --variant DYNC1H1-chr14-101980529

This streams the reads around the variant, keeps a balanced set of them (see how reads are chosen), and writes a verified bundle to the new folder dync1h1, about 100 KB. Each member is one target in one BAM, named like the files osteosarc reads --to writes: BG003082.Aligned.sortedByCoord.out.md.DYNC1H1-chr14-101980529. A sample ID stands for its indexed BAMs, narrowed with --assay or --platform; repeat --variant, and use --sv for an SV candidate or fusion, as in osteosarc test-data make gabbr1 T2_tumor --assay wgs --sv GABBR1-SLC29A1. Commit the folder beside your tests, or make it in CI.

In Python:

from osteosarc import Dataset

data = Dataset.open(offline=False)
data.make_bundle(
    "dync1h1",
    variants=["DYNC1H1-chr14-101980529"],
    files=["rna-seq/reprocessed/BG003082/BG003082.Aligned.sortedByCoord.out.md.bam"],
)

Variants can also be data.variants(...), and files can be samples, as in data.samples["T0_tumor"]; caps change how many reads of each kind are kept.

Use a bundle in tests

import osteosarc

bam = osteosarc.bundle_file("dync1h1", "BG003082.Aligned.sortedByCoord.out.md.DYNC1H1-chr14-101980529")

That's an indexed BAM of exactly the member's records, exported once into the cache, read-only, and reused, offline, by every later test run; format="sam" gives SAM text. The bundle can be a folder or the name of a published bundle, such as openvax-v2. An unknown member's error suggests close names, and osteosarc test-data list dync1h1 lists them all. To write members into a folder instead, use osteosarc test-data export.

Shared test data

To carve out a library's members without reacquiring its source BAMs:

osteosarc test-data make isovar-subset --from openvax-v2 --member isovar/

The Python equivalent is osteosarc.subset_bundle(bundle, members, destination); members contains exact names, whereas the CLI accepts prefixes. This keeps the original source headers, acquisition receipts, selection outcomes and record multiplicities, and pins the parent manifest checksum. Empty, unresolved and omitted members retain their status. A local bundle needs no network access.

New fusion requests use sv-regressions-v2. Its GABBR1--SLC29A1 and OTUD7A--FMN1 breakends retain right/right; PARD3B--CDKN2B retains left/right. The versioned v1 panel and already published bundles retain their original metadata.

Historical retrieval cases

The 49 shared-v1 cases are small indexed-retrieval examples. They use five BAM reference dictionaries and cannot all be merged and queried under one genome build or contig naming convention. Their variant_id is the catalogue identity; it is not necessarily the coordinate in the BAM. Read the reviewed metadata with osteosarc.retrieval.load_retrieval_cases(), query each original BAM using its query_region, and use assert_compatible_retrieval_cases(cases) before a raw merge. Matching dictionaries establish coordinate compatibility, not sample independence.

Case Catalogue position Native BAM query Nuclear assembly Mitochondrial reference
44, MT-ND5 / Tempus chrM:12994 MT:12994 GRCh37 rCRS, 16,569 bases; validation uses hg38 chrM
45, MT-ND5 / CeGaT chrM:12994 chrM:12995 GRCh37 UCSC hg19 chrM, 16,571 bases
48, NR2F2 / CeGaT chr15:96332299 chr15:96875528 GRCh37 Not applicable

Positions in this table are one-based; query_region is zero-based and half-open. The new native-retrieval-v1 metadata preserves the original cases, validation receipts and BAM checksums. annotate_retrieval_manifest(manifest, directory) reproduces it from an existing export, checking every asset and native indexed query. No BAM or historical manifest is silently rewritten or lifted over.

Comprehensive corpus

For the comprehensive vaccine/RNA candidate, see the frozen corpus and build instructions. It includes the union of all vaccine membership sources (51 variants in the 2026-09-28 snapshot), the full per-BAM support matrix, RNA-negative DNA-positive SNV controls, frameshifts and structural candidates. Its local openvax-v3 build preserves openvax-v2's library fixtures; it is not yet a published bundle.

Isovar, Topiary, Varcode and Vaxrank can all test against one bundle of reads, openvax-v2, chosen once, here. It holds:

  • reads at every variant on the site, plus other alleles the libraries test, such as MAP2's allele before its correction and mitochondrial alleles on GRCh37;
  • reads that join the breakends of all 7 RNA fusions and 8 of 14 DNA SVs (the other six have unresolved breakends or no joining reads in these BAMs, and stay listed, empty); each SV says which side of each breakend it keeps, where that's known, and DLG5's deletion gives the junction esvee resolved, with its 24-base insertion;
  • the split alignments of the reads it keeps (the other records their SA tags name), except in pileups;
  • every read in the libraries' test files, record for record, as openvax-v1 had them.

Unlike openvax-v1, it leaves out mates with no position of their own, which none of the libraries' reads need, so it's quicker to rebuild. openvax-v1 stays published, with the same members.

bam = osteosarc.bundle_file("openvax-v2", "IPISRC044_tumor_T2_ucla.redux.DYNC1H1-chr14-101980529")

The first use downloads the bundle (28 MB), checks it against checksums that ship with osteosarc, and keeps it in the cache; later uses work offline, and offline=True makes sure they do. Members are named the BAM's name, then the target; a library's own test files keep their names, such as isovar/chimeric/osteosarc-ont.sam. From the command line, osteosarc test-data list openvax-v2 lists the members and osteosarc test-data export openvax-v2 DIR --member NAME writes them into a folder.

To check that your library's copies match, list them in a JSON file that maps member names to your files, with paths relative to that JSON file: a BAM, SAM or SAM.gz path, or, for SAM lines kept inside JSON, {"json": path, "pointer": "/path/to/lines"}. Every SAM line under the pointer counts, whatever fields hold it, such as Isovar's sam and partner_sam; leave the pointer out for the whole file. Then:

osteosarc test-data check openvax-v2 fixtures.json

Records are compared as SAM text, repeats included, so formats and headers don't matter. The command fails if any file differs.

How reads are chosen

Bundles you make and openvax-v2 choose reads the same way (openvax-v2 also adds split alignments). At each small variant, osteosarc sorts every template (a read with its mate) by what it shows across the allele: alt, ref, something else, or nothing, when it doesn't span it. Indels are judged across any repeat they could slide within. It keeps up to 20 alt, 10 ref, 5 other and 2 uncallable templates, in an order fixed by a hash of each read's group and name, plus the two lowest-quality alt templates. At a fusion or SV it keeps up to 50 templates with aligned bases within 1 kb of every breakend that join them: split and chimeric reads, pairs the aligner didn't call proper, and reads spliced or deleted exactly from one breakend to another. A read that simply runs across the breakends, a proper pair on either side, or a read spliced between exons that merely lie near them doesn't count. A kept template keeps all its records that were read (not a mate with no position, unless you ask). Each record is pinned by checksum, so rebuilding gives the same bytes, and any change upstream fails loudly. openvax-v2's reads come from the T2 tumor RNA-seq and WGS BAMs for every target, from any BAM a library already uses there, and from each RNA BAM the SV candidates saw a junction in; a bundle you make reads only the BAMs you name. Leaving out mates with no position of their own (reads) makes an RNA-seq BAM much quicker to read. make_bundle(..., unplaced_mates=True), or test-data make --unplaced-mates, keeps them.

Making the next version. As the libraries move their test reads here and delete their own copies, each new version carries their members forward from the one before, pinned by checksum. To make openvax-v3, copy osteosarc/data/bundles/openvax-v2.spec.json to openvax-v3.spec.json, set its id to openvax-v3 (and its snapshot or targets, if they change), then:

python scripts/shared_test_data/build.py openvax-v3 build --carry openvax-v2

The build finds those records in the public BAMs again, chooses the rest, builds the bundle and packs it for a GitHub release. A library that needs different reads gives a fresh list with --required, which replaces everything it had. In a spec's selection, unplaced_mates false leaves out mates with no position, except where a library's reads include one, and supplementary true adds the split alignments of the reads kept.

openvax-v1 came from the libraries' committed test files: the required_isovar.py, required_topiary.py, required_vaxrank.py and required_varcode.py scripts in scripts/shared_test_data list their SAM lines (for Varcode, the read names behind its junctions) at the revision you give, and openvax-v1 as the revision means the commits it was built from. A published release record is never replaced by a rebuild.

Recipes

For anything make doesn't cover (your own alleles or regions, named reads, other selection rules), write a recipe: the JSON a bundle is built from. Every bundle keeps its own as recipe.json, so one you made is a good start. Then:

osteosarc test-data make bundle --recipe recipe.json
osteosarc --offline test-data make bundle --recipe recipe.json --source rna=archive.bam

The second uses a BAM you already have for the recipe's source rna. In Python, generate_bundle(recipe, "bundle", dataset=data) does the same. Any snapshot will do that lists each BAM as the recipe pins it (the same URL, key, size and modification time): with no --snapshot, osteosarc uses the recipe's own if you have it, else your newest.

A recipe has targets (what each piece of test data is about), sources (the BAMs its reads come from) and members (one target in one source, with a rule for picking reads). Osteosarc only follows the rule: deciding which reads support an allele is up to the library that uses them, and a member's reads are never an estimate of VAF.

targets:   small_variant   one-based position, ref and alt, and where it came from
           sv              two or more breakends (zero-based, with orientation + / - / null,
                           and the side each keeps, left or right, if known), and any
                           sequence inserted at the junction
           fixture         one of a library's test files, with a description
           unresolved      a reason; gets no reads
sources:   identity        which file: {"key": ...} or {"url": ...}
           assembly, sample, library, product   (null when unknown)
members:   target, source, regions (zero-based, half-open), and a policy:
           regional        reads overlapping the regions, optionally capped
           exact           specific records, by checksum and count, each with reasons
           witnesses       named reads, each with a reason
           stratified      named reads, plus a reproducible sample of others by group
           empty, omitted  nothing, on purpose (omitted says why)

Named reads give their read group and name (and optionally which mate), a reason, and the tool that chose them; they must be present unless marked optional. Caps sample the optional reads reproducibly from the policy's seed, whatever the read order. Identical records keep their repeats. validate_recipe(recipe) checks a recipe before any reads are touched.

Each member ends up selected, truncated, empty, unresolved or omitted, and the manifest gives the reason each record was kept.

Mates and split reads. A member can also keep the mates and split alignments of its reads, when its source was fetched with mate and split-read recovery (reads). A recovery that timed out is recorded as incomplete: finding no reads then isn't evidence that there are none.

Records. Bundles identify each record by a checksum of its stored bytes, so two BAMs match only when they hold the same records the same number of times, whatever their compression or order.

Bundles

A bundle holds each source's records once, as indexed BAMs, with the original headers, the recipe, what was fetched, file checksums, and every member's records and reasons. Verifying checks every file, record and index offline; give the manifest's checksum when using someone else's bundle, since without it the check shows the bundle is intact but not who made it. Exporting writes one sorted, indexed BAM per member (or SAM) into any folder, named after the member; empty members become valid empty BAMs, and a file already there with different contents is never replaced. A bundle's size limit is 64 MiB unless you set another. Installing osteosarc never downloads data.

Target lists

load_panel(name) returns a shipped list of targets to build recipes from:

Name Holds
vaccine-loci-v1 The vaccine target alleles
sv-regressions-v1 Three RNA fusions and five candidate SVs used in regression tests
sv-candidates-v1 All 637 SV candidates

How the libraries use it

Osteosarc fetches, selects, packs and checks reads; each library keeps its own science: which variants it tests, how it reads alleles, and what results it expects. Osteosarc never imports Isovar, Topiary or Vaxrank. All four rely on openvax-v1 for their Sid reads (openvax-v2 has the same members), and keep only their non-read test data (variants, expected results, recipes):

Library From openvax-v1
Isovar (1.39.0) Every Sid read file, exported under its original name
Topiary (5.75.0) 14 read files; its rearrangement fixtures are checked against their members
Vaxrank (3.26.0) Read cohorts, rebuilt byte for byte from members with its packaged recipe
Varcode (10.5.8) No read files; its 3 junction fixtures are checked against their members

A change to a library's expected results needs its own review, even when its test data rebuilds cleanly. The tests in tests/test_fixtures.py and tests/test_bundles.py build recipes and BAMs offline, and cover each rule above.