Alt-Depth Reconciliation — VarDict vs Dragen vs BAM

newpanel-i100val-RUN42-DNA-21 · six clinical sites · read-level forensic analysis
Job: job-2b400aecBucket: s3://ziatibbana Workflow: bwaAmpliconWorkflow (BWA amplicon)BAM: real job output (338 MB) Generated: 2026-08-17 17:57 UTC

Bottom line

The three callers do not disagree because of statistical noise. They disagree because at most of these sites the ALT allele is confined to a single minor amplicon whose primer 3′ end lands exactly on the variant position — so the "variant" base is the first sequenced base of every read in that amplicon (read-position 0), at high quality. Each tool includes, excludes, or double-counts that amplicon differently:

What we are doing about it (in progress): a per-variant structural-QC step reads the BAM once and stamps every called variant with the amplicon / read-position / fragment evidence below, plus an advisory verdict. It annotates — it never drops a variant. The prototype is validated against this sample (§6) and folds into the existing bam-coverage-analyzerclassify_northside_variants pipeline.

1. The four-way discrepancy, per site

Every number below is measured from this job's own BAM. "BAM truth" = distinct DNA fragments carrying ALT ÷ distinct fragments overlapping the site (mate overlap removed).

SiteVarDict
vd/dp
Dragen
ad/cov
mpileup
capped -d8000
mpileup
uncapped -Q20
BAM truth
frag-level
Verdict
TET2 4:106157519 A>G 92/9042
1.02%
17/8725
0.19%
15/7950
0.19%
55/9103
0.60%
57/9012
0.63%
PRIMER-EDGE ARTIFACT
PRPF8 17:1565088 T>C 245/11093
2.21%
15/10922
0.14%
167/7604
2.20%
249/11251
2.21%
246/11108
2.21%
LIKELY REAL low-level variant
PRPF8 17:1579263 G>A 271/12938
2.09%
13/12282
0.11%
168/8006
2.10%
507/13429
3.78%
270/12959
2.08%
PRIMER-EDGE ARTIFACT
ASXL1 20:31024666 A>G 267/22925
1.16%
13/11142
0.12%
8/7857
0.10%
271/23334
1.16%
270/22950
1.18%
REAL @ VAF, hidden by depth-cap
ZRSR2 X:15821873 A>G 82/5531
1.48%
11/5251
0.21%
11/5342
0.21%
49/5508
0.89%
48/5507
0.87%
PRIMER-EDGE ARTIFACT
PHF6 X:133511751 A>G 145/9518
1.52%
11/9170
0.12%
9/6979
0.13%
130/9557
1.36%
79/9458
0.83%
PRIMER-EDGE ARTIFACT

VarDict/Dragen/mpileup depths differ because they count different things: VarDict & Dragen report fragment-level depth; mpileup-capped is limited by -d 8000; mpileup-uncapped counts overlap-collapsed fragments. This panel is ~99% overlapping mate pairs (R1 and R2 both span each site), so raw read count ≈ 2× fragment count.

2. Deep dive — ZRSR2 chrX:15,821,873 A>G (your example)

VarDict says 82 / 5,531 = 1.48%. Dragen says 11 / 5,251 = 0.21%. Raw BAM has 94 ALT reads across 10,899 read-bases. The truth: these resolve to 48 ALT fragments out of 5,507 = 0.87% — and 69/70 of them come from ONE amplicon.

2a. Where the ALT reads actually live (amplicon decomposition)

Amplicon span (start–end)lenreadsG (alt)alt %
X:15821721-1582197725610329200.19%
X:15821721-158219912709300.00%
X:15821843-158219771348600.00%
X:15821872-15821977105706998.57%
X:15821721-158218741533000.00%
X:15821842-158219771352900.00%

The main amplicon 15821721–15821977 carries 20 / 10,329 = 0.19% G — ordinary background error. The minor amplicon 15821872–15821977 (starts 1 bp before the variant) carries 69 / 70 = 98.6% G. A genuine heterozygous or somatic variant would appear at the same fraction in every amplicon covering the base. It does not.

2b. The artifact signature (measured on the 69 ALT reads of the hot amplicon)

