Skip to main content

Open-source Radiology Information System (RIS) — order management, DICOM MWL, structured reporting, FHIR R4, HL7v2

Project description

SautiRIS

Open-source Radiology Information System built on FastAPI

PyPI version Python versions License Tests Coverage Downloads


SautiRIS is a complete, production-ready Radiology Information System backend with order management, DICOM Modality Worklist, structured reporting, peer review QA, radiation dose tracking, FHIR R5 interoperability, HL7v2 messaging, and AI integration hooks.

Built for healthcare facilities in Kenya and across Africa, SautiRIS can run standalone or be mounted into any existing FastAPI application. It ships as a single pip install with pluggable authentication, multi-tenancy, and fine-grained RBAC out of the box.

Table of Contents

Features

Clinical Workflow

  • Order Management -- Full radiology order lifecycle with 8-state machine (REQUESTED -> SCHEDULED -> IN_PROGRESS -> COMPLETED -> REPORTED -> VERIFIED -> DISTRIBUTED -> CANCELLED), accession number generation, and audit trail
  • Structured Reporting -- Draft -> Preliminary -> Final -> Amended workflow with version history, report templates (JSONB), and addendum support
  • Critical Alerts -- Critical finding notification dispatch with configurable escalation timeouts, acknowledgment tracking, and multi-channel delivery
  • Peer Review & QA -- Radiology peer review with weighted random/targeted assignment, agreement scoring, discrepancy tracking, and radiologist scorecards with trend analysis

Imaging Integration

  • DICOM Modality Worklist (MWL) -- C-FIND SCP that serves scheduled procedures directly to scanners via pynetdicom
  • DICOM MPPS -- Modality Performed Procedure Step (N-CREATE/N-SET) for real-time exam status tracking
  • DICOM C-STORE -- Storage SCP accepting images from 8 SOP classes
  • PACS Connectivity -- Orthanc DICOMweb adapter (QIDO-RS, WADO-RS, STOW-RS) with dcm4chee stub
  • OHIF Viewer -- Study-level deep linking URL builder and OHIF config generator

Interoperability

  • FHIR R5 -- Build and serve ImagingStudy, DiagnosticReport, ServiceRequest resources with full CapabilityStatement
  • HL7v2 -- Parse and build ORM^O01 (orders) and ORU^R01 (results) messages with round-trip fidelity
  • AI Integration -- CAD overlay hooks, async study submission, HMAC-SHA256 webhook verification

Operations

  • Radiation Dose Tracking -- CTDIvol, DLP, DAP recording with Kenya NHIF DRL compliance checking and automated DRLExceeded alerts
  • Billing -- CPT/ICD code management with order-level assignment and revenue analytics by modality/month
  • Analytics -- Turnaround time metrics (5 intervals), workload analysis, volume statistics, operational dashboard
  • Scheduling -- Room and technologist scheduling with conflict detection and availability queries

Platform

  • Multi-Tenancy -- Every table is tenant-scoped with context-based isolation via TenantAwareRepository
  • Pluggable Auth -- Keycloak OIDC, generic OAuth2/JWKS, or API key authentication -- swap with one config change
  • RBAC -- 20 fine-grained permissions across 5 roles (radiologist, technologist, referring_physician, clerk, admin), enforced on every endpoint
  • Domain Events -- Async pub/sub event bus (OrderCreated, ReportFinalized, DRLExceeded, etc.) for extensibility
  • Audit Logging -- Full audit trail with user, action, resource, and before/after state

Quickstart

pip install sautiris
# Set your database URL
export SAUTIRIS_DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/sautiris

# Run migrations
sautiris db upgrade

# Start the server
sautiris serve --host 0.0.0.0 --port 8080

Open http://localhost:8080/docs for the interactive API documentation.

Installation

From PyPI (Recommended)

pip install sautiris

From Source

git clone https://github.com/iWorld-Afric/sautiris.git
cd sautiris
pip install -e ".[dev]"

Requirements

  • Python 3.12+
  • PostgreSQL 14+ (with asyncpg driver)
  • Optional: Keycloak (for OIDC auth), Orthanc (for PACS), OHIF Viewer

Usage

Standalone Server

# Minimal setup
export SAUTIRIS_DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/sautiris
export SAUTIRIS_AUTH_PROVIDER=apikey  # simplest auth for testing

