Beispiel #1
0
        /// <summary>
        /// Create a PDQ search message
        /// </summary>
        /// <param name="filters">The parameters for query</param>
        private QBP_Q21 CreatePDQSearch(int offset, int count, object state, params KeyValuePair <String, String>[] filters)
        {
            // Search - Construct a v2 message this is found in IHE ITI TF-2:3.21
            QBP_Q21 message = new QBP_Q21();

            this.UpdateMSH(message.MSH, "QBP_Q21", "QBP", "Q22");
            //message.MSH.VersionID.VersionID.Value = "2.3.1";

            // Message query
            message.QPD.MessageQueryName.Identifier.Value             = "Patient Demographics Query";
            message.DSC.ContinuationPointer.Value                     = state?.ToString();
            message.RCP.QuantityLimitedRequest.Quantity.Value         = count.ToString();
            message.RCP.QuantityLimitedRequest.Units.Identifier.Value = "RD";

            // Sometimes it is easier to use a terser
            Terser terser = new Terser(message);

            terser.Set("/QPD-2", Guid.NewGuid().ToString());    // Tag of the query
            terser.Set("/QPD-1", "Patient Demographics Query"); // Name of the query
            for (int i = 0; i < filters.Length; i++)
            {
                terser.Set(String.Format("/QPD-3({0})-1", i), filters[i].Key);
                terser.Set(String.Format("/QPD-3({0})-2", i), filters[i].Value);
            }

            return(message);
        }
        /// <summary>
        /// Process a response message doing query continuation if needed
        /// </summary>
        private void PullPatientsAsync(Object state)
        {
            // Cast request
            QBP_Q21 request = state as QBP_Q21;

            // Send the PDQ message
            try
            {
                var response = this.m_sender.SendAndReceive(request) as RSP_K21;
                AuditUtil.SendPDQAudit(request, response);
                if (response == null || response.MSA.AcknowledgmentCode.Value != "AA")
                {
                    foreach (var err in response.ERR.GetErrorCodeAndLocation())
                    {
                        Trace.TraceError("{0}: CR ERR: {1} ({2})", this.m_context.JobId, err.CodeIdentifyingError.Text, err.CodeIdentifyingError.AlternateText);
                    }
                    // Kill!
                    Trace.TraceError("Stopping sync");
                    this.m_errorState = true;
                }

                // Is there a continuation pointer?
                if (!String.IsNullOrEmpty(response.DSC.ContinuationPointer.Value))
                {
                    Trace.TraceInformation("{0}: Need to continue query", this.m_context.JobId);
                    request.DSC.ContinuationPointer.Value = response.DSC.ContinuationPointer.Value;
                    this.UpdateMSH(request.MSH, "QBP_Q21", "QBP", "Q22");
                    this.m_waitThread.QueueUserWorkItem(this.PullPatientsAsync, request);
                }

                // Process the patients in this response
                lock (this.m_syncState)
                    for (int i = 0; i < response.QUERY_RESPONSERepetitionsUsed; i++)
                    {
                        var responseData = response.GetQUERY_RESPONSE(i);
                        this.m_workerItems.Push(responseData);
                    }

                // Relieve memorypressure
                lock (this.m_syncState)
                    while (this.m_workerItems.Count > 0)
                    {
                        this.m_waitThread.QueueUserWorkItem(this.ProcessPIDAsync, this.m_workerItems.Pop());
                    }
            }
            catch (Exception e)
            {
                Trace.TraceError(e.ToString());
                this.m_errorState = true;
            }
        }
Beispiel #3
0
        /// <summary>
        /// Create a PIX search message
        /// </summary>
        private QBP_Q21 CreatePIXSearch(PatientIdentifier localId, string targetDomain)
        {
            QBP_Q21 retVal = new QBP_Q21();

            this.UpdateMSH(retVal.MSH, "QBP_Q21", "QBP", "Q23");
            Terser terser = new Terser(retVal);

            terser.Set("/QPD-1", "IHE PIX Query");
            terser.Set("/QPD-2", Guid.NewGuid().ToString().Substring(0, 8));
            terser.Set("/QPD-3-1", localId.Value);
            terser.Set("/QPD-3-4-1", localId.Domain);
            terser.Set("/QPD-4-4-1", targetDomain);
            return(retVal);
        }
