Skip to content

v3.3.0 release candidate - #31

Merged
wkgardner merged 74 commits into
mainfrom
v3.3.0-rc
Aug 5, 2026
Merged

v3.3.0 release candidate#31
wkgardner merged 74 commits into
mainfrom
v3.3.0-rc

Conversation

@nrminor

@nrminor nrminor commented Jul 8, 2026

Copy link
Copy Markdown
Member

This is a big one. TL;DR: v3.3.0 refactors the entire pipeline around a paradigm henceforth referred to as "query classes," where each query class can be come its own parallelized shard within the nvd workflows.

The idea behind query classes

Before v3.3.0, nvd collapsed all reads into whatever contigs SPAdes gave us. These contigs were then QC'd and then sent into big BLAST searches against Core NT.

This system works well in that the relatively small number of contigs that fall out of each sample's reads put a low bound on the query search space that BLAST has to contend with. Its downside is that it introduces yet another significant risk of sensitivity loss/false negative introduction. If the assembler can't resolve contigs for a given read, it will simply be lost as a potential fragment of interest and will never be searched against Core NT.

If you're working with a clinical sample where most taxa are expected to have good whole-genome representation, this is unlikely to pose a problem. Most reads will come from those well-represented genomes, giving the assembler plenty of grist to collapse down into representative contigs.

But if your library is highly fragmented, many reads will come from sheared genomes that simply don't have the read representation they'd need to resolve into contigs. These genomes, and the taxa they stem from, will thus be lost in the old nvd paradigm. Despite its claims to be tuned for use on sheared environmental samples, this was a major weakness for nvd, and in many cases it led to visible under-sensitivity compared to other pipelines, e.g., nao-mgs.

Version 3.3.0 introduces an elegant solution to this problem and refactors the entirety of the pipeline around that solution. Rather than all queries being contigs, nvd now tracks whether a read resolved into a contig or not, and also whether a read pair was merged into a consensus read. Each of these kinds of queries (hence, "query classes") then becomes its own BLAST input. For a given sample, contigs will be searched in their own BLAST process. But merged reads that didn't resolve into a contig will also get searched in their own BLAST instance, as will single reads. Retaining reads that were not captured in any of the contigs almost entirely restores sensitivity that was lost in the previous nvd paradigm.

Query classes are still experimental and have a downside

This feature remains experimental (gated behind params.experimental == true); running in the old paradigm still works and remains exercised in our end-to-end testing. That said, this new paradigm is transformative enough that we expect to make it the only run mode supported by nvd.

The major downside to searching many more query sequences in BLAST is that you're now searching many more query sequences in BLAST, and thus nvd as a whole has potential to take 1.5 to 2 times longer. To maintain the fastest possible reporting turnaround, we've thus adjusted our LabKey LIMS integration to upload BLAST results as they become available rather than waiting until all query class searches have finished. With this adjustment, contig-based results still upload more or less as quickly as they did pre-v3.3.0. High-sensitivity read results will become available later as their respective searches complete.

Enabling future feature releases

Query classes also come with a variety of sidecar artifacts that give observability into how query sequences are routed into each of the query classes, with summaries provided at various stages of the pipeline where query classes for each sample are handled in parallel. This helps build toward upcoming changes in v3.4 that enhance the information nvd reports to users.

In addition, v3.3.0 brings a significant semantic reorganization of the results directory in hopes of empowering more intuitive exploration of results by users.

Overall, if cv3.2.0 was the "CRUMBS release", v3.3.0 is the "query classes" release. This all builds toward producing a reimagined results reporting system in #46, which will be up next!

@nrminor
nrminor force-pushed the v3.3.0-rc branch 17 times, most recently from ffef836 to 964587d Compare July 14, 2026 22:12
@nrminor
nrminor force-pushed the v3.3.0-rc branch 4 times, most recently from ef95a63 to 5bbfbe1 Compare July 19, 2026 14:36
@wkgardner

Copy link
Copy Markdown
Collaborator

More LIMS side refactor commits coming tomorrow. I am having to reorder some of the steps towards the end of the BLAST step like Stacking blast results after ADD_READ_COUNTS_TO_BLAST which is necessary row level info for blast uploads.

On the LIMS side it looks like we only need to update the schema in one place:
Hits list (labkey_blast_meta_hits_list): add a query_class text column.

The fasta reports for each query on the LIMS side will be limited to only contigs (both short and long) to prevent blowing up prod lists.

I am also attempting to build a just e2e-labkey test but if LK permissions are too much of a pain I may have to ditch this idea.

