Genomics Glossary (for the PRS / WGS pipeline)
A working reference for the genetics terms that show up in Maple’s polygenic-score and genotype-ingest code. Written for engineers, not geneticists — each entry says what the term means and why it matters to us.
The one sentence everything hangs off: a polygenic score is a weighted sum — for each of thousands of spots in the genome, take how many copies of the risk version this person carries (0, 1, or 2) × how much that version shifts risk, and add them all up. Every term below is machinery for lining up “this person’s variant” with “the scoring file’s variant” correctly.
This file defines the vocabulary — the coordinate terms (§1), what a person carries and how a caller decides it (§2, §2.1), the file formats (§3), and the reference genome itself including where ours comes from (§4, §4.1). For the wider arc — where polygenic scores came from, what a scoring file actually contains, how MODS classifies and computes them, and what is built versus not — see `prs_primer.md`.
If you hit a term here that is used but never defined, that is a bug in this file; say so
and it gets added. calling, contig and FASTA were all in exactly that state until
2026-09-23.
1. Naming a spot in the genome
| Term | Meaning | Why it matters here |
|---|---|---|
| Variant | Any place a person’s DNA differs from the reference genome. | The atoms of everything we do. |
| SNP (single-nucleotide polymorphism) | The common kind of variant: one DNA base swapped (an A where the reference has G). | Polygenic scores are almost entirely SNPs. |
| Indel | A small insertion or deletion of bases. | Common, not rare: 2,300 of 5,377 catalog scores carry at least one. We handle them — but carefully, because a deletion’s ALT is the REF’s anchor base (ATTCT>A), so the alleles alone can’t say which is the reference. See prs_primer.md §5.2. |
Chromosome + position (chr:pos) | A variant’s physical address, e.g. 1:10250. | The coordinate is only meaningful relative to a specific build (§4). |
| Contig | A contiguous run of assembled sequence that carries a name — one >entry in the reference FASTA. In practice: the named chromosomes (1–22, X, Y, MT) plus unplaced scaffolds — real assembled sequence nobody could pin to a chromosome, which is why the tail of our index reads KI270392.1 (971 bases) rather than a chromosome name. “Contiguous” is the whole point — no gaps inside one. | The chr half of chr:pos is a contig name. Our pinned GRCh38 primary assembly has exactly 194, and that count is how we verify we have the right flavour of GRCh38: the “full analysis set” adds alt contigs and decoys and would give a different number (prs_primer.md §2.3). Naming also differs by source — chr1 (UCSC) vs 1 (Ensembl); we normalise to Ensembl. |
| rsID | A stable name for a known variant, assigned by dbSNP (NCBI’s variant database), e.g. rs199706086. | A permanent ID independent of coordinates. Historically the join key for PRS. |
| dbSNP | NCBI’s public catalog of known variants; the authority that issues rsIDs. | Where rsIDs come from. |
The rsID-vs-position point (the heart of our PRS work). To score a variant we
must match “the patient’s genotype here” with “the scoring file’s weight here.”
rsID is the cleanest join key — same rs number on both sides ⇒ same variant.
But many big modern scoring files omit rsIDs entirely and list variants by
chr:pos only. If we match on rsID alone, those files match nothing and
silently contribute a score of zero. Maple therefore matches on rsID first and
falls back to chr:pos — which is what turns “whole catalog” from a lie into
the truth (one real score went from 0 → 2.6 M matched variants once we did this).
“Liftover” — a real term you’ll hear nearby, meaning converting coordinates from one build to another (GRCh37 ↔ GRCh38). Not to be confused with the informal “coverage lift” (how many extra variants get matched) used in our notes.
2. What the person actually carries (genotype)
| Term | Meaning | Why it matters here |
|---|---|---|
| Allele | One version of a variant (A vs G at a SNP). | The thing weights are attached to. |
| Reference / alternate allele (ref / alt) | The reference-genome base vs the variant base. | How VCFs report a variant. |
| Genotype | The two alleles a person carries at a spot (one per chromosome copy). | The raw observation we store per variant. |
Dosage / allele_count = 0, 1, 2 | How many copies of the alt (or, for scoring, the effect) allele: 0 = homozygous-reference, 1 = heterozygous, 2 = homozygous-alt. | This 0/1/2 is literally the number we multiply the weight by. |
| Hom-ref / het / hom-alt | Shorthand for dosage 0 / 1 / 2 (homozygous-reference, heterozygous, homozygous-alternate). | Depth (§3) is what lets us call het vs hom confidently. |
| Effect allele / other allele (scoring-file terms) | The scoring file declares which allele the weight applies to (effect) and which is baseline (other). | We must count the patient’s dosage against the effect allele, not flip it. |
| Strand / strand flip | DNA is double-stranded; a variant can be reported from either strand (A↔T, G↔C are complements). Gate C detects a flip — a row that matches the reference only when complemented — and defers it; correcting it is reserved for files shown to be in an A/B allele convention (GH #376, opt-in per file). | Two sources can describe the same variant with swapped letters; getting it wrong scores backwards. |
| Palindromic SNP | An A/T or C/G variant. Its strand complement is the same pair (A/T read from the other strand is T/A), so the letters cannot say which strand a file was written on. The pair is unchanged, but which letter carries the weight is not: read on the wrong strand, the file’s effect allele is really the other allele. | A strand error here is invisible and inverting: a homozygote’s dosage goes 2 ↔ 0, and only heterozygotes come through unchanged. So Gate C defers these (mods_prs_scorer.is_palindromic, resolve_orientation). GH #404 licenses reading them forward only for one class of file, measured against allele frequencies: [[prs/palindrome_forward_rule |
2.1 Calling — how a genotype gets decided, and how sure we are
A sequencer does not report genotypes. It reports hundreds of millions of short overlapping reads. Calling is the inference step that turns a pile of reads covering one position into a statement about what this person has there. Everything downstream inherits the confidence of that inference — which is why this pipeline insists on separating “we called homozygous-reference here” from “we could not call anything here.” Collapsing those two is precisely the REF-effect bug that invalidated every previously computed score.
| Term | Meaning | Why it matters here |
|---|---|---|
| Variant calling, or just calling | Deciding, from the aligned reads, what genotype a person carries at a position. The output is a call. | Everything we score is a call, not a direct observation. Read “the genome says 0/0 here” as “the caller concluded 0/0, with this much evidence”. |
| A call (noun) | The genotype assertion at one position — 0/0 hom-ref, 0/1 het, 1/1 hom-alt, ./. no-call — together with the evidence behind it. | The GT field of a VCF row. |
| Caller | The software doing the inference (GATK, DRAGEN, DeepVariant…). | Conventions differ between callers, so wgs_dense_callset.py reads the caller’s identity from the gVCF header first and picks thresholds after. Ours: Sequencing.com’s 30× WGS pipeline, reblocked by GvcfReblocker, GATK-style (END/MIN_DP reference blocks, not DRAGEN RGQ). |
No-call (./.) | The caller looked and declined to commit. | Not the same as hom-ref. Hom-ref is a positive claim (“this person matches the reference”); a no-call is an absence of evidence. We emit these as EXPLICIT_MISSING so they can never be silently scored as dosage 0. |
| Callable | A call we are willing to trust: enough depth, passes the caller’s filter, and the REF base agrees with our FASTA. A quality judgement layered on top of the call. | The dense callset sorts every target locus into CALLABLE_HOMREF, CALLABLE_VARIANT, EXPLICIT_MISSING or STRUCTURAL_FAIL, and asserts the four sum to the number of targets. “Callable” is the word doing the work in that policy. |
| Callability policy | Our thresholds for what counts as callable, per call type. | prs_primer.md §5.2. Deliberately different for hom-ref vs variant vs indel. |
| Phred score | A log-scaled way of writing an error probability: Q = −10 × log₁₀(P_error). Q10 = a 1-in-10 chance of being wrong, Q20 = 1-in-100, Q30 = 1-in-1000. Every +10 is a tenfold drop in error. | The common currency for confidence across this whole stack — per-base qualities in a FASTQ, GQ on a genotype, QUAL on a variant record. Worth internalising that it is logarithmic: GQ 40 is not twice as good as GQ 20, it is a hundred times. |
GQ (genotype quality) | Phred-scaled confidence in this genotype being the right one. Higher is better: GQ 20 ≈ 99 % confident, GQ 30 ≈ 99.9 %. | ⚠ Our reblocked gVCF carries GQ = 0 on every reference block — the reblocker does not preserve per-site GQ across a collapsed block. Gating hom-ref on GQ would therefore reject every hom-ref call and silently recreate the bug we were fixing. We gate hom-ref on FILTER + MIN_DP instead, and use GQ only for variant calls. |
DP (depth) | How many reads covered this position. | Low DP means the call rests on little evidence; very high DP often means a collapsed repeat region, so we also apply a ceiling at 3× the autosomal median. |
MIN_DP | The minimum depth anywhere inside a reference block. | A block summarises many positions, so the minimum is the honest figure — the weakest position in the run is what you are actually trusting. |
FILTER / PASS | The caller’s own verdict on the record. PASS means it cleared the caller’s internal checks. | Our first gate. A FAIL record becomes EXPLICIT_MISSING, never a scored zero. |
3. The files (rawest → most processed)
| Term | Meaning | Why it matters here |
|---|---|---|
| WGS (whole-genome sequencing) | Reading (nearly) the entire genome, not a gene panel. | Polygenic scores need genome-wide breadth — a panel can’t supply them. |
Depth / 30× | How many times, on average, each base was read (~30). | Depth is what lets us confidently call het vs hom (1× can’t). |
| FASTQ | The rawest output: the actual sequenced read fragments + per-base quality. | Most verbose, largest (tens of GB); re-alignable to any build. |
| FASTA | A plain-text sequence format: a >name header line, then the bases. No quality scores, no alignment, no coordinates beyond position-within-the-contig. This is how a reference genome ships. Ours opens literally like this: >1 dna:chromosome chromosome:GRCh38:1:1:248956422:1 REF, then 248,956,422 bases of chromosome 1 wrapped at 60 per line. | ⚠ Easy to confuse with FASTQ one letter away, and they are opposites in this pipeline: FASTQ is the patient’s reads with quality; the FASTA here is the reference — the same for everybody, and the arbiter of what the REF allele is at every locus we score. See §4 for ours. |
.fai index | A small sibling file (…fa.fai), one tab-separated line per contig — name, length, byte-offset, bases-per-line, bytes-per-line — so a tool can seek straight to any coordinate without reading the 3.15 GB file. Ours is 194 lines, e.g. 1 248956422 56 60 61. | Built by pyfaidx on first open — a ~28 s one-time cost worth triggering deliberately rather than discovering inside a worker task. Random access is what makes a per-locus REF lookup cheap enough to do millions of times. |
| BAM / CRAM | Those reads aligned to genome positions (CRAM = compressed BAM). | The convenient middle tier — not always provided. |
| VCF (Variant Call Format) | The distilled list of a person’s variants. | What the PRS pipeline actually consumes. |
| gVCF / block-gVCF | A VCF that also records long runs where the person matches the reference (“reference blocks”), not just the variants. | Lets us tell “same as reference here” from “we didn’t look here.” |
| Reference block | A compressed run of hom-ref positions in a gVCF. | Millions of these in a WGS gVCF — we skip them at ingest (they carry no scoreable variant). |
| CNV / SV | Copy-number variants (chunks duplicated/deleted) / structural variants (large rearrangements). | Not used for PRS; candidate knowledge-graph features later. |
4. The reference and the scores
| Term | Meaning | Why it matters here |
|---|---|---|
| Reference genome / build | The standard “map” all coordinates are measured against. GRCh38 is current; GRCh37 (a.k.a. hg19) is the older one. | The same variant has different chr:pos numbers across builds — mismatch and nothing lines up. We keep everything on GRCh38 so no liftover is needed. |
| Primary assembly vs full analysis set | Two flavours of the “same” build. Primary = chromosomes + unplaced scaffolds. Full analysis set additionally carries alt contigs (alternate versions of highly polymorphic regions like HLA) and decoys. | We use the primary assembly, 194 contigs, because that is what the patient’s gVCF was called against. Mixing flavours degrades calls quietly: if reads can align to both an alt contig and its primary counterpart, mapping quality collapses. |
| PGS / PRS | Polygenic Score / polygenic Risk Score — the weighted-sum number (“risk” when the trait is a disease). | The output. |
effect_weight (beta) | The per-variant weight, estimated from a GWAS. Can be negative (a protective allele). | The multiplier in the weighted sum. |
| GWAS (genome-wide association study) | The study design that estimates which variants associate with a trait, and how strongly. | Where the weights come from. |
| LD (linkage disequilibrium) | Variants sitting near each other on a chromosome tend to be inherited together as a block, so knowing one often tells you the others. Two variants are “in LD” when their inheritance is correlated across a population. | Two consequences that shape everything here. (1) A score can use a tag variant that is merely correlated with whatever is biologically doing the work — which is exactly why PRS weights are not causal. (2) Post-2015 methods re-weight every variant against an LD reference panel instead of picking significant hits, which is why the catalog holds scores with millions of variants and no rsIDs, and why file sizes span four orders of magnitude (prs_primer.md §3.3). |
| Allele frequency / EAF (effect allele frequency) | How common an allele is in some population, 0–1. A scoring file may carry allelefrequency_effect — the frequency of the effect allele in the cohort the score was trained on. | The instrument that resolves a palindromic SNP (§2): if the effect allele sits at 0.12, the strand can be inferred by asking which reading matches the panel. One near 0.5 stays genuinely unresolvable. This is why palindrome resolution is Gate D work and not Gate C — it needs a reference panel, not just the scoring file. |
| PGS Catalog | The public repository of published scores (~5,385 with GRCh38-harmonized files). | Our source for the “whole catalog” sweep. |
| Harmonization | The PGS Catalog’s own pre-processing of a submitted scoring file: mapping every variant onto a common build (by liftover or rsID lookup), adding the hm_* columns, and inferring an other-allele where the author gave only one. | We consume only harmonized files, so the Catalog’s liftover is the one we trust and we never lift over at score time (§4, and prs_primer.md §2.4). But harmonization is inference, not fact: hm_inferOtherAllele is a guess, and where the harmonizer cannot choose it emits a candidate set like A/T — which is not a sequence at all, and is why the classifier has an ambiguous_other category. |
Harmonized file / hm_rsID, hm_chr, hm_pos | PGS Catalog’s normalized scoring files with consistent columns. | hm_rsID is frequently empty on big modern scores — exactly why the chr:pos fallback earns its keep. |
| Ancestry / portability | Most published scores were trained largely in European-ancestry cohorts, so accuracy in other ancestries is uncertain. | A caveat for interpreting results, not for the plumbing — flag it before anyone reads a score clinically. |
| Normalization (z-score / percentile) | Raw PRS sums aren’t comparable across scores (different weight scales); they’re only meaningful relative to a reference distribution. | We compute and store raw scores, but they need this step before clinical interpretation — and we don’t ship reference panels by default. |
4.1 Our reference FASTA — where it comes from
We do not compute or assemble it. A reference genome is a published artifact built by the Genome Reference Consortium from a handful of anonymous donors; our job is only to pick one, pin it, and prove we have the exact bytes. “How we calculate ours” has no calculation in it — which is the point, because the FASTA is the arbiter every other check appeals to.
| What | Ensembl release-109, Homo sapiens GRCh38 DNA primary assembly (= GRCh38.p13) |
| Why this one | Its contig naming (1, not chr1) matches both the patient’s gVCF and the PGS Catalog’s hm_chr column, so nothing needs renaming mid-pipeline |
| How it is fetched | backend/scripts/prs/prepare_reference_fasta.py — downloads, verifies, decompresses in one step. Ensembl serves it as plain gzip, not bgzip, so it must be decompressed to a flat .fa before pyfaidx can index it |
| How we know it is right | A pinned sha256 (78777b08…fca93d8c) on the uncompressed file, checked against connectors/reference_fasta.py::PINNED_GRCH38. A mismatch hard-fails; there is no fallback, because scoring a patient against an unverified genome is worse than not scoring her |
| Sanity checks beyond the checksum | 194 contigs (confirms the primary-assembly flavour), and the reference base matches the patient’s own gVCF REF at three known loci: one clinically-informative site on chr11, 5:33951588 = C (SLC45A2), 2:135851076 = G (LCT) |
| Size | ~840 MB compressed, 3.15 GB as .fa, plus a small .fai. Host-only under backend/data/reference/GRCh38/ — gitignored, and it does not follow a git worktree |
One practical note, since it has cost a session before: ftp.ensembl.org throttles per
connection (~100 KB/s), so a single-stream fetch of this file takes ~2.5 h. It honours HTTP
Range and does not throttle in aggregate, so the script fetches byte ranges in parallel and
reassembles — about nine minutes. Correctness still rests on the checksum, not on the
reassembly.
Where this lives in the code
- Genotype ingest:
backend/connectors/genotype_importer.py(iter_vcf_records) →backend/mods_genotype_service.py(upsert_genotype_records) →mods_genotypetable. - Scoring:
backend/connectors/pgs_catalog_connector.py(download + parse) →backend/mods_prs_scorer.py(dosage against the dense callset, position-keyed) →mods_polygenic_scoretable. - Orchestration: worker tasks
genotype_ingest_wgsandmods_prs_computeinworker/task_handlers.py.