sautiris db upgrade
sautiris serve

Mount in an Existing FastAPI App

from fastapi import FastAPI
from sautiris import create_ris_app

app = FastAPI(title="My Hospital System")

ris = create_ris_app(
    database_url="postgresql+asyncpg://localhost/hospital",
    auth_provider="keycloak",
    keycloak_server_url="http://keycloak:8080",
    keycloak_realm="hospital",
    keycloak_client_id="ris-backend",
    keycloak_jwks_url="http://keycloak:8080/realms/hospital/protocol/openid-connect/certs",
)
app.mount("/ris", ris)

Mount in SautiCare

SautiRIS is designed to integrate seamlessly with SautiCare:

from sautiris import create_ris_app
from sautiris.config import SautiRISSettings

settings = SautiRISSettings(
    database_url=os.getenv("DATABASE_URL"),
    auth_provider="keycloak",
    keycloak_server_url=os.getenv("KEYCLOAK_URL"),
    keycloak_realm="sauticare",
    keycloak_client_id="sauticare-backend",
    keycloak_jwks_url=os.getenv("KEYCLOAK_JWKS_URL"),
)
ris_app = create_ris_app(settings=settings)
main_app.mount("/api/v1/ris", ris_app)

CLI Reference

sautiris serve          # Start the HTTP server
sautiris db upgrade     # Run database migrations
sautiris db seed        # Seed reference data
sautiris mwl start      # Start DICOM Modality Worklist SCP
sautiris --help         # Show all commands

API Endpoints

Module Endpoints Description
Orders 11 Full order lifecycle -- create, list, get, update, cancel, schedule, start, complete, history, stats, accession
Schedule 8 Room/slot scheduling with conflict detection and availability queries
Reports 10 Structured reporting -- create, save, finalize, amend, addendum, templates, versions
Worklist 5 DICOM worklist management with MPPS status updates
Billing 5 CPT/ICD code management, order assignment, revenue analytics
Analytics 5 TAT metrics, workload analysis, volume stats, quality metrics, dashboard
Alerts 5 Critical finding alerting, acknowledgment, escalation, stats
Peer Review 6 QA assignment, scoring, discrepancy reporting, scorecards
Dose 5 Radiation dose recording, patient history, DRL compliance
FHIR 6+ Read-only FHIR server -- ImagingStudy, DiagnosticReport, ServiceRequest, CapabilityStatement
Health 3 Liveness, readiness, and detailed system health
Total 69+ Plus 3 DICOM SCP services (MWL, MPPS, C-STORE)

Full API documentation is auto-generated at /docs (Swagger UI) and /redoc (ReDoc) when the server is running.

Architecture

sautiris/
  api/
    v1/              # FastAPI route handlers (69+ endpoints)
    deps.py          # Shared dependencies (auth, DB, RBAC)
  core/
    auth/            # Pluggable auth providers (Keycloak, OAuth2, API Key)
    permissions.py   # RBAC: 20 permissions x 5 roles
    tenancy.py       # Multi-tenant context + middleware
    events.py        # Domain event bus (async pub/sub)
    audit.py         # Audit logging
  models/            # 20 SQLAlchemy ORM models with multi-tenancy
  repositories/      # Tenant-aware data access layer (generic CRUD)
  services/          # Business logic with state machines + domain events
  integrations/
    dicom/           # MWL SCP, MPPS SCP, C-STORE SCP (pynetdicom)
    fhir/            # FHIR R5 resource builders + read-only server
    hl7v2/           # ORM/ORU parser and builder
    pacs/            # Orthanc DICOMweb adapter + dcm4chee stub
    viewer/          # OHIF viewer URL builder + config
    ai/              # AI provider adapter + webhook handler
  migrations/        # Alembic database migrations
  config.py          # Pydantic Settings (40+ env vars)
  app.py             # create_ris_app() factory
  cli.py             # Click-based CLI