@nrminor
nrminor force-pushed the v3.3.0-rc branch 2 times, most recently from 4f9db6c to edfa1ea Compare August 3, 2026 19:33
nrminor added 4 commits August 4, 2026 08:02
When BLAST tasks disagree for one contig, select rows with the highest bitscore and then the lowest e-value before checking taxonomic consistency. Continue to fail on equally strong conflicting assignments instead of selecting one by input order.
Assign DEACON_UNION_INDEXES and ANNOTATE_LEAST_COMMON_ANCESTORS to the existing medium process profile. These operations can exceed the low profile's memory allocation on larger indexes and BLAST result sets, so request the resource class that matches their observed requirements.
Configure Clumpify to perform exact sequence deduplication instead of treating dedupe=2 as a disabled boolean. Disable optical-name matching, substitutions, N-containing matches, and optional pair reordering so duplicate decisions depend on sequence content while interleaved mates remain adjacent.
Force BBDuk to interpret known interleaved batches as paired input and remove the complete pair when either mate is rejected. Mark single-read batches explicitly as unpaired so SRA-style spot suffixes do not control pairing heuristics.
nrminor and others added 26 commits August 4, 2026 19:29
Construct the final LabKey log collection only when LabKey integration is enabled. Avoid eagerly creating the LabKey upload log directory for runs that cannot produce LabKey artifacts.

Preserve the existing final-log collection and storage behavior for LabKey-enabled runs.
Replace separate BLAST and Big Table stackers, and the inline experiment concatenation, with one Polars-backed utility. Sort inputs deterministically, validate matching headers and known numeric fields, and stream concatenated output with bounded memory.

Rename the per-sample and experiment processes around their result scope, update publication selectors and reporting wiring, and remove the obsolete pre-enrichment stack process.

Test focused ordering, empty-input, schema, path, and numeric-validation contracts. Exercise final BLAST equivalence through the importable main API with production-equivalent arguments, and verify in the end-to-end suite that aggregate result tables contain their ordered per-sample rows.
…d machinery

Skip re-inserting a (sample, query_class) batch into the LabKey hits list
when it is already present there, using the list itself as the completion
ledger instead of a separate experiment-ID guard list. Removes the guard
check/register processes and script, uploads each batch in a single atomic
insert, and reconciles the per-batch LIMS wiring left over from the earlier
per-read-type enrichment split.
… on insert error

Re-include both WebDAV upload-done signals in the LabKey upload-completion
gate so Slack cannot fire before those uploads finish, quote the --csv
argument for consistency with sibling flags, and make a failed row insert
exit non-zero (after retries) instead of logging an error while still
reporting success.
Adds LABKEY_WEBDAV_UPLOAD_QUERY_FASTA to upload each per-read-type
query FASTA (contig, merged, single) that was actually BLASTed, both
to WebDAV and under the results dir, threading the batch FASTAs from
PREPARE_BLAST_QUERIES through REPORTING into LIMS_INTEGRATION.
…PLE_BLAST_RESULTS

Route the LIMS_INTEGRATION per-sample BLAST feed through the consolidated
per-sample concatenation output via a dedicated multiMap branch.
Both upload scripts duplicated the destination-list presence check (with the
QueryFilter -> filter-string fallback) and the single atomic insert. Move them
to py_nvd.labkey_io so the two scripts delegate to one implementation.
Gate a real-LabKey run on an nvd preset (LabKey coordinates) plus the
LABKEY_API_KEY Nextflow secret; skip when the preset is absent and fail with a
clear message when it exists but a coordinate or the secret is missing. Use a
random experiment_id, assert the first run inserts per-batch hits carrying
query_class, and assert a rerun adds no rows (dedup on the hits and FASTA
lists).

Keep the run's rows and WebDAV files by default so they can be inspected
(NVD_TEST_LABKEY_KEEP=0 removes them and the experiment's WebDAV directory on
exit). Run it with 'just e2e-labkey' (streams progress, and prompts securely
for LABKEY_API_KEY when the secret is unset), mirroring the other e2e targets.
Parameterize the shared LabKey run helper so the mock and real tests reuse it.
Without --experimental only short_assembly_contig is produced, so the LIMS
e2e never exercised the per-query-class split it exists to verify. Turn on
experimental (with --merge_pairs and the sourmash fixtures) so the run also
emits overlap_merged_pair and single_read, and assert both a contig class and
a read-derived class reach the destination list in the mock and real tests.
…IMS e2e

NVD_TEST_LABKEY_EXPERIMENT_ID pins the experiment id (default random) and
skips the pre-clean, so a rerun against an id that already has rows verifies
the check-ahead skips duplicates across invocations. NVD_TEST_LABKEY_EXPERIMENTAL=0
runs contig-only (faster, short_assembly_contig only); the read-class assertion
is gated on experimental so it holds either way.
The urllib WebDAV test relied on the SSL_CERT_FILE env var to trust the mock's
self-signed cert, but that is only honored if set before the process's first
SSL context is created -- unreliable in a shared pytest process (passed on
macOS, failed on Linux CI with CERTIFICATE_VERIFY_FAILED). Pass an explicit
ssl context built from the mock cert to urlopen instead, so trust is
deterministic and no process-global env state is touched.
CI has no LabKey credentials or secret store, so both LIMS end-to-end tests
(the mock and the real) are marked 'labkey' and run only via 'just e2e-labkey'.
The 'just e2e'/'e2e-test-ci' selection ('slow and network and not labkey') is
thus the SRA test only, as it was before the LIMS work; 'just e2e-labkey' uses
'... and labkey'. The self-contained mock-server unit tests still run in the
fast suite.
This was referenced Aug 5, 2026
@wkgardner
wkgardner merged commit 9f9f22f into main Aug 5, 2026
15 checks passed
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