Beispiel #4
0
        /// <summary>
        /// Send query to master target
        /// </summary>
        private List <Patient> SendQuery(NameValueCollection originalQuery, int count, out int totalResults)
        {
            // Map reverse
            var parmMap = s_map.Map.FirstOrDefault(o => o.Trigger == "Q22");
            List <KeyValuePair <Hl7QueryParameterMapProperty, object> > parameters = new List <KeyValuePair <Hl7QueryParameterMapProperty, object> >();

            foreach (var kv in originalQuery)
            {
                var rmap = parmMap.Parameters.Find(o => o.ModelName == kv.Key);
                if (rmap == null)
                {
                    // Is the value a UUID? If so, it may be an identifier we can use
                    if (Guid.TryParse(kv.Value.First(), out Guid uuid))
                    {
                        // What is the type of this property
                        var property = QueryExpressionParser.BuildPropertySelector <Patient>(kv.Key);
                        if (property == null)
                        {
                            throw new InvalidOperationException($"{kv.Key} is not valid on Patient");
                        }
                        // Is there a classifier? We need it for querying a guaranteed unique property
                        var preferred = property.Body.Type.GetCustomAttribute <ClassifierAttribute>()?.ClassifierProperty;
                        if (String.IsNullOrEmpty(preferred))
                        {
                            throw new InvalidOperationException($"{property.Body.Type} does not have a ClassifierAttribute");
                        }
                        var idp = typeof(IDataPersistenceService <>).MakeGenericType(property.Body.Type);
                        var ids = ApplicationServiceContext.Current.GetService(idp) as IDataPersistenceService;
                        if (ids == null)
                        {
                            throw new InvalidOperationException($"{idp} not found");
                        }
                        var value = ids.Get(uuid);
                        var match = property.Body.Type.GetProperty(preferred).GetValue(value);
                        preferred = property.Body.Type.GetProperty(preferred).GetSerializationName();

                        // Get the parmaeter map for this classifier
                        rmap = parmMap.Parameters.Find(o => o.ModelName == $"{kv.Key}.{preferred}");
                        if (rmap != null)
                        {
                            parameters.Add(new KeyValuePair <Hl7QueryParameterMapProperty, object>(rmap, match));
                        }
                        else
                        {
                            continue;
                        }
                    }
                    else
                    {
                        continue;
                    }
                }
                else
                {
                    parameters.Add(new KeyValuePair <Hl7QueryParameterMapProperty, object>(rmap, kv.Value));
                }
            }

            if (parameters.Count == 0)
            {
                parameters.Add(new KeyValuePair <Hl7QueryParameterMapProperty, object>(parmMap.Parameters.FirstOrDefault(o => o.Hl7Name == "@PID.33"), DateTime.MinValue.AddDays(10)));
            }


            // Construct the basic QBP_Q22
            QBP_Q21 queryRequest = new QBP_Q21();
            var     endpoint     = this.Configuration.Endpoints.First();

            queryRequest.MSH.SetDefault(endpoint.ReceivingDevice, endpoint.ReceivingFacility, endpoint.SecurityToken);
            queryRequest.MSH.MessageType.MessageStructure.Value = "QBP_Q21";
            queryRequest.MSH.MessageType.TriggerEvent.Value     = "Q22";
            queryRequest.MSH.MessageType.MessageCode.Value      = "QBP";

            queryRequest.GetSFT(0).SetDefault();
            queryRequest.RCP.QuantityLimitedRequest.Units.Identifier.Value = "RD";
            queryRequest.RCP.QuantityLimitedRequest.Quantity.Value         = (count).ToString();
            queryRequest.QPD.MessageQueryName.Identifier.Value             = "Q22";
            queryRequest.QPD.MessageQueryName.Text.Value = "Find Candidates";
            queryRequest.QPD.MessageQueryName.NameOfCodingSystem.Value = "HL7";

            Terser tser = new Terser(queryRequest);

            int q = 0;

            foreach (var qp in parameters)
            {
                List <String> filter = qp.Value as List <String> ?? new List <String>()
                {
                    qp.Value.ToString()
                };
                foreach (var val in filter)
                {
                    Terser.Set(queryRequest.QPD, 3, q, 1, 1, qp.Key.Hl7Name);
                    string dval = val;
                    while (new String[] { "<", ">", "!", "=", "~" }.Any(o => dval.StartsWith(o)))
                    {
                        dval = dval.Substring(1);
                    }

                    switch (qp.Key.ParameterType)
                    {
                    case "date":
                        var dt = DateTime.Parse(dval);
                        switch (dval.Length)
                        {
                        case 4:
                            Terser.Set(queryRequest.QPD, 3, q, 2, 1, dt.Year.ToString());
                            break;

                        case 7:
                            Terser.Set(queryRequest.QPD, 3, q, 2, 1, dt.ToString("yyyyMM"));
                            break;

                        case 10:
                            Terser.Set(queryRequest.QPD, 3, q, 2, 1, dt.ToString("yyyyMMdd"));
                            break;

                        default:
                            Terser.Set(queryRequest.QPD, 3, q, 2, 1, dt.ToString("yyyyMMddHHmmss.fffzzzz").Replace(":", ""));
                            break;
                        }
                        break;

                    default:
                        Terser.Set(queryRequest.QPD, 3, q, 2, 1, dval);
                        break;
                    }
                    q++;
                }
            }

            // TODO: Send the query and then maps results
            try
            {
                RSP_K21 response = endpoint.GetSender().SendAndReceive(queryRequest) as RSP_K21;
                // Iterate and create responses
                totalResults = Int32.Parse(response.QAK.HitCount.Value ?? response.QUERY_RESPONSERepetitionsUsed.ToString());
                List <Patient> overr = new List <Patient>();
                // Query response
                for (int i = 0; i < response.QUERY_RESPONSERepetitionsUsed; i++)
                {
                    var ar = response.GetQUERY_RESPONSE(i);
                    // Create patient
                    Bundle patientData = MessageUtils.Parse(ar);
                    patientData.Reconstitute();

                    // Does this patient "really" exist?
                    if (!ar.PID.GetPatientIdentifierList().Any(o => o.AssigningAuthority.NamespaceID.Value == this.m_configuration.LocalAuthority.DomainName) &&
                        !this.m_retrieveHacks.ContainsKey(patientData.Item.OfType <Patient>().First().Key.Value))
                    {
                        var key     = this.m_retrieveHacks.FirstOrDefault(o => o.Value.Any(x => x.Value == ar.PID.GetPatientIdentifierList()[0].IDNumber.Value));
                        var patient = patientData.Item.OfType <Patient>().First();

                        if (key.Key != Guid.Empty)
                        {
                            patient.Key = key.Key;
                        }
                        else
                        {
                            this.m_retrieveHacks.Add(patient.Key.Value, patient.Identifiers);
                        }
                    }

                    // Now we extract the patient
                    var pat = patientData.Item.OfType <Patient>().First();
                    pat.VersionKey = pat.Key;
                    overr.Add(pat);
                }
                return(overr);
            }
            catch (Exception ex)
            {
                totalResults = 0;
                this.m_tracer.TraceEvent(EventLevel.Error, "Error dispatching HL7 query {0}", ex);
                throw new HL7ProcessingException("Error dispatching HL7 query", null, null, 0, 0, ex);
            }
        }
