Skip to content

Genomics

The primary domain of IRIS. Models the complete lifecycle of genomic variant analysis: from raw variant calls through annotation and scoring to clinically classified variants ready for interpretation.

Entity Relationship Diagram

Genomics ERD

Overview

Central aggregate: VariantAnnotation

VariantAnnotation is the main output of the annotation pipeline. It holds a core Variant together with everything derived from external sources:

  • allele frequencies from one or more populations
  • clinical classifications (ClinVar, ACMG, lab-specific)
  • computational scores (CADD, pLI, LOEUF, splice, …)
  • affected genes and per-transcript consequences
  • ancestry annotations
  • free-form Metadata for provenance

All collection fields default to empty tuples, so a partially annotated variant is valid at every stage of the pipeline. Annotations accumulate incrementally as each adapter enriches the object.

Entities

Entity Purpose
Variant Core variant: type, CPRA/HGVS nomenclature, alleles, position
VariantAnnotation Central aggregate — variant + all derived annotations
Gene HGNC gene with optional chromosomal position
GeneAnnotation Full gene knowledge: transcripts, scores, diseases, pathways
Transcript Transcript structure: exons, UTRs, CDS intervals, MANE Select flag
TranscriptVariant Variant effect on a specific transcript (SO terms, HGVS, codon change)
Disease Genetic disease with MONDO/OMIM/Orphanet identifiers and HPO phenotypes
ResolvedGeneRegion Output of resolve_gene_regions: gene + genomic regions + transcript used
ResolvedGeneRegions Aggregate output: all resolved regions + unresolved symbols

Value objects and enumerations (in objects.py)

Key value objects:

  • GenomicPosition — chromosome, start, end (1-based inclusive), strand. Supports overlaps(), contains(), distance_to().
  • AlleleFrequency — allele frequency with population label, AC, AN, and homozygous count.
  • ClinicalClassification — significance level (PATHOGENIC → BENIGN), source, and evidence rank.
  • VariantScore — named computational score with type, value, tier, and metadata.
  • GenotypingMetrics — call quality metrics (depth, call rate, dosage, phasing).
  • VariantNomenclature — CPRA identifier and HGVS notations.
  • GeneNomenclature — HGNC symbol set (backed by AliasSet) and cross-database IDs.
  • GeneQuery — input specification for resolve_gene_regions: gene symbols, file path, transcript tag filter, inheritance mode filter, and disease name filter. Factory methods: from_file(), recessive(), dominant().

Key enumerations:

  • VariantType — SNV, MNV, INDEL, DUP, DEL, CNV, SV, VNTR, OTHER.
  • ClinicalSignificance — 14 levels from PATHOGENIC to BENIGN with pathogenicity_rank.
  • MendelianInheritance — composite flag (AD, AR, XLR, XLD, …) backed by SerializableFlag.
  • Biotype — PROTEIN_CODING, NON_CODING, PSEUDOGENE, IMMUNE_RECEPTOR, TEC, OTHER.
  • Zygosity — HOMOZYGOUS, HETEROZYGOUS, HEMIZYGOUS, UNKNOWN.
  • CellOrigin — GERMLINE, SOMATIC, UNKNOWN.

Filters

filters.py defines composable boolean predicates for VariantAnnotation:

filt = AllOf(
    MaxPopulationFrequency(0.01),
    AnyOf(HasClinicalSignificance(PATHOGENIC), HighImpactConsequence()),
)
passing = [va for va in variants if filt(va)]

Boolean combinators (AllOf, AnyOf, Not) let you build arbitrarily complex filter trees from simple leaf predicates. Every predicate is a callable that accepts a VariantAnnotation and returns bool.

Domain services

Two domain services wrap pure logic and inject ports for operations that require external backends:

  • GenomicRangeService — pure range operations (overlap detection, sorting, normalization) plus delegated set operations via the RangeOperations port (union, intersection, coverage, gap finding…).
  • HpoService — ontology traversal and semantic similarity via the HpoOperations port.

Ports (driven)

Ports are the domain's outward-facing contracts — abstract base classes that adapters implement. The domain never imports adapters directly.

Port Purpose
VariantAnnotator Enrich a VariantAnnotation from an external source
GeneAnnotator Enrich a GeneAnnotation from an external source
RegionReader Read genomic intervals from a file or database
RegionExporter Write genomic intervals to a file or stream
GeneNomenclatureResolver Resolve symbols, aliases, and cross-database IDs
RangeOperations Set-level operations on GenomicPosition collections
HpoOperations HPO ontology traversal and semantic similarity
GenotypeProcessor User-defined computation over a block of genotype data, called by ZarrVariantStore

Coordinate convention

GenomicPosition uses 1-based inclusive coordinates throughout the domain. Adapters are responsible for converting on input:

  • BED files (0-based half-open) → converted in BedRegionReader
  • pysam .fetch() (0-based half-open) → add 1 to start before constructing GenomicPosition

Entities

iris.domain.genomics.entities

Genomics domain entities: Disease, Gene, GeneAnnotation, Transcript, TranscriptStructure, TranscriptVariant, Variant, VariantAnnotation.

Disease

A genetic disease with associated phenotypes, prevalence, and anatomical involvement.

nomenclature carries the canonical name and cross-database identifiers (MONDO, OMIM, Orphanet). phenotypes lists the associated HPO terms. prevalence may be a human-readable string (e.g. "1/10 000") or a float, depending on the data source.

Gene

A genomic gene with its nomenclature and chromosomal position.

Use from_symbol() for the common case of constructing from a bare HGNC symbol. symbol and aliases are convenience properties that delegate to the underlying GeneNomenclature.

symbol property

symbol

Return the primary gene symbol.

aliases property

aliases

Return all gene symbols and aliases as a set.

from_symbol classmethod

from_symbol(symbol, position=None)

Create a Gene using its symbol.

Source code in src/iris/domain/genomics/entities.py
@classmethod
def from_symbol(cls, symbol: str, position: GenomicPosition | None = None) -> Gene:
    """Create a Gene using its symbol."""
    return cls(nomenclature=GeneNomenclature.from_symbol(symbol), position=position)

Transcript

Lightweight transcript identity: nomenclature, biotype, and selection flags.

Carries only the information needed to identify a transcript and determine its role (canonical, MANE Select). Structural data (exons, UTRs, CDS) lives in :class:TranscriptStructure, which is held by :class:GeneAnnotation.

name property

name

Return the primary transcript identifier.

TranscriptStructure

Full structural data for a transcript: position, exons, UTRs, and CDS.

Wraps a lightweight :class:Transcript with the genomic intervals needed for positional annotation. Lives exclusively in :class:GeneAnnotation; never embedded in :class:TranscriptVariant.

CDS intervals mirror GENCODE CDS features: one entry per coding exon. Frame (0/½) is stored in GenomicPosition.markers[0] when available. UTR type (5' vs 3') is encoded in GenomicPosition.name as "5UTR" or "3UTR".

name property

name

Return the primary transcript identifier.

nomenclature property

nomenclature

Return the transcript nomenclature.

biotype property

biotype

Return the transcript biotype.

metadata property

metadata

Return the transcript metadata.

is_canonical property

is_canonical

Return True if this is the Ensembl canonical transcript.

is_mane_select property

is_mane_select

Return True if this is the MANE Select transcript.

GeneAnnotation

Full annotation for a gene including transcripts, scores, diseases, and pathways.

Aggregates all available knowledge about a gene: its canonical transcripts (as :class:TranscriptStructure objects carrying full exon/CDS/UTR data), dosage sensitivity assessments, associated diseases, pathway memberships, expression data, and computational scores (e.g. pLI, LOEUF). All collection fields default to empty tuples so partial annotations are valid.

primary_transcript

primary_transcript()

Return the preferred transcript structure for this gene.

    Selection priority: MANE Select > Ensembl canonical > first available.
    Returns None if the gene has no annotated transcripts.

Example:

ann = repo.get_annotation("BRCA1")
ts = ann.primary_transcript()
if ts:
    exon_regions = list(ts.exons)

Source code in src/iris/domain/genomics/entities.py
def primary_transcript(self) -> "TranscriptStructure | None":
    """Return the preferred transcript structure for this gene.

            Selection priority: MANE Select > Ensembl canonical > first available.
            Returns None if the gene has no annotated transcripts.

    Example:
    ```python
    ann = repo.get_annotation("BRCA1")
    ts = ann.primary_transcript()
    if ts:
        exon_regions = list(ts.exons)
    ```
    """
    for ts in self.transcripts:
        if ts.is_mane_select:
            return ts
    for ts in self.transcripts:
        if ts.is_canonical:
            return ts
    return self.transcripts[0] if self.transcripts else None

TranscriptVariant

The consequence of a variant on a specific transcript.

consequences is a tuple of Sequence Ontology terms (e.g. "missense_variant") as returned by VEP or similar tools. HGVS coding and protein notations, codon/amino-acid changes, and positional fields (exon, intron, cDNA, CDS, protein) are populated when available.

transcript_name property

transcript_name

Return the transcript identifier.

is_mane_select property

is_mane_select

Return True if the transcript is MANE Select.

Variant

A genomic variant defined by its type, nomenclature, alleles, and position.

cpra property

cpra

Return the canonical variant identifier (CPRA string).

is_snv property

is_snv

Return True if the variant is a single nucleotide variant.

is_indel property

is_indel

Return True if the variant is an insertion or deletion.

is_cnv property

is_cnv

Return True if the variant is a copy number variant.

length property

length

Return the length of the variant.

as_key

as_key()

Return (chrom, pos_1based, ref, alt) if all fields are present, else None.

Source code in src/iris/domain/genomics/entities.py
def as_key(self) -> tuple[str, int, str, str] | None:
    """Return (chrom, pos_1based, ref, alt)
    if all fields are present, else None.
    """
    if (
        self.position is not None
        and self.position.start is not None
        and self.alleles is not None
        and self.alleles.alternate
    ):
        return (
            self.position.chromosome,
            self.position.start,
            self.alleles.reference,
            self.alleles.alternate[0],
        )
    return None

VariantAnnotation

Full annotation for a variant including genes, frequencies, classifications, and scores.

Central aggregate of the genomics domain. Holds the core Variant together with all data derived from external sources: allele frequencies from one or more populations, clinical classifications (ClinVar, etc.), computational scores (CADD, LoF, …), affected genes and transcript consequences, ancestry annotations, and free-form metadata. All collection fields default to empty tuples so partially annotated variants are valid at every stage of the pipeline.

gene_symbols property

gene_symbols

Return all gene symbols associated with this variant.

top_classification property

top_classification

Return the classification with the highest pathogenicity rank, or None.

mane_consequence property

mane_consequence

Return the TranscriptVariant for the MANE Select transcript, or None.

all_consequences property

all_consequences

Return all consequence terms across all transcripts, in order.

most_severe_consequence property

most_severe_consequence

Return the most severe consequence term across all transcripts.

Severity follows the standard VEP/Ensembl SO ranking. Returns None if no transcripts are annotated.

is_lof property

is_lof

Return True if a LoF score has been annotated on this variant.

is_pathogenic property

is_pathogenic

Return True if the top classification is Pathogenic or Likely Pathogenic.

af_for

af_for(population)

Return the AlleleFrequency for the given population label, or None.

Source code in src/iris/domain/genomics/entities.py
def af_for(self, population: str) -> AlleleFrequency | None:
    """Return the AlleleFrequency for the given population label, or None."""
    return next((af for af in self.allele_frequencies if af.population == population), None)

score_for

score_for(name)

Return the first VariantScore with the given name, or None.

Source code in src/iris/domain/genomics/entities.py
def score_for(self, name: str) -> VariantScore | None:
    """Return the first VariantScore with the given name, or None."""
    return next((s for s in self.scores if s.name == name), None)

with_top_classification

with_top_classification()

Return a copy retaining only the most pathogenic clinical classification.

A no-op when zero or one classifications are already present. Useful when normalising variants before serialisation or when downstream consumers expect at most one classification per variant (e.g. writing to a flat TSV or an Arrow schema with a single classification column).

The selection criterion is the same as :attr:top_classification: the classification with the highest :attr:~iris.domain.genomics.objects.ClinicalSignificance.pathogenicity_rank.

RETURNS DESCRIPTION
VariantAnnotation

A new VariantAnnotation identical to self except that

VariantAnnotation

classifications contains at most one element. Returns

VariantAnnotation

self unchanged when len(self.classifications) <= 1.

Example
va = annotator.annotate([variant])[0]
# va.classifications may hold several conflicting ClinVar entries
normalised = va.with_top_classification()
assert len(normalised.classifications) <= 1
Source code in src/iris/domain/genomics/entities.py
def with_top_classification(self) -> VariantAnnotation:
    """Return a copy retaining only the most pathogenic clinical classification.

    A no-op when zero or one classifications are already present.
    Useful when normalising variants before serialisation or when
    downstream consumers expect at most one classification per variant
    (e.g. writing to a flat TSV or an Arrow schema with a single
    classification column).

    The selection criterion is the same as :attr:`top_classification`:
    the classification with the highest
    :attr:`~iris.domain.genomics.objects.ClinicalSignificance.pathogenicity_rank`.

    Returns:
        A new ``VariantAnnotation`` identical to ``self`` except that
        ``classifications`` contains at most one element.  Returns
        ``self`` unchanged when ``len(self.classifications) <= 1``.

    Example:
        ```python
        va = annotator.annotate([variant])[0]
        # va.classifications may hold several conflicting ClinVar entries
        normalised = va.with_top_classification()
        assert len(normalised.classifications) <= 1
        ```
    """
    if len(self.classifications) <= 1:
        return self
    import attrs

    return attrs.evolve(self, classifications=(self.top_classification,))

ResolvedGeneRegion

A gene resolved to its genomic query regions, ready for variant extraction.

Produced by the resolve_gene_regions use case. Carries the full GeneAnnotation alongside the actual regions that were used, so downstream callers can inspect which transcript was chosen and why.

ATTRIBUTE DESCRIPTION
gene

Canonical Gene identity (nomenclature, Ensembl ID, position).

TYPE: Gene

annotation

Full GeneAnnotation from the repository — includes all transcripts, diseases, scores, and metadata. Enables post-resolution filtering (e.g. annotation.diseases for disease association).

TYPE: GeneAnnotation

regions

Genomic positions to query for variants — typically the exons of transcript_used after any splice buffer has been applied, or the full gene body if no usable transcript was found.

TYPE: tuple[GenomicPosition, ...]

transcript_used

The TranscriptStructure whose exons were used as the base regions. None when the gene body was used as fallback.

TYPE: TranscriptStructure | None

resolution_method

How the regions were derived: "transcript" (exons of a specific transcript), or "gene_body" (full span of the gene when no transcript with exons could be found).

TYPE: str

ResolvedGeneRegions

Aggregate output of the resolve_gene_regions use case.

Provides convenience methods to extract the flat list of regions or the gene cache needed by downstream components like VepFlightAnnotator.

ATTRIBUTE DESCRIPTION
resolved

One ResolvedGeneRegion per successfully resolved gene, in the order they were requested.

TYPE: tuple[ResolvedGeneRegion, ...]

unresolved_symbols

Gene symbols that could not be resolved to any annotation or genomic position. Callers should log or report these.

TYPE: tuple[str, ...]

all_regions

all_regions()

Return a flat list of all query regions across every resolved gene.

Suitable for passing directly to VariantExtractionPipeline.run().

Example:

result = resolve_gene_regions(query, gene_repository=repo)
pipeline.run(regions=result.all_regions())

Source code in src/iris/domain/genomics/entities.py
def all_regions(self) -> list[GenomicPosition]:
    """Return a flat list of all query regions across every resolved gene.

    Suitable for passing directly to ``VariantExtractionPipeline.run()``.

    Example:
    ```python
    result = resolve_gene_regions(query, gene_repository=repo)
    pipeline.run(regions=result.all_regions())
    ```
    """
    return [r for rg in self.resolved for r in rg.regions]

gene_cache

gene_cache()

Return a mapping of gene identifiers to Gene objects.

Seeds VepFlightAnnotator(gene_cache=...) so that VEP annotation rows are matched to the same Gene instances used everywhere else in the pipeline, preserving object identity across the full run.

Keys include the versioned Ensembl ID (e.g. "ENSG00000012048.23"), the unversioned ID ("ENSG00000012048"), and the HGNC symbol ("BRCA1").

Source code in src/iris/domain/genomics/entities.py
def gene_cache(self) -> dict[str, Gene]:
    """Return a mapping of gene identifiers to Gene objects.

    Seeds ``VepFlightAnnotator(gene_cache=...)`` so that VEP annotation
    rows are matched to the same Gene instances used everywhere else in
    the pipeline, preserving object identity across the full run.

    Keys include the versioned Ensembl ID (e.g. ``"ENSG00000012048.23"``),
    the unversioned ID (``"ENSG00000012048"``), and the HGNC symbol
    (``"BRCA1"``).
    """
    cache: dict[str, Gene] = {}
    for rg in self.resolved:
        gene = rg.gene
        ensembl_id = gene.nomenclature.ensembl_id
        if ensembl_id:
            cache[ensembl_id] = gene
            cache[ensembl_id.split(".")[0]] = gene
        cache[gene.symbol] = gene
    return cache

Objects

iris.domain.genomics.objects

Genomics domain value objects: enumerations, genomic positions, alleles, and nomenclatures.

Biotype

Bases: Enum

Functional biotype classification of a gene or transcript.

CellOrigin

Bases: Enum

Origin of the cell line from which a variant was detected.

ClinicalSignificance

Bases: Enum

ACMG/AMP clinical significance classification for a variant.

pathogenicity_rank property

pathogenicity_rank

Return an integer rank representing pathogenicity severity (higher is more severe).

is_pathogenic property

is_pathogenic

Return True for Pathogenic, Pathogenic_low_penetrance, or Likely_pathogenic.

is_benign property

is_benign

Return True for Benign or Likely_benign.

EvidenceRank

Bases: IntEnum

Ordinal rank for the strength of evidence supporting a clinical assertion.

GeneDosageSensitivityType

Bases: Enum

Type of gene dosage sensitivity.

GeneScoreType

Bases: Enum

Category of a gene-level computational score.

GenotypingMetricType

Bases: Enum

Type of genotyping quality metric recorded for a variant call.

InheritanceOrigin

Bases: Enum

Inheritance origin of a variant in a proband.

MendelianInheritance

Bases: SerializableFlag

A mode of inheritance of diseases traced back to variants in a single gene.

Supports composite modes via bitwise OR:

Example
mode = MendelianInheritance.AD | MendelianInheritance.AR
MendelianInheritance.AD in mode
# True
mode.to_list()
# ['AD', 'AR']
MendelianInheritance.from_list(["AD", "AR"]) == mode
# True

is_x_linked

is_x_linked()

Return True if any X-linked mode is active.

RETURNS DESCRIPTION
bool

True if XLR or XLD is set.

Source code in src/iris/domain/genomics/objects.py
def is_x_linked(self) -> bool:
    """Return True if any X-linked mode is active.

    Returns:
        True if XLR or XLD is set.
    """
    return bool(self & MendelianInheritance.XL)

is_recessive

is_recessive()

Return True if any recessive mode is active.

RETURNS DESCRIPTION
bool

True if AR or XLR is set.

Source code in src/iris/domain/genomics/objects.py
def is_recessive(self) -> bool:
    """Return True if any recessive mode is active.

    Returns:
        True if AR or XLR is set.
    """
    return bool(self & (MendelianInheritance.AR | MendelianInheritance.XLR))

is_dominant

is_dominant()

Return True if any dominant mode is active.

RETURNS DESCRIPTION
bool

True if AD or XLD is set.

Source code in src/iris/domain/genomics/objects.py
def is_dominant(self) -> bool:
    """Return True if any dominant mode is active.

    Returns:
        True if AD or XLD is set.
    """
    return bool(self & (MendelianInheritance.AD | MendelianInheritance.XLD))

to_list

to_list()

Return active primitive member names for Postgres TEXT[] storage.

RETURNS DESCRIPTION
list[str]

Sorted list of primitive member names.

Source code in src/iris/domain/generic/abstracts.py
def to_list(self) -> list[str]:
    """Return active primitive member names for Postgres TEXT[] storage.

    Returns:
        Sorted list of primitive member names.
    """
    return sorted(
        m.name for m in type(self) if m in self and _is_primitive(m) and m.name is not None
    )