Key Design Decisions

  • Repository Pattern -- All database access goes through TenantAwareRepository[T], which auto-filters by tenant_id
  • Domain Events -- Services emit events (OrderCreated, ReportFinalized, etc.) via an async event bus, enabling loose coupling
  • State Machines -- Order and report status transitions are validated against a VALID_TRANSITIONS mapping; invalid transitions raise exceptions
  • Pluggable Adapters -- PACS, Viewer, AI, and Auth all use ABC base classes, making it trivial to swap implementations
  • String Enums in DB -- All enums use String(N) columns (not native PG ENUM) for SQLite test compatibility and easier migrations

Configuration

SautiRIS is configured via environment variables prefixed with SAUTIRIS_:

Variable Default Description
SAUTIRIS_DATABASE_URL required PostgreSQL async connection string
SAUTIRIS_AUTH_PROVIDER keycloak Auth backend: keycloak, oauth2, apikey
SAUTIRIS_KEYCLOAK_SERVER_URL -- Keycloak base URL
SAUTIRIS_KEYCLOAK_REALM -- Keycloak realm name
SAUTIRIS_KEYCLOAK_CLIENT_ID -- OIDC client ID
SAUTIRIS_PACS_TYPE orthanc PACS backend: orthanc, dcm4chee
SAUTIRIS_ORTHANC_URL -- Orthanc DICOMweb base URL
SAUTIRIS_MWL_AE_TITLE SAUTIRIS_MWL DICOM MWL SCP AE Title
SAUTIRIS_MWL_PORT 11112 DICOM MWL SCP port

See docs/configuration.md for the complete list of 40+ configuration options.

Documentation

Guide Description
Getting Started Installation, database setup, first steps
Configuration Complete environment variable reference
API Reference All 69+ endpoints with request/response schemas
Deployment Docker, Cloud Run, scaling, monitoring
DICOM Setup MWL/MPPS/C-STORE configuration, scanner integration
FHIR Interoperability FHIR R5 resources, HL7v2 messaging
SautiCare Integration Mounting in SautiCare, auth pass-through, E2E workflow

Development

Setup

git clone https://github.com/iWorld-Afric/sautiris.git
cd sautiris
pip install -e ".[dev]"

Running Tests

# Run all tests
pytest tests/ -v

# Run with coverage
pytest tests/ --cov=sautiris --cov-report=term-missing

# Run a specific test module
pytest tests/test_services/test_order_service.py -v

Tests use SQLite in-memory -- no PostgreSQL required for development.

Linting and Type Checking

# Lint
ruff check src/ tests/

# Auto-fix
ruff check src/ tests/ --fix

# Type check
mypy src/sautiris/

Building

# Build wheel + sdist
python -m build