Beispiel #5
0
        /// <summary>
        /// Handle a PIX query.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="eventArgs">The <see cref="Hl7MessageReceivedEventArgs" /> instance containing the event data.</param>
        /// <returns>Returns the message result from the query.</returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        internal IMessage HandlePixQuery(QBP_Q21 request, Hl7MessageReceivedEventArgs eventArgs)
        {
            var patientRepositoryService = ApplicationContext.Current.GetService <IPatientRepositoryService>();

            var details = new List <IResultDetail>();

            MessageUtil.Validate(request, details);

            IMessage response = null;

            // Control
            if (request == null)
            {
                return(null);
            }

            try
            {
                // Create Query Data
                var query = MessageUtil.CreateIDQuery(request.QPD);

                if (query == null)
                {
                    throw new InvalidOperationException(ApplicationContext.Current.GetLocaleString("MSGE00A"));
                }

                var count      = int.Parse(request?.RCP?.QuantityLimitedRequest?.Quantity?.Value ?? "0");
                var offset     = 0;
                var totalCount = 0;
                var result     = patientRepositoryService.Find(query, offset, count, out totalCount);

                // Now process the result
                response = MessageUtil.CreateRSPK23(result, details);

                try
                {
                    (response as RSP_K23).QPD.MessageQueryName.Identifier.Value = request.QPD.MessageQueryName.Identifier.Value;

                    Terser reqTerser = new Terser(request), rspTerser = new Terser(response);

                    rspTerser.Set("/QPD-1", reqTerser.Get("/QPD-1"));
                    rspTerser.Set("/QPD-2", reqTerser.Get("/QPD-2"));
                    rspTerser.Set("/QPD-3-1", reqTerser.Get("/QPD-3-1"));
                    rspTerser.Set("/QPD-3-4-1", reqTerser.Get("/QPD-3-4-1"));
                    rspTerser.Set("/QPD-3-4-2", reqTerser.Get("/QPD-3-4-2"));
                    rspTerser.Set("/QPD-3-4-3", reqTerser.Get("/QPD-3-4-3"));
                    rspTerser.Set("/QPD-4-1", reqTerser.Get("/QPD-4-1"));
                    rspTerser.Set("/QPD-4-4-1", reqTerser.Get("/QPD-4-4-1"));
                    rspTerser.Set("/QPD-4-4-2", reqTerser.Get("/QPD-4-4-2"));
                    rspTerser.Set("/QPD-4-4-3", reqTerser.Get("/QPD-4-4-3"));
                }
                catch (Exception e)
                {
                    this.traceSource.TraceEvent(TraceEventType.Error, 0, e.ToString());
                }

                MessageUtil.UpdateMSH(new Terser(response), request);
            }
            catch (Exception e)
            {
                this.traceSource.TraceEvent(TraceEventType.Error, 0, e.ToString());

                response = MessageUtil.CreateNack(request, details, typeof(RSP_K23));

                var errTerser = new Terser(response);

                // HACK: Fix the generic ACK with a real ACK for this message
                errTerser.Set("/MSH-9-2", "K23");
                errTerser.Set("/MSH-9-3", "RSP_K23");
                errTerser.Set("/QAK-2", "AE");
                errTerser.Set("/MSA-1", "AE");
                errTerser.Set("/QAK-1", request.QPD.QueryTag.Value);
            }

            return(response);
        }
        /// <summary>
        /// Pull clients
        /// </summary>
        public void PullClients()
        {
            Trace.TraceInformation("{0}: -- Starting PULL of patients from CR --", this.m_context.JobId);

            QBP_Q21 request = null;

            // Get the last sync to be completed
            using (SyncData dao = new SyncData())
            {
                // Last modified filter
                var      lastSync           = dao.GetLastSync();
                DateTime?lastModifiedFilter = lastSync == null ? null : (DateTime?)lastSync.StartTime;

                // Create a PDQ message
                if (lastModifiedFilter.HasValue)
                {
                    Trace.TraceInformation("{0}: Last sync was on {1}", this.m_context.JobId, lastModifiedFilter.Value
                                           );
                    //request = this.CreatePDQSearch(new KeyValuePair<string, string>("@PID.33", new TS(lastModifiedFilter.Value))) as QBP_Q21;
                    ////Trace.TraceInformation("{0}: Only PULL patients modified on {1:yyyy-MMM-dd}", this.m_context.JobId, new TS(this.m_context.StartTime.AddDays(-i), DatePrecision.Day).DateValue);
                    //this.m_waitThread.QueueUserWorkItem(this.PullPatientsAsync, request);

                    // Create a series of OR parameters representing days we're out of sync
                    for (int i = 0; i <= this.m_context.StartTime.Subtract(lastModifiedFilter.Value).TotalDays; i++)
                    {
                        request = this.CreatePDQSearch(new KeyValuePair <string, string>("@PID.33", new TS(this.m_context.StartTime.AddDays(-i), DatePrecision.Day)),
                                                       new KeyValuePair <string, string>("@PID.8", "M")) as QBP_Q21;
                        Trace.TraceInformation("{0}: Only PULL MALE patients modified on {1:yyyy-MMM-dd}", this.m_context.JobId, new TS(this.m_context.StartTime.AddDays(-i), DatePrecision.Day).DateValue);
                        this.m_waitThread.QueueUserWorkItem(this.PullPatientsAsync, request);
                        request = this.CreatePDQSearch(new KeyValuePair <string, string>("@PID.33", new TS(this.m_context.StartTime.AddDays(-i), DatePrecision.Day)),
                                                       new KeyValuePair <string, string>("@PID.8", "F")) as QBP_Q21;
                        Trace.TraceInformation("{0}: Only PULL FEMALE patients modified on {1:yyyy-MMM-dd}", this.m_context.JobId, new TS(this.m_context.StartTime.AddDays(-i), DatePrecision.Day).DateValue);
                        this.m_waitThread.QueueUserWorkItem(this.PullPatientsAsync, request);
                    }
                }
                else // No last modification date, we have to trick the CR into giving us a complete list
                {
                    for (int i = DateTime.Now.Year - 3; i <= DateTime.Now.Year; i++)
                    {
                        request = this.CreatePDQSearch(new KeyValuePair <String, String>("@PID.8", "F"), new KeyValuePair <String, String>("@PID.7", string.Format("{0:0000}", i))) as QBP_Q21;
                        this.m_waitThread.QueueUserWorkItem(this.PullPatientsAsync, request);
                        request = this.CreatePDQSearch(new KeyValuePair <String, String>("@PID.8", "M"), new KeyValuePair <String, String>("@PID.7", string.Format("{0:0000}", i))) as QBP_Q21;
                        this.m_waitThread.QueueUserWorkItem(this.PullPatientsAsync, request);
                    }
                }
            }

            // The wtp worker for query contiuation
            // This is when the response is not complete but there are more results waiting
            this.m_waitThread.WaitOne();
            if (this.m_errorState)
            {
                throw new InvalidOperationException("Sync resulted in error state");
            }

            // Work items
            while (this.m_workerItems.Count > 0)
            {
                this.m_waitThread.QueueUserWorkItem(this.ProcessPIDAsync, this.m_workerItems.Pop());
            }

            this.m_waitThread.WaitOne();

            if (this.m_errorState)
            {
                throw new InvalidOperationException("Sync resulted in error state");
            }
        }