to_str

to_str()

Return a pipe-separated string for VARCHAR storage.

RETURNS DESCRIPTION
str

Pipe-separated primitive member names (e.g. 'AD|AR').

Source code in src/iris/domain/generic/abstracts.py
def to_str(self) -> str:
    """Return a pipe-separated string for VARCHAR storage.

    Returns:
        Pipe-separated primitive member names (e.g. 'AD|AR').
    """
    return "|".join(self.to_list())

from_list classmethod

from_list(values)

Reconstruct from a list of member names.

PARAMETER DESCRIPTION
values

List of member names (e.g. ['AD', 'AR']).

TYPE: list[str]

RETURNS DESCRIPTION
SerializableFlag

Combined flag instance.

RAISES DESCRIPTION
KeyError

If any value is not a valid member name.

Source code in src/iris/domain/generic/abstracts.py
@classmethod
def from_list(cls, values: list[str]) -> SerializableFlag:
    """Reconstruct from a list of member names.

    Args:
        values: List of member names (e.g. ['AD', 'AR']).

    Returns:
        Combined flag instance.

    Raises:
        KeyError: If any value is not a valid member name.
    """
    if not values:
        return cls(0)
    result = cls(0)
    for name in values:
        result |= cls[name]
    return result

from_str classmethod

from_str(value)

Reconstruct from a pipe-separated string.

PARAMETER DESCRIPTION
value

Pipe-separated member names (e.g. 'AD|AR').

TYPE: str

RETURNS DESCRIPTION
SerializableFlag

Combined flag instance.

RAISES DESCRIPTION
KeyError

If any part is not a valid member name.

Source code in src/iris/domain/generic/abstracts.py
@classmethod
def from_str(cls, value: str) -> SerializableFlag:
    """Reconstruct from a pipe-separated string.

    Args:
        value: Pipe-separated member names (e.g. 'AD|AR').

    Returns:
        Combined flag instance.

    Raises:
        KeyError: If any part is not a valid member name.
    """
    return cls.from_list([p.strip() for p in value.split("|") if p.strip()])

primitives

primitives()

Return active primitive (non-composite) members.

RETURNS DESCRIPTION
list[Self]

List of primitive flag members.

Source code in src/iris/domain/generic/abstracts.py
def primitives(self) -> list[Self]:
    """Return active primitive (non-composite) members.

    Returns:
        List of primitive flag members.
    """
    return [m for m in type(self) if m in self and _is_primitive(m)]

ScorePredictionRank

Bases: IntEnum

Ordinal rank for the predicted effect tier of a computational score.

Strand

Bases: Enum

Genomic strand orientation.

SuperPopulation

Bases: Enum

Five-letter continental super-population codes used in population genetics databases.

VariantType

Bases: Enum

Classification of a genetic variant by its structural type.

Covers the full spectrum from single-nucleotide changes to large structural rearrangements. Used throughout the domain to drive type-specific logic in filters, annotators, and exporters.

ATTRIBUTE DESCRIPTION
SNV

Single nucleotide variant — one base substituted for another (e.g. A>T). Also called SNP when present in a population.

MNV

Multi-nucleotide variant — two or more consecutive bases substituted simultaneously (e.g. AT>GC). Distinguished from adjacent independent SNVs by co-occurrence on the same haplotype.

INDEL

Small insertion or deletion, typically under 50 bp. Includes pure insertions (ref shorter than alt), pure deletions (alt shorter than ref), and complex indels (both lengths differ and ref ≠ alt).

DUP

Duplication — a segment of the genome is copied one or more additional times. Represented in VCF as symbolic alleles such as <DUP> or <DUP:TANDEM>.

DEL

Deletion — a segment of the genome is absent relative to the reference. Represented as <DEL> for large events; small deletions are captured by :attr:VariantType.INDEL.

CNV

Copy number variation — gain or loss of a genomic segment without specifying the precise breakpoints or mechanism. Broader than :attr:VariantType.DUP or :attr:VariantType.DEL when the directionality is unknown or mixed.

SV

Structural variant — large-scale rearrangement (typically >50 bp) including inversions, translocations, and complex events not captured by the more specific types above.

VNTR

Variable number tandem repeat — a locus where a short motif is repeated a variable number of times across individuals (e.g. microsatellites, minisatellites).

OTHER

Catch-all for variant representations that do not fit any of the categories above (e.g. unknown symbolic alleles, malformed records).

Example
VariantType.from_string("snv")
# <VariantType.SNV: 'snv'>

VariantType.from_key(("1", 100, "A", "T"))
# <VariantType.SNV: 'snv'>

VariantType.from_key(("1", 100, "A", "<DUP>"))
# <VariantType.DUP: 'duplication'>

from_string classmethod

from_string(value)

Parse a string into a VariantType, case-insensitive.

PARAMETER DESCRIPTION
value

String representation of the variant type. Matched against member values after stripping whitespace and lowercasing (e.g. 'SNV', 'indel', 'Deletion').

TYPE: str

RETURNS DESCRIPTION
VariantType

Matching VariantType member.

RAISES DESCRIPTION
ValueError

If no member value matches the input string.

Example
VariantType.from_string("SNV")
# <VariantType.SNV: 'snv'>
VariantType.from_string("  Indel  ")
# <VariantType.INDEL: 'indel'>
Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_string(cls, value: str) -> VariantType:
    """Parse a string into a VariantType, case-insensitive.

    Args:
        value: String representation of the variant type. Matched against
               member values after stripping whitespace and lowercasing
               (e.g. ``'SNV'``, ``'indel'``, ``'Deletion'``).

    Returns:
        Matching VariantType member.

    Raises:
        ValueError: If no member value matches the input string.

    Example:
        ```python
        VariantType.from_string("SNV")
        # <VariantType.SNV: 'snv'>
        VariantType.from_string("  Indel  ")
        # <VariantType.INDEL: 'indel'>
        ```
    """
    normalized = value.strip().lower()
    for member in cls:
        if member.value == normalized:
            return member
    raise ValueError(f"Unknown VariantType: {value!r}")

from_alleles classmethod

from_alleles(ref, alt, *, symbolic_map=None)

Infer a VariantType from reference and alternate allele strings.

Applies rules in the following order:

  1. Symbolic ALT <TOKEN> → look up TOKEN in symbolic_map if provided; otherwise fall back to substring matching (DUP → :attr:DUP, DEL → :attr:DEL, CNV → :attr:CNV); any other symbolic allele → :attr:SV.
  2. Missing/spanning ALT ("*" or "") → :attr:OTHER.
  3. len(ref) == 1 and len(alt) == 1 → :attr:SNV.
  4. len(ref) > 1 and len(alt) > 1 and equal lengths → :attr:MNV.
  5. len(ref) != len(alt) → :attr:INDEL.
  6. Anything else → :attr:OTHER.
PARAMETER DESCRIPTION
ref

Reference allele string.

TYPE: str

alt

Alternate allele string (may be symbolic, e.g. "<DUP>").

TYPE: str

symbolic_map

Optional mapping from symbolic ALT tokens (without angle brackets) to :class:VariantType members. When provided, exact token matches take precedence over the default substring fallback. Typical source: a TOML configuration file next to the reader/annotator.

TYPE: Mapping[str, VariantType] | None DEFAULT: None

RETURNS DESCRIPTION
Inferred

class:VariantType.

TYPE: VariantType

Example
VariantType.from_alleles("A", "T")
# <VariantType.SNV: 'snv'>
VariantType.from_alleles("ATG", "A")
# <VariantType.INDEL: 'indel'>
VariantType.from_alleles("A", "<DUP>")
# <VariantType.DUP: 'duplication'>
VariantType.from_alleles("A", "<DUP>", symbolic_map={"DUP": VariantType.CNV})
# <VariantType.CNV: 'cnv'>
VariantType.from_alleles("A", "*")
# <VariantType.OTHER: 'other'>
Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_alleles(
    cls, ref: str, alt: str, *, symbolic_map: Mapping[str, VariantType] | None = None
) -> VariantType:
    """Infer a VariantType from reference and alternate allele strings.

    Applies rules in the following order:

    1. Symbolic ALT ``<TOKEN>`` → look up *TOKEN* in *symbolic_map* if
       provided; otherwise fall back to substring matching (``DUP`` →
       :attr:`DUP`, ``DEL`` → :attr:`DEL`, ``CNV`` → :attr:`CNV`); any
       other symbolic allele → :attr:`SV`.
    2. Missing/spanning ALT (``"*"`` or ``""``) → :attr:`OTHER`.
    3. ``len(ref) == 1`` and ``len(alt) == 1`` → :attr:`SNV`.
    4. ``len(ref) > 1`` and ``len(alt) > 1`` and equal lengths → :attr:`MNV`.
    5. ``len(ref) != len(alt)`` → :attr:`INDEL`.
    6. Anything else → :attr:`OTHER`.

    Args:
        ref: Reference allele string.
        alt: Alternate allele string (may be symbolic, e.g. ``"<DUP>"``).
        symbolic_map: Optional mapping from symbolic ALT tokens (without
            angle brackets) to :class:`VariantType` members.  When
            provided, exact token matches take precedence over the default
            substring fallback.  Typical source: a TOML configuration file
            next to the reader/annotator.

    Returns:
        Inferred :class:`VariantType`.

    Example:
        ```python
        VariantType.from_alleles("A", "T")
        # <VariantType.SNV: 'snv'>
        VariantType.from_alleles("ATG", "A")
        # <VariantType.INDEL: 'indel'>
        VariantType.from_alleles("A", "<DUP>")
        # <VariantType.DUP: 'duplication'>
        VariantType.from_alleles("A", "<DUP>", symbolic_map={"DUP": VariantType.CNV})
        # <VariantType.CNV: 'cnv'>
        VariantType.from_alleles("A", "*")
        # <VariantType.OTHER: 'other'>
        ```
    """
    if alt.startswith("<"):
        token = alt[1:-1]
        if symbolic_map and token in symbolic_map:
            return symbolic_map[token]
        alt_upper = alt.upper()
        if "DUP" in alt_upper:
            return cls.DUP
        if "DEL" in alt_upper:
            return cls.DEL
        if "CNV" in alt_upper:
            return cls.CNV
        return cls.SV
    if not alt or alt in ("*", "."):
        return cls.OTHER
    if len(ref) == 1 and len(alt) == 1:
        return cls.SNV
    if len(ref) > 1 and len(alt) > 1 and len(ref) == len(alt):
        return cls.MNV
    if len(ref) != len(alt):
        return cls.INDEL
    return cls.OTHER

from_key classmethod

from_key(key)

Infer a VariantType from a genomic key tuple (chrom, pos, ref, alt).

Delegates to :meth:from_alleles using ref and alt from the key. Pass a symbolic_map via :meth:from_alleles directly when format- specific symbolic allele mappings are needed (e.g. VCF readers).

PARAMETER DESCRIPTION
key

Four-tuple (chrom, pos, ref, alt).

TYPE: tuple[str, int, str, str]

RETURNS DESCRIPTION
VariantType

Inferred VariantType.

RAISES DESCRIPTION
ValueError

If both ref and alt are empty strings.

Example
VariantType.from_key(("1", 100, "A", "T"))
# <VariantType.SNV: 'snv'>
VariantType.from_key(("1", 100, "AT", "GC"))
# <VariantType.MNV: 'mnv'>
VariantType.from_key(("1", 100, "ATG", "A"))
# <VariantType.INDEL: 'indel'>
VariantType.from_key(("1", 100, "A", "<DUP>"))
# <VariantType.DUP: 'duplication'>
Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_key(cls, key: tuple[str, int, str, str]) -> VariantType:
    """Infer a VariantType from a genomic key tuple ``(chrom, pos, ref, alt)``.

    Delegates to :meth:`from_alleles` using *ref* and *alt* from the key.
    Pass a *symbolic_map* via :meth:`from_alleles` directly when format-
    specific symbolic allele mappings are needed (e.g. VCF readers).

    Args:
        key: Four-tuple ``(chrom, pos, ref, alt)``.

    Returns:
        Inferred VariantType.

    Raises:
        ValueError: If both *ref* and *alt* are empty strings.

    Example:
        ```python
        VariantType.from_key(("1", 100, "A", "T"))
        # <VariantType.SNV: 'snv'>
        VariantType.from_key(("1", 100, "AT", "GC"))
        # <VariantType.MNV: 'mnv'>
        VariantType.from_key(("1", 100, "ATG", "A"))
        # <VariantType.INDEL: 'indel'>
        VariantType.from_key(("1", 100, "A", "<DUP>"))
        # <VariantType.DUP: 'duplication'>
        ```
    """
    _, _, ref, alt = key
    if not ref and not alt:
        raise ValueError(f"Invalid key with empty reference and alternate alleles: {key!r}")
    return cls.from_alleles(ref, alt)

VariantScoreType

Bases: Enum

Category of a variant-level computational score.

Zygosity

Bases: Enum

Zygosity state of a variant in a specific sample.

ATTRIBUTE DESCRIPTION
REF

Homozygous reference — no alternate allele called.

HET

Heterozygous — one reference and one alternate allele.

COM

Compound heterozygous — two different alternate alleles on different haplotypes. Assigned by domain logic, not inferred from a single genotype call.

HOM

Homozygous alternate — both alleles are the same alternate.

HEM

Hemizygous — single allele (e.g. X-linked in males).

NUL

Nullizygous — no copies of the locus present. Assigned by domain logic.

UNKNOWN

Zygosity could not be determined.

from_gt classmethod

from_gt(gt, alt_idx=1, *, allow_negative=False)

Infer zygosity from a genotype tuple of allele indices.

Handles pysam-style tuples (None for missing) and vcf2zarr-style tuples (-1 for missing, -2 for polyploid fill). Returns None when the genotype is unresolvable rather than guessing.

PARAMETER DESCRIPTION
gt

Tuple of integer allele indices as returned by pysam (record.samples[s]["GT"]) or vcf2zarr. Index 0 means reference, 1 the first alternate, etc. None values indicate missing calls.

TYPE: tuple[int | None, ...]

alt_idx

1-based index of the alternate allele to evaluate. Defaults to 1 (first alternate).

TYPE: int DEFAULT: 1

allow_negative

If False (default), any negative value in gt makes the call unresolvable and returns None. Set to True only if negative values have been remapped upstream (e.g. -1None).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
Zygosity | None

Inferred Zygosity, or None if the genotype is missing,

Zygosity | None

partially called, or unresolvable.

Example
Zygosity.from_gt((0, 1))  # HET
Zygosity.from_gt((1, 1))  # HOM
Zygosity.from_gt((0, 0))  # REF
Zygosity.from_gt((1,))  # HEM
Zygosity.from_gt((None, 1))  # None — partial call
Zygosity.from_gt((-1, 1))  # None — vcf2zarr missing
Zygosity.from_gt((0, 2), alt_idx=2)  # HET for second alt
Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_gt(
    cls, gt: tuple[int | None, ...], alt_idx: int = 1, *, allow_negative: bool = False
) -> Zygosity | None:
    """Infer zygosity from a genotype tuple of allele indices.

    Handles pysam-style tuples (``None`` for missing) and vcf2zarr-style
    tuples (``-1`` for missing, ``-2`` for polyploid fill). Returns
    ``None`` when the genotype is unresolvable rather than guessing.

    Args:
        gt: Tuple of integer allele indices as returned by pysam
            (``record.samples[s]["GT"]``) or vcf2zarr. Index ``0``
            means reference, ``1`` the first alternate, etc.
            ``None`` values indicate missing calls.
        alt_idx: 1-based index of the alternate allele to evaluate.
            Defaults to ``1`` (first alternate).
        allow_negative: If ``False`` (default), any negative value in
            ``gt`` makes the call unresolvable and returns ``None``.
            Set to ``True`` only if negative values have been remapped
            upstream (e.g. ``-1`` → ``None``).

    Returns:
        Inferred ``Zygosity``, or ``None`` if the genotype is missing,
        partially called, or unresolvable.

    Example:
        ```python
        Zygosity.from_gt((0, 1))  # HET
        Zygosity.from_gt((1, 1))  # HOM
        Zygosity.from_gt((0, 0))  # REF
        Zygosity.from_gt((1,))  # HEM
        Zygosity.from_gt((None, 1))  # None — partial call
        Zygosity.from_gt((-1, 1))  # None — vcf2zarr missing
        Zygosity.from_gt((0, 2), alt_idx=2)  # HET for second alt
        ```
    """
    if not gt:
        return None

    # Treat None as unresolvable (pysam missing)
    if any(a is None for a in gt):
        return None

    # Cast safe — None already excluded
    indices: tuple[int, ...] = tuple(a for a in gt if a is not None)

    # Treat negatives as unresolvable (vcf2zarr missing=-1, fill=-2)
    if not allow_negative and any(a < 0 for a in indices):
        return None

    # Hemizygous — single allele locus (e.g. chrY, chrX in males)
    if len(indices) == 1:
        return cls.HEM if indices[0] == alt_idx else cls.REF

    # Alt not present in this sample
    if alt_idx not in indices:
        return cls.REF if all(a == 0 for a in indices) else None

    # All alleles are this alt → homozygous
    if all(a == alt_idx for a in indices):
        return cls.HOM

    # Mix of ref and alt → heterozygous
    return cls.HET

from_string classmethod

from_string(value)

Parse a zygosity string from any external source.

Recognizes VCF-style genotype strings ("0/1", "1|1"), abbreviated labels ("HET", "HOM", "HEM"), full names ("heterozygous"): common software conventions. Matching is case-insensitive and strips surrounding whitespace.

PARAMETER DESCRIPTION
value

Zygosity string from an external source. None or empty string returns None without raising.

TYPE: str | None

RETURNS DESCRIPTION
Zygosity | None

Matching Zygosity, or None if unrecognized.

Example
Zygosity.from_string("0/1")  # HET
Zygosity.from_string("1|1")  # HOM
Zygosity.from_string("0/0")  # REF
Zygosity.from_string("HET")  # HET
Zygosity.from_string("heterozygous")  # HET
Zygosity.from_string("compound_het")  # COM
Zygosity.from_string("hemizygous")  # HEM
Zygosity.from_string(None)  # None
Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_string(cls, value: str | None) -> Zygosity | None:
    """Parse a zygosity string from any external source.

    Recognizes VCF-style genotype strings (``"0/1"``, ``"1|1"``),
    abbreviated labels (``"HET"``, ``"HOM"``, ``"HEM"``), full names
    (``"heterozygous"``): common software conventions.
    Matching is case-insensitive and strips surrounding whitespace.

    Args:
        value: Zygosity string from an external source. ``None`` or
            empty string returns ``None`` without raising.

    Returns:
        Matching ``Zygosity``, or ``None`` if unrecognized.

    Example:
        ```python
        Zygosity.from_string("0/1")  # HET
        Zygosity.from_string("1|1")  # HOM
        Zygosity.from_string("0/0")  # REF
        Zygosity.from_string("HET")  # HET
        Zygosity.from_string("heterozygous")  # HET
        Zygosity.from_string("compound_het")  # COM
        Zygosity.from_string("hemizygous")  # HEM
        Zygosity.from_string(None)  # None
        ```
    """
    if not value:
        return None

    normalized = value.strip().upper()

    # VCF-style genotype strings: "0/1", "1|1", "0|0", "1/2", etc.
    if "/" in normalized or "|" in normalized:
        return cls._from_gt_string(normalized)

    return _ZYGOSITY_STRING_MAP.get(normalized)

PositionRelation

Bases: Enum

Spatial relationship between two GenomicPosition objects on the same assembly.

Allele

Reference and alternate alleles with optional zygosity and inheritance origin.

alternate is a tuple to support multiallelic sites; single-alt variants have a one-element tuple. zygosity and origin are optional and populated when genotype information is available (e.g. from a VCF with sample columns).

is_multiallelic property

is_multiallelic

Return True if there are more than one alternate allele.

is_reference property

is_reference

Return True if this allele represents the reference state (no alternate alleles).

is_variant property

is_variant

Return True if this allele has one or more alternate alleles.

ref property

ref

Return the reference allele.

alt property

alt

Return the first alternate allele or None.

genotype

genotype(*, as_nucleotides=False)

Return a string representation of the genotype.

For numeric notation, the separator is '|' when origin is known and phased (maternal or paternal), '/' otherwise. Maternal variants place the alternate allele on the left (maternal haplotype first): '1|0'. Paternal variants place it on the right: '0|1'.