# Verify package
twine check dist/*

Project Structure for Contributors

tests/
  test_api/          # API endpoint tests
  test_core/         # Auth, permissions, events, tenancy tests
  test_dicom/        # DICOM SCP tests
  test_integrations/ # FHIR, HL7v2, PACS, Viewer, AI tests
  test_repositories/ # Data access layer tests
  test_services/     # Business logic tests
  conftest.py        # Shared fixtures (SQLite, mock auth, factories)
  factories.py       # Factory Boy factories for all 15+ models

Contributing

We welcome contributions from the community! Whether it's a bug report, feature request, documentation improvement, or code contribution -- every bit helps.

How to Contribute

  1. Fork the repository on GitHub
  2. Create a feature branch from main:
    git checkout -b feat/your-feature-name
    
  3. Make your changes following our coding standards (see below)
  4. Write or update tests -- we require tests for all new functionality
  5. Ensure all quality gates pass:
    ruff check src/ tests/
    mypy src/sautiris/
    pytest tests/ -v
    
  6. Commit with a clear message:
    git commit -m "feat: add support for XYZ"
    
  7. Push your branch and open a Pull Request against main

Coding Standards

  • Python 3.12+ -- Use modern syntax (type unions with |, StrEnum, etc.)
  • Type hints on ALL functions -- No Any unless absolutely necessary
  • Async by default -- All DB operations and HTTP calls must be async
  • Repository pattern -- Database access goes through repositories, never raw queries in services
  • Pydantic v2 -- Request/response schemas, settings, and validation
  • ruff -- Line length 100, select rules: E, F, I, N, UP, B, SIM
  • mypy strict -- Full strict mode with Pydantic plugin

Commit Message Convention

We follow Conventional Commits:

Prefix Use Case
feat: New feature
fix: Bug fix
docs: Documentation only
refactor: Code change that neither fixes nor adds
test: Adding or updating tests
chore: Build, CI, tooling changes
perf: Performance improvement
security: Security fix

Pull Request Guidelines

  • One PR per feature/fix -- Keep PRs focused and reviewable
  • Link to an issue -- Reference the GitHub issue number in your PR description
  • Include tests -- PRs without tests for new functionality will be requested to add them
  • Update docs -- If your change affects the API or configuration, update the relevant docs
  • No breaking changes without discussion -- Open an issue first if you need to change the public API

Reporting Bugs

Open a GitHub Issue with:

  1. Description of the bug
  2. Steps to reproduce
  3. Expected vs actual behavior
  4. Environment (Python version, OS, database version)
  5. Error logs (with PHI/PII redacted)

Requesting Features

Open a GitHub Issue with:

  1. Use case -- What problem does this solve?
  2. Proposed solution -- How should it work?
  3. Alternatives considered -- What other approaches did you evaluate?

Code of Conduct

This project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to dev@iworldafric.com.

Security

Reporting Vulnerabilities

Do NOT open a public issue for security vulnerabilities.

Instead, email dev@iworldafric.com with:

  1. Description of the vulnerability
  2. Steps to reproduce
  3. Potential impact assessment
  4. Suggested fix (if any)

We will acknowledge receipt within 48 hours and provide a timeline for a fix. Security fixes are released as patch versions.

Security Features

  • RBAC with 20 permissions enforced on every endpoint
  • Pluggable auth with JWT verification (JWKS rotation supported)
  • Multi-tenant data isolation at the repository layer
  • Input validation via Pydantic on all request schemas
  • HMAC-SHA256 verification on AI webhook payloads
  • Audit logging of all state-changing operations
  • No hardcoded secrets -- all credentials via environment variables

Roadmap

v1.0.0 (Stable Release)

  • GitHub Actions CI/CD pipeline
  • 90%+ test coverage
  • Docker image on GHCR
  • Helm chart for Kubernetes
  • Integration tests against real PostgreSQL

v1.1.0

  • Teaching file management
  • Speech-to-text dictation integration
  • Report distribution (HL7v2 ORU, FHIR messaging, email PDF)
  • Multi-language report templates (English, Swahili)

v1.2.0

  • Mammography-specific workflow (BI-RADS)
  • Prior study comparison workflow
  • Advanced analytics with configurable dashboards
  • Audit log export (ATNA/Syslog)

v2.0.0

  • Real-time collaboration (WebSocket-based)
  • DICOM SR (Structured Report) native support
  • IHE profile compliance (SWF, RWF, KIN)
  • Federated learning integration for AI models

License

Licensed under the Apache License 2.0.

Copyright 2026 iWorldAfric

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Acknowledgments


Built with care for healthcare in Africa

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sautiris-1.0.0a2.tar.gz (110.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sautiris-1.0.0a2-py3-none-any.whl (112.6 kB view details)

Uploaded Python 3

File details

Details for the file sautiris-1.0.0a2.tar.gz.

File metadata

  • Download URL: sautiris-1.0.0a2.tar.gz
  • Upload date:
  • Size: 110.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.0

File hashes

Hashes for sautiris-1.0.0a2.tar.gz
Algorithm Hash digest
SHA256 cb53605bbb270ed42a367844bf94f68684784d1d4e989a9665abf9effc2c61e2
MD5 8df89a567d6b334ed0298353702ada6d
BLAKE2b-256 3de1d525a3b300174135bb0f0e1e1f8d81c9dee7bc005a4fea87035e44cdf853

See more details on using hashes here.

File details

Details for the file sautiris-1.0.0a2-py3-none-any.whl.

File metadata

  • Download URL: sautiris-1.0.0a2-py3-none-any.whl
  • Upload date:
  • Size: 112.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.0

File hashes

Hashes for sautiris-1.0.0a2-py3-none-any.whl
Algorithm Hash digest
SHA256 e78f1e10f6557125a4e8a9706184fb923486aebf83ecdba4d0399b59dde05bfd
MD5 0cfc0480f67ff2ceff13500db9178111
BLAKE2b-256 a4311a4ce526814b884c974fa72c92553dd7b0fd3c31bd4da376eb0326b651db

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page