Beispiel #7
0
        /// <summary>
        /// Handle the pdq query
        /// </summary>
        private IMessage HandlePdqQuery(QBP_Q21 request, Hl7MessageReceivedEventArgs evt)
        {
            // Get config
            var config      = this.Context.GetService(typeof(ISystemConfigurationService)) as ISystemConfigurationService;
            var locale      = this.Context.GetService(typeof(ILocalizationService)) as ILocalizationService;
            var dataService = this.Context.GetService(typeof(IClientRegistryDataService)) as IClientRegistryDataService;

            // Create a details array
            List <IResultDetail> dtls = new List <IResultDetail>();

            // Validate the inbound message
            MessageUtil.Validate((IMessage)request, config, dtls, this.Context);

            IMessage response = null;

            // Control
            if (request == null)
            {
                return(null);
            }


            // Data controller
            //DataUtil dataUtil = new DataUtil() { Context = this.Context };
            AuditUtil auditUtil = new AuditUtil()
            {
                Context = this.Context
            };
            // Construct appropriate audit
            AuditData audit = null;

            try
            {
                // Create Query Data
                ComponentUtility cu = new ComponentUtility()
                {
                    Context = this.Context
                };
                DeComponentUtility dcu = new DeComponentUtility()
                {
                    Context = this.Context
                };
                var data = cu.CreateQueryComponentsPdq(request, dtls);
                if (data == null)
                {
                    Trace.TraceError("{0} problems mapping message:", dtls.Count);
                    foreach (var itm in dtls)
                    {
                        Trace.TraceError($"\t{itm.Type} : {itm.Message}");
                    }
                    throw new InvalidOperationException();// locale?.GetString("MSGE00A") ?? "Could not process components");
                }

                // Is this a continue or new query?
                RegistryQueryResult result = dataService.Query(data);

                audit = auditUtil.CreateAuditData("ITI-21", ActionType.Execute, OutcomeIndicator.Success, evt, result);

                // Now process the result
                response = dcu.CreateRSP_K21(result, data, dtls);
                //MessageUtil.CopyQPD((response as RSP_K21).QPD, request.QPD, data);
                MessageUtil.UpdateMSH(new NHapi.Base.Util.Terser(response), request, config);
                Terser ters = new Terser(response);
                ters.Set("/MSH-9-2", "K22");
            }
            catch (Exception e)
            {
                Trace.TraceError(e.ToString());


                if (!dtls.Exists(o => o.Message == e.Message || o.Exception == e))
                {
                    if (dtls.Count == 0)
                    {
                        dtls.Add(new ResultDetail(ResultDetailType.Error, e.Message, e));
                    }
                }
                // HACK: Only one error allowed in nHAPI for some reason :
                // TODO: Fix NHapi
                dtls.RemoveAll(o => o.Type != ResultDetailType.Error);
                while (dtls.Count > 1)
                {
                    dtls.RemoveAt(1);
                }
                response = MessageUtil.CreateNack(request, dtls, this.Context, typeof(RSP_K21));

                Terser errTerser = new Terser(response);
                // HACK: Fix the generic ACK with a real ACK for this message
                errTerser.Set("/MSH-9-2", "K22");
                errTerser.Set("/MSH-9-3", "RSP_K21");
                errTerser.Set("/QAK-2", "AE");
                errTerser.Set("/MSA-1", "AE");
                errTerser.Set("/QAK-1", request.QPD.QueryTag.Value);
                audit = auditUtil.CreateAuditData("ITI-21", ActionType.Execute, OutcomeIndicator.EpicFail, evt, new List <VersionedDomainIdentifier>());
            }
            finally
            {
                IAuditorService auditSvc = this.Context.GetService(typeof(IAuditorService)) as IAuditorService;
                if (auditSvc != null)
                {
                    auditSvc.SendAudit(audit);
                }
            }

            return(response);
        }