For nucleotide notation, only SNVs are supported (single-base ref and alt). HOM returns the alternate repeated (e.g. 'TT'). HET returns ref+alt or alt+ref depending on origin.

PARAMETER DESCRIPTION
as_nucleotides

If True, returns allele sequences (e.g. 'AT', 'TA'). Restricted to SNVs — raises ValueError for indels or multiallelic variants.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
str

Genotype string.

RAISES DESCRIPTION
ValueError

If as_nucleotides=True and the allele is not a SNV.exit

Example
# HET, nucleotides
Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET).genotype(as_nucleotides=True)  # snv only
# 'AT'
# HET, paternal --> alternate on right
Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET, origin=InheritanceOrigin.PATERNAL).genotype()
# '0|1'
# HET, maternal --> alternate on left
Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET, origin=InheritanceOrigin.MATERNAL).genotype()
# '1|0'
# HET, paternal, nucleotides
Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET, origin=InheritanceOrigin.PATERNAL).genotype(
    as_nucleotides=True
)
# 'AT'
# HET, maternal, nucleotides --> alt on left
Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET, origin=InheritanceOrigin.MATERNAL).genotype(
    as_nucleotides=True
)
# 'TA'
Source code in src/iris/domain/genomics/objects.py
def genotype(self, *, as_nucleotides: bool = False) -> str:
    """Return a string representation of the genotype.

    For numeric notation, the separator is '|' when origin is known and phased
    (maternal or paternal), '/' otherwise. Maternal variants place the alternate
    allele on the left (maternal haplotype first): '1|0'. Paternal variants
    place it on the right: '0|1'.

    For nucleotide notation, only SNVs are supported (single-base ref and alt).
    HOM returns the alternate repeated (e.g. 'TT'). HET returns ref+alt or
    alt+ref depending on origin.

    Args:
        as_nucleotides: If True, returns allele sequences (e.g. 'AT', 'TA').
                        Restricted to SNVs — raises ValueError for indels or
                        multiallelic variants.

    Returns:
        Genotype string.

    Raises:
        ValueError: If as_nucleotides=True and the allele is not a SNV.exit

    Example:
        ```python
        # HET, nucleotides
        Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET).genotype(as_nucleotides=True)  # snv only
        # 'AT'
        # HET, paternal --> alternate on right
        Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET, origin=InheritanceOrigin.PATERNAL).genotype()
        # '0|1'
        # HET, maternal --> alternate on left
        Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET, origin=InheritanceOrigin.MATERNAL).genotype()
        # '1|0'
        # HET, paternal, nucleotides
        Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET, origin=InheritanceOrigin.PATERNAL).genotype(
            as_nucleotides=True
        )
        # 'AT'
        # HET, maternal, nucleotides --> alt on left
        Allele(reference="A", alternate=("T",), zygosity=Zygosity.HET, origin=InheritanceOrigin.MATERNAL).genotype(
            as_nucleotides=True
        )
        # 'TA'
        ```
    """  # noqa: E501
    phased = self.origin in (InheritanceOrigin.MATERNAL, InheritanceOrigin.PATERNAL)
    sep = "|" if phased else "/"
    maternal = self.origin == InheritanceOrigin.MATERNAL

    if self.is_reference:
        return f"{self.reference}{self.reference}" if as_nucleotides else f"0{sep}0"

    if as_nucleotides:
        # Restrict to SNVs: single-base ref and all alts must be length 1
        if len(self.reference) != 1 or self.is_multiallelic:
            raise ValueError(
                "as_nucleotides=True is only supported for SNVs "
                "(single-base ref and exactly one single-base alt). "
                f"Got ref={self.reference!r}, alt={self.alternate!r}."
            )
        alt = self.alternate[0]
        if len(alt) != 1:
            raise ValueError(
                "as_nucleotides=True is only supported for SNVs "
                f"(single-base alt required, got {alt!r})."
            )

        if self.zygosity == Zygosity.HOM:
            return f"{alt}{alt}"

        # HET: maternal -> alt|ref (alternate on left), paternal/unknown -> ref|alt
        return f"{alt}{self.reference}" if maternal else f"{self.reference}{alt}"

    # Numeric VCF-style
    if self.is_reference:
        return f"0{sep}0"

    if self.is_multiallelic:
        # Multiallelic: 0/1/2/... — phasing not well-defined, use '/'
        indices = "/".join(str(i + 1) for i in range(len(self.alternate)))
        return f"0/{indices}"

    if self.zygosity == Zygosity.HOM:
        return f"1{sep}1"

    # HET: maternal -> 1|0, paternal/unknown -> 0|1
    return f"1{sep}0" if maternal else f"0{sep}1"

AlleleFrequency

Allele frequency of a specific allele in a population with optional count and total.

is_rare

is_rare(threshold=0.01)

Return True if the frequency is defined and strictly below the threshold.

Source code in src/iris/domain/genomics/objects.py
def is_rare(self, threshold: float = 0.01) -> bool:
    """Return True if the frequency is defined and strictly below the threshold."""
    return self.frequency is not None and self.frequency < threshold

is_common

is_common(threshold=0.01)

Return True if the frequency is defined and at or above the threshold.

Source code in src/iris/domain/genomics/objects.py
def is_common(self, threshold: float = 0.01) -> bool:
    """Return True if the frequency is defined and at or above the threshold."""
    return self.frequency is not None and self.frequency >= threshold

GenotypeCount

Raw genotype counts for one ALT allele across a sample cohort.

Counts assume diploid samples. Hemizygous loci (e.g. chrX in males) are not modelled separately: each called allele contributes to AN regardless of ploidy.

All derived statistics (AF, AN, AC, missingness) are computed properties so that downstream analyses only need to store this one object.

ac property

ac

Allele count: number of ALT alleles observed (HET + 2×HOM_ALT).

an property

an

Allele number: total called alleles (2 per diploid non-missing sample).

af property

af

Allele frequency: AC / AN, or None when AN == 0.

n_called property

n_called

Number of samples with a non-missing genotype call.

missingness property

missingness

Fraction of samples with a missing genotype call.

to_allele_frequency

to_allele_frequency(population)

Convert to an AlleleFrequency domain object for use in VariantAnnotation.

Source code in src/iris/domain/genomics/objects.py
def to_allele_frequency(self, population: str) -> AlleleFrequency:
    """Convert to an AlleleFrequency domain object for use in VariantAnnotation."""
    return AlleleFrequency(
        frequency=self.af,
        count=self.ac,
        total=self.an,
        population=population,
        allele=self.allele,
    )

AncestryGroup

Population and super-population labels for an ancestry group.

Both fields are optional to allow partial labelling (e.g. only a super_population is known). Used as the grouping key inside AncestryAnnotation.

AncestryAnnotation

Variant annotation values for a specific ancestry group.

Aggregates the ancestry-level scores that accompany a variant in population-scale resources (e.g. MCPS ancestry strata). Any numeric field may be None when the resource does not report that metric for the given group.

ChromosomeSet

An ordered set of chromosome names with optional length map.

aliases is an AliasSet that stores canonical chromosome names (e.g. "1", "X") and any alternative forms ("chr1"). lengths maps canonical names to base-pair sizes; construction raises ValueError if a length key is not a recognized name.

resolve

resolve(value)

Return the canonical chromosome name for value, resolving aliases.

Source code in src/iris/domain/genomics/objects.py
def resolve(self, value: str) -> str:
    """Return the canonical chromosome name for value, resolving aliases."""
    return self.aliases.resolve(value)

length_of

length_of(value)

Return the length of the chromosome identified by value.

Source code in src/iris/domain/genomics/objects.py
def length_of(self, value: str) -> int:
    """Return the length of the chromosome identified by value."""
    canonical = self.resolve(value)
    return self.lengths[canonical]

normalize classmethod

normalize(chromosome)

Return the canonical chromosome name: strip 'chr' prefix, uppercase, M → MT.

This is a generic normalization independent of any particular ChromosomeSet instance — it does not consult aliases or lengths, only applies the standard UCSC-to-bare-name transformation.

PARAMETER DESCRIPTION
chromosome

Raw chromosome name (e.g. 'chr1', 'chrX', 'chrM', '1').

TYPE: str

RETURNS DESCRIPTION
str

Normalized chromosome name (e.g. '1', 'X', 'MT').

Source code in src/iris/domain/genomics/objects.py
@classmethod
def normalize(cls, chromosome: str) -> str:
    """Return the canonical chromosome name: strip 'chr' prefix, uppercase, M → MT.

    This is a generic normalization independent of any particular
    ChromosomeSet instance — it does not consult aliases or lengths,
    only applies the standard UCSC-to-bare-name transformation.

    Args:
        chromosome: Raw chromosome name (e.g. 'chr1', 'chrX', 'chrM', '1').

    Returns:
        Normalized chromosome name (e.g. '1', 'X', 'MT').
    """
    chrom = chromosome.strip()
    if chrom.lower().startswith("chr"):
        chrom = chrom[3:]
    chrom = chrom.upper()
    if chrom == "M":
        chrom = "MT"
    return chrom

ClinicalClassification

Represents a clinical classification for a genetic variant.

Stores the ACMG/AMP significance tier (classification), optional strength of evidence (evidence), supporting literature references, and the originating database (source, e.g. "clinvar"). Multiple classifications may be attached to a VariantAnnotation; use top_classification to retrieve the most pathogenic one.

is_pathogenic property

is_pathogenic

Return True if the classification is Pathogenic or Likely Pathogenic.

is_benign property

is_benign

Return True if the classification is Benign or Likely Benign.

DiseaseNomenclature

Canonical identifiers and aliases for a genetic disease.

names holds the primary display name and any synonyms via AliasSet. Cross-references to MONDO, OMIM, and Orphanet are stored as optional string identifiers and may be None when the mapping is unavailable.

name property

name

Return the primary disease name.

from_name classmethod

from_name(
    name,
    *,
    normalize_function=None,
    extra_ids=None,
    mondo_id=None,
    omim_id=None,
    orphanet_id=None,
    mesh_id=None,
    icd10_id=None,
    add_ids_as_aliases=True,
)

Build a DiseaseNomenclature from a primary name and optional database identifiers.

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_name(
    cls,
    name: str,
    *,
    normalize_function: Callable[[str], str] | None = None,
    extra_ids: Iterable[str] | None = None,
    mondo_id: str | None = None,
    omim_id: str | None = None,
    orphanet_id: str | None = None,
    mesh_id: str | None = None,
    icd10_id: str | None = None,
    add_ids_as_aliases: bool = True,
) -> DiseaseNomenclature:
    """Build a DiseaseNomenclature from a primary name and optional database identifiers."""
    if not name:
        raise ValueError("name must be a non-empty string")

    aliases: dict[str, str] = {}

    if normalize_function is not None:
        normalized = normalize_function(name)
        if normalized and normalized != name:
            aliases[normalized] = name

    if extra_ids:
        for ext_id in extra_ids:
            if ext_id and ext_id != name:
                aliases[ext_id] = name

    if add_ids_as_aliases:
        for ext_id in (mondo_id, omim_id, orphanet_id, mesh_id, icd10_id):
            if ext_id and ext_id != name:
                aliases[ext_id] = name

    return cls(
        names=AliasSet(names=[name], aliases=aliases),
        mondo_id=mondo_id,
        omim_id=omim_id,
        orphanet_id=orphanet_id,
        mesh_id=mesh_id,
        icd10_id=icd10_id,
    )

GeneDosageSensitivity

Dosage sensitivity assessment for a gene with evidence rank.

GeneFunction

A functional description of a gene with supporting references.

GeneNomenclature

Canonical symbol and cross-database identifiers for a gene.

symbols is an AliasSet whose primary entry is the HGNC-approved symbol; alternative names and synonyms are stored as aliases. Ensembl, HGNC, and RefSeq identifiers are optional and can be added via from_symbol() with add_ids_as_aliases=True to make them resolvable through the alias map.

symbol property

symbol

Return the primary gene symbol.

from_symbol classmethod

from_symbol(
    symbol,
    *,
    normalize_function=None,
    extra_aliases=None,
    ensembl_id=None,
    hgnc_id=None,
    refseq_id=None,
    add_ids_as_aliases=True,
)

Build a GeneNomenclature from a primary symbol and optional database identifiers.

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_symbol(
    cls,
    symbol: str,
    *,
    normalize_function: Callable[[str], str] | None = None,
    extra_aliases: Iterable[str] | None = None,
    ensembl_id: str | None = None,
    hgnc_id: str | None = None,
    refseq_id: str | None = None,
    add_ids_as_aliases: bool = True,
) -> GeneNomenclature:
    """Build a GeneNomenclature from a primary symbol and optional database identifiers."""
    if not symbol:
        raise ValueError("symbol must be a non-empty string")

    aliases: dict[str, str] = {}

    if normalize_function is not None:
        normalized = normalize_function(symbol)
        if normalized and normalized != symbol:
            aliases[normalized] = symbol

    if extra_aliases:
        for alias in extra_aliases:
            if alias and alias != symbol:
                aliases[alias] = symbol

    if add_ids_as_aliases:
        for ext_id in (ensembl_id, hgnc_id, refseq_id):
            if ext_id and ext_id != symbol:
                aliases[ext_id] = symbol

    return cls(
        symbols=AliasSet(names=[symbol], aliases=aliases),
        ensembl_id=ensembl_id,
        hgnc_id=hgnc_id,
        refseq_id=refseq_id,
    )

from_list classmethod

from_list(symbols, *, sep=', ', normalize_function=None)

Build multiple GeneNomenclature instances from a delimited string or iterable.

PARAMETER DESCRIPTION
symbols

Either a delimited string (e.g. 'BRCA1, TP53, EGFR') or any iterable of symbol strings.

TYPE: str | Iterable[str]

sep

Delimiter used to split the string. Ignored if symbols is already an iterable of strings. Defaults to ', '.

TYPE: str DEFAULT: ', '

normalize_function

Optional normalization applied to each symbol before building the AliasSet. Passed through to from_symbol.

TYPE: Callable[[str], str] | None DEFAULT: None

RETURNS DESCRIPTION
tuple[GeneNomenclature, ...]

Tuple of GeneNomenclature instances, one per symbol, in input order.

RAISES DESCRIPTION
ValueError

If any symbol is empty after stripping.

Example
GeneNomenclature.from_list("BRCA1, TP53, EGFR")
# (GeneNomenclature(symbol='BRCA1'), GeneNomenclature(symbol='TP53'), ...)
GeneNomenclature.from_list(["BRCA1", "TP53"])
# (GeneNomenclature(symbol='BRCA1'), GeneNomenclature(symbol='TP53'))
Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_list(
    cls,
    symbols: str | Iterable[str],
    *,
    sep: str = ", ",
    normalize_function: Callable[[str], str] | None = None,
) -> tuple[GeneNomenclature, ...]:
    """Build multiple GeneNomenclature instances from a delimited string or iterable.

    Args:
        symbols: Either a delimited string (e.g. 'BRCA1, TP53, EGFR') or
                any iterable of symbol strings.
        sep: Delimiter used to split the string. Ignored if symbols is
            already an iterable of strings. Defaults to ', '.
        normalize_function: Optional normalization applied to each symbol
                            before building the AliasSet. Passed through
                            to from_symbol.

    Returns:
        Tuple of GeneNomenclature instances, one per symbol, in input order.

    Raises:
        ValueError: If any symbol is empty after stripping.

    Example:
        ```python
        GeneNomenclature.from_list("BRCA1, TP53, EGFR")
        # (GeneNomenclature(symbol='BRCA1'), GeneNomenclature(symbol='TP53'), ...)
        GeneNomenclature.from_list(["BRCA1", "TP53"])
        # (GeneNomenclature(symbol='BRCA1'), GeneNomenclature(symbol='TP53'))
        ```
    """
    raw: Iterable[str] = symbols.split(sep) if isinstance(symbols, str) else symbols
    return tuple(
        cls.from_symbol(s.strip(), normalize_function=normalize_function)
        for s in raw
        if s.strip()
    )

GeneScore

A named computational score for a gene with an optional prediction tier.

GenomicPosition

A genomic locus using 1-based inclusive coordinates on a named chromosome.

Both start and end are 1-based and inclusive (as in VCF and GFF). Point variants have start == end; interval regions have start < end. Consumers reading BED or pysam (0-based half-open) must convert before constructing this object. The optional name field carries a gene symbol or region label when the position was read from a BED file.

coordinates_as_string property

coordinates_as_string

Return a string representation of the genomic coordinates.

coordinates_as_tuple property

coordinates_as_tuple

Return a tuple representation of the genomic coordinates.

length property

length

Return the length of the region, or None if start or end is missing.

from_cytoband classmethod

from_cytoband(cytoband, *, assembly=None)

Parse cytoband-like strings such as '7q31.2', 'Xp22.31', or 'chr17p13.1'.

Coordinates are not inferred; the cytoband is stored as name.

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_cytoband(cls, cytoband: str, *, assembly: str | None = None) -> GenomicPosition:
    """Parse cytoband-like strings such as '7q31.2', 'Xp22.31', or 'chr17p13.1'.

    Coordinates are not inferred; the cytoband is stored as name.
    """
    value = cytoband.strip()

    match = re.match(r"^(?:chr)?(?P<chrom>[0-9]+|X|Y|M|MT)(?P<band>[pq].+)$", value, re.I)
    if not match:
        raise ValueError(f"Unknown cytoband string: {cytoband!r}")

    chrom = match.group("chrom").upper()
    if chrom == "M":
        chrom = "MT"

    return cls(chromosome=cls._normalize_chromosome(chrom), assembly=assembly, name=value)

from_region classmethod

from_region(
    region, *, assembly=None, strand=None, name=None
)

Parse region strings such as 'chr1:100-200', '1:100', or 'X:1,000-2,000'.

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_region(
    cls,
    region: str,
    *,
    assembly: str | None = None,
    strand: Strand | None = None,
    name: str | None = None,
) -> GenomicPosition:
    """Parse region strings such as 'chr1:100-200', '1:100', or 'X:1,000-2,000'."""
    value = region.strip().replace(",", "")

    match = re.match(
        r"^(?:chr)?(?P<chrom>[A-Za-z0-9_.]+):(?P<start>\d+)(?:-(?P<end>\d+))?$", value
    )
    if not match:
        raise ValueError(f"Invalid genomic region: {region!r}")

    start = int(match.group("start"))
    end = int(match.group("end") or start)

    if end < start:
        raise ValueError(f"Invalid region with end < start: {region!r}")

    return cls(
        chromosome=cls._normalize_chromosome(match.group("chrom")),
        assembly=assembly,
        start=start,
        end=end,
        strand=strand,
        name=name,
    )

from_bed classmethod

from_bed(
    *,
    chrom,
    start0,
    end0,
    assembly=None,
    strand=None,
    name=None,
)

Build a GenomicPosition from 0-based half-open BED coordinates [start0, end0).

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_bed(
    cls,
    *,
    chrom: str,
    start0: int,
    end0: int,
    assembly: str | None = None,
    strand: Strand | None = None,
    name: str | None = None,
) -> GenomicPosition:
    """Build a GenomicPosition from 0-based half-open BED coordinates [start0, end0)."""
    if start0 < 0:
        raise ValueError(f"BED start must be >= 0: {start0}")
    if end0 < start0:
        raise ValueError(f"BED end must be >= start: {start0}, {end0}")

    return cls(
        chromosome=cls._normalize_chromosome(chrom),
        assembly=assembly,
        start=start0 + 1,
        end=end0,
        strand=strand,
        name=name,
    )

from_tuple classmethod

from_tuple(value, *, assembly=None, strand=None, name=None)

Build a GenomicPosition from a 2- or 3-tuple of (chrom, start) or (chrom, start, end).

Coordinates are assumed 1-based inclusive.

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_tuple(
    cls,
    value: tuple[str, int] | tuple[str, int, int],
    *,
    assembly: str | None = None,
    strand: Strand | None = None,
    name: str | None = None,
) -> GenomicPosition:
    """Build a GenomicPosition from a 2- or 3-tuple of (chrom, start) or (chrom, start, end).

    Coordinates are assumed 1-based inclusive.
    """
    if len(value) == 2:
        chrom, start = value
        end = start
    elif len(value) == 3:
        chrom, start, end = value
    else:
        raise ValueError(f"Invalid tuple for GenomicPosition: {value!r}")

    if start < 1:
        raise ValueError(f"start must be >= 1: {start}")
    if end < start:
        raise ValueError(f"end must be >= start: {start}, {end}")

    return cls(
        chromosome=cls._normalize_chromosome(chrom),
        assembly=assembly,
        start=start,
        end=end,
        strand=strand,
        name=name,
    )

