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
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
- Quickstart
- Installation
- Usage
- API Endpoints
- Architecture
- Configuration
- Documentation
- Development
- Contributing
- Security
- Roadmap
- License
- Acknowledgments
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 bytenant_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_TRANSITIONSmapping; 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
- Fork the repository on GitHub
- Create a feature branch from
main:git checkout -b feat/your-feature-name
- Make your changes following our coding standards (see below)
- Write or update tests -- we require tests for all new functionality
- Ensure all quality gates pass:
ruff check src/ tests/ mypy src/sautiris/ pytest tests/ -v
- Commit with a clear message:
git commit -m "feat: add support for XYZ"
- 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
Anyunless 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:
- Description of the bug
- Steps to reproduce
- Expected vs actual behavior
- Environment (Python version, OS, database version)
- Error logs (with PHI/PII redacted)
Requesting Features
Open a GitHub Issue with:
- Use case -- What problem does this solve?
- Proposed solution -- How should it work?
- 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:
- Description of the vulnerability
- Steps to reproduce
- Potential impact assessment
- 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 by iWorldAfric for the African healthcare community
- Part of the SautiCare healthcare platform
- Powered by FastAPI, SQLAlchemy, pynetdicom, HAPI FHIR
- DICOM standards by NEMA
- FHIR standard by HL7 International
Built with care for healthcare in Africa
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb53605bbb270ed42a367844bf94f68684784d1d4e989a9665abf9effc2c61e2
|
|
| MD5 |
8df89a567d6b334ed0298353702ada6d
|
|
| BLAKE2b-256 |
3de1d525a3b300174135bb0f0e1e1f8d81c9dee7bc005a4fea87035e44cdf853
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e78f1e10f6557125a4e8a9706184fb923486aebf83ecdba4d0399b59dde05bfd
|
|
| MD5 |
0cfc0480f67ff2ceff13500db9178111
|
|
| BLAKE2b-256 |
a4311a4ce526814b884c974fa72c92553dd7b0fd3c31bd4da376eb0326b651db
|