Integrating OpenMRS Reporting with External Systems
This page is derived from older reporting documentation and may not reflect the current behavior of the reporting module. Contributions to align it with the latest version are welcome.
Comprehensive guide to integrating OpenMRS reporting capabilities with external systems, web applications, and other OpenMRS modules.
Modern healthcare environments require seamless integration between different systems and applications. OpenMRS reporting provides multiple integration pathways that enable organizations to embed reporting functionality into existing workflows, external applications, and automated processes without requiring users to directly access the OpenMRS interface.
Table of Contents
HTML Form Entry Integration
Why HTML Form Entry Integration Enhances Workflows
HTML Form Entry (HFE) is a popular OpenMRS module that provides flexible form creation capabilities. Integrating reporting functionality directly into HFE forms enables real-time data visualization, automated report generation triggered by form submissions, and embedded analytics that provide immediate feedback to data entry users.
HFE integration benefits:
Real-time feedback - Display relevant metrics during data entry
Contextual reporting - Show patient-specific or program-specific reports within forms
Automated triggers - Generate reports automatically when forms are submitted
Enhanced user experience - Reduce context switching between forms and reports
Data validation - Use reports to validate data entry patterns and identify outliers
Embedding Reports in HFE Forms
Understanding HFE Report Integration
HFE forms can include embedded reports through custom tags and JavaScript integration. This enables forms to display relevant data visualizations, summary statistics, or patient-specific information that enhances the data entry experience.
<!-- HFE form with embedded reporting -->
<htmlform formUuid="12345-form-uuid" formName="Patient Assessment with Reports">
<!-- Standard form fields -->
<section>
<h3>Patient Information</h3>
<p>
<label>Date:</label>
<encounterDate default="today" />
</p>
<p>
<label>Location:</label>
<encounterLocation />
</p>
</section>
<!-- Embedded patient-specific report -->
<section>
<h3>Patient History Summary</h3>
<div id="patient-history-report">
<script type="text/javascript">
// Load patient-specific report
loadPatientReport({
reportId: 'patient-history-uuid',
patientId: '<lookup expression="patient.patientId"/>',
renderingMode: 'WEB'
});
</script>
</div>
</section>
<!-- Clinical data entry -->
<section>
<h3>Current Assessment</h3>
<p>
<label>Blood Pressure:</label>
<obs conceptId="5085" />/<obs conceptId="5086" />
</p>
<p>
<label>Weight:</label>
<obs conceptId="5089" units="kg" />
</p>
</section>
<!-- Contextual program reports -->
<section>
<h3>Program Performance</h3>
<div id="program-metrics">
<script type="text/javascript">
// Load program-specific metrics
loadProgramMetrics({
programId: '<lookup expression="patient.activePrograms[0].program.programId"/>',
period: 'current-month'
});
</script>
</div>
</section>
</htmlform>
JavaScript Integration Functions
// JavaScript functions for HFE report integration
function loadPatientReport(config) {
const reportUrl = buildReportUrl('/openmrs', {
reportId: config.reportId,
renderingMode: 'org.openmrs.module.reporting.report.renderer.WebReportRenderer',
patientId: config.patientId
});
fetch(reportUrl, {
credentials: 'same-origin'
})
.then(response => response.text())
.then(html => {
document.getElementById('patient-history-report').innerHTML = html;
})
.catch(error => {
console.error('Failed to load patient report:', error);
document.getElementById('patient-history-report').innerHTML =
'<p class="error">Unable to load patient history</p>';
});
}
function loadProgramMetrics(config) {
const metricsUrl = `/openmrs/ws/rest/v1/reportingrest/dataSet/${config.programId}?format=json&period=${config.period}`;
fetch(metricsUrl, {
credentials: 'same-origin'
})
.then(response => response.json())
.then(data => {
renderProgramMetrics(data);
})
.catch(error => {
console.error('Failed to load program metrics:', error);
});
}
function renderProgramMetrics(data) {
const metricsHtml = `
<div class="program-metrics">
<div class="metric">
<span class="label">Active Patients:</span>
<span class="value">${data.activePatients || 0}</span>
</div>
<div class="metric">
<span class="label">New Enrollments:</span>
<span class="value">${data.newEnrollments || 0}</span>
</div>
<div class="metric">
<span class="label">Completion Rate:</span>
<span class="value">${data.completionRate || 0}%</span>
</div>
</div>
`;
document.getElementById('program-metrics').innerHTML = metricsHtml;
}
// Real-time data validation using reports
function validateDataEntry(fieldName, value) {
const validationUrl = `/openmrs/ws/rest/v1/reportingrest/validate/${fieldName}`;
fetch(validationUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
value: value,
patientId: getPatientId(),
context: getFormContext()
}),
credentials: 'same-origin'
})
.then(response => response.json())
.then(validation => {
if (!validation.valid) {
showValidationWarning(fieldName, validation.message);
} else {
clearValidationWarning(fieldName);
}
});
}
Form Submission Triggers
Why Automated Report Generation Improves Workflows
Form submissions often represent significant clinical events that require immediate reporting to stakeholders. Automated report generation triggered by form submissions ensures timely communication without requiring manual intervention from clinical staff.
HFE JavaScript Hooks for Report Generation
// HFE form submission hooks for automated reporting
beforeSubmit.push(function() {
// Pre-submission validation using reports
return validateFormData();
});
beforeSave.push(function() {
// Generate reports before saving the encounter
prepareTriggeredReports();
});
afterSubmit.push(function() {
// Generate and send reports after successful submission
triggerAutomaticReports();
});
function validateFormData() {
return new Promise((resolve, reject) => {
const validationReportUrl = buildReportUrl('/openmrs', {
reportId: 'form-validation-report-uuid',
renderingMode: 'org.openmrs.module.reporting.report.renderer.JsonReportRenderer',
patientId: getPatientId(),
formData: getFormData()
});
fetch(validationReportUrl, { credentials: 'same-origin' })
.then(response => response.json())
.then(validationResults => {
if (validationResults.hasErrors) {
displayValidationErrors(validationResults.errors);
resolve(false); // Prevent submission
} else {
resolve(true); // Allow submission
}
})
.catch(error => {
console.error('Validation failed:', error);
resolve(true); // Allow submission on validation error
});
});
}
function triggerAutomaticReports() {
const formType = getFormType();
const patientId = getPatientId();
const encounterId = getEncounterId();
// Define report triggers based on form type
const reportTriggers = {
'hiv_care_visit': [
{
reportId: 'hiv-care-summary-uuid',
recipients: ['hiv-coordinator@clinic.com'],
condition: 'viral_load_available'
}
],
'tb_screening': [
{
reportId: 'tb-screening-alert-uuid',
recipients: ['tb-nurse@clinic.com', 'physician@clinic.com'],
condition: 'high_risk_screening'
}
],
'maternal_health': [
{
reportId: 'anc-visit-summary-uuid',
recipients: ['midwife@clinic.com'],
condition: 'anc_visit'
}
]
};
const triggers = reportTriggers[formType] || [];
triggers.forEach(trigger => {
if (evaluateCondition(trigger.condition)) {
generateTriggeredReport(trigger, {
patientId: patientId,
encounterId: encounterId,
formType: formType
});
}
});
}
function generateTriggeredReport(trigger, context) {
const reportRequest = {
reportId: trigger.reportId,
renderingMode: 'org.openmrs.module.reporting.report.renderer.ExcelReportRenderer',
patientId: context.patientId,
encounterId: context.encounterId,
generateTimestamp: new Date().toISOString(),
recipients: trigger.recipients
};
// Queue report for generation and delivery
fetch('/openmrs/ws/rest/v1/reportingrest/queue', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reportRequest),
credentials: 'same-origin'
})
.then(response => response.json())
.then(result => {
console.log('Report queued:', result.requestId);
showNotification(`Report ${trigger.reportId} queued for delivery`);
})
.catch(error => {
console.error('Failed to queue report:', error);
});
}
function evaluateCondition(condition) {
// Evaluate trigger conditions based on form data
const formData = getFormData();
switch (condition) {
case 'viral_load_available':
return formData.viralLoad && formData.viralLoad !== '';
case 'high_risk_screening':
return formData.tbSymptoms && formData.tbSymptoms.length > 2;
case 'anc_visit':
return formData.visitType === 'anc';
default:
return true;
}
}
Using in Other Modules
Why Inter-Module Integration is Powerful
OpenMRS's modular architecture enables reporting functionality to be integrated into other modules, creating seamless user experiences and powerful cross-module workflows. This integration allows specialized modules to leverage reporting capabilities without duplicating code or requiring users to switch contexts.
Inter-module integration benefits:
Contextual reporting - Display relevant reports within specialized module interfaces
Shared functionality - Leverage reporting infrastructure across multiple modules
Consistent user experience - Uniform reporting interface regardless of module
Code reusability - Avoid duplicating reporting logic in multiple modules
Enhanced workflows - Embed reporting into specialized business processes
Module API Integration
Understanding OpenMRS Module Integration Patterns
OpenMRS modules can integrate with reporting functionality through service interfaces, web controllers, and JavaScript APIs. Understanding these integration patterns enables robust cross-module functionality.
// Java API integration for custom modules
@Component
public class CustomModuleReportingService {
@Autowired
private ReportService reportService;
@Autowired
private ReportDefinitionService reportDefinitionService;
/**
* Generate report for custom module context
*/
public ReportData generateModuleReport(String reportUuid, Map<String, Object> parameters) {
try {
ReportDefinition reportDef = reportDefinitionService.getDefinitionByUuid(reportUuid);
if (reportDef == null) {
throw new IllegalArgumentException("Report definition not found: " + reportUuid);
}
// Create evaluation context with module-specific parameters
EvaluationContext context = new EvaluationContext();
// Add standard parameters
context.addParameterValue("startDate", parameters.get("startDate"));
context.addParameterValue("endDate", parameters.get("endDate"));
context.addParameterValue("location", parameters.get("location"));
// Add module-specific parameters
parameters.entrySet().stream()
.filter(entry -> !isStandardParameter(entry.getKey()))
.forEach(entry -> context.addParameterValue(entry.getKey(), entry.getValue()));
// Generate report
ReportData reportData = reportService.evaluate(reportDef, context);
// Log report generation for audit
logReportGeneration(reportUuid, parameters);
return reportData;
} catch (Exception e) {
log.error("Failed to generate module report: " + reportUuid, e);
throw new RuntimeException("Report generation failed", e);
}
}
/**
* Generate and render report in specified format
*/
public byte[] generateAndRenderReport(String reportUuid, String renderingMode,
Map<String, Object> parameters) {
ReportData reportData = generateModuleReport(reportUuid, parameters);
// Get rendering mode
ReportRenderer renderer = getReportRenderer(renderingMode);
if (renderer == null) {
throw new IllegalArgumentException("Unsupported rendering mode: " + renderingMode);
}
// Render report
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
RenderingMode mode = new RenderingMode(renderer, "default", null, 100);
renderer.render(reportData, mode.getArgument(), outputStream);
return outputStream.toByteArray();
} catch (Exception e) {
log.error("Failed to render report: " + reportUuid, e);
throw new RuntimeException("Report rendering failed", e);
}
}
/**
* Get available reports for module context
*/
public List<ReportDefinition> getModuleReports(String moduleContext) {
return reportDefinitionService.getAllDefinitions(false)
.stream()
.filter(reportDef -> isRelevantForModule(reportDef, moduleContext))
.collect(Collectors.toList());
}
private boolean isStandardParameter(String paramName) {
Set<String> standardParams = Set.of("startDate", "endDate", "location",
"program", "user", "provider");
return standardParams.contains(paramName);
}
private boolean isRelevantForModule(ReportDefinition reportDef, String moduleContext) {
// Check report metadata for module context
String reportContext = reportDef.getParameter("moduleContext");
return moduleContext.equals(reportContext) || "global".equals(reportContext);
}
private ReportRenderer getReportRenderer(String renderingMode) {
List<ReportRenderer> renderers = Context.getRegisteredComponents(ReportRenderer.class);
return renderers.stream()
.filter(renderer -> renderer.getClass().getName().equals(renderingMode))
.findFirst()
.orElse(null);
}
private void logReportGeneration(String reportUuid, Map<String, Object> parameters) {
log.info("Module report generated: {} with parameters: {}", reportUuid, parameters);
// Could also create audit log entry in database
// auditService.createAuditEntry("REPORT_GENERATED", reportUuid, parameters);
}
}
Web Controller Integration