Skip to content

Architecture

IRIS is structured around Domain-Driven Design (DDD) with a Hexagonal Architecture (Ports & Adapters).

Core principles

Pure domain — domain classes have zero infrastructure dependencies. They hold rules, invariants, and value objects, not I/O.

Immutable objects — every domain class is @define(frozen=True) via attrs. State is never mutated; attrs.evolve() is used to produce new instances.

Ports & Adapters — the domain declares what it needs (ports); adapters implement how it is fulfilled (Tabix files, VCFs, databases).

Layer map

┌─────────────────────────────────────────────┐
│                   Domain                    │
│  generic/   genomics/   lims/   hr/   …     │
│  (pure Python, no I/O)                      │
└──────────────────┬──────────────────────────┘
                   │ ports (ABCs)
┌──────────────────▼──────────────────────────┐
│               Adapters (driven)             │
│  readers/   annotators/   stores/           │
│  exporters/   pipeline.py                   │
│  (pysam, zarr, numpy, openpyxl, …)          │
└─────────────────────────────────────────────┘

Domains

Domain Description
generic Shared bases: Metadata, AliasSet, NamedEntity, repository ABCs
genomics Variants, genes, annotations, filters — the primary domain
lims Patients, samples, assays, reports
human_resources Staff, roles, contracts
operations Operational workflows
marketing Referrals, campaigns
sysadmin System configuration and users

Applications

applications_structure

Repository hierarchy

Repository[T]           get(id) → Optional[T]
├── ReadRepository[T]   + find_all() → Sequence[T]
├── WriteRepository[T]  + save(T) → T, delete(id)
└── CrudRepository[T]   ReadRepository + WriteRepository

Genomics repositories are read-only (Repository[T]). LIMS, HR, and marketing repositories are CrudRepository[T].

Variant extraction pipeline

VariantExtractionPipeline orchestrates a two-phase workflow:

  1. ExtractionVariantAnnotationStore.fetch(region) is called in parallel across all regions using a ThreadPoolExecutor. Zarr stores release the GIL for array reads, so multi-threading yields near-linear speedups.
  2. Annotation — collected VariantAnnotation objects pass through each VariantAnnotator sequentially.
regions / variants
VariantExtractionPipeline
  ├── VariantAnnotationStore.fetch()  ← parallel (N workers)
  │     └── ZarrVariantStore         ← thread-safe Zarr reads
  └── VariantAnnotator.annotate()    ← sequential batch
        ├── ClinvarAnnotator
        └── WesLofteeAnnotator

VariantStore[T] is symmetric to Annotator[T]: annotators enrich existing VariantAnnotation objects; stores produce them from raw data sources.

Genomic coordinates

GenomicPosition uses 1-based inclusive coordinates throughout the domain. Conversions happen at adapter boundaries:

  • BED (0-based half-open) → converted in BedRegionReader
  • pysam .fetch() (0-based half-open) → start - 1 at call site
  • vcf2zarr variant_positionalready 1-based (VCF native), no conversion needed in ZarrVariantStore