/** * The contents of this file are subject to the OpenMRS Public License * Version 1.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://license.openmrs.org * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * Copyright (C) OpenMRS, LLC. All Rights Reserved. */ package org.openmrs.hl7.handler; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.Location; import org.openmrs.Patient; import org.openmrs.PatientIdentifier; import org.openmrs.PatientIdentifierType; import org.openmrs.PersonName; import org.openmrs.User; import org.openmrs.api.PatientIdentifierException; import org.openmrs.api.context.Context; import org.openmrs.util.OpenmrsConstants; import org.openmrs.validator.PatientIdentifierValidator; import ca.uhn.hl7v2.HL7Exception; import ca.uhn.hl7v2.app.Application; import ca.uhn.hl7v2.app.ApplicationException; import ca.uhn.hl7v2.model.Message; import ca.uhn.hl7v2.model.v25.datatype.CX; import ca.uhn.hl7v2.model.v25.datatype.ID; import ca.uhn.hl7v2.model.v25.datatype.TS; import ca.uhn.hl7v2.model.v25.datatype.XPN; import ca.uhn.hl7v2.model.v25.message.ADT_A05; import ca.uhn.hl7v2.model.v25.segment.MSH; import ca.uhn.hl7v2.model.v25.segment.PID; /* HL7 using HAPI to handle ADT A28 Messages * * ADT/ACK - Add person or patient information (Event A28) * * FYI: The 3rd field of MSH contains the sending application. * For example, the Rwanda lab system uses 'neal_lims'. * If neal_lims exists as an OpenMRS username, then this handler * will use that user as the creator for patients it creates. * If the sending application isn't setup as an OpenMRS user, * the creator will default to the user running this task. * * TODO: You may wonder why the createPatient, validate, getMSH, * getPIH and tsToDate code is duplicated in this file (and the R01 * message handler file? It would be more useful to have these in the * HL7 Utility file. It's a good question, and it will happen * soon. * * The HL7 v2.5 manual table 0354 (section 2.17.3) describes A28. * * There are many cases in HL7 where events (like A05, A14, A28, and A31) * share a common structure. This table also represented in HL7APIs * eventmap properties file (http://tinyurl.com/2almfx) -- describes * exactly which events share which structures. * * So the answer to the A28 event is to use the ADT_A05 message * structure from within the v2.5 object hierarchy. Without going * to the table, you can see this relationship in the description * of the A28 event message structure (3.3.28), which is labeled as * ADT^A28^ADT_A05. This represents the message type (ADT), * event (A28), and message structure (ADT_A05). * * TODO: This ADT A28 handler does NOT currently handle ALL possible segments. * Some of the segments that are not handled include these: * * EVN (Event type) - required to be backwardly compatible * SFT (Software segment) * PD1 (Additional demographics) (*) * ROL (Role) * NK1 (Next of Kin / associated parties) (*) * PV1/2 (Patient visit - additional information) (*) * DB1 (Disability information) * OBX (Observation / result) (***) * AL1 (Allergy information) * DG1 (Diagnosis information) * DRG (Diagnosis related group) * PR1 (Procedures) * GT1 (Guarantor) * IN1 (Insurance) * ACC (Accident information) * UB1/2 (Universal Bill Information) * * NOTE: The ones with (*) could be useful in the near future. */ public class ADTA28Handler implements Application { private Log log = LogFactory.getLog(ADTA28Handler.class); /** * Always returns true, assuming that the router calling this handler will only call this * handler with ADT_A28 messages. * * @return true */ public boolean canProcess(Message message) { return message != null && "ADT_A28".equals(message.getName()); } /** * Processes an ADT A28 event message */ public Message processMessage(Message message) throws ApplicationException { log.debug("Processing ADT_A28 message"); if (!(message instanceof ADT_A05)) throw new ApplicationException("Invalid message sent to ADT_A28 handler"); Message response; try { ADT_A05 adt = (ADT_A05) message; response = processADT_A28(adt); } catch (ClassCastException e) { log.error("Error casting " + message.getClass().getName() + " to ADT_A28", e); throw new ApplicationException("Invalid message type for handler"); } catch (HL7Exception e) { log.error("Error while processing ADT_A28 message", e); throw new ApplicationException(e); } log.debug("Finished processing ADT_A28 message"); return response; } private Message processADT_A28(ADT_A05 adt) throws HL7Exception { // validate HL7 version validate(adt); // extract segments for convenient use below MSH msh = getMSH(adt); PID pid = getPID(adt); // Obtain message control id (unique ID for message from sending // application). Eventually avoid replaying the same message. String messageControlId = msh.getMessageControlID().getValue(); log.debug("Found HL7 message in inbound queue with control id = " + messageControlId); // Add creator of the patient to application String sendingApp = msh.getSendingApplication().getComponent(0).toString(); log.debug("SendingApplication = " + sendingApp); // Search for the patient Integer patientId = findPatientId(pid); // Create new patient if the patient id doesn't exist yet if (patientId == null) { log.info("Creating new patient in response to ADT_A28 " + messageControlId); Patient patient = createPatient(pid, sendingApp); if (patient == null) throw new HL7Exception("Couldn't create Patient object from PID"); Context.getPatientService().savePatient(patient); } else { // TODO: Add a global property that enables different behavior here. log.info("Ignoring ADT_A28 message because patient (" + patientId + ") already exists."); } // Assumption: all observations (OBX) messages will be in the R01 return adt; } // Look for patient using the patient id private Integer findPatientId(PID pid) throws HL7Exception { Integer patientId = Context.getHL7Service().resolvePatientId(pid); if (patientId == null) { return null; } else { return patientId; } } // Create a new patient when this patient doesn't exist in the database private Patient createPatient(PID pid, String creatorName) throws HL7Exception { Patient patient = new Patient(); // Try to use the specified username as the creator User creator = Context.getUserService().getUserByUsername(creatorName); if (creator != null) { patient.setCreator(creator); } // Create all patient identifiers specified in the message // Copied code from resolvePatientId() in HL7ServiceImpl.java CX[] idList = pid.getPatientIdentifierList(); if (idList == null || idList.length < 1) throw new HL7Exception("Missing patient identifier in PID segment"); List<PatientIdentifier> goodIdentifiers = new ArrayList<PatientIdentifier>(); for (CX id : idList) { String assigningAuthority = id.getAssigningAuthority().getNamespaceID().getValue(); String hl7PatientId = id.getIDNumber().getValue(); log.debug("identifier has id=" + hl7PatientId + " assigningAuthority=" + assigningAuthority); if (assigningAuthority != null && assigningAuthority.length() > 0) { try { PatientIdentifierType pit = Context.getPatientService().getPatientIdentifierTypeByName( assigningAuthority); if (pit == null) { log.warn("Can't find PatientIdentifierType named '" + assigningAuthority + "'"); continue; // skip identifiers with unknown type } PatientIdentifier pi = new PatientIdentifier(); if (creator != null) { pi.setCreator(creator); } pi.setIdentifierType(pit); pi.setIdentifier(hl7PatientId); // Get default location Location location = Context.getLocationService().getDefaultLocation(); if (location == null) { throw new HL7Exception("Cannot find default location"); } pi.setLocation(location); try { PatientIdentifierValidator.validateIdentifier(pi); goodIdentifiers.add(pi); } catch (PatientIdentifierException ex) { log.warn("Patient identifier in PID is invalid: " + pi, ex); } } catch (Exception e) { log.error("Uncaught error parsing/creating patient identifier '" + hl7PatientId + "' for assigning authority '" + assigningAuthority + "'", e); } } else { log.error("PID contains identifier with no assigning authority"); continue; } } if (goodIdentifiers.size() == 0) { throw new HL7Exception("PID segment has no recognizable patient identifiers."); } patient.addIdentifiers(goodIdentifiers); // Extract patient name from the message XPN patientNameX = pid.getPatientName(0); if (patientNameX == null) throw new HL7Exception("Missing patient name in the PID segment"); // Patient name PersonName name = new PersonName(); name.setFamilyName(patientNameX.getFamilyName().getSurname().getValue()); name.setGivenName(patientNameX.getGivenName().getValue()); name.setMiddleName(patientNameX.getSecondAndFurtherGivenNamesOrInitialsThereof().getValue()); if (creator != null) { name.setCreator(creator); } patient.addName(name); // Gender (checks for null, but not for 'M' or 'F') String gender = pid.getAdministrativeSex().getValue(); if (gender == null) throw new HL7Exception("Missing gender in the PID segment"); gender = gender.toUpperCase(); if (!OpenmrsConstants.GENDER().containsKey(gender)) throw new HL7Exception("Unrecognized gender: " + gender); patient.setGender(gender); // Date of Birth TS dateOfBirth = pid.getDateTimeOfBirth(); if (dateOfBirth == null || dateOfBirth.getTime() == null || dateOfBirth.getTime().getValue() == null) throw new HL7Exception("Missing birth date in the PID segment"); patient.setBirthdate(tsToDate(dateOfBirth)); // Estimated birthdate? ID precisionTemp = dateOfBirth.getDegreeOfPrecision(); if (precisionTemp != null && precisionTemp.getValue() != null) { String precision = precisionTemp.getValue().toUpperCase(); log.debug("The birthdate is estimated: " + precision); if (precision.equals("Y") || precision.equals("L")) patient.setBirthdateEstimated(true); } return patient; } // TODO: Move these to hl7 handler utilities // Check version, etc. private void validate(Message message) throws HL7Exception { message.getVersion().toString(); } private MSH getMSH(ADT_A05 adt) { return adt.getMSH(); } private PID getPID(ADT_A05 adt) { return adt.getPID(); } //TODO: Debug (and use) methods in HL7Util instead private Date tsToDate(TS ts) throws HL7Exception { // need to handle timezone String dtm = ts.getTime().getValue(); int year = Integer.parseInt(dtm.substring(0, 4)); int month = (dtm.length() >= 6 ? Integer.parseInt(dtm.substring(4, 6)) - 1 : 0); int day = (dtm.length() >= 8 ? Integer.parseInt(dtm.substring(6, 8)) : 1); int hour = (dtm.length() >= 10 ? Integer.parseInt(dtm.substring(8, 10)) : 0); int min = (dtm.length() >= 12 ? Integer.parseInt(dtm.substring(10, 12)) : 0); int sec = (dtm.length() >= 14 ? Integer.parseInt(dtm.substring(12, 14)) : 0); Calendar cal = Calendar.getInstance(); cal.set(year, month, day, hour, min, sec); return cal.getTime(); } }