from_mapping classmethod

from_mapping(data)

Build a GenomicPosition from a dict-like record with flexible key aliases.

Accepted aliases: chromosome: chromosome, chrom, chr, contig start: start, pos, position end: end, stop

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_mapping(cls, data: Mapping[str, Any]) -> GenomicPosition:
    """Build a GenomicPosition from a dict-like record with flexible key aliases.

    Accepted aliases:
    chromosome: chromosome, chrom, chr, contig
    start: start, pos, position
    end: end, stop
    """
    chrom = (
        data.get("chromosome") or data.get("chrom") or data.get("chr") or data.get("contig")
    )
    if chrom is None:
        raise ValueError(f"Missing chromosome in mapping: {data!r}")

    start = data.get("start", data.get("pos", data.get("position")))
    end = data.get("end", data.get("stop", start))

    return cls(
        chromosome=cls._normalize_chromosome(chrom),
        assembly=data.get("assembly"),
        start=int(start) if start is not None else None,
        end=int(end) if end is not None else None,
        strand=data.get("strand"),
        name=data.get("name"),
        markers=tuple(data.get("markers", ())),
    )

chromosome_only classmethod

chromosome_only(chromosome, *, assembly=None, name=None)

Build a GenomicPosition referencing an entire chromosome with no start or end.

Source code in src/iris/domain/genomics/objects.py
@classmethod
def chromosome_only(
    cls, chromosome: str, *, assembly: str | None = None, name: str | None = None
) -> GenomicPosition:
    """Build a GenomicPosition referencing an entire chromosome with no start or end."""
    return cls(chromosome=cls._normalize_chromosome(chromosome), assembly=assembly, name=name)

overlaps

overlaps(other)

Return True if this position overlaps with another.

A chromosome-level position (no start) is treated as spanning the whole chromosome. A point position (start only, no end) occupies a single base.

Source code in src/iris/domain/genomics/objects.py
def overlaps(self, other: GenomicPosition) -> bool:
    """Return True if this position overlaps with another.

    A chromosome-level position (no start) is treated as spanning the whole
    chromosome. A point position (start only, no end) occupies a single base.
    """
    if self.chromosome != other.chromosome:
        return False
    if self.start is None or other.start is None:
        return True  # chromosome-level spans everything on that chromosome
    s_end = self.end if self.end is not None else self.start
    o_end = other.end if other.end is not None else other.start
    return self.start <= o_end and other.start <= s_end

contains

contains(other)

Return True if this position fully contains another.

A chromosome-level position (no start) contains everything on that chromosome. No interval can contain a chromosome-level position.

Source code in src/iris/domain/genomics/objects.py
def contains(self, other: GenomicPosition) -> bool:
    """Return True if this position fully contains another.

    A chromosome-level position (no start) contains everything on that
    chromosome. No interval can contain a chromosome-level position.
    """
    if self.chromosome != other.chromosome:
        return False
    if self.start is None:
        return True  # chromosome-level contains everything
    if other.start is None:
        return False  # bounded interval cannot contain a chromosome-level position
    s_end = self.end if self.end is not None else self.start
    o_end = other.end if other.end is not None else other.start
    return self.start <= other.start and s_end >= o_end

distance_to

distance_to(other)

Return the distance in base pairs to another position, or None if not comparable.

Source code in src/iris/domain/genomics/objects.py
def distance_to(self, other: GenomicPosition) -> int | None:
    """Return the distance in base pairs to another position, or None if not comparable."""
    if self.chromosome != other.chromosome:
        return None
    if self.end is not None and other.start is not None and self.end < other.start:
        return other.start - self.end
    if self.start is not None and other.end is not None and self.start > other.end:
        return self.start - other.end
    return 0

is_contained_in

is_contained_in(other)

Return True if this position is fully contained within another.

Source code in src/iris/domain/genomics/objects.py
def is_contained_in(self, other: GenomicPosition) -> bool:
    """Return True if this position is fully contained within another."""
    return other.contains(self)

locate

locate(other)

Return the spatial relationship of this position relative to another.

Source code in src/iris/domain/genomics/objects.py
def locate(self, other: GenomicPosition) -> PositionRelation:
    """Return the spatial relationship of this position relative to another."""
    if self.chromosome != other.chromosome:
        return PositionRelation.DIFFERENT_CHROMOSOME
    if self.overlaps(other):
        if self.contains(other):
            return PositionRelation.CONTAINS
        if other.contains(self):
            return PositionRelation.CONTAINED_BY
        return PositionRelation.OVERLAPS
    s_end = self.end if self.end is not None else self.start
    if s_end is not None and other.start is not None and s_end < other.start:
        return PositionRelation.UPSTREAM
    return PositionRelation.DOWNSTREAM

GenotypingMetrics

A named genotyping quality metric with an optional numeric value.

Phenotype

A clinical phenotype with an optional HPO identifier.

TranscriptNomenclature

Canonical name and cross-database identifiers for a transcript.

names is an AliasSet whose primary entry is the preferred transcript identifier (e.g. an Ensembl or RefSeq accession). CCDS, Ensembl, RefSeq, and UniProt identifiers are optional; the optional gene back-reference links the transcript to its parent GeneNomenclature.

name property

name

Return the primary transcript name.

from_name classmethod

from_name(
    name,
    *,
    normalize_function=None,
    extra_names=None,
    ccds_id=None,
    ensembl_id=None,
    refseq_id=None,
    uniprot_id=None,
    gene=None,
    add_ids_as_aliases=True,
)

Build a TranscriptNomenclature from a primary name and optional database identifiers.

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_name(
    cls,
    name: str,
    *,
    normalize_function: Callable[[str], str] | None = None,
    extra_names: Iterable[str] | None = None,
    ccds_id: str | None = None,
    ensembl_id: str | None = None,
    refseq_id: str | None = None,
    uniprot_id: str | None = None,
    gene: GeneNomenclature | None = None,
    add_ids_as_aliases: bool = True,
) -> TranscriptNomenclature:
    """Build a TranscriptNomenclature from a primary name and optional database identifiers."""
    if not name:
        raise ValueError("name must be a non-empty string")

    aliases: dict[str, str] = {}

    if normalize_function is not None:
        normalized = normalize_function(name)
        if normalized and normalized != name:
            aliases[normalized] = name

    if extra_names:
        for ext_name in extra_names:
            if ext_name and ext_name != name:
                aliases[ext_name] = name

    if add_ids_as_aliases:
        for ext_id in (ccds_id, ensembl_id, refseq_id, uniprot_id):
            if ext_id and ext_id != name:
                aliases[ext_id] = name

    return cls(
        names=AliasSet(names=[name], aliases=aliases),
        ccds_id=ccds_id,
        ensembl_id=ensembl_id,
        refseq_id=refseq_id,
        uniprot_id=uniprot_id,
        gene=gene,
    )

VariantNomenclature

Canonical identifier and cross-database references for a variant.

The primary identifier (vid) follows the CPRA format chrom:pos:ref:alt. ids is an AliasSet that also stores dbSNP rs-IDs, ClinVar accessions, COSMIC identifiers, and HGVS genomic notation when available. Use from_key() to construct from a raw (chrom, pos, ref, alt) tuple.

vid property

vid

Return the primary variant identifier (CPRA or canonical ID).

has_rsid property

has_rsid

Return True if a dbSNP rsID is available.

from_key classmethod

from_key(
    key,
    *,
    normalize_function=None,
    extra_ids=None,
    dbsnp_id=None,
    clinvar_id=None,
    cosmic_id=None,
    hgvs_genomic=None,
    add_ids_as_aliases=True,
)

Build a VariantNomenclature from genomic coordinates and optional database IDs.

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_key(
    cls,
    key: tuple[str, int, str, str],  # (chrom, pos, ref, alt),
    *,
    normalize_function: Callable[[tuple[str, int, str, str]], str] | None = None,
    extra_ids: Iterable[str] | None = None,
    dbsnp_id: str | None = None,
    clinvar_id: str | None = None,
    cosmic_id: str | None = None,
    hgvs_genomic: str | None = None,
    add_ids_as_aliases: bool = True,
) -> VariantNomenclature:
    """Build a VariantNomenclature from genomic coordinates and optional database IDs."""
    chrom, pos, ref, alt = key
    if not chrom or pos < 1 or (ref == "" and alt == ""):
        raise ValueError(f"Invalid genomic key: {key!r}")

    variant_type = VariantType.from_key(key)
    if (
        variant_type == VariantType.CNV
        or variant_type == VariantType.DEL
        or variant_type == VariantType.DUP
    ):
        cpra = f"{variant_type.value.upper()}:{chrom}:{pos}:{ref}"
    else:
        cpra = f"{chrom}:{pos}:{ref}:{alt}"

    aliases: dict[str, str] = {}

    if normalize_function is not None:
        normalized = normalize_function(key)
        if normalized and normalized != cpra:
            aliases[normalized] = cpra

    if extra_ids:
        for ext_id in extra_ids:
            if ext_id and ext_id != cpra:
                aliases[ext_id] = cpra

    if add_ids_as_aliases:
        for ext_id in (dbsnp_id, clinvar_id, cosmic_id):
            if ext_id and ext_id != cpra:
                aliases[ext_id] = cpra

    return cls(
        ids=AliasSet(names=[cpra], aliases=aliases),
        dbsnp_id=dbsnp_id,
        clinvar_id=clinvar_id,
        cosmic_id=cosmic_id,
        hgvs_genomic=hgvs_genomic,
    )

resolve

resolve(value)

Return the primary variant identifier for value, resolving aliases.

Source code in src/iris/domain/genomics/objects.py
def resolve(self, value: str) -> str:
    """Return the primary variant identifier for value, resolving aliases."""
    return self.ids.resolve(value)

VariantScore

A named computational score for a variant with an optional prediction tier.

is_high_impact property

is_high_impact

Return True if the prediction tier is HIGH or VERY_HIGH.

GeneEnrichment

Statistical enrichment of a gene given a phenotype set.

Produced by a :class:~iris.domain.genomics.ports.driven.enrichment.PhenotypeEnrichmentModel from a patient's HPO profile. p_value is the result of the statistical test (hypergeometric by default); fold is the fold enrichment; count is the number of overlapping annotated HPO terms supporting the gene.

is_significant

is_significant(alpha=0.05)

Return True if the p-value is strictly below alpha.

PARAMETER DESCRIPTION
alpha

Significance threshold. Defaults to 0.05.

TYPE: float DEFAULT: 0.05

RETURNS DESCRIPTION
bool

True if p_value < alpha.

Source code in src/iris/domain/genomics/objects.py
def is_significant(self, alpha: float = 0.05) -> bool:
    """Return True if the p-value is strictly below ``alpha``.

    Args:
        alpha: Significance threshold. Defaults to ``0.05``.

    Returns:
        True if ``p_value < alpha``.
    """
    return self.p_value < alpha

DiseaseEnrichment

Statistical enrichment of an OMIM disease given a phenotype set.

Produced by a :class:~iris.domain.genomics.ports.driven.enrichment.PhenotypeEnrichmentModel from a patient's HPO profile. p_value is the result of the statistical test (hypergeometric by default); fold is the fold enrichment; count is the number of overlapping annotated HPO terms supporting the disease.

is_significant

is_significant(alpha=0.05)

Return True if the p-value is strictly below alpha.

PARAMETER DESCRIPTION
alpha

Significance threshold. Defaults to 0.05.

TYPE: float DEFAULT: 0.05

RETURNS DESCRIPTION
bool

True if p_value < alpha.

Source code in src/iris/domain/genomics/objects.py
def is_significant(self, alpha: float = 0.05) -> bool:
    """Return True if the p-value is strictly below ``alpha``.

    Args:
        alpha: Significance threshold. Defaults to ``0.05``.

    Returns:
        True if ``p_value < alpha``.
    """
    return self.p_value < alpha

GeneQuery

Specification of which genes to resolve, expressed in domain terms.

A GeneQuery lets callers express their intent in biological language without tying the use case to a specific backend (file, database, API). The resolve_gene_regions use case interprets the query and delegates to the appropriate repository methods.

ATTRIBUTE DESCRIPTION
symbols

Explicit gene symbols to resolve (e.g. ("BRCA1", "CFTR")). Combined with any names read from symbols_file before resolution.

TYPE: tuple[str, ...]

symbols_file

Path to a plain-text file with one gene symbol per line. Lines starting with "#" and empty lines are ignored. Resolved to symbols by the use case, not by this class.

TYPE: Path | None

transcript_tags

Tags that the chosen transcript must carry in the annotation source (e.g. ("MANE_Select", "Ensembl_canonical")). Passed to the repository's tag filter when building the index. An empty tuple means no tag restriction (accept all transcripts).

TYPE: tuple[str, ...]

inheritance_modes

Post-load filter — only include genes whose annotation.diseases list has at least one disease associated with any of these inheritance modes. Applied in the use case after loading all annotations, not in the repository.

TYPE: tuple[MendelianInheritance, ...]

disease_names

Post-load filter — only include genes with at least one disease whose name or identifier matches any of these strings (case-insensitive substring match). Applied after loading.

TYPE: tuple[str, ...]

Example:

# By explicit symbols:
GeneQuery(symbols=("BRCA1", "CFTR", "HEXA"))

# From a text file — one symbol per line:
GeneQuery.from_file(Path("recgenes.txt"))

# All autosomal recessive genes in the annotation:
GeneQuery.recessive()

# AR genes from a curated file:
GeneQuery.recessive(symbols_file=Path("panel.txt"))

from_file classmethod

from_file(path, **kwargs)

Build a GeneQuery reading gene symbols from a text file.

PARAMETER DESCRIPTION
path

Plain-text file with one HGNC symbol per line. Lines starting with "#" and blank lines are ignored.

TYPE: Path

**kwargs

Additional keyword arguments forwarded to the constructor (e.g. transcript_tags, inheritance_modes).

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
'GeneQuery'

GeneQuery with symbols populated from the file and

'GeneQuery'

symbols_file set to the resolved path.

Example:

query = GeneQuery.from_file(Path("recgenes.txt"))

Source code in src/iris/domain/genomics/objects.py
@classmethod
def from_file(cls, path: Path, **kwargs: Any) -> "GeneQuery":
    """Build a GeneQuery reading gene symbols from a text file.

    Args:
        path: Plain-text file with one HGNC symbol per line.
                Lines starting with ``"#"`` and blank lines are ignored.
        **kwargs: Additional keyword arguments forwarded to the constructor
            (e.g. ``transcript_tags``, ``inheritance_modes``).

    Returns:
        GeneQuery with ``symbols`` populated from the file and
        ``symbols_file`` set to the resolved path.

    Example:
    ```python
    query = GeneQuery.from_file(Path("recgenes.txt"))
    ```
    """
    symbols = tuple(
        line.strip()
        for line in path.read_text().splitlines()
        if line.strip() and not line.strip().startswith("#")
    )
    return cls(symbols=symbols, symbols_file=path, **kwargs)

recessive classmethod

recessive(**kwargs)

Build a GeneQuery filtering to autosomal recessive genes.

Post-load filter: only genes with at least one disease associated with MendelianInheritance.AR will be included in the result.

PARAMETER DESCRIPTION
**kwargs

Additional keyword arguments forwarded to the constructor (e.g. symbols, symbols_file, transcript_tags).

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
'GeneQuery'

GeneQuery with inheritance_modes=(MendelianInheritance.AR,).

Example:

# All AR genes in GENCODE:
GeneQuery.recessive()

# AR genes from a curated list:
GeneQuery.recessive(symbols_file=Path("panel.txt"))

Source code in src/iris/domain/genomics/objects.py
@classmethod
def recessive(cls, **kwargs: Any) -> "GeneQuery":
    """Build a GeneQuery filtering to autosomal recessive genes.

    Post-load filter: only genes with at least one disease associated
    with ``MendelianInheritance.AR`` will be included in the result.

    Args:
        **kwargs: Additional keyword arguments forwarded to the constructor
            (e.g. ``symbols``, ``symbols_file``, ``transcript_tags``).

    Returns:
        GeneQuery with ``inheritance_modes=(MendelianInheritance.AR,)``.

    Example:
    ```python
    # All AR genes in GENCODE:
    GeneQuery.recessive()

    # AR genes from a curated list:
    GeneQuery.recessive(symbols_file=Path("panel.txt"))
    ```
    """
    return cls(inheritance_modes=(MendelianInheritance.AR,), **kwargs)

dominant classmethod

dominant(**kwargs)

Build a GeneQuery filtering to autosomal dominant genes.

Post-load filter: only genes with at least one disease associated with MendelianInheritance.AD will be included in the result.

PARAMETER DESCRIPTION
**kwargs

Additional keyword arguments forwarded to the constructor.

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
'GeneQuery'

GeneQuery with inheritance_modes=(MendelianInheritance.AD,).

Source code in src/iris/domain/genomics/objects.py
@classmethod
def dominant(cls, **kwargs: Any) -> "GeneQuery":
    """Build a GeneQuery filtering to autosomal dominant genes.

    Post-load filter: only genes with at least one disease associated
    with ``MendelianInheritance.AD`` will be included in the result.

    Args:
        **kwargs: Additional keyword arguments forwarded to the constructor.

    Returns:
        GeneQuery with ``inheritance_modes=(MendelianInheritance.AD,)``.
    """
    return cls(inheritance_modes=(MendelianInheritance.AD,), **kwargs)

Repositories

iris.domain.genomics.repositories

Repository interfaces for the genomics domain.

GeneRepository

Bases: Repository[Gene]

Read-only repository for Gene and GeneAnnotation entities.

get_annotation abstractmethod

get_annotation(identifier)

Return a fully annotated gene, or None if not found.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def get_annotation(self, identifier: str) -> GeneAnnotation | None:
    """Return a fully annotated gene, or None if not found."""

find_by_position abstractmethod

find_by_position(position)

Return all genes that overlap the given genomic region.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def find_by_position(self, position: GenomicPosition) -> Sequence[Gene]:
    """Return all genes that overlap the given genomic region."""

find_by_phenotype abstractmethod

find_by_phenotype(phenotype)

Return all genes associated with the given phenotype.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def find_by_phenotype(self, phenotype: Phenotype) -> Sequence[Gene]:
    """Return all genes associated with the given phenotype."""

get abstractmethod

get(identifier)

Return the entity matching identifier, or None if not found.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def get(self, identifier: str) -> T | None:
    """Return the entity matching `identifier`, or None if not found."""

TranscriptRepository

Bases: Repository[TranscriptStructure]

Read-only repository for TranscriptStructure entities.

get_canonical abstractmethod

get_canonical(gene_identifier)

Return the canonical transcript for a gene, or None if unavailable.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def get_canonical(self, gene_identifier: str) -> TranscriptStructure | None:
    """Return the canonical transcript for a gene, or None if unavailable."""

find_by_gene abstractmethod

find_by_gene(gene_identifier)

Return all transcripts for a given gene identifier.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def find_by_gene(self, gene_identifier: str) -> Sequence[TranscriptStructure]:
    """Return all transcripts for a given gene identifier."""

get abstractmethod

get(identifier)

Return the entity matching identifier, or None if not found.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def get(self, identifier: str) -> T | None:
    """Return the entity matching `identifier`, or None if not found."""

VariantRepository

Bases: Repository[Variant]

Read-only repository for Variant and VariantAnnotation entities.

get_annotation abstractmethod

get_annotation(identifier)

Return a fully annotated variant, or None if not found.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def get_annotation(self, identifier: str) -> VariantAnnotation | None:
    """Return a fully annotated variant, or None if not found."""

find_by_position abstractmethod

find_by_position(position)

Return all variants within the given genomic region.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def find_by_position(self, position: GenomicPosition) -> Sequence[Variant]:
    """Return all variants within the given genomic region."""

find_by_gene abstractmethod

find_by_gene(gene_identifier)

Return all variants associated with a given gene identifier.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def find_by_gene(self, gene_identifier: str) -> Sequence[Variant]:
    """Return all variants associated with a given gene identifier."""

get abstractmethod

get(identifier)

