Skip to main content

Radiology Service — Data Model

Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD


1. ID Prefix Convention

EntityPrefixExample
ImagingStudyRefSTU_STU_01J...
RadiologyReportRPT_RPT_01J...
PacsEndpointPACS_PACS_01J...
ViewerLaunchTokenVLT_VLT_01J...

2. TypeScript Interfaces

export interface ImagingStudyRef {
id: string; // STU_ prefixed ULID
tenantId: string;
patientId: string;
encounterId?: string;
orderId?: string;
accessionNumber?: string;
modality: string;
startedAt?: Date;
status: ImagingStudyStatus;
pacsStudyUid: string;
pacsEndpointId: string;
seriesCount?: number;
instanceCount?: number;
fhirImagingStudyId?: string;
payloadJson?: object; // FHIR ImagingStudy resource
version: number;
createdAt: Date;
updatedAt: Date;
}

export type ImagingStudyStatus =
| 'registered' | 'available' | 'cancelled' | 'entered-in-error' | 'unknown';

export interface RadiologyReport {
id: string; // RPT_ prefixed ULID
tenantId: string;
imagingStudyId: string;
status: ReportStatus;
authorId: string;
authoredAt: Date;
contentText: string;
contentStructured?: object; // SR JSON if structured
criticalFindingFlag: boolean;
priorReportId?: string;
amendmentReason?: string;
fhirDiagnosticReportId?: string;
version: number;
createdAt: Date;
updatedAt: Date;
}

export type ReportStatus =
| 'preliminary' | 'final' | 'amended' | 'superseded' | 'canceled';

export interface PacsEndpoint {
id: string; // PACS_ prefixed ULID
tenantId: string;
nodeId?: string;
name: string;
qidoUrl: string;
wadoUrl: string;
stowUrl?: string;
authMode: 'none' | 'bearer' | 'basic' | 'oauth2-client-credentials';
authConfig?: object; // Encrypted JSON
facilityId?: string;
active: boolean;
createdAt: Date;
updatedAt: Date;
}

3. PostgreSQL Schema

CREATE TABLE pacs_endpoints (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
node_id TEXT,
name TEXT NOT NULL,
qido_url TEXT NOT NULL,
wado_url TEXT NOT NULL,
stow_url TEXT,
auth_mode TEXT NOT NULL DEFAULT 'none',
auth_config JSONB, -- encrypted at application layer
facility_id TEXT,
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_pacs_tenant ON pacs_endpoints(tenant_id, active);
ALTER TABLE pacs_endpoints ENABLE ROW LEVEL SECURITY;
CREATE POLICY pacs_tenant ON pacs_endpoints
USING (tenant_id = current_setting('app.current_tenant_id'));

---

CREATE TABLE imaging_studies (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
patient_id TEXT NOT NULL,
encounter_id TEXT,
order_id TEXT,
accession_number TEXT,
modality TEXT NOT NULL,
started_at TIMESTAMPTZ,
status TEXT NOT NULL DEFAULT 'registered',
pacs_study_uid TEXT NOT NULL,
pacs_endpoint_id TEXT NOT NULL REFERENCES pacs_endpoints(id),
series_count INTEGER,
instance_count INTEGER,
fhir_imaging_study_id TEXT,
payload_json JSONB,
version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (tenant_id, pacs_study_uid)
);

CREATE INDEX idx_studies_patient ON imaging_studies(tenant_id, patient_id, started_at DESC);
CREATE INDEX idx_studies_order ON imaging_studies(tenant_id, order_id);
CREATE INDEX idx_studies_modality ON imaging_studies(tenant_id, modality, started_at DESC);
ALTER TABLE imaging_studies ENABLE ROW LEVEL SECURITY;
CREATE POLICY studies_tenant ON imaging_studies
USING (tenant_id = current_setting('app.current_tenant_id'));

---

CREATE TABLE radiology_reports (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
imaging_study_id TEXT NOT NULL REFERENCES imaging_studies(id),
status TEXT NOT NULL DEFAULT 'preliminary',
author_id TEXT NOT NULL,
authored_at TIMESTAMPTZ NOT NULL,
content_text TEXT NOT NULL,
content_structured JSONB,
critical_finding_flag BOOLEAN NOT NULL DEFAULT FALSE,
prior_report_id TEXT REFERENCES radiology_reports(id),
amendment_reason TEXT,
fhir_diagnostic_report_id TEXT,
version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_reports_study ON radiology_reports(tenant_id, imaging_study_id, authored_at DESC);
ALTER TABLE radiology_reports ENABLE ROW LEVEL SECURITY;
CREATE POLICY reports_tenant ON radiology_reports
USING (tenant_id = current_setting('app.current_tenant_id'));

---

CREATE TABLE outbox_events (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
subject TEXT NOT NULL,
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_rad_outbox_unpublished ON outbox_events(published, created_at)
WHERE published = FALSE;