First Script

Build a BioLang script that reads FASTQ records, computes quality statistics, filters reads, and writes a tab-separated report.

Inputs

BioLang scripts are .bl files evaluated from top to bottom. The current bl run command accepts a script path, so configure input values in the script or read them from environment variables with env().

# qc_report.bl
let input_path = env("FASTQ_INPUT") ?? "sample_R1.fastq"
let min_quality = 25.0
let output_path = "qc_report.tsv"

println(f"Processing: {input_path}")
println(f"Quality threshold: {min_quality}")

Read FASTQ Records

let records = read_fastq(input_path)
let total = len(records)
println(f"Total reads: {total}")

Compute Per-Read Metrics

let qualities = records |> map(|record| {
  {
    name: record.id,
    length: record.length,
    mean_quality: mean_phred(record.quality),
    gc_content: gc_content(record.seq)
  }
})

let mean_qual = qualities |> map(|r| r.mean_quality) |> mean()
let mean_len = qualities |> map(|r| r.length) |> mean()
let mean_gc = qualities |> map(|r| r.gc_content) |> mean()

println(f"Mean quality: {round(mean_qual, 2)}")
println(f"Mean length: {round(mean_len, 1)}")
println(f"Mean GC: {round(mean_gc, 3)}")

Filter and Classify

fn quality_tier(mean_q) {
  if mean_q >= 35.0 {
    "excellent"
  } else if mean_q >= 25.0 {
    "good"
  } else if mean_q >= 20.0 {
    "acceptable"
  } else {
    "poor"
  }
}

let passing = qualities
  |> filter(|r| r.mean_quality >= min_quality)
  |> filter(|r| r.length >= 50)

let pass_rate = float(len(passing)) / float(total) * 100.0
println(f"Passing reads: {len(passing)} ({round(pass_rate, 1)}%)")

let tier_counts = passing
  |> map(|r| quality_tier(r.mean_quality))
  |> frequencies()

for tier in keys(tier_counts) {
  println(f"  {tier}: {tier_counts[tier]}")
}

Write the Report

let header = "read_name\tlength\tmean_quality\tgc_content\ttier"
let lines = passing |> map(|r| {
  let tier = quality_tier(r.mean_quality)
  f"{r.name}\t{r.length}\t{round(r.mean_quality, 2)}\t{round(r.gc_content, 4)}\t{tier}"
})

write_text(output_path, join(concat([header], lines), "\n"))
println(f"Report written to {output_path}")

Complete Script

# qc_report.bl
let input_path = env("FASTQ_INPUT") ?? "sample_R1.fastq"
let min_quality = 25.0
let output_path = "qc_report.tsv"

fn quality_tier(mean_q) {
  if mean_q >= 35.0 { "excellent" }
  else if mean_q >= 25.0 { "good" }
  else if mean_q >= 20.0 { "acceptable" }
  else { "poor" }
}

let records = read_fastq(input_path)
let qualities = records |> map(|record| {
  {
    name: record.id,
    length: record.length,
    mean_quality: mean_phred(record.quality),
    gc_content: gc_content(record.seq)
  }
})

let passing = qualities
  |> filter(|r| r.mean_quality >= min_quality)
  |> filter(|r| r.length >= 50)

let header = "read_name\tlength\tmean_quality\tgc_content\ttier"
let lines = passing |> map(|r| {
  let tier = quality_tier(r.mean_quality)
  f"{r.name}\t{r.length}\t{round(r.mean_quality, 2)}\t{round(r.gc_content, 4)}\t{tier}"
})

write_text(output_path, join(concat([header], lines), "\n"))
println(f"Done. {len(passing)}/{len(records)} reads passed. Report: {output_path}")

Run It

# PowerShell
$env:FASTQ_INPUT = "sample_R1.fastq"
bl run qc_report.bl

# Bash
FASTQ_INPUT=sample_R1.fastq bl run qc_report.bl