Return the entity matching identifier, or None if not found.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def get(self, identifier: str) -> T | None:
    """Return the entity matching `identifier`, or None if not found."""

DiseaseRepository

Bases: Repository[Disease]

Read-only repository for Disease entities.

find_by_phenotype abstractmethod

find_by_phenotype(phenotype)

Return all diseases associated with the given phenotype.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def find_by_phenotype(self, phenotype: Phenotype) -> Sequence[Disease]:
    """Return all diseases associated with the given phenotype."""

find_by_gene abstractmethod

find_by_gene(gene_identifier)

Return all diseases associated with a given gene identifier.

Source code in src/iris/domain/genomics/repositories.py
@abstractmethod
def find_by_gene(self, gene_identifier: str) -> Sequence[Disease]:
    """Return all diseases associated with a given gene identifier."""

get abstractmethod

get(identifier)

Return the entity matching identifier, or None if not found.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def get(self, identifier: str) -> T | None:
    """Return the entity matching `identifier`, or None if not found."""

Filters

iris.domain.genomics.filters

Composable variant filters for genomic analysis pipelines.

VariantFilter

Bases: ABC

Abstract base class for all variant filters.

matches abstractmethod

matches(va)

Return True if the given VariantAnnotation satisfies this filter.

Source code in src/iris/domain/genomics/filters.py
@abstractmethod
def matches(self, va: VariantAnnotation) -> bool:
    """Return True if the given VariantAnnotation satisfies this filter."""

AllOf

Bases: VariantFilter

A composite filter that passes only when all constituent filters pass.

matches

matches(va)

Return True if all constituent filters match the given annotation.

Source code in src/iris/domain/genomics/filters.py
def matches(self, va: VariantAnnotation) -> bool:
    """Return True if all constituent filters match the given annotation."""
    return all(f.matches(va) for f in self.filters)

AnyOf

Bases: VariantFilter

A composite filter that passes when at least one constituent filter passes.

matches

matches(va)

Return True if any constituent filter matches the given annotation.

Source code in src/iris/domain/genomics/filters.py
def matches(self, va: VariantAnnotation) -> bool:
    """Return True if any constituent filter matches the given annotation."""
    return any(f.matches(va) for f in self.filters)

Not

Bases: VariantFilter

A filter that inverts the result of another filter.

matches

matches(va)

Return True if the wrapped filter does not match the given annotation.

Source code in src/iris/domain/genomics/filters.py
def matches(self, va: VariantAnnotation) -> bool:
    """Return True if the wrapped filter does not match the given annotation."""
    return not self.filter.matches(va)

MaxPopulationFrequency

Bases: VariantFilter

Passes when the allele frequency is below max_af.

Variants absent from the database pass by default (absent_passes=True), since absence implies the variant is novel or ultra-rare.

matches

matches(va)

Return True if the variant's population frequency is below max_af.

Source code in src/iris/domain/genomics/filters.py
def matches(self, va: VariantAnnotation) -> bool:
    """Return True if the variant's population frequency is below max_af."""
    entry = max(
        (
            af.frequency
            for af in va.allele_frequencies
            if af.population in self.sources and af.frequency is not None
        ),
        default=None,
    )
    if entry is None:
        return self.absent_passes
    return entry < self.max_af

HasClinicalSignificance

Bases: VariantFilter

Passes when at least one classification matches the given set.

matches

matches(va)

Return True if any classification matches the target significance set.

Source code in src/iris/domain/genomics/filters.py
def matches(self, va: VariantAnnotation) -> bool:
    """Return True if any classification matches the target significance set."""
    return any(cl.classification in self.significances for cl in va.classifications)

NoClinVar

Bases: VariantFilter

Passes when the variant has no classification.

matches

matches(va)

Return True if the variant has no ClinVar classifications.

Source code in src/iris/domain/genomics/filters.py
def matches(self, va: VariantAnnotation) -> bool:
    """Return True if the variant has no ClinVar classifications."""
    return not va.classifications

WithinRegion

Bases: VariantFilter

Passes when the variant position overlaps the given genomic region.

Uses 1-based inclusive coordinates (same as GenomicPosition internally). If the region has no start/end, matches any variant on the same chromosome.

matches

matches(va)

Return True if the variant overlaps the target genomic region.

Source code in src/iris/domain/genomics/filters.py
def matches(self, va: VariantAnnotation) -> bool:
    """Return True if the variant overlaps the target genomic region."""
    pos = va.variant.position
    if pos is None:
        return self.absent_passes
    if pos.chromosome != self.region.chromosome:
        return False
    if self.region.start is None:
        return True
    v_start = pos.start or 0
    v_end = pos.end or v_start
    r_start = self.region.start
    r_end = self.region.end or r_start
    return v_start <= r_end and v_end >= r_start

HighImpactConsequence

Bases: VariantFilter

Passes when the most severe consequence belongs to the given set.

Defaults to HIGH_IMPACT_CONSEQUENCES (frameshift, stop, splice, etc.).

matches

matches(va)

Return True if any transcript consequence belongs to the high-impact set.

Source code in src/iris/domain/genomics/filters.py
def matches(self, va: VariantAnnotation) -> bool:
    """Return True if any transcript consequence belongs to the high-impact set."""
    return any(c in self.consequences for tc in va.transcripts for c in tc.consequences)

all_of

all_of(*filters)

Return an AllOf filter combining the given filters.

Source code in src/iris/domain/genomics/filters.py
def all_of(*filters: VariantFilter) -> AllOf:
    """Return an AllOf filter combining the given filters."""
    return AllOf(filters=filters)

any_of

any_of(*filters)

Return an AnyOf filter combining the given filters.

Source code in src/iris/domain/genomics/filters.py
def any_of(*filters: VariantFilter) -> AnyOf:
    """Return an AnyOf filter combining the given filters."""
    return AnyOf(filters=filters)

apply

apply(filter_, variants)

Return the subset of variants that match the given filter.

Source code in src/iris/domain/genomics/filters.py
def apply(
    filter_: VariantFilter, variants: Sequence[VariantAnnotation]
) -> list[VariantAnnotation]:
    """Return the subset of variants that match the given filter."""
    return [va for va in variants if filter_.matches(va)]

Services

iris.domain.genomics.services

Domain services for genomic operations.

Two services are provided:

  • :class:GenomicRangeService — pure domain logic and delegated backend operations for collections of :class:~iris.domain.genomics.objects.GenomicPosition.
  • :class:HpoService — pure domain logic and delegated HPO operations for collections of :class:~iris.domain.genomics.objects.Phenotype.

Pure operations are implemented directly; complex or ontology-aware operations are delegated to an injected port.

GenomicRangeService

GenomicRangeService(ops)

Domain service for genomic range computations.

Separates pure domain logic from backend-specific operations. Pure methods operate directly on GenomicPosition objects using only the domain model. Complex methods delegate to the injected RangeOperations port.

PARAMETER DESCRIPTION
ops

Concrete implementation of the RangeOperations port.

TYPE: RangeOperations

Example
from iris.adapters.driven.genomic_ranges.biocpy import BiocpyRangeOperations

service = GenomicRangeService(ops=BiocpyRangeOperations())
overlaps = service.overlapping(query, subject)
Source code in src/iris/domain/genomics/services.py
def __init__(self, ops: RangeOperations) -> None:
    self._ops = ops

overlapping

overlapping(query, subject)

Find all overlapping pairs between query and subject ranges.

PARAMETER DESCRIPTION
query

Query ranges.

TYPE: Sequence[GenomicPosition]

subject

Subject ranges to test against.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[tuple[int, int]]

List of (query_index, subject_index) pairs where the ranges overlap.

Source code in src/iris/domain/genomics/services.py
def overlapping(
    self, query: Sequence[GenomicPosition], subject: Sequence[GenomicPosition]
) -> list[tuple[int, int]]:
    """Find all overlapping pairs between query and subject ranges.

    Args:
        query: Query ranges.
        subject: Subject ranges to test against.

    Returns:
        List of (query_index, subject_index) pairs where the ranges overlap.
    """
    return [
        (i, j) for i, q in enumerate(query) for j, s in enumerate(subject) if q.overlaps(s)
    ]

filter_overlapping

filter_overlapping(query, subject)

Return query ranges that overlap at least one subject range.

PARAMETER DESCRIPTION
query

Ranges to filter.

TYPE: Sequence[GenomicPosition]

subject

Ranges to test overlap against.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Subset of query ranges that overlap any range in subject.

Source code in src/iris/domain/genomics/services.py
def filter_overlapping(
    self, query: Sequence[GenomicPosition], subject: Sequence[GenomicPosition]
) -> list[GenomicPosition]:
    """Return query ranges that overlap at least one subject range.

    Args:
        query: Ranges to filter.
        subject: Ranges to test overlap against.

    Returns:
        Subset of query ranges that overlap any range in subject.
    """
    return [q for q in query if any(q.overlaps(s) for s in subject)]

filter_by_chromosome

filter_by_chromosome(ranges, chromosome)

Return ranges on a specific chromosome.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

chromosome

Chromosome name (normalized, e.g. '1', 'X', 'MT').

TYPE: str

RETURNS DESCRIPTION
list[GenomicPosition]

Ranges whose chromosome matches the given name.

Source code in src/iris/domain/genomics/services.py
def filter_by_chromosome(
    self, ranges: Sequence[GenomicPosition], chromosome: str
) -> list[GenomicPosition]:
    """Return ranges on a specific chromosome.

    Args:
        ranges: Input genomic ranges.
        chromosome: Chromosome name (normalized, e.g. '1', 'X', 'MT').

    Returns:
        Ranges whose chromosome matches the given name.
    """
    chrom = HUMAN_CHROMOSOMES.resolve(chromosome)
    return [r for r in ranges if r.chromosome == chrom]

sort

sort(ranges)

Sort ranges by chromosome, start, and end.

Chromosome order follows natural sort (1, 2, ..., 10, X, Y, MT).

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Sorted list of genomic ranges.

Source code in src/iris/domain/genomics/services.py
def sort(self, ranges: Sequence[GenomicPosition]) -> list[GenomicPosition]:
    """Sort ranges by chromosome, start, and end.

    Chromosome order follows natural sort (1, 2, ..., 10, X, Y, MT).

    Args:
        ranges: Input genomic ranges.

    Returns:
        Sorted list of genomic ranges.
    """
    return sorted(
        ranges, key=lambda r: (chromosome_sort_key(r.chromosome), r.start or 0, r.end or 0)
    )

merge_adjacent

merge_adjacent(ranges, *, max_gap=0)

Merge overlapping or adjacent ranges on the same chromosome.

PARAMETER DESCRIPTION
ranges

Input genomic ranges. Need not be sorted.

TYPE: Sequence[GenomicPosition]

max_gap

Maximum gap in bases between ranges to still merge. Default 0 merges only overlapping or book-ended ranges.

TYPE: int DEFAULT: 0

RETURNS DESCRIPTION
list[GenomicPosition]

Merged ranges sorted by chromosome and start position.

Source code in src/iris/domain/genomics/services.py
def merge_adjacent(
    self, ranges: Sequence[GenomicPosition], *, max_gap: int = 0
) -> list[GenomicPosition]:
    """Merge overlapping or adjacent ranges on the same chromosome.

    Args:
        ranges: Input genomic ranges. Need not be sorted.
        max_gap: Maximum gap in bases between ranges to still merge.
                 Default 0 merges only overlapping or book-ended ranges.

    Returns:
        Merged ranges sorted by chromosome and start position.
    """
    if not ranges:
        return []

    sorted_ranges = self.sort(ranges)
    merged: list[GenomicPosition] = []
    current = sorted_ranges[0]

    for nxt in sorted_ranges[1:]:
        if (
            current.chromosome == nxt.chromosome
            and current.start is not None
            and current.end is not None
            and nxt.start is not None
            and nxt.start - current.end <= max_gap + 1
        ):
            # Extend current range to cover nxt
            current = GenomicPosition(
                chromosome=current.chromosome,
                assembly=current.assembly,
                start=current.start,
                end=max(current.end, nxt.end or nxt.start or current.end),
                strand=current.strand if current.strand == nxt.strand else None,
            )
        else:
            merged.append(current)
            current = nxt

    merged.append(current)
    return merged

total_length

total_length(ranges)

Compute total base pairs covered by all ranges (with overlaps counted once).

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
int

Total number of unique base positions covered.

Source code in src/iris/domain/genomics/services.py
def total_length(self, ranges: Sequence[GenomicPosition]) -> int:
    """Compute total base pairs covered by all ranges (with overlaps counted once).

    Args:
        ranges: Input genomic ranges.

    Returns:
        Total number of unique base positions covered.
    """
    merged = self.merge_adjacent(ranges)
    return sum(r.length or 0 for r in merged)

by_chromosome

by_chromosome(ranges)

Group ranges by chromosome.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
dict[str, list[GenomicPosition]]

Dict mapping chromosome name to list of ranges on that chromosome.

Source code in src/iris/domain/genomics/services.py
def by_chromosome(self, ranges: Sequence[GenomicPosition]) -> dict[str, list[GenomicPosition]]:
    """Group ranges by chromosome.

    Args:
        ranges: Input genomic ranges.

    Returns:
        Dict mapping chromosome name to list of ranges on that chromosome.
    """
    result: dict[str, list[GenomicPosition]] = {}
    for r in ranges:
        result.setdefault(r.chromosome, []).append(r)
    return result

union

union(a, b)

Return the union of two sets of genomic ranges.

PARAMETER DESCRIPTION
a

First set of genomic ranges.

TYPE: Sequence[GenomicPosition]

b

Second set of genomic ranges.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Merged, non-overlapping ranges covering all positions in a or b.

Source code in src/iris/domain/genomics/services.py
def union(
    self, a: Sequence[GenomicPosition], b: Sequence[GenomicPosition]
) -> list[GenomicPosition]:
    """Return the union of two sets of genomic ranges.

    Args:
        a: First set of genomic ranges.
        b: Second set of genomic ranges.

    Returns:
        Merged, non-overlapping ranges covering all positions in a or b.
    """
    return self._ops.union(a, b)

intersect

intersect(a, b)

Return ranges present in both a and b.

PARAMETER DESCRIPTION
a

First set of genomic ranges.

TYPE: Sequence[GenomicPosition]

b

Second set of genomic ranges.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Ranges representing the intersection of a and b.

Source code in src/iris/domain/genomics/services.py
def intersect(
    self, a: Sequence[GenomicPosition], b: Sequence[GenomicPosition]
) -> list[GenomicPosition]:
    """Return ranges present in both a and b.

    Args:
        a: First set of genomic ranges.
        b: Second set of genomic ranges.

    Returns:
        Ranges representing the intersection of a and b.
    """
    return self._ops.intersect(a, b)

setdiff

setdiff(a, b)

Return ranges in a not covered by b.

PARAMETER DESCRIPTION
a

Query ranges.

TYPE: Sequence[GenomicPosition]

b

Ranges to subtract from a.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Ranges in a not overlapping any range in b.

Source code in src/iris/domain/genomics/services.py
def setdiff(
    self, a: Sequence[GenomicPosition], b: Sequence[GenomicPosition]
) -> list[GenomicPosition]:
    """Return ranges in a not covered by b.

    Args:
        a: Query ranges.
        b: Ranges to subtract from a.

    Returns:
        Ranges in a not overlapping any range in b.
    """
    return self._ops.setdiff(a, b)

coverage

coverage(ranges)

Compute per-base coverage across all ranges.

PARAMETER DESCRIPTION
ranges

Genomic ranges to compute coverage for.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
dict[str, list[int]]

Dict mapping chromosome name to a list of per-base coverage values.

Source code in src/iris/domain/genomics/services.py
def coverage(self, ranges: Sequence[GenomicPosition]) -> dict[str, list[int]]:
    """Compute per-base coverage across all ranges.

    Args:
        ranges: Genomic ranges to compute coverage for.

    Returns:
        Dict mapping chromosome name to a list of per-base coverage values.
    """
    return self._ops.coverage(ranges)

gaps

gaps(ranges, *, genome=None)

Return gaps between ranges on each chromosome.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

genome

Optional chromosome lengths for boundary gaps.

TYPE: dict[str, int] | None DEFAULT: None

RETURNS DESCRIPTION
list[GenomicPosition]

Ranges representing uncovered regions.

Source code in src/iris/domain/genomics/services.py
def gaps(
    self, ranges: Sequence[GenomicPosition], *, genome: dict[str, int] | None = None
) -> list[GenomicPosition]:
    """Return gaps between ranges on each chromosome.

    Args:
        ranges: Input genomic ranges.
        genome: Optional chromosome lengths for boundary gaps.

    Returns:
        Ranges representing uncovered regions.
    """
    return self._ops.gaps(ranges, genome=genome)

disjoin

disjoin(ranges)

Split ranges into non-overlapping disjoint intervals.

PARAMETER DESCRIPTION
ranges

Input genomic ranges, possibly overlapping.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Maximal set of non-overlapping ranges.

Source code in src/iris/domain/genomics/services.py
def disjoin(self, ranges: Sequence[GenomicPosition]) -> list[GenomicPosition]:
    """Split ranges into non-overlapping disjoint intervals.

    Args:
        ranges: Input genomic ranges, possibly overlapping.

    Returns:
        Maximal set of non-overlapping ranges.
    """
    return self._ops.disjoin(ranges)

nearest

nearest(query, subject, *, ignore_strand=True)

Find the nearest subject range for each query range.

PARAMETER DESCRIPTION
query

Ranges to search for neighbors.

TYPE: Sequence[GenomicPosition]

subject

Ranges to search within.

TYPE: Sequence[GenomicPosition]

ignore_strand

If False, only consider same-strand ranges.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
list[int | None]

List of indices into subject. None if no neighbor found.

Source code in src/iris/domain/genomics/services.py
def nearest(
    self,
    query: Sequence[GenomicPosition],
    subject: Sequence[GenomicPosition],
    *,
    ignore_strand: bool = True,
) -> list[int | None]:
    """Find the nearest subject range for each query range.

    Args:
        query: Ranges to search for neighbors.
        subject: Ranges to search within.
        ignore_strand: If False, only consider same-strand ranges.

    Returns:
        List of indices into subject. None if no neighbor found.
    """
    return self._ops.nearest(query, subject, ignore_strand=ignore_strand)

precede

precede(query, subject, *, ignore_strand=True)

Find the subject range immediately upstream of each query range.

PARAMETER DESCRIPTION
query

Ranges to search for upstream neighbors.

TYPE: Sequence[GenomicPosition]

subject

Ranges to search within.

TYPE: Sequence[GenomicPosition]

ignore_strand

If False, only consider same-strand ranges.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
list[int | None]

List of indices into subject. None if no upstream range found.

Source code in src/iris/domain/genomics/services.py
def precede(
    self,
    query: Sequence[GenomicPosition],
    subject: Sequence[GenomicPosition],
    *,
    ignore_strand: bool = True,
) -> list[int | None]:
    """Find the subject range immediately upstream of each query range.

    Args:
        query: Ranges to search for upstream neighbors.
        subject: Ranges to search within.
        ignore_strand: If False, only consider same-strand ranges.

    Returns:
        List of indices into subject. None if no upstream range found.
    """
    return self._ops.precede(query, subject, ignore_strand=ignore_strand)

follow

follow(query, subject, *, ignore_strand=True)

Find the subject range immediately downstream of each query range.

PARAMETER DESCRIPTION
query

Ranges to search for downstream neighbors.

TYPE: Sequence[GenomicPosition]

subject

Ranges to search within.

TYPE: Sequence[GenomicPosition]

ignore_strand

If False, only consider same-strand ranges.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
list[int | None]

List of indices into subject. None if no downstream range found.

Source code in src/iris/domain/genomics/services.py
def follow(
    self,
    query: Sequence[GenomicPosition],
    subject: Sequence[GenomicPosition],
    *,
    ignore_strand: bool = True,
) -> list[int | None]:
    """Find the subject range immediately downstream of each query range.

    Args:
        query: Ranges to search for downstream neighbors.
        subject: Ranges to search within.
        ignore_strand: If False, only consider same-strand ranges.

    Returns:
        List of indices into subject. None if no downstream range found.
    """
    return self._ops.follow(query, subject, ignore_strand=ignore_strand)

flank

flank(ranges, width, *, start=True, both=False)

Return flanking regions for each input range.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

width

Width of the flanking region in bases.

TYPE: int

