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-analyzer → classify_northside_variants pipeline.
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).
| Site | VarDict 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.
| Amplicon span (start–end) | len | reads | G (alt) | alt % |
|---|---|---|---|---|
| X:15821721-15821977 | 256 | 10329 | 20 | 0.19% |
| X:15821721-15821991 | 270 | 93 | 0 | 0.00% |
| X:15821843-15821977 | 134 | 86 | 0 | 0.00% |
| X:15821872-15821977 | 105 | 70 | 69 | 98.57% |
| X:15821721-15821874 | 153 | 30 | 0 | 0.00% |
| X:15821842-15821977 | 135 | 29 | 0 | 0.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.
| Property | Value | What it rules out |
|---|---|---|
| Read position of the variant base | 0 / 69 reads at position 0 | Random seq error (would scatter) |
| Base quality of ALT base | Q38 (67/69) | Low-quality miscall |
| Mapping quality | MAPQ 60 (67/69) | Pseudogene / multi-map (ZRSR2P) |
| Strand balance (ALT) | fwd 35 / rev 34 | Single-strand / oxidation artifact |
| Mate balance (ALT) | R1 35 / R2 34 | One-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.
| Method | ALT | Depth | VAF | Mechanism (measured) |
|---|---|---|---|---|
| mpileup -Q0 raw | 94 | 10,899 | 0.86% | Every read-base; double-counts overlapping mates |
| mpileup -Q20 uncapped | 49 | 5,508 | 0.89% | Default overlap-detection collapses mates → ≈ fragment count (48) |
| mpileup default -d 8000 | 11 | 5,342 | 0.21% | Cap keeps first-8000-by-coord; artifact amplicon (highest start) dropped → keep-first-8000 sim = 12 ALT |
| Dragen | 11 | 5,251 | 0.21% | Primer/read-end trimming + fragment counting → biological background only |
| VarDict | 82 | 5,531 | 1.48% | Per-read ALT count (no overlap-collapse), keeps primer-edge reads → inflated |
| BAM truth (fragments) | 48 | 5,507 | 0.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.
Two distinct failure modes emerge. Read-position-0 fraction and the hottest minor amplicon are measured from the BAM.
| Site | ALT reads / frags | frag VAF | Hottest amplicon | alt/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 |
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.
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.
| Column | Meaning | Flags |
|---|---|---|
| frag_alt / frag_depth / frag_vaf | Overlap-corrected fragment counts — the honest VAF | replaces read-inflated VAF |
| n_alt_amplicons | Distinct amplicons carrying the alt (≥3 reads) | 1 = no cross-check |
| hot_pct | Highest single-amplicon alt fraction | ≥0.80 = near-fixed primer product |
| alt_readpos0_frac | Fraction of alt reads with the base at read-start | high = primer 3′ base |
| cap8k_alt / cap_suppressed | Alt surviving samtools -d8000 | flags mpileup under-reporting |
| structural_verdict | PASS / PRIMER_EDGE / CAP_SUPPRESSED / NEEDS_CONFIRM / INDEL_NA | advisory only |
| Verdict | Rule (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_NA | indel | fragment depth only; base-level metrics are SNV-specific |
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.
| Verdict | n | share |
|---|---|---|
| PASS | 451 | 64% |
| INDEL_NA | 183 | 26% |
| CAP_SUPPRESSED | 48 | 7% |
| PRIMER_EDGE | 15 | 2% |
| NEEDS_CONFIRM | 3 | 0% |
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.
| Variant | frag VAF | hot_pct | read-pos-0 | callers | hot amplicon |
|---|---|---|---|---|---|
| 7:101923643 A>T | 95.455% | 0.975 | 1.0 | freebayes | 7:101923392-101923643 |
| 4:153332477 G>A | 4.126% | 1.0 | 1.0 | freebayes | 4:153332476-153332527 |
| 7:101877523 A>G | 3.872% | 1.0 | 1.0 | freebayes | 7:101877522-101877579 |
| 10:89725218 A>C | 3.125% | 1.0 | 0.889 | freebayes | 10:89725217-89725283 |
| 7:101740709 G>C | 1.95% | 0.855 | 1.0 | freebayes | 7:101740475-101740709 |
| 10:89711867 T>G | 1.917% | 1.0 | 1.0 | freebayes | 10:89711866-89712043 |
| 7:140454027 T>A | 1.557% | 1.0 | 0.944 | freebayes | 7:140454026-140454087 |
| 12:12038933 A>G | 1.317% | 1.0 | 0.885 | freebayes | 12:12038932-12038995 |
| 20:31022413 A>T | 1.256% | 1.0 | 0.917 | both | 20:31022135-31022413 |
| 13:48923126 A>C | 1.178% | 1.0 | 0.991 | freebayes | 13:48923036-48923126 |
| X:15821844 G>A | 0.878% | 1.0 | 0.896 | vardict | X:15821843-15821977 |
| X:15821873 A>G | 0.872% | 1.0 | 0.787 | vardict | X:15821872-15821991 |
| X:133511751 A>G | 0.835% | 0.97 | 0.859 | vardict | X:133511750-133511815 |
| 4:106196404 T>C | 0.675% | 1.0 | 0.815 | vardict | 4:106196403-106196464 |
| 4:106157519 A>G | 0.632% | 1.0 | 0.67 | vardict | 4: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).
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.