just_dna_enricher.gene_spans¶
just_dna_enricher.gene_spans ¶
Gene symbol → GRCh38 span, from the operator-built MANE snapshot (0.7, RM194).
There was no such helper in this tier before, which is why this is a module with a plain name
rather than a private function inside the pass that first needed one. The rule is
@roster-is-as-wide-as-the-tables-it-reads's last line — grep for the question, not the bug, since
a private name keeps the second caller from finding the first. "Where does a gene's span come from"
is a question three different passes could ask, and RM194's entry lists the three candidate answers
precisely because nobody had written one down.
A span from here is a QUERY HINT, never an attribution. That distinction is what makes the
design decisions below easy, and it is @gene-map-is-another-sources-attribution read carefully: a
source with no gene column is drafted by gene through another source's per-record attribution and
never from a span, because a min/max span is a false gene claim wherever genes overlap or nest.
RM194's case inverts that — AlphaGenome is the attributing source, it names the gene on every
record it returns, and the span only decides which interval to ask about. So a span that is too wide
costs query time and nothing else, and a span that is stale means variants were missed rather than
wrongly labelled. Nothing downstream may use a span from here to assign a gene to anything.
Why MANE and not the alternatives. The Ensembl snapshot is eliminated by its own schema — it is a
variant table with no gene column at all. A module's own authored gene is a symbol rather than a
span, so it needs one of the others anyway. MANE is the remaining candidate and is operator-built, so
absence is the ordinary case and has to be an answer rather than an exception.
Three outcomes, not two (@unreachable-not-absent). No snapshot is nobody asked; a snapshot
that does not name the symbol is asked and absent; and a symbol placed on a contig RefSeq does not
number in the primary assembly is a third, because returning a coordinate space we cannot name would
be worse than saying so. SpanLookup keeps them apart by reason, and a caller that collapses them
back into a bare None has thrown away the difference between "build the MANE lane" and "check the
spelling of that symbol".
GeneSpanError ¶
Bases: RuntimeError
The MANE snapshot is present and could not be read — a defect, not an absence.
GeneSpan
dataclass
¶
One gene's GRCh38 extent, 1-based inclusive, as MANE places it.
widened ¶
(chrom, start, end) with the attribution horizon added either side, clamped at 1.
Clamped rather than allowed to go negative: start is a 1-based VCF position everywhere in
this workspace (@start-1based), and a gene within 512 kb of a contig's start is the ordinary
case on the short arms rather than an edge worth refusing.
Source code in enricher/src/just_dna_enricher/gene_spans.py
SpanLookup
dataclass
¶
What happened when a symbol was looked up. Exactly one of span/reason is set.
gene_span ¶
The GRCh38 span MANE gives symbol, or a reason there is none.
Rows are unioned rather than picked between, and that is a consequence of this being a hint. A symbol can carry more than one summary row — MANE Select plus MANE Plus Clinical — and the two are different transcripts of one gene, so choosing one would be choosing a transcript on the caller's behalf for a value that is not about transcripts at all. The union is the widest window, and a window that is too wide costs query time while a window that is too narrow drops the distal variants RM194 exists to find. Asymmetric costs, so take the wide side.
Rows disagreeing on the contig is the one case that refuses: that is not a wider gene, it is two genes wearing one symbol, and a span spanning both would name a region containing neither.