start

If True, flank the start; if False, flank the end.

TYPE: bool DEFAULT: True

both

If True, return flanks on both sides.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
list[GenomicPosition]

Flanking ranges.

Source code in src/iris/domain/genomics/services.py
def flank(
    self,
    ranges: Sequence[GenomicPosition],
    width: int,
    *,
    start: bool = True,
    both: bool = False,
) -> list[GenomicPosition]:
    """Return flanking regions for each input range.

    Args:
        ranges: Input genomic ranges.
        width: Width of the flanking region in bases.
        start: If True, flank the start; if False, flank the end.
        both: If True, return flanks on both sides.

    Returns:
        Flanking ranges.
    """
    return self._ops.flank(ranges, width, start=start, both=both)

resize

resize(ranges, width, *, fix='start')

Resize ranges to a fixed width anchored at start, end, or center.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

width

Target width in bases.

TYPE: int

fix

Anchor point — one of 'start', 'end', or 'center'.

TYPE: str DEFAULT: 'start'

RETURNS DESCRIPTION
list[GenomicPosition]

Resized ranges.

Source code in src/iris/domain/genomics/services.py
def resize(
    self, ranges: Sequence[GenomicPosition], width: int, *, fix: str = "start"
) -> list[GenomicPosition]:
    """Resize ranges to a fixed width anchored at start, end, or center.

    Args:
        ranges: Input genomic ranges.
        width: Target width in bases.
        fix: Anchor point — one of 'start', 'end', or 'center'.

    Returns:
        Resized ranges.
    """
    return self._ops.resize(ranges, width, fix=fix)

shift

shift(ranges, shift)

Shift all ranges by a fixed number of bases.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

shift

Bases to shift. Positive = downstream, negative = upstream.

TYPE: int

RETURNS DESCRIPTION
list[GenomicPosition]

Shifted ranges.

Source code in src/iris/domain/genomics/services.py
def shift(self, ranges: Sequence[GenomicPosition], shift: int) -> list[GenomicPosition]:
    """Shift all ranges by a fixed number of bases.

    Args:
        ranges: Input genomic ranges.
        shift: Bases to shift. Positive = downstream, negative = upstream.

    Returns:
        Shifted ranges.
    """
    return self._ops.shift(ranges, shift)

HpoService

HpoService(ops)

Domain service for Human Phenotype Ontology operations.

Separates pure domain logic from backend-specific operations. Pure methods operate directly on :class:~iris.domain.genomics.objects.Phenotype objects without any external dependencies. Ontology-aware methods delegate to the injected :class:~iris.domain.genomics.ports.driven.hpo_ops.HpoOperations port.

PARAMETER DESCRIPTION
ops

Concrete implementation of the HpoOperations port.

TYPE: HpoOperations

Example
from iris.adapters.driven.genomic_analysis.pyhpo import PyHpoOperations

service = HpoService(ops=PyHpoOperations())
results = service.search("seizure", limit=5)
Source code in src/iris/domain/genomics/services.py
def __init__(self, ops: HpoOperations) -> None:
    self._ops = ops

with_id

with_id(phenotypes)

Return only phenotypes that carry an HPO identifier.

PARAMETER DESCRIPTION
phenotypes

Input phenotypes, mixed with and without IDs.

TYPE: Sequence[Phenotype]

RETURNS DESCRIPTION
list[Phenotype]

Subset of phenotypes where hpo_id is not None.

Source code in src/iris/domain/genomics/services.py
def with_id(self, phenotypes: Sequence[Phenotype]) -> list[Phenotype]:
    """Return only phenotypes that carry an HPO identifier.

    Args:
        phenotypes: Input phenotypes, mixed with and without IDs.

    Returns:
        Subset of phenotypes where ``hpo_id`` is not ``None``.
    """
    return [p for p in phenotypes if p.hpo_id is not None]

without_id

without_id(phenotypes)

Return only phenotypes that lack an HPO identifier.

Useful for identifying phenotypes that still need to be mapped to an HPO term.

PARAMETER DESCRIPTION
phenotypes

Input phenotypes.

TYPE: Sequence[Phenotype]

RETURNS DESCRIPTION
list[Phenotype]

Subset of phenotypes where hpo_id is None.

Source code in src/iris/domain/genomics/services.py
def without_id(self, phenotypes: Sequence[Phenotype]) -> list[Phenotype]:
    """Return only phenotypes that lack an HPO identifier.

    Useful for identifying phenotypes that still need to be mapped to
    an HPO term.

    Args:
        phenotypes: Input phenotypes.

    Returns:
        Subset of phenotypes where ``hpo_id`` is ``None``.
    """
    return [p for p in phenotypes if p.hpo_id is None]

by_id

by_id(phenotypes)

Index phenotypes by their HPO identifier.

Phenotypes without an hpo_id are silently excluded.

PARAMETER DESCRIPTION
phenotypes

Input phenotypes.

TYPE: Sequence[Phenotype]

RETURNS DESCRIPTION
dict[str, Phenotype]

Dict mapping hpo_id to the corresponding Phenotype.

dict[str, Phenotype]

When duplicates exist, the last occurrence wins.

Source code in src/iris/domain/genomics/services.py
def by_id(self, phenotypes: Sequence[Phenotype]) -> dict[str, Phenotype]:
    """Index phenotypes by their HPO identifier.

    Phenotypes without an ``hpo_id`` are silently excluded.

    Args:
        phenotypes: Input phenotypes.

    Returns:
        Dict mapping ``hpo_id`` to the corresponding ``Phenotype``.
        When duplicates exist, the last occurrence wins.
    """
    return {p.hpo_id: p for p in phenotypes if p.hpo_id is not None}

search

search(query, *, limit=10)

Search HPO terms by name or synonym.

PARAMETER DESCRIPTION
query

Free-text query matched against term names and synonyms.

TYPE: str

limit

Maximum number of results to return. Defaults to 10.

TYPE: int DEFAULT: 10

RETURNS DESCRIPTION
list[Phenotype]

Matching phenotypes ordered by relevance, up to limit results.

Source code in src/iris/domain/genomics/services.py
def search(self, query: str, *, limit: int = 10) -> list[Phenotype]:
    """Search HPO terms by name or synonym.

    Args:
        query: Free-text query matched against term names and synonyms.
        limit: Maximum number of results to return. Defaults to ``10``.

    Returns:
        Matching phenotypes ordered by relevance, up to ``limit`` results.
    """
    return self._ops.search(query, limit=limit)

get

get(hpo_id)

Retrieve a single HPO term by its identifier.

PARAMETER DESCRIPTION
hpo_id

HPO term identifier in 'HP:XXXXXXX' format.

TYPE: str

RETURNS DESCRIPTION
Phenotype | None

The corresponding Phenotype, or None if not found.

Source code in src/iris/domain/genomics/services.py
def get(self, hpo_id: str) -> Phenotype | None:
    """Retrieve a single HPO term by its identifier.

    Args:
        hpo_id: HPO term identifier in ``'HP:XXXXXXX'`` format.

    Returns:
        The corresponding ``Phenotype``, or ``None`` if not found.
    """
    return self._ops.get(hpo_id)

parents

parents(phenotype)

Return the direct parent terms of a phenotype.

PARAMETER DESCRIPTION
phenotype

Source phenotype. Must be resolvable to an HPO term.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

Direct parent phenotypes (one level up in the hierarchy).

RAISES DESCRIPTION
ValueError

If the phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/services.py
def parents(self, phenotype: Phenotype) -> list[Phenotype]:
    """Return the direct parent terms of a phenotype.

    Args:
        phenotype: Source phenotype. Must be resolvable to an HPO term.

    Returns:
        Direct parent phenotypes (one level up in the hierarchy).

    Raises:
        ValueError: If the phenotype cannot be resolved to an HPO term.
    """
    return self._ops.parents(phenotype)

children

children(phenotype)

Return the direct child terms of a phenotype.

PARAMETER DESCRIPTION
phenotype

Source phenotype. Must be resolvable to an HPO term.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

Direct child phenotypes (one level down in the hierarchy).

RAISES DESCRIPTION
ValueError

If the phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/services.py
def children(self, phenotype: Phenotype) -> list[Phenotype]:
    """Return the direct child terms of a phenotype.

    Args:
        phenotype: Source phenotype. Must be resolvable to an HPO term.

    Returns:
        Direct child phenotypes (one level down in the hierarchy).

    Raises:
        ValueError: If the phenotype cannot be resolved to an HPO term.
    """
    return self._ops.children(phenotype)

ancestors

ancestors(phenotype)

Return all ancestor terms up to the ontology root.

PARAMETER DESCRIPTION
phenotype

Source phenotype. Must be resolvable to an HPO term.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

All ancestor phenotypes, from closest parent to root.

RAISES DESCRIPTION
ValueError

If the phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/services.py
def ancestors(self, phenotype: Phenotype) -> list[Phenotype]:
    """Return all ancestor terms up to the ontology root.

    Args:
        phenotype: Source phenotype. Must be resolvable to an HPO term.

    Returns:
        All ancestor phenotypes, from closest parent to root.

    Raises:
        ValueError: If the phenotype cannot be resolved to an HPO term.
    """
    return self._ops.ancestors(phenotype)

descendants

descendants(phenotype)

Return all descendant terms below the given phenotype.

PARAMETER DESCRIPTION
phenotype

Source phenotype. Must be resolvable to an HPO term.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

All descendant phenotypes reachable via child relationships.

RAISES DESCRIPTION
ValueError

If the phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/services.py
def descendants(self, phenotype: Phenotype) -> list[Phenotype]:
    """Return all descendant terms below the given phenotype.

    Args:
        phenotype: Source phenotype. Must be resolvable to an HPO term.

    Returns:
        All descendant phenotypes reachable via child relationships.

    Raises:
        ValueError: If the phenotype cannot be resolved to an HPO term.
    """
    return self._ops.descendants(phenotype)

similarity

similarity(a, b, *, kind='omim')

Compute semantic similarity between two HPO terms.

PARAMETER DESCRIPTION
a

First phenotype.

TYPE: Phenotype

b

Second phenotype.

TYPE: Phenotype

kind

IC flavour — one of 'omim', 'gene', 'orpha', 'decipher'. Defaults to 'omim'.

TYPE: str DEFAULT: 'omim'

RETURNS DESCRIPTION
float

Similarity score in [0, 1].

RAISES DESCRIPTION
ValueError

If either phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/services.py
def similarity(self, a: Phenotype, b: Phenotype, *, kind: str = "omim") -> float:
    """Compute semantic similarity between two HPO terms.

    Args:
        a: First phenotype.
        b: Second phenotype.
        kind: IC flavour — one of ``'omim'``, ``'gene'``, ``'orpha'``,
            ``'decipher'``. Defaults to ``'omim'``.

    Returns:
        Similarity score in ``[0, 1]``.

    Raises:
        ValueError: If either phenotype cannot be resolved to an HPO term.
    """
    return self._ops.similarity(a, b, kind=kind)

set_similarity

set_similarity(a, b, *, kind='omim', combine='funSimAvg')

Compute semantic similarity between two sets of HPO terms.

PARAMETER DESCRIPTION
a

First phenotype set.

TYPE: Sequence[Phenotype]

b

Second phenotype set.

TYPE: Sequence[Phenotype]

kind

IC flavour — one of 'omim', 'gene', 'orpha', 'decipher'. Defaults to 'omim'.

TYPE: str DEFAULT: 'omim'

combine

Aggregation strategy — one of 'funSimAvg', 'funSimMax', 'BMA'. Defaults to 'funSimAvg'.

TYPE: str DEFAULT: 'funSimAvg'

RETURNS DESCRIPTION
float

Similarity score in [0, 1].

RAISES DESCRIPTION
ValueError

If any phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/services.py
def set_similarity(
    self,
    a: Sequence[Phenotype],
    b: Sequence[Phenotype],
    *,
    kind: str = "omim",
    combine: str = "funSimAvg",
) -> float:
    """Compute semantic similarity between two sets of HPO terms.

    Args:
        a: First phenotype set.
        b: Second phenotype set.
        kind: IC flavour — one of ``'omim'``, ``'gene'``, ``'orpha'``,
            ``'decipher'``. Defaults to ``'omim'``.
        combine: Aggregation strategy — one of ``'funSimAvg'``,
            ``'funSimMax'``, ``'BMA'``. Defaults to ``'funSimAvg'``.

    Returns:
        Similarity score in ``[0, 1]``.

    Raises:
        ValueError: If any phenotype cannot be resolved to an HPO term.
    """
    return self._ops.set_similarity(a, b, kind=kind, combine=combine)

common_ancestors

common_ancestors(a, b)

Return the common ancestors of two HPO terms.

PARAMETER DESCRIPTION
a

First phenotype.

TYPE: Phenotype

b

Second phenotype.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

Phenotypes that are ancestors of both a and b.

RAISES DESCRIPTION
ValueError

If either phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/services.py
def common_ancestors(self, a: Phenotype, b: Phenotype) -> list[Phenotype]:
    """Return the common ancestors of two HPO terms.

    Args:
        a: First phenotype.
        b: Second phenotype.

    Returns:
        Phenotypes that are ancestors of both ``a`` and ``b``.

    Raises:
        ValueError: If either phenotype cannot be resolved to an HPO term.
    """
    return self._ops.common_ancestors(a, b)

Events

iris.domain.genomics.events

Domain events for the genomics bounded context.

VariantAnnotated

Emitted when a variant is successfully annotated.

VariantClassified

Emitted when a variant receives a clinical significance classification.

GeneAnnotated

Emitted when a gene is successfully annotated.

DiseaseAssociationRecorded

Emitted when a gene-disease association is recorded.

Exceptions

iris.domain.genomics.exceptions

Exceptions for the genomics domain.

GenomicsError

Bases: Exception

Base exception for the genomics domain.

VariantNotFoundError

Bases: GenomicsError

Raised when a variant cannot be found by the given identifier.

GeneNotFoundError

Bases: GenomicsError

Raised when a gene cannot be found by the given identifier.

TranscriptNotFoundError

Bases: GenomicsError

Raised when a transcript cannot be found by the given identifier.

DiseaseNotFoundError

Bases: GenomicsError

Raised when a disease cannot be found by the given identifier.

InvalidGenomicPositionError

Bases: GenomicsError

Raised when a genomic coordinate is malformed or out of range.

InvalidAlleleError

Bases: GenomicsError

Raised when an allele string does not match the expected format.

AnnotationError

Bases: GenomicsError

Raised when an annotation pipeline fails to produce a result.

AmbiguousIdentifierError

Bases: GenomicsError

Raised when an identifier matches more than one entity.

Utils

iris.domain.genomics.utils

Pure utility functions for the genomics domain.

These functions are stateless helpers used across the domain and adapters. They live here rather than in objects.py to keep that module focused on data modeling, and rather than in services.py to avoid conflating pure utilities with stateful domain services.

HUMAN_CHROMOSOMES module-attribute

HUMAN_CHROMOSOMES = ChromosomeSet(
    aliases=AliasSet(
        names=_HUMAN_CHROMOSOME_NAMES,
        aliases=_human_chromosome_aliases(),
    )
)

Canonical ChromosomeSet for the human genome (autosomes 1-22, X, Y, MT).

Built once at import time. Use resolve_human_chromosome() rather than HUMAN_CHROMOSOMES.resolve() directly when the input may carry an NCBI RefSeq version suffix (e.g. 'NC_000001.11') — the wrapper strips it first.

Example
HUMAN_CHROMOSOMES.resolve("chr1")
# '1'
HUMAN_CHROMOSOMES.resolve("chrM")
# 'MT'
"X" in HUMAN_CHROMOSOMES
# True

normalize_chromosome

normalize_chromosome(chromosome)

Return the canonical chromosome name: strip 'chr' prefix, uppercase, M → MT.

Thin wrapper around ChromosomeSet.normalize() kept for backwards compatibility with existing adapter imports.

PARAMETER DESCRIPTION
chromosome

Raw chromosome name (e.g. 'chr1', 'chrX', 'chrM', '1').

TYPE: str

RETURNS DESCRIPTION
str

Normalized chromosome name (e.g. '1', 'X', 'MT').

Source code in src/iris/domain/genomics/utils.py
def normalize_chromosome(chromosome: str) -> str:
    """Return the canonical chromosome name: strip 'chr' prefix, uppercase, M → MT.

    Thin wrapper around ChromosomeSet.normalize() kept for backwards
    compatibility with existing adapter imports.

    Args:
        chromosome: Raw chromosome name (e.g. 'chr1', 'chrX', 'chrM', '1').

    Returns:
        Normalized chromosome name (e.g. '1', 'X', 'MT').
    """
    return ChromosomeSet.normalize(chromosome)

resolve_chromosome

resolve_chromosome(chrom, contigs)

Return the contig name as it appears in an index, or None if absent.

Tries the bare name first, then toggles the 'chr' prefix so that files indexed as 'chrX' work with bare names ('X') and vice-versa.

PARAMETER DESCRIPTION
chrom

Normalized chromosome name (e.g. '1', 'X', 'MT').

TYPE: str

contigs

Frozenset of contig names as they appear in the index.

TYPE: frozenset[str]

RETURNS DESCRIPTION
str | None

Matching contig name from the index, or None if not found.

Source code in src/iris/domain/genomics/utils.py
def resolve_chromosome(chrom: str, contigs: frozenset[str]) -> str | None:
    """Return the contig name as it appears in an index, or None if absent.

    Tries the bare name first, then toggles the 'chr' prefix so that files
    indexed as 'chrX' work with bare names ('X') and vice-versa.

    Args:
        chrom: Normalized chromosome name (e.g. '1', 'X', 'MT').
        contigs: Frozenset of contig names as they appear in the index.

    Returns:
        Matching contig name from the index, or None if not found.
    """
    if chrom in contigs:
        return chrom
    toggled = chrom[3:] if chrom.startswith("chr") else f"chr{chrom}"
    return toggled if toggled in contigs else None

chromosome_sort_key

chromosome_sort_key(chrom)

Return a sort key that orders chromosomes numerically then lexicographically.

Numeric chromosomes (1–22) sort before X, Y, MT, and any other names.

PARAMETER DESCRIPTION
chrom

Normalized chromosome name (e.g. '1', 'X', 'MT').

TYPE: str

RETURNS DESCRIPTION
tuple[int, str]

Tuple of (numeric_order, name) for use as a sort key.

Source code in src/iris/domain/genomics/utils.py
def chromosome_sort_key(chrom: str) -> tuple[int, str]:
    """Return a sort key that orders chromosomes numerically then lexicographically.

    Numeric chromosomes (1–22) sort before X, Y, MT, and any other names.

    Args:
        chrom: Normalized chromosome name (e.g. '1', 'X', 'MT').

    Returns:
        Tuple of (numeric_order, name) for use as a sort key.
    """
    digits = "".join(c for c in chrom if c.isdigit())
    return (int(digits) if digits else 9999, chrom)

batched

batched(items, size)

Yield ordered non-overlapping chunks from a sequence.

PARAMETER DESCRIPTION
items

Input sequence.

TYPE: Sequence[_KT]

size

Maximum chunk size.

TYPE: int

YIELDS DESCRIPTION
Iterable[Sequence[_KT]]

Sequence slices preserving input order.

Source code in src/iris/domain/genomics/utils.py
def batched(items: Sequence[_KT], size: int) -> Iterable[Sequence[_KT]]:
    """Yield ordered non-overlapping chunks from a sequence.

    Args:
        items: Input sequence.
        size: Maximum chunk size.

    Yields:
        Sequence slices preserving input order.
    """
    if size <= 0:
        raise ValueError("batch size must be > 0")

    for i in range(0, len(items), size):
        yield items[i : i + size]

group_positions_by_chromosome_sorted

group_positions_by_chromosome_sorted(positions)

Group positions by chromosome with chromosome-sort order.

Source code in src/iris/domain/genomics/utils.py
def group_positions_by_chromosome_sorted(
    positions: list[GenomicPosition],
) -> dict[str, list[GenomicPosition]]:
    """Group positions by chromosome with chromosome-sort order."""
    grouped = group_by_chromosome(positions)
    return {chrom: grouped[chrom] for chrom in sorted(grouped, key=chromosome_sort_key)}

group_positions

group_positions(positions, key)

Partition positions into groups defined by key, preserving order within each group.

PARAMETER DESCRIPTION
positions

