/// <summary> /// Retrieve the list of clinical problems for a patient encounter. /// </summary> /// <param name="id">encounter id</param> /// <returns>An xml serializtion string of a list of clinical problems</returns> string IDecisionSupportService.RetrieveProblemList(int id) { var encounter = db.EncounterContextItemDefinitionFind(id); if (encounter == null) return ""; try { var list = new List<ClinicalProblemInstanceSimpleObject>(); foreach (var instance in AssociationHelper.SearchClinicalProblemInstance(encounter, db)) { var simpleObject = new ClinicalProblemInstanceSimpleObject() { Reports = new List<KeyValuePair<string, string>>(), Facts = new List<string>(), OrderSet = new List<string>(), History = new List<string>() }; simpleObject.Id = instance.Id; simpleObject.Priority = instance.Priority; simpleObject.State = instance.State; if(instance.ClinicalProblemDefinition!=null && string.IsNullOrEmpty(instance.ClinicalProblemDefinition.Name)==false) simpleObject.Name = instance.ClinicalProblemDefinition.Name; if(instance.ChangeRecord!=null && instance.ChangeRecord.Count()>0) simpleObject.TimeStamp = instance.ChangeRecord.ElementAt(0).TimeStamp.Value; if (instance.ClinicalProblemDefinition != null && instance.ClinicalProblemDefinition.TriggerRule != null) simpleObject.TriggerRule = RuleSetHelper.GetLaymanConditionString(instance.ClinicalProblemDefinition.TriggerRule.RuleSet); foreach (var fact in instance.Facts) { var factString = string.IsNullOrEmpty(fact.ContextItemDefinition.ReferenceRange) ? fact.ContextItemDefinition.Name + " = " + fact.ValueString() : fact.ContextItemDefinition.Name + " = " + fact.ValueString() + " (参考范围:" + fact.ContextItemDefinition.ReferenceRange + ")"; simpleObject.Facts.Add(factString); } foreach (var report in instance.Reports) { simpleObject.Reports.Add(new KeyValuePair<string, string>(report.TimeStamp + " " + report.ReportType, report.URL)); } // Don't need to show history for now. //foreach (var record in instance.ChangeRecord) //{ // simpleObject.History.Add(); //} int count = 0; foreach (var order in db.MedicalOrderSet) { simpleObject.OrderContextItemDefinitionAdd(order.Name); if (count++ > 10) break; } list.Add(simpleObject); } var obj = list as object; return SerializationHelper.Serialize(ref obj); } catch { return ""; } }
/// <summary> /// Innter Implemention of I/F Notify(): /// void IDecisionSupportService.Notify(int id) /// </summary> /// <param name="id">event id</param> /// <returns>log items. /// For each log keypair, DateTime is timestamp, string is log content.</returns> public IEnumerable<KeyValuePair<DateTime, string>> Notify(int id) { var log = new List<KeyValuePair<DateTime, string>>(); var sw = Stopwatch.StartNew(); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "[BEGIN]")); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "Call I/F Notify(" + id + ")")); // TODO: Search EMR db and construct event, report, encounter, etc. in CDS db var evt = db.Event.Find(id); if (evt == null) { log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "Event with specified id doesnot exist. Exit funciton now.")); return log; } var encounter = evt.Encounter; if (encounter == null) { log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "Associated encounter doesnot exist. Exit funciton now.")); return log; } // Collect abnormal facts from the event // var abnormalFacts = new List<Fact>(); var relatedProblemInstances = new List<ClinicalProblemInstance>(); var solvedSuspectProblemInstances = new List<ClinicalProblemInstance>(); foreach (var x in evt.Report) { // Get facts from report and update to profile. var facts = (new FactServiceMocker()).GetFactsFromReportMock(x.Id, db).ToList(); // TODO: var facts = factService.GetFactsFromReport(x.URL).ToList(); facts.ForEach(y => { db.UpdateFactCache(encounter.Id, y); if (y.ContextItemDefinition.Type == EnumContextItemType.ClinicalProblem.ToString() && y.IsAbnormal == false) { var instance = db.ClinicalProblemInstance.FirstOrDefault(z => z.State == EnumProblemState.New.ToString() && z.ClinicalProblemDefinition.Name == y.ContextItemDefinition.Name); if (instance != null) { solvedSuspectProblemInstances.Add(instance); } } // collect related problem instances associated with the fact. Later, use Distinct() to get full list. foreach (var z in db.ClinicalProblemInstance) { if ((z.State == EnumProblemState.New.ToString() || z.State == EnumProblemState.ResolvedSuspected.ToString()) && z.ClinicalProblemDefinition.ContextItemDefinition.Contains(y.ContextItemDefinition, new PropertyComparer<ContextItemDefinition>("Id"))) { relatedProblemInstances.Add(z); } } // Collect abnormal facts as triggers if (y.IsAbnormal == true) { abnormalFacts.Add(y); } }); }; // Update problems that are suspected no longer existing. solvedSuspectProblemInstances.Distinct(new PropertyComparer<ClinicalProblemInstance>("Id")).ToList().ForEach(x => { db.UpdateProblemState(x.Id, EnumProblemState.ResolvedSuspected.ToString(), EnumProblemStateChangeReason.CDS.ToString(), CurrentUser); }); // Update related active problems with new report, for tracking. relatedProblemInstances.Distinct(new PropertyComparer<ClinicalProblemInstance>("Id")).ToList().ForEach(x => { var result = ReEvaluate(x.Id); if (result.HasValue) { if (result.Value == false) { db.UpdateProblemState(x.Id, EnumProblemState.ResolvedSuspected.ToString(), EnumProblemStateChangeReason.CDS.ToString(),CurrentUser); } else { db.UpdateProblemState(x.Id, x.State, EnumProblemStateChangeReason.CDS.ToString(),CurrentUser); } } }); if (abnormalFacts.Count <= 0) { log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "No abnormal facts were detected. Exit funciton now.")); return log; } log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "")); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, abnormalFacts.Count + " abnormal facts were detected:")); abnormalFacts.ForEach(x => { log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, x.ContextItemDefinition.Name + " = " + x.ValueString())); }); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "")); var triggerFacts = new List<Fact>(); abnormalFacts.ForEach(x => { if (x.ContextItemDefinition.Type==EnumContextItemType.ClinicalProblem.ToString()) // The fact itself is a problem. e.g. the fact comes from a diagnosis event. { int ID = db.CreateClinicalProblemInstance(x.ContextItemDefinition.Name, encounter.Id, false); if (ID != -1) { db.UpdateProblemState(ID, EnumProblemState.New.ToString(), EnumProblemStateChangeReason.CDS.ToString(), CurrentUser, true); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "!!! New Problem[" + x.ContextItemDefinition.Name + "] is detected directly from fact[" + x.ContextItemDefinition.Name + "=" + x.ValueString() + "]. (the fact itself is a problem)")); } } else // The fact needs reasoning by rule engine to generate problem. { triggerFacts.Add(x); } }); // // Get candidate problems by abnormal facts // var candidateProblems = GetCandidateProblems(triggerFacts); if (candidateProblems.Count() <= 0) { log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "No candidate problems were detected. Exit function now.")); return log; } log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "")); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, candidateProblems.Count() + " candidate problems were detected:")); candidateProblems.ToList().ForEach(x => { log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, x.Name)); }); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "")); // // Construct reasoning context for candidate problems // IEnumerable<ClinicalProblemDefinition> filteredProblems; Context reasoningContext; ConstructReasoningContext(encounter, candidateProblems, out filteredProblems, out reasoningContext); if (filteredProblems.Count() <= 0) { log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "Lacking reasoning facts for all candidate problems. Exit function now.")); return log; } log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "")); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "After checking data availability, " + filteredProblems.Count() + " problems can go through rule engine:")); filteredProblems.ToList().ForEach(x => { log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, x.Name)); }); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "")); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "Reasoning context contains: " + reasoningContext.GetAllKeys().Count() + " items:")); foreach (var x in reasoningContext.GetAllKeys()) { log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, x + " = " + reasoningContext.GetValue(x))); } log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "")); // // Reason by Rule Engine // var triggeredProblems = Reason(filteredProblems, reasoningContext); if (triggeredProblems != null && triggeredProblems.Count() > 0) { triggeredProblems.ToList().ForEach(x => { int ID = db.CreateClinicalProblemInstance(x, encounter.Id, true); if (relatedProblemInstances.Any(y => y.Id == ID) == false // means the problem state is already updated above && ID != -1) { db.UpdateProblemState(ID, EnumProblemState.New.ToString(), EnumProblemStateChangeReason.CDS.ToString(), CurrentUser); var instance = db.ClinicalProblemInstance.Find(ID); if (instance != null && instance.TriggerRule != null && string.IsNullOrEmpty(instance.TriggerRule.RuleSet) == false) log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "!!! New Problem[" + x + "] is detected from rule[" + RuleSetHelper.GetLaymanConditionString(instance.TriggerRule.RuleSet) + "].")); } }); } sw.Stop(); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "")); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "This I/F call consumes " + sw.ElapsedMilliseconds + " ms")); log.Add(new KeyValuePair<DateTime, string>(DateTime.Now, "[END]")); return log; }