PropertyValueWhat it rules out
Read position of the variant base0 / 69 reads at position 0Random seq error (would scatter)
Base quality of ALT baseQ38 (67/69)Low-quality miscall
Mapping qualityMAPQ 60 (67/69)Pseudogene / multi-map (ZRSR2P)
Strand balance (ALT)fwd 35 / rev 34Single-strand / oxidation artifact
Mate balance (ALT)R1 35 / R2 34One-mate adapter read-through

Every alt read carries G as its first sequenced base, at high quality and high MAPQ, on both strands and both mates, and only in this one short amplicon. That is the fingerprint of a primer / amplicon-edge artifact: the primer's 3′ terminus sits on the variant position, so the synthetic primer base is read into the insert as a constant "allele" independent of the template.

2c. Why each caller reports a different number — reconstructed from the reads

MethodALTDepthVAFMechanism (measured)
mpileup -Q0 raw9410,8990.86% Every read-base; double-counts overlapping mates
mpileup -Q20 uncapped495,5080.89% Default overlap-detection collapses mates → ≈ fragment count (48)
mpileup default -d 8000115,3420.21% Cap keeps first-8000-by-coord; artifact amplicon (highest start) dropped → keep-first-8000 sim = 12 ALT
Dragen115,2510.21% Primer/read-end trimming + fragment counting → biological background only
VarDict825,5311.48% Per-read ALT count (no overlap-collapse), keeps primer-edge reads → inflated
BAM truth (fragments)485,5070.87% Overlap-corrected; still dominated by the artifact amplicon

Cap simulation: sorting the 10,899 reads by coordinate and keeping the first 8,000 leaves 12 ALT; keeping the last 8,000 leaves 92 ALT. samtools' -d cap discards the late-starting amplicon where the artifact lives — reproducing Dragen's 11 by pure sampling geometry, not biology.

3. The same pattern across all six sites

Two distinct failure modes emerge. Read-position-0 fraction and the hottest minor amplicon are measured from the BAM.

SiteALT reads
/ frags
frag VAF Hottest ampliconalt/reads
there
ALT <8bp
from end
ALT kept
by -d8000
Verdict
TET2
4:106157519 A>G
109
57 frag
0.63% 4:106157518-106157749 49/53
92.45%
73/109 16 PRIMER-EDGE ARTIFACT
PRPF8
17:1565088 T>C
486
246 frag
2.21% 17:1564850-1565095 2/25
8.00%
2/486 181 LIKELY REAL low-level variant
PRPF8
17:1579263 G>A
527
270 frag
2.08% 17:1579205-1579265 480/816
58.82%
497/527 504 PRIMER-EDGE ARTIFACT
ASXL1
20:31024666 A>G
535
270 frag
1.18% 20:31024658-31024881 444/19316
2.30%
521/535 7 REAL @ VAF, hidden by depth-cap
ZRSR2
X:15821873 A>G
94
48 frag
0.87% X:15821872-15821977 69/70
98.57%
74/94 12 PRIMER-EDGE ARTIFACT
PHF6
X:133511751 A>G
156
79 frag
0.83% X:133511750-133511815 131/135
97.04%
134/156 8 PRIMER-EDGE ARTIFACT

4. What to trust — per site

5. Remediation — per-variant structural QC (proceeding)

One BAM pass stamps every called variant with the structural evidence and an advisory verdict, joined into the combined Excel as new columns. Annotate, never filter — the reviewer and downstream QC gate decide; no variant is silently removed.

Where it goes in the deployed pipeline

post-process-amplicon  →  discover_files (bam + fb.vcf + vd.vcf)
  ├─ bam-coverage-analyzer  + structural_flags() → variant_structural_flags.tsv
  └─ classify_northside_variants  LEFT JOIN the sidecar → columns + verdict in Excel

The structural math lives in bam-coverage-analyzer (it already opens this BAM — no second download). classify_northside_variants gains one input and joins it. QC must finish before classify, so those two run gated instead of in parallel.

New Excel columns (per variant)

ColumnMeaningFlags
frag_alt / frag_depth / frag_vafOverlap-corrected fragment counts — the honest VAFreplaces read-inflated VAF
n_alt_ampliconsDistinct amplicons carrying the alt (≥3 reads)1 = no cross-check
hot_pctHighest single-amplicon alt fraction≥0.80 = near-fixed primer product
alt_readpos0_fracFraction of alt reads with the base at read-starthigh = primer 3′ base
cap8k_alt / cap_suppressedAlt surviving samtools -d8000flags mpileup under-reporting
structural_verdictPASS / PRIMER_EDGE / CAP_SUPPRESSED / NEEDS_CONFIRM / INDEL_NAadvisory only