List of genomic positions to group.

TYPE: list[GenomicPosition]

key

Function that extracts the grouping key from a position.

TYPE: Callable[[GenomicPosition], _KT]

RETURNS DESCRIPTION
dict[_KT, list[GenomicPosition]]

Dict mapping each key value to the list of positions that produced it.

Source code in src/iris/domain/genomics/utils.py
def group_positions(
    positions: list[GenomicPosition], key: Callable[[GenomicPosition], _KT]
) -> dict[_KT, list[GenomicPosition]]:
    """Partition *positions* into groups defined by *key*, preserving order within each group.

    Args:
        positions: List of genomic positions to group.
        key: Function that extracts the grouping key from a position.

    Returns:
        Dict mapping each key value to the list of positions that produced it.
    """
    result: dict[_KT, list[GenomicPosition]] = defaultdict(list)
    for pos in positions:
        result[key(pos)].append(pos)
    return dict(result)

chrom_blocks

chrom_blocks(chrom, length, block_size=10000000)

Partition a chromosome into non-overlapping GenomicPosition blocks.

Coordinates are 1-based inclusive (GenomicPosition convention). The last block is clipped to length so no block extends past the chromosome end.

PARAMETER DESCRIPTION
chrom

Normalized chromosome name (e.g. "1", "X").

TYPE: str

length

Total chromosome length in bases.

TYPE: int

block_size

Size of each block in bases (default 10 Mb).

TYPE: int DEFAULT: 10000000

RETURNS DESCRIPTION
list[GenomicPosition]

Ordered list of GenomicPosition blocks.

Example
chrom_blocks("1", 25_000_000, block_size=10_000_000)
# [GenomicPosition(chrom=1, 1-10_000_000),
#  GenomicPosition(chrom=1, 10_000_001-20_000_000),
#  GenomicPosition(chrom=1, 20_000_001-25_000_000)]
Source code in src/iris/domain/genomics/utils.py
def chrom_blocks(chrom: str, length: int, block_size: int = 10_000_000) -> list[GenomicPosition]:
    """Partition a chromosome into non-overlapping GenomicPosition blocks.

    Coordinates are 1-based inclusive (GenomicPosition convention).  The last
    block is clipped to *length* so no block extends past the chromosome end.

    Args:
        chrom: Normalized chromosome name (e.g. ``"1"``, ``"X"``).
        length: Total chromosome length in bases.
        block_size: Size of each block in bases (default 10 Mb).

    Returns:
        Ordered list of GenomicPosition blocks.

    Example:
        ```python
        chrom_blocks("1", 25_000_000, block_size=10_000_000)
        # [GenomicPosition(chrom=1, 1-10_000_000),
        #  GenomicPosition(chrom=1, 10_000_001-20_000_000),
        #  GenomicPosition(chrom=1, 20_000_001-25_000_000)]
        ```
    """
    from iris.domain.genomics.objects import GenomicPosition  # local: avoids circular import

    blocks: list[GenomicPosition] = []
    start = 1
    while start <= length:
        blocks.append(
            GenomicPosition(chromosome=chrom, start=start, end=min(start + block_size - 1, length))
        )
        start += block_size
    return blocks

group_by_chromosome

group_by_chromosome(positions)

Partition positions by chromosome, preserving original order within each group.

PARAMETER DESCRIPTION
positions

List of genomic positions to group.

TYPE: list[GenomicPosition]

RETURNS DESCRIPTION
dict[str, list[GenomicPosition]]

Dict mapping chromosome name to the positions on that chromosome.

Source code in src/iris/domain/genomics/utils.py
def group_by_chromosome(positions: list[GenomicPosition]) -> dict[str, list[GenomicPosition]]:
    """Partition *positions* by chromosome, preserving original order within each group.

    Args:
        positions: List of genomic positions to group.

    Returns:
        Dict mapping chromosome name to the positions on that chromosome.
    """
    return group_positions(positions, lambda p: p.chromosome)

resolve_human_chromosome

resolve_human_chromosome(value)

Resolve any common human chromosome representation to its canonical name.

Handles UCSC ('chr1'), plain ('1'), and versioned NCBI RefSeq ('NC_000001.11') forms uniformly. This is the recommended replacement for normalize_chromosome() going forward — it additionally supports RefSeq accessions, which normalize_chromosome() never handled.

PARAMETER DESCRIPTION
value

Raw chromosome identifier from any source (VCF contig line, BAM/zarr metadata, user input).

TYPE: str

RETURNS DESCRIPTION
str

Canonical name ('1'..'22', 'X', 'Y', 'MT').

RAISES DESCRIPTION
KeyError

If value does not match any known human chromosome name or alias.

Example
resolve_human_chromosome("NC_000001.11")
# '1'
resolve_human_chromosome("chrX")
# 'X'
resolve_human_chromosome("NC_012920.1")
# 'MT'
Source code in src/iris/domain/genomics/utils.py
def resolve_human_chromosome(value: str) -> str:
    """Resolve any common human chromosome representation to its canonical name.

    Handles UCSC ('chr1'), plain ('1'), and versioned NCBI RefSeq
    ('NC_000001.11') forms uniformly. This is the recommended replacement
    for normalize_chromosome() going forward — it additionally supports
    RefSeq accessions, which normalize_chromosome() never handled.

    Args:
        value: Raw chromosome identifier from any source (VCF contig line,
            BAM/zarr metadata, user input).

    Returns:
        Canonical name ('1'..'22', 'X', 'Y', 'MT').

    Raises:
        KeyError: If value does not match any known human chromosome name
            or alias.

    Example:
        ```python
        resolve_human_chromosome("NC_000001.11")
        # '1'
        resolve_human_chromosome("chrX")
        # 'X'
        resolve_human_chromosome("NC_012920.1")
        # 'MT'
        ```
    """
    # Strip RefSeq version suffix if present (e.g. "NC_000001.11" -> "NC_000001").
    base = value.split(".")[0] if value.startswith("NC_") else value
    return HUMAN_CHROMOSOMES.resolve(base)

chromosome_set_from_contigs

chromosome_set_from_contigs(contigs)

Build a ChromosomeSet from the literal contig names of an index file.

Unlike HUMAN_CHROMOSOMES, this does not assume canonical human naming — it takes whatever contig strings appear in a tabix/zarr index (which may use 'chr1', '1', or something else entirely) and builds aliases by toggling the 'chr' prefix, mirroring the old resolve_chromosome() logic. Use this when resolving against a specific file's contigs rather than a fixed catalog.

PARAMETER DESCRIPTION
contigs

Contig names exactly as they appear in the file's index.

TYPE: Iterable[str]

RETURNS DESCRIPTION
ChromosomeSet

ChromosomeSet whose canonical names are the literal contig strings,

ChromosomeSet

with chr-prefix-toggled aliases for convenience.

Example
cs = chromosome_set_from_contigs(["chr1", "chr2", "chrX"])
cs.resolve("1")
# 'chr1'
Source code in src/iris/domain/genomics/utils.py
def chromosome_set_from_contigs(contigs: Iterable[str]) -> ChromosomeSet:
    """Build a ChromosomeSet from the literal contig names of an index file.

    Unlike HUMAN_CHROMOSOMES, this does not assume canonical human naming —
    it takes whatever contig strings appear in a tabix/zarr index (which may
    use 'chr1', '1', or something else entirely) and builds aliases by
    toggling the 'chr' prefix, mirroring the old resolve_chromosome() logic.
    Use this when resolving against a specific file's contigs rather than
    a fixed catalog.

    Args:
        contigs: Contig names exactly as they appear in the file's index.

    Returns:
        ChromosomeSet whose canonical names are the literal contig strings,
        with chr-prefix-toggled aliases for convenience.

    Example:
        ```python
        cs = chromosome_set_from_contigs(["chr1", "chr2", "chrX"])
        cs.resolve("1")
        # 'chr1'
        ```
    """
    contig_list = list(contigs)
    aliases: dict[str, str] = {}
    for contig in contig_list:
        toggled = contig[3:] if contig.startswith("chr") else f"chr{contig}"
        if toggled != contig:
            aliases[toggled] = contig
    return ChromosomeSet(aliases=AliasSet(names=contig_list, aliases=aliases))

Ports

Annotators

iris.domain.genomics.ports.driven.annotators

Annotator port interfaces for the genomics domain.

Annotator

Bases: ABC, Generic[T]

Abstract base class for annotation sources that enrich genomic entities.

annotate abstractmethod

annotate(items)

Return new items enriched with this source's data.

Source code in src/iris/domain/genomics/ports/driven/annotators.py
@abstractmethod
def annotate(self, items: Sequence[T]) -> list[T]:
    """Return new items enriched with this source's data."""

VariantAnnotator

Bases: Annotator[VariantAnnotation]

Abstract annotator that enriches VariantAnnotation objects.

annotate abstractmethod

annotate(items)

Return new VariantAnnotation objects enriched with this source's data.

Source code in src/iris/domain/genomics/ports/driven/annotators.py
@abstractmethod
def annotate(self, items: Sequence[VariantAnnotation]) -> list[VariantAnnotation]:
    """Return new VariantAnnotation objects enriched with this source's data."""

GeneAnnotator

Bases: Annotator[GeneAnnotation]

Abstract annotator that enriches GeneAnnotation objects.

annotate abstractmethod

annotate(items)

Return new GeneAnnotation objects enriched with this source's data.

Source code in src/iris/domain/genomics/ports/driven/annotators.py
@abstractmethod
def annotate(self, items: Sequence[GeneAnnotation]) -> list[GeneAnnotation]:
    """Return new GeneAnnotation objects enriched with this source's data."""

Variant stores

iris.domain.genomics.ports.driven.variant_store

VariantStore port interfaces for the genomics domain.

VariantStore

Bases: ABC, Generic[T]

Abstract port for sources that deliver variants for a genomic region.

Symmetric to :class:~iris.domain.genomics.ports.driven.annotators.Annotator: annotators enrich existing :class:~iris.domain.genomics.entities.VariantAnnotation objects; stores produce them from raw data sources (Zarr, VCF, tabix, …).

fetch abstractmethod

fetch(region)

Return all variants whose positions fall within region.

PARAMETER DESCRIPTION
region

1-based inclusive genomic window. If start or end is None the store may return all variants on the chromosome.

TYPE: GenomicPosition

RETURNS DESCRIPTION
list[T]

Possibly-empty list of variants within the region.

Source code in src/iris/domain/genomics/ports/driven/variant_store.py
@abstractmethod
def fetch(self, region: GenomicPosition) -> list[T]:
    """Return all variants whose positions fall within *region*.

    Args:
        region: 1-based inclusive genomic window. If ``start`` or ``end``
            is ``None`` the store may return all variants on the chromosome.

    Returns:
        Possibly-empty list of variants within the region.
    """

VariantAnnotationStore

Bases: VariantStore[VariantAnnotation]

Abstract store that delivers VariantAnnotation objects per region.

Concrete implementations include Zarr cohort stores, tabix-indexed VCF files, or any other indexed variant source.

fetch abstractmethod

fetch(region)

Return all variant annotations within region.

PARAMETER DESCRIPTION
region

1-based inclusive genomic window.

TYPE: GenomicPosition

RETURNS DESCRIPTION
list[VariantAnnotation]

Possibly-empty list of

list[VariantAnnotation]

class:~iris.domain.genomics.entities.VariantAnnotation.

Source code in src/iris/domain/genomics/ports/driven/variant_store.py
@abstractmethod
def fetch(self, region: GenomicPosition) -> list[VariantAnnotation]:
    """Return all variant annotations within *region*.

    Args:
        region: 1-based inclusive genomic window.

    Returns:
        Possibly-empty list of
        :class:`~iris.domain.genomics.entities.VariantAnnotation`.
    """

Exporters

iris.domain.genomics.ports.driven.exporters

Exporter port interface for variant annotation output.

VariantExporter

Bases: ABC

Abstract exporter for writing VariantAnnotation collections to a stream.

export abstractmethod

export(variants, handle)

Write variants to handle in the target format.

Source code in src/iris/domain/genomics/ports/driven/exporters.py
@abstractmethod
def export(self, variants: Sequence[VariantAnnotation], handle: TextIO) -> None:
    """Write variants to handle in the target format."""

Region reader

iris.domain.genomics.ports.driven.region_reader

Reader port interface for genomic region input.

RegionReader

Bases: ABC

Abstract reader for parsing genomic region data from a stream.

read abstractmethod

read(handle, *, assembly=None)

Read genomic regions from handle and return them as GenomicPosition objects.

Source code in src/iris/domain/genomics/ports/driven/region_reader.py
@abstractmethod
def read(self, handle: TextIO, *, assembly: str | None = None) -> list[GenomicPosition]:
    """Read genomic regions from handle and return them as GenomicPosition objects."""

Region exporter

iris.domain.genomics.ports.driven.region_exporter

Exporter port interface for genomic region output.

RegionExporter

Bases: ABC

Abstract exporter for writing GenomicPosition collections to a stream.

export abstractmethod

export(positions, handle)

Write genomic positions to handle in the target format.

Source code in src/iris/domain/genomics/ports/driven/region_exporter.py
@abstractmethod
def export(self, positions: Sequence[GenomicPosition], handle: TextIO) -> None:
    """Write genomic positions to handle in the target format."""

Gene nomenclature resolver

iris.domain.genomics.ports.driven.gene_nomenclature

Port for resolving and enriching GeneNomenclature from external databases.

Defines the abstract interface that any gene nomenclature backend must implement. The domain uses this port to resolve symbols, aliases, and cross-references without depending on a specific database or API.

GeneNomenclatureResolver

Bases: ABC

Abstract port for resolving gene nomenclature from external sources.

Implementations may be backed by local databases (PyHGNC), REST APIs (HGNC REST API), or other reference sources. All methods return GeneNomenclature objects enriched with the identifiers available in the underlying source.

resolve_symbol abstractmethod

resolve_symbol(symbol)

Resolve a gene symbol to a canonical GeneNomenclature.

Searches both approved symbols and known aliases.

PARAMETER DESCRIPTION
symbol

Gene symbol to resolve (e.g. 'BRCA1', 'TP53').

TYPE: str

RETURNS DESCRIPTION
GeneNomenclature | None

Canonical GeneNomenclature if found, None otherwise.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def resolve_symbol(self, symbol: str) -> GeneNomenclature | None:
    """Resolve a gene symbol to a canonical GeneNomenclature.

    Searches both approved symbols and known aliases.

    Args:
        symbol: Gene symbol to resolve (e.g. 'BRCA1', 'TP53').

    Returns:
        Canonical GeneNomenclature if found, None otherwise.
    """

resolve_hgnc_id abstractmethod

resolve_hgnc_id(hgnc_id)

Resolve a gene by its HGNC identifier.

PARAMETER DESCRIPTION
hgnc_id

HGNC identifier string (e.g. 'HGNC:1100').

TYPE: str

RETURNS DESCRIPTION
GeneNomenclature | None

GeneNomenclature if found, None otherwise.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def resolve_hgnc_id(self, hgnc_id: str) -> GeneNomenclature | None:
    """Resolve a gene by its HGNC identifier.

    Args:
        hgnc_id: HGNC identifier string (e.g. 'HGNC:1100').

    Returns:
        GeneNomenclature if found, None otherwise.
    """

resolve_ensembl_id abstractmethod

resolve_ensembl_id(ensembl_id)

Resolve a gene by its Ensembl gene identifier.

PARAMETER DESCRIPTION
ensembl_id

Ensembl gene ID (e.g. 'ENSG00000012048').

TYPE: str

RETURNS DESCRIPTION
GeneNomenclature | None

GeneNomenclature if found, None otherwise.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def resolve_ensembl_id(self, ensembl_id: str) -> GeneNomenclature | None:
    """Resolve a gene by its Ensembl gene identifier.

    Args:
        ensembl_id: Ensembl gene ID (e.g. 'ENSG00000012048').

    Returns:
        GeneNomenclature if found, None otherwise.
    """

resolve_refseq_id abstractmethod

resolve_refseq_id(refseq_id)

Resolve a gene by its RefSeq accession.

PARAMETER DESCRIPTION
refseq_id

RefSeq accession (e.g. 'NM_007294').

TYPE: str

RETURNS DESCRIPTION
GeneNomenclature | None

GeneNomenclature if found, None otherwise.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def resolve_refseq_id(self, refseq_id: str) -> GeneNomenclature | None:
    """Resolve a gene by its RefSeq accession.

    Args:
        refseq_id: RefSeq accession (e.g. 'NM_007294').

    Returns:
        GeneNomenclature if found, None otherwise.
    """

resolve_entrez_id abstractmethod

resolve_entrez_id(entrez_id)

Resolve a gene by its NCBI Entrez gene ID.

PARAMETER DESCRIPTION
entrez_id

NCBI Entrez gene identifier (e.g. 672).

TYPE: int

RETURNS DESCRIPTION
GeneNomenclature | None

GeneNomenclature if found, None otherwise.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def resolve_entrez_id(self, entrez_id: int) -> GeneNomenclature | None:
    """Resolve a gene by its NCBI Entrez gene ID.

    Args:
        entrez_id: NCBI Entrez gene identifier (e.g. 672).

    Returns:
        GeneNomenclature if found, None otherwise.
    """

search_by_name abstractmethod

search_by_name(name)

Search genes by full or partial name.

Supports wildcard matching where the backend allows it. Use '%' as wildcard (e.g. 'breast cancer%').

PARAMETER DESCRIPTION
name

Full or partial gene name.

TYPE: str

RETURNS DESCRIPTION
list[GeneNomenclature]

List of matching GeneNomenclature objects, possibly empty.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def search_by_name(self, name: str) -> list[GeneNomenclature]:
    """Search genes by full or partial name.

    Supports wildcard matching where the backend allows it.
    Use '%' as wildcard (e.g. 'breast cancer%').

    Args:
        name: Full or partial gene name.

    Returns:
        List of matching GeneNomenclature objects, possibly empty.
    """

aliases abstractmethod

aliases(symbol)

Return all known alias symbols for a gene.

PARAMETER DESCRIPTION
symbol

Approved gene symbol.

TYPE: str

RETURNS DESCRIPTION
list[str]

List of alias symbols. Empty if gene not found or no aliases.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def aliases(self, symbol: str) -> list[str]:
    """Return all known alias symbols for a gene.

    Args:
        symbol: Approved gene symbol.

    Returns:
        List of alias symbols. Empty if gene not found or no aliases.
    """

gene_family abstractmethod

gene_family(symbol)

Return the gene family names associated with a gene symbol.

PARAMETER DESCRIPTION
symbol

Approved gene symbol.

TYPE: str

RETURNS DESCRIPTION
list[str]

List of gene family names. Empty if none found.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def gene_family(self, symbol: str) -> list[str]:
    """Return the gene family names associated with a gene symbol.

    Args:
        symbol: Approved gene symbol.

    Returns:
        List of gene family names. Empty if none found.
    """

orthologs abstractmethod

orthologs(symbol, *, species=None)

Return orthology predictions for a gene symbol.

PARAMETER DESCRIPTION
symbol

Approved gene symbol.

TYPE: str

species

Optional NCBI taxonomy ID to filter by species (e.g. 10090 for Mus musculus).

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
list[dict[str, str]]

List of dicts with ortholog data. Each dict contains at minimum

list[dict[str, str]]

'species', 'symbol', and 'ortholog_species_id'.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def orthologs(self, symbol: str, *, species: int | None = None) -> list[dict[str, str]]:
    """Return orthology predictions for a gene symbol.

    Args:
        symbol: Approved gene symbol.
        species: Optional NCBI taxonomy ID to filter by species
                 (e.g. 10090 for Mus musculus).

    Returns:
        List of dicts with ortholog data. Each dict contains at minimum
        'species', 'symbol', and 'ortholog_species_id'.
    """

resolve_many abstractmethod

resolve_many(symbols)

Resolve multiple gene symbols in a single operation.

More efficient than calling resolve_symbol in a loop when the backend supports batch queries.

PARAMETER DESCRIPTION
symbols

Sequence of gene symbols to resolve.

TYPE: Sequence[str]

RETURNS DESCRIPTION
dict[str, GeneNomenclature | None]

Dict mapping each input symbol to its GeneNomenclature,

dict[str, GeneNomenclature | None]

or None if the symbol could not be resolved.

