Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Validate alignment quality with insert size distribution, proper pairing rates, GC bias, strand balance, and other post-alignment metrics. Use when verifying alignment data quality before variant calling or quantification.
.claude/skills/bio-alignment-validation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 136% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 98% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 128% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 114% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 59% | 0% |
<!--
#
#
-->
Post-alignment quality control to verify alignment quality and identify issues.
Insert size should match library preparation protocol.
bashsamtools stats input.bam > stats.txt grep "^IS" stats.txt | cut -f2,3 > insert_sizes.txt
bashjava -jar picard.jar CollectInsertSizeMetrics \ I=input.bam \ O=insert_metrics.txt \ H=insert_histogram.pdf
| Library Type | Expected Size | |--------------|---------------| | Standard WGS | 300-500 bp | | PCR-free | 350-550 bp | | RNA-seq | 150-300 bp | | ChIP-seq | 150-300 bp | | ATAC-seq | Multimodal |
pythonimport pysam import numpy as np import matplotlib.pyplot as plt def get_insert_sizes(bam_file, max_reads=100000): sizes = [] bam = pysam.AlignmentFile(bam_file, 'rb') for i, read in enumerate(bam.fetch()): if i >= max_reads: break if read.is_proper_pair and not read.is_secondary and read.template_length > 0: sizes.append(read.template_length) bam.close() return sizes sizes = get_insert_sizes('sample.bam') print(f'Median insert size: {np.median(sizes):.0f}') print(f'Mean insert size: {np.mean(sizes):.0f}') print(f'Std dev: {np.std(sizes):.0f}') plt.hist(sizes, bins=100, range=(0, 1000)) plt.xlabel('Insert Size') plt.ylabel('Count') plt.savefig('insert_size_dist.pdf')
Percentage of reads correctly paired.
bashsamtools flagstat input.bam samtools flagstat input.bam | grep "properly paired"
bashproper=$(samtools view -c -f 2 input.bam) mapped=$(samtools view -c -F 4 input.bam) rate=$(echo "scale=4; $proper / $mapped * 100" | bc) echo "Proper pairing rate: ${rate}%"
| Metric | Good | Marginal | Poor | |--------|------|----------|------| | Proper pair | > 90% | 80-90% | < 80% | | Mapped | > 95% | 90-95% | < 90% | | Singletons | < 5% | 5-10% | > 10% |
GC content correlation with coverage.
bashjava -jar picard.jar CollectGcBiasMetrics \ I=input.bam \ O=gc_bias_metrics.txt \ CHART=gc_bias_chart.pdf \ S=gc_summary.txt \ R=reference.fa
bashcomputeGCBias \ -b input.bam \ --effectiveGenomeSize 2913022398 \ -g hg38.2bit \ -o gc_bias.txt \ --biasPlot gc_bias.pdf
| Issue | Symptom | |-------|---------| | Under-representation | Low GC coverage drops | | Over-representation | High GC coverage elevated | | PCR bias | Strong correlation |
Forward and reverse strand should be balanced.
bashforward=$(samtools view -c -F 16 input.bam) reverse=$(samtools view -c -f 16 input.bam) echo "Forward: $forward" echo "Reverse: $reverse" ratio=$(echo "scale=4; $forward / $reverse" | bc) echo "F/R ratio: $ratio"
bashfor chr in chr1 chr2 chr3; do fwd=$(samtools view -c -F 16 input.bam $chr) rev=$(samtools view -c -f 16 input.bam $chr) echo "$chr: F=$fwd R=$rev ratio=$(echo "scale=2; $fwd/$rev" | bc)" done
bashsamtools view input.bam | cut -f5 | sort -n | uniq -c | sort -k2 -n
bashsamtools view input.bam | awk '{sum+=$5; count++} END {print "Mean MAPQ:", sum/count}'
| MAPQ | Meaning | |------|---------| | 0 | Multi-mapper | | 1-10 | Low confidence | | 20-30 | Moderate | | 40+ | High confidence | | 60 | Unique (BWA) |
bashsamtools idxstats input.bam | awk '{print $1, $3/$2}' | head -25
bashsamtools idxstats input.bam | awk '$3 > 0 { sum += $3 len[$1] = $2 reads[$1] = $3 } END { for (chr in reads) { expected = len[chr] / sum * reads[chr] ratio = reads[chr] / expected if (ratio < 0.8 || ratio > 1.2) print chr, ratio } }'
bashjava -jar picard.jar CollectAlignmentSummaryMetrics \ I=input.bam \ R=reference.fa \ O=alignment_summary.txt
| Metric | Description | Good Value | |--------|-------------|------------| | PCT_PF_READS_ALIGNED | Mapped % | > 95% | | PF_MISMATCH_RATE | Mismatches | < 1% | | PF_INDEL_RATE | Indels | < 0.1% | | STRAND_BALANCE | Strand ratio | ~0.5 |
bash#!/bin/bash BAM=$1 REF=$2 NAME=$(basename $BAM .bam) OUTDIR=${3:-qc} mkdir -p $OUTDIR echo "=== Alignment Validation: $NAME ===" | tee $OUTDIR/report.txt echo -e "\n--- Flagstat ---" | tee -a $OUTDIR/report.txt samtools flagstat $BAM | tee -a $OUTDIR/report.txt echo -e "\n--- Mapping Rate ---" | tee -a $OUTDIR/report.txt mapped=$(samtools view -c -F 4 $BAM) total=$(samtools view -c $BAM) rate=$(echo "scale=2; $mapped / $total * 100" | bc) echo "Mapping rate: ${rate}%" | tee -a $OUTDIR/report.txt echo -e "\n--- Proper Pairing ---" | tee -a $OUTDIR/report.txt proper=$(samtools view -c -f 2 $BAM) pair_rate=$(echo "scale=2; $proper / $mapped * 100" | bc) echo "Proper pairing: ${pair_rate}%" | tee -a $OUTDIR/report.txt echo -e "\n--- Insert Size ---" | tee -a $OUTDIR/report.txt samtools stats $BAM | grep "insert size average" | tee -a $OUTDIR/report.txt echo -e "\n--- Strand Balance ---" | tee -a $OUTDIR/report.txt fwd=$(samtools view -c -F 16 $BAM) rev=$(samtools view -c -f 16 $BAM) strand_ratio=$(echo "scale=3; $fwd / $rev" | bc) echo "Forward: $fwd, Reverse: $rev, Ratio: $strand_ratio" | tee -a $OUTDIR/report.txt echo -e "\n--- Chromosome Coverage ---" | tee -a $OUTDIR/report.txt samtools idxstats $BAM | head -25 | tee -a $OUTDIR/report.txt echo -e "\nReport: $OUTDIR/report.txt"
pythonimport pysam import numpy as np from collections import Counter class AlignmentValidator: def __init__(self, bam_file): self.bam = pysam.AlignmentFile(bam_file, 'rb') def mapping_rate(self): stats = self.bam.get_index_statistics() mapped = sum(s.mapped for s in stats) unmapped = sum(s.unmapped for s in stats) return mapped / (mapped + unmapped) * 100 def proper_pair_rate(self, sample_size=100000): proper = 0 paired = 0 for i, read in enumerate(self.bam.fetch()): if i >= sample_size: break if read.is_paired: paired += 1 if read.is_proper_pair: proper += 1 return proper / paired * 100 if paired > 0 else 0 def mapq_distribution(self, sample_size=100000): mapqs = [] for i, read in enumerate(self.bam.fetch()): if i >= sample_size: break if not read.is_unmapped: mapqs.append(read.mapping_quality) return Counter(mapqs) def strand_balance(self, sample_size=100000): forward = 0 reverse = 0 for i, read in enumerate(self.bam.fetch()): if i >= sample_size: break if not read.is_unmapped: if read.is_reverse: reverse += 1 else: forward += 1 return forward / (forward + reverse) if (forward + reverse) > 0 else 0.5 def report(self): print(f'Mapping rate: {self.mapping_rate():.1f}%') print(f'Proper pairing: {self.proper_pair_rate():.1f}%') print(f'Strand balance: {self.strand_balance():.3f}') mapq_dist = self.mapq_distribution() high_qual = sum(v for k, v in mapq_dist.items() if k >= 30) total = sum(mapq_dist.values()) print(f'High MAPQ (>=30): {high_qual/total*100:.1f}%') def close(self): self.bam.close() validator = AlignmentValidator('sample.bam') validator.report() validator.close()
| Metric | Good | Warning | Fail | |--------|------|---------|------| | Mapping rate | > 95% | 90-95% | < 90% | | Proper pairing | > 90% | 80-90% | < 80% | | Duplicate rate | < 10% | 10-20% | > 20% | | Strand balance | 0.48-0.52 | 0.45-0.55 | Outside | | Mean MAPQ | > 40 | 30-40 | < 30 | | GC bias | < 1.2x | 1.2-1.5x | > 1.5x |
<!-- AUTHOR_SIGNATURE: 9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE -->
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 9,780 | 12,760 | +30% | 1 | 1 | 0% | 1,936 | 5,702 | +195% | 0 | 0 | — |
case-02 | pass→pass | 9,397 | 5,895 | -37% | 1 | 1 | 0% | 1,835 | 4,173 | +127% | 0 | 0 | — |
case-03 | pass→pass | 7,002 | 4,775 | -32% | 1 | 1 | 0% | 1,382 | 4,021 | +191% | 0 | 0 | — |
case-04 | pass→pass | 5,959 | 5,862 | -2% | 1 | 1 | 0% | 1,149 | 4,070 | +254% | 0 | 0 | — |
case-05 | pass→pass | 6,787 | 4,934 | -27% | 1 | 1 | 0% | 1,271 | 4,021 | +216% | 0 | 0 | — |
case-06 | fail→pass | 9,440 | 27,514 | +191% | 1 | 1 | 0% | 1,759 | 4,149 | +136% | 0 | 0 | — |
case-07 | fail→pass | 11,984 | 5,991 | -50% | 1 | 1 | 0% | 2,087 | 4,126 | +98% | 0 | 0 | — |
case-08 | pass→pass | 14,034 | 6,273 | -55% | 1 | 1 | 0% | 2,517 | 4,203 | +67% | 0 | 0 | — |
case-09 | pass→pass | 2,472 | 2,589 | +5% | 1 | 1 | 0% | 460 | 3,568 | +676% | 0 | 0 | — |
case-10 | pass→pass | 6,649 | 4,166 | -37% | 1 | 1 | 0% | 1,254 | 3,892 | +210% | 0 | 0 | — |
case-11 | pass→pass | 6,049 | 3,426 | -43% | 1 | 1 | 0% | 1,168 | 3,784 | +224% | 0 | 0 | — |
case-12 | fail→pass | 12,565 | 8,883 | -29% | 1 | 1 | 0% | 2,016 | 4,595 | +128% | 0 | 0 | — |
case-13 | pass→pass | 4,935 | 4,350 | -12% | 1 | 1 | 0% | 1,031 | 3,887 | +277% | 0 | 0 | — |
case-14 | pass→pass | 11,961 | 7,935 | -34% | 1 | 1 | 0% | 2,077 | 4,579 | +120% | 0 | 0 | — |
case-15 | fail→pass | 14,008 | 7,219 | -48% | 1 | 1 | 0% | 2,086 | 4,455 | +114% | 0 | 0 | — |
case-16 | pass→pass | 17,506 | 6,659 | -62% | 1 | 1 | 0% | 2,330 | 4,410 | +89% | 0 | 0 | — |
case-17 | pass→pass | 11,317 | 4,482 | -60% | 1 | 1 | 0% | 2,296 | 3,933 | +71% | 0 | 0 | — |
case-18 | fail→pass | 14,713 | 5,307 | -64% | 1 | 1 | 0% | 2,589 | 4,107 | +59% | 0 | 0 | — |
case-19 | pass→pass | 6,333 | 4,263 | -33% | 1 | 1 | 0% | 1,292 | 3,969 | +207% | 0 | 0 | — |
case-20 | pass→pass | 13,879 | 7,817 | -44% | 1 | 1 | 0% | 2,473 | 4,478 | +81% | 0 | 0 | — |
case-21 | pass→pass | 12,657 | 6,015 | -52% | 1 | 1 | 0% | 2,128 | 4,138 | +94% | 0 | 0 | — |
case-22 | fail→pass | 14,437 | 7,232 | -50% | 1 | 1 | 0% | 2,593 | 4,075 | +57% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 7/26/2026 | +9% |
Other measured skills in the registry, with their headline benchmark lift.