How to Build a Modern RIS with Seamless PACS and EHR Integration
Complete guide to developing a production-ready Radiology Information System with DICOM connectivity, HL7 interfacing, and cloud-based image distribution.
A modern Radiology Information System (RIS) must orchestrate complex workflows spanning scheduling, image acquisition, radiologist assignment, interpretation, and result deliveryāall while integrating seamlessly with PACS for images, EHR for orders and results, and billing systems for charges. This guide shows you how to build a production-ready RIS that handles 100,000+ exams annually with 99.9% uptime and sub-second response times.
Understanding RIS Architecture
A comprehensive RIS manages the entire radiology workflow:
Pre-Examination:
- Order entry and management
- Exam scheduling and resource allocation
- Patient registration and insurance verification
- Exam preparation instructions
- Protocol selection
Examination:
- Modality worklist management (DICOM)
- Image acquisition coordination
- Quality control and verification
- Technologist documentation
Interpretation:
- Radiologist assignment and worklist
- Image viewing integration (PACS)
- Report creation (voice recognition, templates)
- Critical finding notification
- Peer consultation
Post-Examination:
- Report finalization and distribution
- Image and report archiving
- Billing and coding
- Quality metrics and analytics
JustCopy.aiās RIS template includes all components pre-configured, with 10 AI agents handling database design, DICOM integration, HL7 messaging, report generation, and EHR connectivity automatically.
System Architecture
// Modern RIS architecture
interface RISArchitecture {
// Frontend applications
applications: {
scheduling: SchedulingWorkstation;
technologist: TechWorkstation;
radiologist: ReadingWorkstation;
referring: ReferringPhysicianPortal;
patient: PatientPortal;
admin: AdministrativeConsole;
};
// Core RIS services
core Services: {
orderManagement: OrderManagementService;
scheduling: SchedulingEngine;
worklist: WorklistManagement;
reporting: ReportingEngine;
distribution: ResultDistribution;
};
// Integration layer
integrations: {
pacs: PACSInterface; // DICOM connectivity
ehr: EHRInterface; // HL7 messaging
modalities: ModalityWorklistServer; // DICOM MWL
billing: BillingInterface;
voiceRecognition: SpeechRecognitionEngine;
};
// Data layer
dataServices: {
examDatabase: ExamRepository;
reportDatabase: ReportRepository;
imageMetadata: ImageIndexService;
analytics: AnalyticsEngine;
};
// AI capabilities
aiFeatures: {
scheduling: AIScheduler;
protocolSelection: ProtocolAI;
criticalFindingDetection: CriticalFindingAI;
reportQA: ReportQualityAI;
predictiveAnalytics: PredictiveModels;
};
}
JustCopy.ai provides this complete architecture out-of-the-box, with AI agents configuring each component for your specific needs.
Database Schema
-- RIS database schema
CREATE SCHEMA radiology;
-- Exam orders
CREATE TABLE radiology.exam_orders (
order_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
accession_number VARCHAR(50) UNIQUE NOT NULL, -- Unique exam identifier
-- Patient information
patient_id UUID NOT NULL REFERENCES patients(patient_id),
patient_mrn VARCHAR(50) NOT NULL,
-- Ordering information
ordered_by_provider_id UUID REFERENCES providers(provider_id),
ordering_provider_npi VARCHAR(20),
ordered_at TIMESTAMP NOT NULL DEFAULT NOW(),
-- Exam details
modality VARCHAR(10) NOT NULL, -- CT, MRI, XR, US, NM, PET, MG
exam_code VARCHAR(20) NOT NULL, -- CPT code
exam_description TEXT NOT NULL,
body_part VARCHAR(100),
laterality VARCHAR(10), -- left, right, bilateral
-- Clinical information
clinical_indication TEXT,
clinical_history TEXT,
relevant_prior_exams TEXT[],
icd10_codes TEXT[],
-- Exam requirements
with_contrast BOOLEAN DEFAULT FALSE,
contrast_type VARCHAR(50),
protocol_name VARCHAR(255),
special_instructions TEXT,
-- Scheduling
priority VARCHAR(20), -- stat, urgent, routine
requested_date DATE,
scheduled_datetime TIMESTAMP,
scheduled_scanner VARCHAR(100),
estimated_duration_minutes INTEGER,
-- Status tracking
order_status VARCHAR(50) NOT NULL, -- pending, scheduled, in-progress, completed, cancelled
status_updated_at TIMESTAMP DEFAULT NOW(),
-- Exam performance
exam_start_time TIMESTAMP,
exam_end_time TIMESTAMP,
performing_technologist VARCHAR(255),
-- Reading
assigned_radiologist_id UUID REFERENCES providers(provider_id),
reading_started_at TIMESTAMP,
preliminary_report_at TIMESTAMP,
final_report_at TIMESTAMP,
reported_by_radiologist_id UUID REFERENCES providers(provider_id),
-- Imaging
image_count INTEGER DEFAULT 0,
pacs_study_uid VARCHAR(255), -- DICOM Study Instance UID
-- Billing
billing_code VARCHAR(20),
billing_status VARCHAR(50),
billed_at TIMESTAMP,
-- Metadata
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Reports
CREATE TABLE radiology.reports (
report_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
accession_number VARCHAR(50) NOT NULL REFERENCES radiology.exam_orders(accession_number),
-- Report content
report_text TEXT NOT NULL,
impression TEXT,
findings TEXT,
technique TEXT,
comparison TEXT,
-- Structured reporting
structured_findings JSONB, -- Machine-readable findings
-- Report metadata
report_status VARCHAR(50) NOT NULL, -- draft, preliminary, final, amended
report_version INTEGER DEFAULT 1,
-- Authoring
dictated_by_radiologist_id UUID REFERENCES providers(provider_id),
dictated_at TIMESTAMP,
transcribed_by VARCHAR(255),
transcribed_at TIMESTAMP,
signed_by_radiologist_id UUID REFERENCES providers(provider_id),
signed_at TIMESTAMP,
-- Amendments
amended_from_report_id UUID REFERENCES radiology.reports(report_id),
amendment_reason TEXT,
-- Critical findings
critical_finding BOOLEAN DEFAULT FALSE,
critical_finding_notified_at TIMESTAMP,
critical_finding_acknowledged_by VARCHAR(255),
-- Distribution
distributed_to_ehr BOOLEAN DEFAULT FALSE,
distributed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Report templates
CREATE TABLE radiology.report_templates (
template_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
template_name VARCHAR(255) NOT NULL,
modality VARCHAR(10),
body_part VARCHAR(100),
-- Template content
template_text TEXT NOT NULL,
sections JSONB, -- Structured template sections
-- Usage
created_by_radiologist_id UUID REFERENCES providers(provider_id),
times_used INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW()
);
-- Radiologist worklist
CREATE TABLE radiology.radiologist_worklist (
worklist_item_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
radiologist_id UUID NOT NULL REFERENCES providers(provider_id),
accession_number VARCHAR(50) NOT NULL REFERENCES radiology.exam_orders(accession_number),
-- Priority and ordering
priority_score NUMERIC(5,2), -- AI-calculated priority
assigned_at TIMESTAMP DEFAULT NOW(),
due_by TIMESTAMP,
-- Status
status VARCHAR(50), -- pending, in-progress, completed
started_at TIMESTAMP,
completed_at TIMESTAMP,
-- Workflow
is_urgent BOOLEAN DEFAULT FALSE,
requires_consultation BOOLEAN DEFAULT FALSE,
consultation_with_radiologist_id UUID REFERENCES providers(provider_id)
);
-- Critical findings tracking
CREATE TABLE radiology.critical_findings (
critical_finding_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
accession_number VARCHAR(50) NOT NULL,
report_id UUID REFERENCES radiology.reports(report_id),
-- Finding details
finding_category VARCHAR(100), -- pulmonary-embolism, pneumothorax, etc.
finding_description TEXT NOT NULL,
finding_severity VARCHAR(20),
-- Notification
identified_at TIMESTAMP NOT NULL DEFAULT NOW(),
identified_by_radiologist_id UUID REFERENCES providers(provider_id),
notified_to_provider_id UUID REFERENCES providers(provider_id),
notification_method VARCHAR(50), -- phone, page, secure-message
notified_at TIMESTAMP,
acknowledged_at TIMESTAMP,
acknowledged_by VARCHAR(255),
-- Follow-up
action_taken TEXT,
escalated BOOLEAN DEFAULT FALSE,
escalated_to VARCHAR(255),
escalated_at TIMESTAMP,
-- Compliance
notification_compliant BOOLEAN, -- Met time requirement
required_notification_time_minutes INTEGER,
actual_notification_time_minutes INTEGER
);
-- Image metadata (links to PACS)
CREATE TABLE radiology.image_metadata (
image_metadata_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
accession_number VARCHAR(50) NOT NULL,
-- DICOM identifiers
study_instance_uid VARCHAR(255) UNIQUE NOT NULL,
series_instance_uid VARCHAR(255) NOT NULL,
sop_instance_uid VARCHAR(255),
-- Image details
modality VARCHAR(10) NOT NULL,
series_number INTEGER,
instance_number INTEGER,
image_count INTEGER,
-- Acquisition info
acquisition_datetime TIMESTAMP,
scanner_model VARCHAR(255),
scanner_serial_number VARCHAR(100),
-- Storage
pacs_location VARCHAR(500), -- URL or path in PACS
archived BOOLEAN DEFAULT FALSE,
archive_location VARCHAR(500),
created_at TIMESTAMP DEFAULT NOW()
);
-- Quality metrics
CREATE TABLE radiology.quality_metrics (
metric_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
accession_number VARCHAR(50) NOT NULL,
-- TAT metrics
order_to_schedule_minutes INTEGER,
schedule_to_exam_minutes INTEGER,
exam_to_prelim_minutes INTEGER,
prelim_to_final_minutes INTEGER,
order_to_final_minutes INTEGER,
-- Quality indicators
critical_finding_notification_minutes INTEGER,
repeat_exam BOOLEAN DEFAULT FALSE,
repeat_reason TEXT,
-- Patient experience
patient_wait_time_minutes INTEGER,
patient_satisfaction_score INTEGER,
recorded_at TIMESTAMP DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_orders_patient ON radiology.exam_orders(patient_id);
CREATE INDEX idx_orders_status ON radiology.exam_orders(order_status);
CREATE INDEX idx_orders_scheduled ON radiology.exam_orders(scheduled_datetime);
CREATE INDEX idx_orders_modality ON radiology.exam_orders(modality);
CREATE INDEX idx_reports_accession ON radiology.reports(accession_number);
CREATE INDEX idx_worklist_radiologist ON radiology.radiologist_worklist(radiologist_id, status);
CREATE INDEX idx_images_study_uid ON radiology.image_metadata(study_instance_uid);
JustCopy.aiās AI agents generate optimized database schemas based on your volume, with proper indexing, partitioning, and performance tuning.
DICOM Integration
// DICOM connectivity for modality worklist and image storage
class DICOMInterface {
private dimseServer: DIMSEServer;
private storeSCP: StoreSCP;
async initialize() {
// Start DICOM services
await this.startModalityWorklistServer();
await this.startStorageServer();
}
async startModalityWorklistServer() {
// DICOM Modality Worklist (MWL) server
// Provides scheduled exams to modalities (CT, MRI, etc.)
this.dimseServer = new DIMSEServer({
port: 104, // Standard DICOM port
ae_title: 'RIS_MWL',
max_connections: 50
});
// Handle C-FIND requests from modalities
this.dimseServer.on('C-FIND-RQ', async (request, response) => {
// Modality is requesting worklist
const query = request.dataset;
// Get scheduled exams for this modality
const scheduled_exams = await db.exam_orders.find({
scheduled_datetime: {
$gte: query.ScheduledProcedureStepStartDate,
$lte: addDays(query.ScheduledProcedureStepStartDate, 1)
},
scheduled_scanner: query.ScheduledStationAETitle,
order_status: 'scheduled'
});
// Return each exam as DICOM dataset
for (const exam of scheduled_exams) {
const dataset = this.buildMWLDataset(exam);
response.send('pending', dataset);
}
response.send('success'); // All results sent
});
await this.dimseServer.listen();
}
buildMWLDataset(exam: ExamOrder): DICOMDataset {
return {
// Scheduled Procedure Step Sequence
'00400100': [{
// Scheduled Procedure Step Start Date
'00400002': format(exam.scheduled_datetime, 'YYYYMMDD'),
// Scheduled Procedure Step Start Time
'00400003': format(exam.scheduled_datetime, 'HHmmss'),
// Modality
'00080060': exam.modality,
// Scheduled Station AE Title
'00400001': exam.scheduled_scanner,
// Scheduled Procedure Step Description
'00400007': exam.exam_description,
// Scheduled Protocol Code Sequence
'00400008': [{
'00080100': exam.protocol_code,
'00080102': 'RIS',
'00080104': exam.protocol_name
}],
// Scheduled Procedure Step ID
'00400009': exam.accession_number
}],
// Patient information
'00100010': `${exam.patient.last_name}^${exam.patient.first_name}`, // Patient Name
'00100020': exam.patient_mrn, // Patient ID
'00100030': format(exam.patient.dob, 'YYYYMMDD'), // Patient Birth Date
'00100040': exam.patient.gender, // Patient Sex
// Study information
'0020000D': generateStudyUID(exam.accession_number), // Study Instance UID
'00080050': exam.accession_number, // Accession Number
// Requesting Physician
'00321032': `${exam.ordering_provider.last_name}^${exam.ordering_provider.first_name}`,
// Requested Procedure Description
'00321060': exam.exam_description
};
}
async startStorageServer() {
// DICOM Storage SCP - receives images from modalities
this.storeSCP = new StoreSCP({
port: 11112, // Secondary port for storage
ae_title: 'RIS_STORE',
storage_path: config.tempImageStorage
});
// Handle C-STORE requests (image upload)
this.storeSCP.on('C-STORE-RQ', async (request) => {
const dataset = request.dataset;
// Extract DICOM metadata
const metadata = {
study_instance_uid: dataset['0020000D'],
series_instance_uid: dataset['0020000E'],
sop_instance_uid: dataset['00080018'],
accession_number: dataset['00080050'],
modality: dataset['00080060'],
acquisition_datetime: this.parseDICOMDateTime(
dataset['00080021'], // Series Date
dataset['00080031'] // Series Time
)
};
// Store image metadata in RIS database
await db.image_metadata.create(metadata);
// Forward image to PACS for permanent storage
await this.forwardToPACS(dataset, metadata);
// Update exam status
await db.exam_orders.update({
accession_number: metadata.accession_number,
pacs_study_uid: metadata.study_instance_uid,
image_count: { $inc: 1 }, // Increment image count
order_status: 'in-progress'
});
return { status: 'success' };
});
await this.storeSCP.listen();
}
async forwardToPACS(dataset: DICOMDataset, metadata: ImageMetadata) {
// Send image to PACS for archiving
const pacsConnection = new DICOMConnection({
host: config.pacs.host,
port: config.pacs.port,
ae_title: config.pacs.ae_title,
calling_ae_title: 'RIS'
});
await pacsConnection.store(dataset);
await pacsConnection.close();
// Log forwarding
await db.image_metadata.update({
sop_instance_uid: metadata.sop_instance_uid,
pacs_location: `dicom://${config.pacs.host}:${config.pacs.port}/${metadata.study_instance_uid}`,
archived: true
});
}
}
JustCopy.ai handles all DICOM connectivity automatically, supporting all modalities and PACS systems with pre-configured interfaces.
Reporting Engine
// Radiology reporting with voice recognition
class ReportingEngine {
private voiceRecognition: SpeechRecognitionEngine;
private templateEngine: TemplateEngine;
async createReport(accession_number: string, radiologist: Radiologist) {
// Get exam details
const exam = await db.exam_orders.findOne({ accession_number });
// Load appropriate template
const template = await this.selectTemplate(exam);
// Create draft report
const report = await db.reports.create({
accession_number: accession_number,
report_text: template.template_text,
report_status: 'draft',
dictated_by_radiologist_id: radiologist.id
});
return report;
}
async dictateFinding(report_id: string, audio: AudioStream) {
// Voice recognition transcription
const transcript = await this.voiceRecognition.transcribe(audio);
// Medical terminology correction
const corrected = await this.correctMedicalTerms(transcript);
// Update report
await db.reports.update({
report_id: report_id,
findings: { $concat: [{ $fields: 'findings' }, '\n\n', corrected] },
dictated_at: new Date()
});
return corrected;
}
async detectCriticalFinding(report: Report) {
// AI analyzes report text for critical findings
const analysis = await criticalFindingAI.analyze({
report_text: report.report_text,
impression: report.impression,
modality: report.exam.modality,
body_part: report.exam.body_part
});
if (analysis.critical_findings.length > 0) {
// Create critical finding record
for (const finding of analysis.critical_findings) {
const critical = await db.critical_findings.create({
accession_number: report.accession_number,
report_id: report.report_id,
finding_category: finding.category,
finding_description: finding.description,
finding_severity: finding.severity,
identified_by_radiologist_id: report.signed_by_radiologist_id
});
// Immediately notify ordering provider
await this.notifyCriticalFinding(critical, report.exam);
}
// Mark report
await db.reports.update({
report_id: report.report_id,
critical_finding: true
});
}
return analysis;
}
async notifyCriticalFinding(finding: CriticalFinding, exam: ExamOrder) {
// Multi-channel notification
const provider = exam.ordering_provider;
// Try phone first
const phoneNotification = await phoneSystem.call({
to: provider.phone,
message: `CRITICAL FINDING: ${finding.finding_category} for patient ${exam.patient.name}, accession ${exam.accession_number}. Please call radiology immediately.`,
priority: 'urgent',
repeat_if_no_answer: 3,
escalate_after_minutes: 15
});
if (phoneNotification.answered) {
await db.critical_findings.update({
critical_finding_id: finding.critical_finding_id,
notification_method: 'phone',
notified_at: new Date(),
notified_to_provider_id: provider.id
});
// Wait for acknowledgment
await this.awaitAcknowledgment(finding, provider);
} else {
// Escalate to backup contact
await this.escalateCriticalFinding(finding, exam);
}
}
}
JustCopy.aiās reporting engine includes voice recognition, template management, critical finding detection, and automated notificationāall working together seamlessly.
Best Practices
- DICOM Compliance: Test with all modality vendors before go-live
- Accession Number Format: Use meaningful, sequential numbers
- Critical Finding SOP: Define clear policies and test notification workflows
- Report Templates: Start with 10-15 high-volume exam templates
- Radiologist Training: 4-hour hands-on training minimum
JustCopy.ai provides implementation support with AI agents handling system configuration, DICOM testing, and staff training.
Conclusion
A modern RIS with seamless PACS and EHR integration streamlines radiology workflows, improves report turnaround times, and ensures critical findings reach ordering providers immediately. Proper DICOM connectivity eliminates manual data entry, automated worklist management improves scanner productivity, and intelligent reporting tools accelerate radiologist workflows.
JustCopy.ai makes RIS development effortless, with 10 AI agents handling database design, DICOM integration, HL7 messaging, report generation, and critical finding detection automatically.
Ready to build your modern RIS? Explore JustCopy.aiās radiology solutions and discover how AI-powered development can deliver a production-ready system in weeks.
Streamline radiology. Accelerate care. Start with JustCopy.ai today.
Build This with JustCopy.ai
Skip months of development with 10 specialized AI agents. JustCopy.ai can copy, customize, and deploy this application instantly. Our AI agents write code, run tests, handle deployment, and monitor your applicationāall following healthcare industry best practices and HIPAA compliance standards.