Source code in src/iris/domain/genomics/ports/driven/gene_nomenclature.py
@abstractmethod
def resolve_many(self, symbols: Sequence[str]) -> dict[str, GeneNomenclature | None]:
    """Resolve multiple gene symbols in a single operation.

    More efficient than calling resolve_symbol in a loop when the
    backend supports batch queries.

    Args:
        symbols: Sequence of gene symbols to resolve.

    Returns:
        Dict mapping each input symbol to its GeneNomenclature,
        or None if the symbol could not be resolved.
    """

Range operations

iris.domain.genomics.ports.driven.range_ops

Port for complex genomic range operations.

Defines the abstract interface that any range-computation backend must implement. The domain service uses this port; concrete adapters (BiocPy, bedtools, etc.) provide the implementation.

RangeOperations

Bases: ABC

Abstract port for set-level and arithmetic operations on genomic ranges.

All coordinates follow the GenomicPosition convention: 1-based inclusive. Implementations are responsible for any coordinate conversion required by the underlying backend.

union abstractmethod

union(a, b)

Return the union of two sets of genomic ranges.

PARAMETER DESCRIPTION
a

First set of genomic ranges.

TYPE: Sequence[GenomicPosition]

b

Second set of genomic ranges.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Merged, non-overlapping ranges covering all positions in a or b.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def union(
    self, a: Sequence[GenomicPosition], b: Sequence[GenomicPosition]
) -> list[GenomicPosition]:
    """Return the union of two sets of genomic ranges.

    Args:
        a: First set of genomic ranges.
        b: Second set of genomic ranges.

    Returns:
        Merged, non-overlapping ranges covering all positions in a or b.
    """

intersect abstractmethod

intersect(a, b)

Return ranges present in both a and b.

PARAMETER DESCRIPTION
a

First set of genomic ranges.

TYPE: Sequence[GenomicPosition]

b

Second set of genomic ranges.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Ranges representing the intersection of a and b.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def intersect(
    self, a: Sequence[GenomicPosition], b: Sequence[GenomicPosition]
) -> list[GenomicPosition]:
    """Return ranges present in both a and b.

    Args:
        a: First set of genomic ranges.
        b: Second set of genomic ranges.

    Returns:
        Ranges representing the intersection of a and b.
    """

setdiff abstractmethod

setdiff(a, b)

Return ranges in a that are not covered by b.

PARAMETER DESCRIPTION
a

Query ranges.

TYPE: Sequence[GenomicPosition]

b

Ranges to subtract from a.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Ranges in a not overlapping any range in b.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def setdiff(
    self, a: Sequence[GenomicPosition], b: Sequence[GenomicPosition]
) -> list[GenomicPosition]:
    """Return ranges in a that are not covered by b.

    Args:
        a: Query ranges.
        b: Ranges to subtract from a.

    Returns:
        Ranges in a not overlapping any range in b.
    """

coverage abstractmethod

coverage(ranges)

Compute per-base coverage across all ranges.

PARAMETER DESCRIPTION
ranges

Genomic ranges to compute coverage for.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
dict[str, list[int]]

Dict mapping chromosome name to a list of per-base coverage values.

dict[str, list[int]]

Index 0 corresponds to position 1 on that chromosome.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def coverage(self, ranges: Sequence[GenomicPosition]) -> dict[str, list[int]]:
    """Compute per-base coverage across all ranges.

    Args:
        ranges: Genomic ranges to compute coverage for.

    Returns:
        Dict mapping chromosome name to a list of per-base coverage values.
        Index 0 corresponds to position 1 on that chromosome.
    """

gaps abstractmethod

gaps(ranges, *, genome=None)

Return the gaps between ranges on each chromosome.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

genome

Optional dict mapping chromosome name to its length. When provided, gaps at chromosome boundaries are included.

TYPE: dict[str, int] | None DEFAULT: None

RETURNS DESCRIPTION
list[GenomicPosition]

Ranges representing uncovered regions between the input ranges.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def gaps(
    self, ranges: Sequence[GenomicPosition], *, genome: dict[str, int] | None = None
) -> list[GenomicPosition]:
    """Return the gaps between ranges on each chromosome.

    Args:
        ranges: Input genomic ranges.
        genome: Optional dict mapping chromosome name to its length.
                When provided, gaps at chromosome boundaries are included.

    Returns:
        Ranges representing uncovered regions between the input ranges.
    """

disjoin abstractmethod

disjoin(ranges)

Split ranges into non-overlapping disjoint intervals.

PARAMETER DESCRIPTION
ranges

Input genomic ranges, possibly overlapping.

TYPE: Sequence[GenomicPosition]

RETURNS DESCRIPTION
list[GenomicPosition]

Maximal set of non-overlapping ranges derived from the input.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def disjoin(self, ranges: Sequence[GenomicPosition]) -> list[GenomicPosition]:
    """Split ranges into non-overlapping disjoint intervals.

    Args:
        ranges: Input genomic ranges, possibly overlapping.

    Returns:
        Maximal set of non-overlapping ranges derived from the input.
    """

nearest abstractmethod

nearest(query, subject, *, ignore_strand=True)

Find the nearest subject range for each query range.

PARAMETER DESCRIPTION
query

Ranges to search for neighbors.

TYPE: Sequence[GenomicPosition]

subject

Ranges to search within.

TYPE: Sequence[GenomicPosition]

ignore_strand

If False, only consider ranges on the same strand.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
list[int | None]

List of indices into subject (one per query). None if no neighbor found.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def nearest(
    self,
    query: Sequence[GenomicPosition],
    subject: Sequence[GenomicPosition],
    *,
    ignore_strand: bool = True,
) -> list[int | None]:
    """Find the nearest subject range for each query range.

    Args:
        query: Ranges to search for neighbors.
        subject: Ranges to search within.
        ignore_strand: If False, only consider ranges on the same strand.

    Returns:
        List of indices into subject (one per query). None if no neighbor found.
    """

precede abstractmethod

precede(query, subject, *, ignore_strand=True)

Find the subject range immediately upstream of each query range.

PARAMETER DESCRIPTION
query

Ranges to search for upstream neighbors.

TYPE: Sequence[GenomicPosition]

subject

Ranges to search within.

TYPE: Sequence[GenomicPosition]

ignore_strand

If False, only consider ranges on the same strand.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
list[int | None]

List of indices into subject. None if no upstream range found.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def precede(
    self,
    query: Sequence[GenomicPosition],
    subject: Sequence[GenomicPosition],
    *,
    ignore_strand: bool = True,
) -> list[int | None]:
    """Find the subject range immediately upstream of each query range.

    Args:
        query: Ranges to search for upstream neighbors.
        subject: Ranges to search within.
        ignore_strand: If False, only consider ranges on the same strand.

    Returns:
        List of indices into subject. None if no upstream range found.
    """

follow abstractmethod

follow(query, subject, *, ignore_strand=True)

Find the subject range immediately downstream of each query range.

PARAMETER DESCRIPTION
query

Ranges to search for downstream neighbors.

TYPE: Sequence[GenomicPosition]

subject

Ranges to search within.

TYPE: Sequence[GenomicPosition]

ignore_strand

If False, only consider ranges on the same strand.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
list[int | None]

List of indices into subject. None if no downstream range found.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def follow(
    self,
    query: Sequence[GenomicPosition],
    subject: Sequence[GenomicPosition],
    *,
    ignore_strand: bool = True,
) -> list[int | None]:
    """Find the subject range immediately downstream of each query range.

    Args:
        query: Ranges to search for downstream neighbors.
        subject: Ranges to search within.
        ignore_strand: If False, only consider ranges on the same strand.

    Returns:
        List of indices into subject. None if no downstream range found.
    """

flank abstractmethod

flank(ranges, width, *, start=True, both=False)

Return flanking regions for each input range.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

width

Width of the flanking region in bases.

TYPE: int

start

If True, flank the start; if False, flank the end.

TYPE: bool DEFAULT: True

both

If True, return flanks on both sides (overrides start).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
list[GenomicPosition]

Flanking ranges, one per input range (or two per range if both=True).

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def flank(
    self,
    ranges: Sequence[GenomicPosition],
    width: int,
    *,
    start: bool = True,
    both: bool = False,
) -> list[GenomicPosition]:
    """Return flanking regions for each input range.

    Args:
        ranges: Input genomic ranges.
        width: Width of the flanking region in bases.
        start: If True, flank the start; if False, flank the end.
        both: If True, return flanks on both sides (overrides start).

    Returns:
        Flanking ranges, one per input range (or two per range if both=True).
    """

resize abstractmethod

resize(ranges, width, *, fix='start')

Resize ranges to a fixed width anchored at start, end, or center.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

width

Target width in bases.

TYPE: int

fix

Anchor point — one of 'start', 'end', or 'center'.

TYPE: str DEFAULT: 'start'

RETURNS DESCRIPTION
list[GenomicPosition]

Resized ranges with the same anchor point as the input.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def resize(
    self, ranges: Sequence[GenomicPosition], width: int, *, fix: str = "start"
) -> list[GenomicPosition]:
    """Resize ranges to a fixed width anchored at start, end, or center.

    Args:
        ranges: Input genomic ranges.
        width: Target width in bases.
        fix: Anchor point — one of 'start', 'end', or 'center'.

    Returns:
        Resized ranges with the same anchor point as the input.
    """

shift abstractmethod

shift(ranges, shift)

Shift all ranges by a fixed number of bases.

PARAMETER DESCRIPTION
ranges

Input genomic ranges.

TYPE: Sequence[GenomicPosition]

shift

Number of bases to shift. Positive = downstream, negative = upstream.

TYPE: int

RETURNS DESCRIPTION
list[GenomicPosition]

Shifted ranges preserving width and strand.

Source code in src/iris/domain/genomics/ports/driven/range_ops.py
@abstractmethod
def shift(self, ranges: Sequence[GenomicPosition], shift: int) -> list[GenomicPosition]:
    """Shift all ranges by a fixed number of bases.

    Args:
        ranges: Input genomic ranges.
        shift: Number of bases to shift. Positive = downstream, negative = upstream.

    Returns:
        Shifted ranges preserving width and strand.
    """

HPO operations

iris.domain.genomics.ports.driven.hpo_ops

Port for HPO (Human Phenotype Ontology) operations.

Defines the abstract interface that any HPO backend must implement. The domain service uses this port; concrete adapters (PyHPO, etc.) provide the implementation.

HpoOperations

Bases: ABC

Abstract port for ontology-aware HPO operations.

All methods accept and return :class:~iris.domain.genomics.objects.Phenotype domain objects. Implementations are responsible for resolving a Phenotype to an HPO term (by hpo_id when available, by name as a fallback) and for any library-specific initialization.

Valid kind values for similarity methods depend on the information-content flavour used by the backend:

  • 'omim' — OMIM disease-based IC (default)
  • 'gene' — gene-based IC
  • 'orpha' — Orphanet disease-based IC
  • 'decipher' — DECIPHER-based IC

Valid combine strategies for :meth:set_similarity:

  • 'funSimAvg' — Schlicker et al. 2006 (default)
  • 'funSimMax' — Schlicker et al. 2006
  • 'BMA' — Best Match Average, Deng et al. 2015

search abstractmethod

search(query, *, limit=10)

Search HPO terms by name or synonym.

PARAMETER DESCRIPTION
query

Free-text query matched against term names and synonyms.

TYPE: str

limit

Maximum number of results to return. Defaults to 10.

TYPE: int DEFAULT: 10

RETURNS DESCRIPTION
list[Phenotype]

Matching phenotypes ordered by relevance, up to limit results.

Source code in src/iris/domain/genomics/ports/driven/hpo_ops.py
@abstractmethod
def search(self, query: str, *, limit: int = 10) -> list[Phenotype]:
    """Search HPO terms by name or synonym.

    Args:
        query: Free-text query matched against term names and synonyms.
        limit: Maximum number of results to return. Defaults to ``10``.

    Returns:
        Matching phenotypes ordered by relevance, up to ``limit`` results.
    """

get abstractmethod

get(hpo_id)

Retrieve a single HPO term by its identifier.

PARAMETER DESCRIPTION
hpo_id

HPO term identifier in 'HP:XXXXXXX' format.

TYPE: str

RETURNS DESCRIPTION
Phenotype | None

The corresponding Phenotype, or None if not found.

Source code in src/iris/domain/genomics/ports/driven/hpo_ops.py
@abstractmethod
def get(self, hpo_id: str) -> Phenotype | None:
    """Retrieve a single HPO term by its identifier.

    Args:
        hpo_id: HPO term identifier in ``'HP:XXXXXXX'`` format.

    Returns:
        The corresponding ``Phenotype``, or ``None`` if not found.
    """

parents abstractmethod

parents(phenotype)

Return the direct parent terms of a phenotype.

PARAMETER DESCRIPTION
phenotype

Source phenotype. Must be resolvable to an HPO term.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

Direct parent phenotypes (one level up in the hierarchy).

RAISES DESCRIPTION
ValueError

If the phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/ports/driven/hpo_ops.py
@abstractmethod
def parents(self, phenotype: Phenotype) -> list[Phenotype]:
    """Return the direct parent terms of a phenotype.

    Args:
        phenotype: Source phenotype. Must be resolvable to an HPO term.

    Returns:
        Direct parent phenotypes (one level up in the hierarchy).

    Raises:
        ValueError: If the phenotype cannot be resolved to an HPO term.
    """

children abstractmethod

children(phenotype)

Return the direct child terms of a phenotype.

PARAMETER DESCRIPTION
phenotype

Source phenotype. Must be resolvable to an HPO term.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

Direct child phenotypes (one level down in the hierarchy).

RAISES DESCRIPTION
ValueError

If the phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/ports/driven/hpo_ops.py
@abstractmethod
def children(self, phenotype: Phenotype) -> list[Phenotype]:
    """Return the direct child terms of a phenotype.

    Args:
        phenotype: Source phenotype. Must be resolvable to an HPO term.

    Returns:
        Direct child phenotypes (one level down in the hierarchy).

    Raises:
        ValueError: If the phenotype cannot be resolved to an HPO term.
    """

ancestors abstractmethod

ancestors(phenotype)

Return all ancestor terms up to the ontology root.

PARAMETER DESCRIPTION
phenotype

Source phenotype. Must be resolvable to an HPO term.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

All ancestor phenotypes, ordered from closest to root.

RAISES DESCRIPTION
ValueError

If the phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/ports/driven/hpo_ops.py
@abstractmethod
def ancestors(self, phenotype: Phenotype) -> list[Phenotype]:
    """Return all ancestor terms up to the ontology root.

    Args:
        phenotype: Source phenotype. Must be resolvable to an HPO term.

    Returns:
        All ancestor phenotypes, ordered from closest to root.

    Raises:
        ValueError: If the phenotype cannot be resolved to an HPO term.
    """

descendants abstractmethod

descendants(phenotype)

Return all descendant terms below the given phenotype.

PARAMETER DESCRIPTION
phenotype

Source phenotype. Must be resolvable to an HPO term.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

All descendant phenotypes reachable via child relationships.

RAISES DESCRIPTION
ValueError

If the phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/ports/driven/hpo_ops.py
@abstractmethod
def descendants(self, phenotype: Phenotype) -> list[Phenotype]:
    """Return all descendant terms below the given phenotype.

    Args:
        phenotype: Source phenotype. Must be resolvable to an HPO term.

    Returns:
        All descendant phenotypes reachable via child relationships.

    Raises:
        ValueError: If the phenotype cannot be resolved to an HPO term.
    """

similarity abstractmethod

similarity(a, b, *, kind='omim')

Compute semantic similarity between two HPO terms.

Uses information-content-based similarity (Lin by default via the backend's default method).

PARAMETER DESCRIPTION
a

First phenotype.

TYPE: Phenotype

b

Second phenotype.

TYPE: Phenotype

kind

IC flavour to use — one of 'omim', 'gene', 'orpha', 'decipher'.

TYPE: str DEFAULT: 'omim'

RETURNS DESCRIPTION
float

Similarity score in [0, 1].

RAISES DESCRIPTION
ValueError

If either phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/ports/driven/hpo_ops.py
@abstractmethod
def similarity(self, a: Phenotype, b: Phenotype, *, kind: str = "omim") -> float:
    """Compute semantic similarity between two HPO terms.

    Uses information-content-based similarity (Lin by default via the
    backend's default method).

    Args:
        a: First phenotype.
        b: Second phenotype.
        kind: IC flavour to use — one of ``'omim'``, ``'gene'``,
            ``'orpha'``, ``'decipher'``.

    Returns:
        Similarity score in ``[0, 1]``.

    Raises:
        ValueError: If either phenotype cannot be resolved to an HPO term.
    """

set_similarity abstractmethod

set_similarity(a, b, *, kind='omim', combine='funSimAvg')

Compute semantic similarity between two sets of HPO terms.

PARAMETER DESCRIPTION
a

First phenotype set.

TYPE: Sequence[Phenotype]

b

Second phenotype set.

TYPE: Sequence[Phenotype]

kind

IC flavour — one of 'omim', 'gene', 'orpha', 'decipher'.

TYPE: str DEFAULT: 'omim'

combine

Aggregation strategy — one of 'funSimAvg', 'funSimMax', 'BMA'.

TYPE: str DEFAULT: 'funSimAvg'

RETURNS DESCRIPTION
float

Similarity score in [0, 1].

RAISES DESCRIPTION
ValueError

If any phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/ports/driven/hpo_ops.py
@abstractmethod
def set_similarity(
    self,
    a: Sequence[Phenotype],
    b: Sequence[Phenotype],
    *,
    kind: str = "omim",
    combine: str = "funSimAvg",
) -> float:
    """Compute semantic similarity between two sets of HPO terms.

    Args:
        a: First phenotype set.
        b: Second phenotype set.
        kind: IC flavour — one of ``'omim'``, ``'gene'``, ``'orpha'``,
            ``'decipher'``.
        combine: Aggregation strategy — one of ``'funSimAvg'``,
            ``'funSimMax'``, ``'BMA'``.

    Returns:
        Similarity score in ``[0, 1]``.

    Raises:
        ValueError: If any phenotype cannot be resolved to an HPO term.
    """

common_ancestors abstractmethod

common_ancestors(a, b)

Return the common ancestors of two HPO terms.

PARAMETER DESCRIPTION
a

First phenotype.

TYPE: Phenotype

b

Second phenotype.

TYPE: Phenotype

RETURNS DESCRIPTION
list[Phenotype]

Phenotypes that are ancestors of both a and b.

RAISES DESCRIPTION
ValueError

If either phenotype cannot be resolved to an HPO term.

Source code in src/iris/domain/genomics/ports/driven/hpo_ops.py
@abstractmethod
def common_ancestors(self, a: Phenotype, b: Phenotype) -> list[Phenotype]:
    """Return the common ancestors of two HPO terms.

    Args:
        a: First phenotype.
        b: Second phenotype.

    Returns:
        Phenotypes that are ancestors of both ``a`` and ``b``.

    Raises:
        ValueError: If either phenotype cannot be resolved to an HPO term.
    """

Genotype processor

iris.domain.genomics.ports.driven.genotype_processor

Port for user-defined genotype computation over variant blocks.

Defines the GenotypeProcessor protocol that ZarrVariantStore calls for each block of variants, enabling cohort-level statistics, stratified analyses, and regression models without subclassing the store.

GenotypeProcessor

Bases: Protocol

Protocol for user-defined computation over a block of genotype data.

Implementations receive the raw genotype arrays for a block of variants
(all samples, one ALT allele at a time) and return a dict of computed
values to attach to each variant's metadata.

The processor is called **once per fetch block per ALT allele** with the
full sample dimension intact, enabling vectorised cohort-level statistics,
stratified allele frequencies, and regression models.

Contract:
    - Input arrays have shape ``(n_variants, n_samples, ploidy)``.
    - Output dict values must be either:
        - arrays of shape ``(n_variants,)`` or ``(n_variants, k)``
        - scalars (applied to all variants in the block)
    - The processor must not mutate the input arrays.
    - Missing calls are encoded as ``-1`` in ``gt`` and/or ``True``
      in ``gt_mask``.

Example:

class MyProcessor:
    def __call__(self, gt: Any, gt_mask: Any | None, alt_idx: int, context: dict[str, Any]) -> dict[str, Any]:
        import numpy as np

        is_alt = (gt == alt_idx).sum(axis=-1)
        return {"ac": is_alt.sum(axis=1)}