def _create_message(self, results, ack_code): """ Creates the final RSP_K21 response message. If no errors occurred and some results have been found, the message will carry as many different PID segments as the number of the results. :type: results ``list`` :param: results a list providing the Gnuhealth ORM query results :type: ack_code ``str`` :param: ack_code it is the ack:code to be put in the MSA segment. If no errors occurred, the code wull be of positive ACK (AA); else, one of the negative acks (AE, AR) :return: an hl7apy ``Message`` class instance, which is the final RSP_K21 message """ try: qry_ack_code = 'OK' if len(results) == 0: qry_ack_code = 'NF' is_pdqv = self.is_pdqv_message() logger.debug("Creating response message...") if is_pdqv: message_profile = load_message_profile(os.path.join(_get_message_profiles_dir(), "pdqv_response")) message_structure = PDQV_MESSAGE_STRUCTURE else: message_profile = load_message_profile(os.path.join(_get_message_profiles_dir(), "pdq_response")) message_structure = PDQ_MESSAGE_STRUCTURE message = Message(message_structure, version='2.5', validation_level=VALIDATION_LEVEL.STRICT, reference=message_profile) self._set_msh(message, is_pdqv) msa = message.add_segment("MSA") msa.msa_1 = ack_code msa.msa_2 = self.msg.msh.msh_10.msh_10_1 qak = message.add_segment("QAK") qak.qak_1 = self.msg.qpd.qpd_2 qak.qak_2 = qry_ack_code qak.qak_4.qak_4_1 = str(len(results)) # total results qak.qak_5.qak_5_1 = str(len(results)) # sent results qak.qak_6.qak_6_1 = "0" # remaining results message.add_segment("QPD") message.qpd = self.msg.qpd message = self._create_pdq_res_list(message, message_structure, results) return message except Exception, e: logger.error('Error during Message Creation, %s' % e)
def setUp(self): self.rsp_k21 = \ 'MSH|^~\&|SEND APP|SEND FAC|REC APP|REC FAC|20110708163514||RSP^K22^RSP_K21|1234|D|2.5|||||ITA||EN\r' \ 'MSA|AA|26775702551812240|\r' \ 'QAK|111069|OK||1|1|0\r' \ 'QPD|IHE PDQ Query|111069|@PID.3.1^[email protected]^SMITH||||\r' \ 'PID|1||10101^^^GATEWAY&1.3.6.1.4.1.21367.2011.2.5.17&ISO||JOHN^SMITH^^^^^A||19690113|M|||VIA DELLE VIE^^CAGLIARI^^^100^H^^092009||||||||||||CAGLIARI|||||\r' # it misses some PID.3 components self.invalid_rsp_k21 = \ 'MSH|^~\&|SEND APP|SEND FAC|REC APP|REC FAC|20110708163514||RSP^K22^RSP_K21|1234|D|2.5|||||ITA||EN\r' \ 'MSA|AA|26775702551812240|\r' \ 'QAK|111069|OK||1|1|0\r' \ 'QPD|IHE PDQ Query|111069|@PID.3.1^[email protected]^SMITH||||\r' \ 'PID|1||10101^^^||JOHN^SMITH^^^^^A||19690113|M|||VIA DELLE VIE^^CAGLIARI^^^100^H^^092009|||||||||||||||||\r' self.rsp_k21_27 = \ 'MSH|^~\&#|SEND APP|SEND FAC|REC APP|REC FAC|20110708163514||RSP^K22^RSP_K21|1234|D|2.7|||||ITA||EN\r' \ 'MSA|AA|26775702551812240\r' \ 'QAK|111069|OK||1|1|0\r' \ 'QPD|IHE PDQ Query|111069|@PID.3.1^[email protected]^SMITH\r' \ 'PID|1||10101^^^GATEWAY&1.3.6.1.4.1.21367.2011.2.5.17&ISO||JOHN^SMITH^^^^^A||19690113|M|||VIA DELLE VIE^^CAGLIARI^^^100^H^^092009||||||||||||CAGLIARI' self.rsp_k21_27_no_truncation = \ 'MSH|^~\&|SEND APP|SEND FAC|REC APP|REC FAC|20110708163514||RSP^K22^RSP_K21|1234|D|2.7|||||ITA||EN\r' \ 'MSA|AA|26775702551812240\r' \ 'QAK|111069|OK||1|1|0\r' \ 'QPD|IHE PDQ Query|111069|@PID.3.1^[email protected]^SMITH\r' \ 'PID|1||10101^^^GATEWAY&1.3.6.1.4.1.21367.2011.2.5.17&ISO||JOHN^SMITH^^^^^A||19690113|M|||VIA DELLE VIE^^CAGLIARI^^^100^H^^092009||||||||||||CAGLIARI' base_path = os.path.abspath(os.path.dirname(__file__)) path = os.path.join(base_path, 'profiles/iti_21') self.rsp_k21_mp = hl7apy.load_message_profile(path)
def _check_incoming_message(self): """ Controls the incoming HL7 message, after parsing, according to some IHE PDQ message request rules. It checks that: - All query parameters codes and values are correct in the request message - The incoming message contains at least one query parameter - For the birth date query parameter, the date value format is HL7 - compliant - If the Allowed Application Filter is enabled. checks if the MSH_3 field contains a value included in the Allowed_application list table :raises: :exc:`MissingQueryParameters` if the incoming message has not any query parameters (QPD_3 hl7 message field is empty) :raises: :exc:`InvalidQueryParameterCode` if the incoming message has a query parameter code (QPD_3_1) in a not-allowed format :raises: :exc:`MissingQueryParameterValue` if the incoming message has a query parameter (QPD_3_1) correct but without the corrispondent value (QPD_3_2) :raises: :exc:`InvalidDateParameterValue` if the incoming message has a query parameter (QPD_3_1) asking for the patient birth date, but providing a not allowed date format in the value :raise: :exc:`InvalidSendingApplicationParameterValue if the Application Filter is enabled and the MSH_3 field of the incoming message is not included into the Allowed Applications list` """ try: message_profiles_dir = _get_message_profiles_dir() logger.debug("Checking for message profile from dir: %s" % message_profiles_dir) if self.is_pdq_message(): message_profile = load_message_profile(os.path.join(message_profiles_dir, 'pdq_request')) elif self.is_pdqv_message(): message_profile = load_message_profile(os.path.join(message_profiles_dir, 'pdqv_request')) else: raise MessageProfileNotFound() self.msg = parse_message(self.raw_msg, validation_level=VALIDATION_LEVEL.STRICT, message_profile=message_profile) except MessageProfileNotFound, e: # NO PDQ logger.error("No message profile found for message: %s -> %s" % (self.raw_msg, str(e))) raise PDQMessageProfileNotFound
def setUp(self): self.rsp_k21 = \ 'MSH|^~\&|SENDING APP|SENDING FAC|RECEIVING APP|RECEIVING FAC|20140410170011||RSP^K22^RSP_K21|11111111|P|2.5\r' \ 'MSA|AA|20140410170015\r' \ 'QAK|222222222|OK\r' \ 'QPD|IHE PDQ Query|222222222|@PID.3.1.1^3333333|||||^^^IHEFACILITY&1.3.6.1.4.1.21367.3000.1.6&ISO^|\r' \ 'PID|1||10101109091948^^^GATEWAY&1.3.6.1.4.1.21367.2011.2.5.17&ISO||JOHN^SMITH^^^^^A||19690113|M|||VIA DELLE VIE^^CAGLIARI^^^ITA^H^^092009||||||||||||CAGLIARI|||\r' base_path = os.path.abspath(os.path.dirname(__file__)) path = os.path.join(base_path, 'profiles/iti_21') self.rsp_k21_mp = hl7apy.load_message_profile(path) self.report_file = '/tmp/hl7apy_test_rf'
def parse_messages(directory, validation_level=VL.STRICT, find_groups=True, limit=-1, output_file=None, message_profile=None): exceptions = {VL.TOLERANT: [], VL.STRICT: []} msg_per_versions = { VL.TOLERANT: defaultdict(int), VL.STRICT: defaultdict(int) } msg_per_type = {VL.TOLERANT: defaultdict(int), VL.STRICT: defaultdict(int)} parsing_time = {VL.TOLERANT: [], VL.STRICT: []} encoding_time = [] files = _get_files(directory)[:limit] if limit != -1 else _get_files( directory) n_messages = {VL.TOLERANT: 0, VL.STRICT: 0} mp = load_message_profile( message_profile) if message_profile is not None else None start = time.time() for f in sorted(files): with open(f) as hl7_file: msg_str = hl7_file.read() msg_str = msg_str.replace('\r\n', '\r') msg_str = msg_str.replace('\n', '\r') error_occurred = False validations = [VL.STRICT, VL.TOLERANT] if validation_level == VL.TOLERANT: validations.remove(VL.STRICT) for vl in validations: # it parses TOLERANT only if the user asked (validation_level == VL.TOLERANT) # or an error occurred if vl == VL.TOLERANT and (validation_level != VL.TOLERANT and not error_occurred): continue file_base_name = os.path.basename(hl7_file.name) try: msg_start = time.time() msg = parse_message(msg_str, vl, find_groups=find_groups, message_profile=mp) msg_end = time.time() msg_per_versions[vl][msg.version] += 1 msg_per_type[vl][msg.msh.msh_9.to_er7()] += 1 parsing_time[vl].append( (file_base_name, len(msg.children), msg.msh.msh_9.to_er7(), msg_end - msg_start)) except MessageProfileNotFound: continue except InvalidEncodingChars: if mp is None: exceptions[vl].append({ 'type': 'parsing', 'ex': e, 'file_name': f, 'msg': msg_str }) if vl == VL.STRICT: error_occurred = True else: continue except Exception as e: exceptions[vl].append({ 'type': 'parsing', 'ex': e, 'file_name': f, 'msg': msg_str }) if vl == VL.STRICT: error_occurred = True n_messages[vl] += 1 try: encoding_start = time.time() msg.to_er7() encoding_end = time.time() encoding_time.append((file_base_name, len(msg.children), msg.msh.msh_9.to_er7(), encoding_end - encoding_start)) except Exception as e: exceptions[vl].append({ 'type': 'encoding', 'ex': e, 'file_name': f, 'msg': msg_str }) elapsed_time = time.time() - start print_report(n_messages, msg_per_versions, msg_per_type, exceptions, elapsed_time, output_file, parsing_time, encoding_time, validation_level, message_profile)
def parse_messages(directory, validation_level=VL.STRICT, find_groups=True, limit=-1, output_file=None, message_profile=None): exceptions = {VL.TOLERANT: [], VL.STRICT: []} msg_per_versions = {VL.TOLERANT: defaultdict(int), VL.STRICT: defaultdict(int)} msg_per_type = {VL.TOLERANT: defaultdict(int), VL.STRICT: defaultdict(int)} parsing_time = {VL.TOLERANT : [], VL.STRICT : []} encoding_time = [] files = _get_files(directory)[:limit] if limit != -1 else _get_files(directory) n_messages = {VL.TOLERANT: 0, VL.STRICT: 0} mp = load_message_profile(message_profile) if message_profile is not None else None start = time.time() for f in sorted(files): with open(f) as hl7_file: msg_str = hl7_file.read() msg_str = msg_str.replace('\r\n', '\r') msg_str = msg_str.replace('\n', '\r') error_occurred = False validations = [VL.STRICT, VL.TOLERANT] if validation_level == VL.TOLERANT: validations.remove(VL.STRICT) for vl in validations: # it parses TOLERANT only if the user asked (validation_level == VL.TOLERANT) # or an error occurred if vl == VL.TOLERANT and (validation_level != VL.TOLERANT and not error_occurred): continue file_base_name = os.path.basename(hl7_file.name) try: msg_start = time.time() msg = parse_message(msg_str, vl, find_groups=find_groups, message_profile=mp) msg_end = time.time() msg_per_versions[vl][msg.version] += 1 msg_per_type[vl][msg.msh.msh_9.to_er7()] += 1 parsing_time[vl].append((file_base_name, len(msg.children), msg.msh.msh_9.to_er7(), msg_end - msg_start)) except MessageProfileNotFound: continue except InvalidEncodingChars: if mp is None: exceptions[vl].append({'type': 'parsing', 'ex': e, 'file_name': f, 'msg': msg_str}) if vl == VL.STRICT: error_occurred = True else: continue except Exception as e: exceptions[vl].append({'type': 'parsing', 'ex': e, 'file_name': f, 'msg': msg_str}) if vl == VL.STRICT: error_occurred = True n_messages[vl] += 1 try: encoding_start = time.time() msg.to_er7() encoding_end = time.time() encoding_time.append((file_base_name, len(msg.children), msg.msh.msh_9.to_er7(), encoding_end - encoding_start)) except Exception as e: exceptions[vl].append({'type': 'encoding', 'ex': e, 'file_name': f, 'msg': msg_str}) elapsed_time = time.time() - start print_report(n_messages, msg_per_versions, msg_per_type, exceptions, elapsed_time, output_file, parsing_time, encoding_time, validation_level, message_profile)
# See license-GPLv2.txt or license-MIT.txt import os import re import uuid import socket import hashlib from django.conf import settings from hl7apy import load_message_profile, VALIDATION_LEVEL as VL from hl7apy.core import Message from hl7apy.parser import parse_message _MP_ROOT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "hl7_profiles") _PDQ_REQ_MP = load_message_profile(os.path.join(_MP_ROOT_PATH, "pdq_request")) _PDQV_REQ_MP = load_message_profile(os.path.join(_MP_ROOT_PATH, "pdqv_request")) _PDQ_RES_MP = load_message_profile(os.path.join(_MP_ROOT_PATH, "pdq_response")) _PDQV_RES_MP = load_message_profile(os.path.join(_MP_ROOT_PATH, "pdqv_response")) def make_new_uid(): return hashlib.sha1(os.urandom(20)).hexdigest() def json_response(): pass def send_hl7_message(host, port, er7_message): sb = "\x0b"
import uuid import logging import datetime from hl7apy.v2_5 import DTM from hl7apy.utils import check_date from hl7apy.mllp import AbstractHandler from hl7apy.parser import parse_message from hl7apy.core import Message from hl7apy import load_message_profile from dao import DAO _ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) PDQ_REQ_MP = load_message_profile(os.path.join(_ROOT_PATH, './pdq_req')) PDQ_RES_MP = load_message_profile(os.path.join(_ROOT_PATH, './pdq_res')) logger = logging.getLogger(__name__) class PDQHandler(AbstractHandler): REQ_MP, RES_MP = PDQ_REQ_MP, PDQ_RES_MP PDQ_FIELD_NAMES = { '@PID.3.1': "IDENTIFIER", '@PID.5.1.1': 'SURNAME', '@PID.5.2': 'NAME', '@PID.7.1': 'DOB' }
class PDQSupplier(AbstractHandler): REQ_MP = load_message_profile(os.path.join(_ROOT_PATH, './pdq_req')) RES_MP = load_message_profile(os.path.join(_ROOT_PATH, './pdq_res')) FIELD_NAMES = { '@PID.5.1.1': 'SURNAME', '@PID.5.2': 'NAME', '@PID.7.1': 'DOB' } MISSING_PARAMS = 1 def __init__(self, message): msg = parse_message(message, message_profile=self.REQ_MP) super(PDQSupplier, self).__init__(msg) def _create_response(self, ack_code, query_ack_code, patients): res = Message('RSP_K21', reference=self.RES_MP) res.msh.msh_5 = self.incoming_message.msh.msh_3 res.msh.msh_6 = self.incoming_message.msh.msh_4 res.msh.msh_7.ts_1 = DTM(datetime.datetime.now()) res.msh.msh_9 = 'RSP^K22^RSP_K21' res.msh.msh_10 = uuid.uuid4().hex # MSA creation res.msa.msa_1 = ack_code res.msa.msa_2 = self.incoming_message.msh.msh_10.msh_10_1 # QAK creation res.qak.qak_1 = self.incoming_message.qpd.qpd_2 res.qak.qak_2 = 'OK' if len(patients) > 0 else 'NF' res.qak.qak_4 = str(len(patients)) # QPD creation res.qpd = self.incoming_message.qpd # RSP_K21_QUERY_RESPONSE creation res.add_group('rsp_k21_query_response') g = res.rsp_k21_query_response for i, p in enumerate(patients): # add a pid segment for every patient g.add_segment('PID') g.pid[i].pid_3.cx_1, g.pid[i].pid_5.xpn_1, g.pid[i].pid_5.xpn_2 = p[:] return res.to_mllp() def _create_error(self, error_code): res = self._create_response('AR', 'AR', []) return res def reply(self): print('Received a message') print(repr(self.incoming_message.to_er7())) query_params = dict((self.FIELD_NAMES[q.qip_1.value], q.qip_2.value) for q in self.incoming_message.qpd.qpd_3 if q.qip_1.value in self.FIELD_NAMES) print("Extracted query params: {}".format(query_params)) if '' in query_params.values(): return self._create_error(1) else: patients = [('0001', 'John', 'Smith')] return self._create_response('AA', 'OK', patients)