How the verdict is decided (advisory, tunable thresholds)

VerdictRule (measured from reads)Meaning for the reviewer
PRIMER_EDGE hot_pct ≥ 0.80 and alt_readpos0_frac ≥ 0.50 Alt is ~fixed in one amplicon at the primer 3′ base — not a template allele
CAP_SUPPRESSED raw_reads > 8000 and cap8k_alt ≤ 0.30 × frag_alt and VAF > 0.5% Real signal that samtools -d8000 under-reports — trust the VAF, not mpileup
NEEDS_CONFIRM VAF < 1% and n_alt_amplicons < 2 Low-level, single amplicon — no cross-check; needs orthogonal confirmation
PASS alt across ≥2 amplicons, not near-fixed, not read-end clustered Structural evidence consistent with a real variant
INDEL_NAindelfragment depth only; base-level metrics are SNV-specific

6. Prototype validated on this sample (700 candidate alleles)

The prototype ran over the full VarDict+FreeBayes candidate union for RUN42-DNA-21 (700 alleles, 61 ms each). Every count reproduces the hand analysis in §1–3.

Verdictnshare
PASS45164%
INDEL_NA18326%
CAP_SUPPRESSED487%
PRIMER_EDGE152%
NEEDS_CONFIRM30%
Two findings that reshape the "low-VAF" question:

Every variant flagged PRIMER_EDGE in this sample (15)

Sorted by VAF. Note the spread — from 95% down to 0.6% — and that one is called by both callers. VAF magnitude does not separate these from real variants; structure does.

Variantfrag VAFhot_pctread-pos-0callershot amplicon
7:101923643 A>T95.455%0.9751.0freebayes7:101923392-101923643
4:153332477 G>A4.126%1.01.0freebayes4:153332476-153332527
7:101877523 A>G3.872%1.01.0freebayes7:101877522-101877579
10:89725218 A>C3.125%1.00.889freebayes10:89725217-89725283
7:101740709 G>C1.95%0.8551.0freebayes7:101740475-101740709
10:89711867 T>G1.917%1.01.0freebayes10:89711866-89712043
7:140454027 T>A1.557%1.00.944freebayes7:140454026-140454087
12:12038933 A>G1.317%1.00.885freebayes12:12038932-12038995
20:31022413 A>T1.256%1.00.917both20:31022135-31022413
13:48923126 A>C1.178%1.00.991freebayes13:48923036-48923126
X:15821844 G>A0.878%1.00.896vardictX:15821843-15821977
X:15821873 A>G0.872%1.00.787vardictX:15821872-15821991
X:133511751 A>G0.835%0.970.859vardictX:133511750-133511815
4:106196404 T>C0.675%1.00.815vardict4:106196403-106196464
4:106157519 A>G0.632%1.00.67vardict4:106157518-106157758

48 variants were flagged CAP_SUPPRESSED — genuine signal that samtools' default -d 8000 cap under-reports (like ASXL1: 7 alt kept of 270). Any verification step that pileups with the default cap will call these falsely low. 3 low-VAF single-amplicon calls were flagged NEEDS_CONFIRM. Nothing was dropped — all 700 variants carry forward into the Excel with these columns attached.

All six clinical sites classify correctly: ZRSR2 / PHF6 / TET2 → PRIMER_EDGE, ASXL1 → CAP_SUPPRESSED, both PRPF8 → PASS. Full table in variant_structural_flags.tsv; function in variant_structural_qc.py (61 ms/variant, one BAM pass).

7. Methods

Read-level counts computed with pysam 0.24 over the job BAM newpanel-i100val-RUN42-DNA-21_S2_L001_001.bam, pileup with truncate=True, max_depth=10⁷, stepper=nofilter, ignore_overlaps=False, min_base_quality=0 (no hidden filtering). Fragments = distinct query names. Amplicon = distinct (reference_start, reference_end) span. mpileup configs: -A -B -q20 -Q20 (capped, default -d), -d 0 (uncapped), -q0 -Q0 -d 0 (raw). VarDict fields from the job's .vardict.vcf.gz; Dragen from Dragen_Illumina_caller.sites.txt. Every count in this report is reproducible from readlevel_analysis.json, vardict_sites.json, mpileup_rerun.json in this folder.