feat(variant): retain unevaluated variants in a sideline container (#106) - #211
feat(variant): retain unevaluated variants in a sideline container (#106)#211TimD1-bot wants to merge 1 commit into
Conversation
2fadee3 to
b9367b5
Compare
a01dbdc to
df9f7dd
Compare
|
Claude Opus 5 🤖: Planned rework, filed as #229. A sidelined record is emitted verbatim, so once the writer is htslib-based this becomes No action needed until #229 lands. |
b9367b5 to
6504773
Compare
df9f7dd to
8669bad
Compare
) parse_variants() discarded every record that failed FILTER or fell below --min-qual, so nothing survived but an aggregate counter. Both are now retained in a per-contig, per-callset sideline container and written to the summary VCF as calls that were not evaluated. Retained variants never enter ctgVariants, so every loop over poss/n in clustering, alignment, phasing, and reporting is unreachable for them and "excluded from all analysis" holds by construction rather than by a check at each of those sites. The writer's merge walk grows from two streams to four, interleaving each callset's retained records by position. Records are built through the same htslib helpers the evaluated ones use: set_record_qual/id/filters/info for the site columns, and set_record_samples over a sample_fields pair for the two samples. A retained record reports BD=N on the one sample that called it, and bcf_add_filter() adds the VCFDIST_-prefixed tag naming why it was not evaluated, which displaces a lone PASS. Nothing normalized or split a retained record, so it keeps the whole source ALT list and every Number=A/R/G value indexing it, rather than being subset to one allele the way an evaluated variant is. Its GT is likewise the source's own and may name any ALT ordinal. Source columns are now retained before any filtering decision, so a retained record still carries its own ID, QUAL, FILTER, INFO, and FORMAT.
8669bad to
6064810
Compare
|
Claude Opus 5 🤖: #229 has landed (it is in this PR's base as Done. The hand-rolled parts are gone. Not done: replacing the container with a
There is also a memory point: a So the ~186-line removal isn't reachable, but the duplication it was aimed at is: One behavior change worth calling out, which #229 made correct rather than my doing: a retained record now keeps its whole source ALT list and its |
Note
Authorship: the content below was drafted by Claude Opus 5 (an AI coding agent) and
filed via
ghunder @TimD1-bot, a bot account operated by @TimD1. It reflects theagent's analysis, not a statement authored by @TimD1.
Closes #106. Step 6 of 8 toward #48. Stacked on #105 (
105_td_subset-num-a-r-g); review that first.parse_variants()discarded every record that failedFILTERor fell below--min-qual, sonothing survived but an aggregate counter. Both are now retained and written to the summary VCF
as calls that were not evaluated.
Sideline container
ctgSidelineis a per-contig, per-callset container of retained variants, keyed by(record ordinal, haplotype). Both reasons wired up here precede thefor (hap)loop, soneither can partially drop a record; each stores
SIDELINE_ALL_HAPS, leaving the per-allelekey for the reasons that arrive next.
Retained variants never enter
ctgVariants, so every loop overposs/ninsrc/cluster.cpp,src/dist.cpp,src/phase.cpp, andsrc/print.cppis unreachable for them and "excluded fromall analysis" holds by construction, rather than by an
ignoredcheck at each of those siteswhere one missed check would silently corrupt supercluster indices or precision/recall counts.
An entry carries the source
POS/REF/ALTandGTverbatim, since nothing normalized orsplit a retained record, plus the reason code that selects its
FILTERtag.FILTER tags
sideline_strs/sideline_descsinsrc/globals.cpphold the tag and description of eachSIDELINE_*reason.region_strscould not be reused: its display values are space-padded(
"OFF CTG"), which is not a legal FILTER ID.VCFDIST_FAILED_FILTERFILTERholds none of the filters--filterselectedVCFDIST_LOW_QUALQUALis below--min-qualPer the VCF spec
FILTERlists the filters a record failed, so the tag joins the record's ownlist; a lone
PASS(or.) is replaced instead, since a record excluded from evaluation passednothing it was held to. Tags are
VCFDIST_-prefixed against collision with input FILTER IDs anduppercase to match the convention for FILTER IDs.
Writer
write_summary_vcf()'s merge walk grows from two streams to four, interleaving each callset'sretained records into the position-ordered walk. A retained record was excluded before any
comparison ran, so it is never matched: one sample holds its call, reporting
BD=N— the GA4GHdecision for a call that was not assessed — and nothing else, and the other sample is entirely
missing. The header gains a
##FILTERline per reason andBD's description coversN.INFO/FORMATfields whose values index the sourceALTlist are dropped from a retainedrecord: it keeps the whole
ALTlist it was written with, so no single element is the one itsallele carries, while the propagated header declares those fields at the cardinality a split
record carries.
Other changes
Source column retention now runs before any filtering decision, so a record excluded from
evaluation still carries its own
ID,QUAL,FILTER,INFO, andFORMAT. Thesrc_info/src_fmt_valssubsetting helpers became free functions so both containers can sharethem. The two parse-time
INFOlines now say "not evaluated" rather than "skipped".Tests
column preservation, the lone-
PASSreplacement, and that neither an evaluated variant nor arecord dropped for any other reason is retained. Writer tests pin
BD=N, the empty companionsample, the
FILTERcontents with and without an input filter, and position interleaving.sideline: one run exercising both reasons, both with and without a non-PASSinput
FILTERon the same record.from by hand, diffing every analysis output. All eight are byte-identical.
Locally: 119 pytest tests pass (633 unit assertions), a clean
-Wall -Wextrabuild adds nowarnings, and
doxygenis clean.