///<summary>This is the first step of automation, this checks to see if the new object matches one of the trigger conditions. </summary> /// <param name="triggerObject">Can be DiseaseDef, ICD9, Icd10, Snomed, Medication, RxNorm, Cvx, AllerfyDef, EHRLabResult, Patient, or VitalSign.</param> /// <param name="PatCur">Triggers and intervention are currently always dependant on current patient. </param> /// <returns>Returns a dictionary keyed on triggers and a list of all the objects that the trigger matched on. Should be used to generate CDS intervention message and later be passed to FormInfobutton for knowledge request.</returns> public static List <CDSIntervention> TriggerMatch(object triggerObject, Patient PatCur) { if (RemotingClient.RemotingRole == RemotingRole.ClientWeb) { return(Meth.GetObject <List <CDSIntervention> >(MethodBase.GetCurrentMethod(), triggerObject, PatCur)); } //Dictionary<string,List<object>> retVal=new Dictionary<string,List<object>>(); List <CDSIntervention> retVal = new List <CDSIntervention>(); //Define objects to be used in matching triggers. DiseaseDef diseaseDef; ICD9 icd9; Icd10 icd10; Snomed snomed; Medication medication; RxNorm rxNorm; Cvx cvx; AllergyDef allergyDef; EhrLabResult ehrLabResult; Patient pat; Vitalsign vitalsign; string triggerObjectMessage = ""; string command = ""; switch (triggerObject.GetType().Name) { case "DiseaseDef": diseaseDef = (DiseaseDef)triggerObject; command = "SELECT * FROM ehrtrigger" + " WHERE ProblemDefNumList LIKE '% " + POut.String(diseaseDef.DiseaseDefNum.ToString()) + " %'"; // '% <code> %' so that we can get exact matches. if (diseaseDef.ICD9Code != "") { command += " OR ProblemIcd9List LIKE '% " + POut.String(diseaseDef.ICD9Code) + " %'"; triggerObjectMessage += " -" + diseaseDef.ICD9Code + "(Icd9) " + ICD9s.GetByCode(diseaseDef.ICD9Code).Description + "\r\n"; } if (diseaseDef.Icd10Code != "") { command += " OR ProblemIcd10List LIKE '% " + POut.String(diseaseDef.Icd10Code) + " %'"; triggerObjectMessage += " -" + diseaseDef.Icd10Code + "(Icd10) " + Icd10s.GetByCode(diseaseDef.Icd10Code).Description + "\r\n"; } if (diseaseDef.SnomedCode != "") { command += " OR ProblemSnomedList LIKE '% " + POut.String(diseaseDef.SnomedCode) + " %'"; triggerObjectMessage += " -" + diseaseDef.SnomedCode + "(Snomed) " + Snomeds.GetByCode(diseaseDef.SnomedCode).Description + "\r\n"; } break; case "ICD9": icd9 = (ICD9)triggerObject; //TODO: TriggerObjectMessage command = "SELECT * FROM ehrtrigger" + " WHERE Icd9List LIKE '% " + POut.String(icd9.ICD9Code) + " %'"; // '% <code> %' so that we can get exact matches. break; case "Icd10": icd10 = (Icd10)triggerObject; //TODO: TriggerObjectMessage command = "SELECT * FROM ehrtrigger" + " WHERE Icd10List LIKE '% " + POut.String(icd10.Icd10Code) + " %'"; // '% <code> %' so that we can get exact matches. break; case "Snomed": snomed = (Snomed)triggerObject; //TODO: TriggerObjectMessage command = "SELECT * FROM ehrtrigger" + " WHERE SnomedList LIKE '% " + POut.String(snomed.SnomedCode) + " %'"; // '% <code> %' so that we can get exact matches. break; case "Medication": medication = (Medication)triggerObject; triggerObjectMessage = " - " + medication.MedName + (medication.RxCui == 0?"":" (RxCui:" + RxNorms.GetByRxCUI(medication.RxCui.ToString()).RxCui + ")") + "\r\n"; command = "SELECT * FROM ehrtrigger" + " WHERE MedicationNumList LIKE '% " + POut.String(medication.MedicationNum.ToString()) + " %'"; // '% <code> %' so that we can get exact matches. if (medication.RxCui != 0) { command += " OR RxCuiList LIKE '% " + POut.String(medication.RxCui.ToString()) + " %'"; // '% <code> %' so that we can get exact matches. } break; case "RxNorm": rxNorm = (RxNorm)triggerObject; triggerObjectMessage = " - " + rxNorm.Description + "(RxCui:" + rxNorm.RxCui + ")\r\n"; command = "SELECT * FROM ehrtrigger" + " WHERE RxCuiList LIKE '% " + POut.String(rxNorm.RxCui) + " %'"; // '% <code> %' so that we can get exact matches. break; case "Cvx": cvx = (Cvx)triggerObject; //TODO: TriggerObjectMessage command = "SELECT * FROM ehrtrigger" + " WHERE CvxList LIKE '% " + POut.String(cvx.CvxCode) + " %'"; // '% <code> %' so that we can get exact matches. break; case "AllergyDef": allergyDef = (AllergyDef)triggerObject; //TODO: TriggerObjectMessage command = "SELECT * FROM ehrtrigger" + " WHERE AllergyDefNumList LIKE '% " + POut.String(allergyDef.AllergyDefNum.ToString()) + " %'"; // '% <code> %' so that we can get exact matches. break; case "EhrLabResult": //match loinc only, no longer ehrLabResult = (EhrLabResult)triggerObject; //TODO: TriggerObjectMessage command = "SELECT * FROM ehrtrigger WHERE " + "(LabLoincList LIKE '% " + ehrLabResult.ObservationIdentifierID + " %'" //LOINC may be in one of two fields + "OR LabLoincList LIKE '% " + ehrLabResult.ObservationIdentifierIDAlt + " %')"; //LOINC may be in one of two fields break; case "Patient": pat = (Patient)triggerObject; List <string> triggerNums = new List <string>(); //TODO: TriggerObjectMessage command = "SELECT * FROM ehrtrigger WHERE DemographicsList !=''"; List <EhrTrigger> triggers = Crud.EhrTriggerCrud.SelectMany(command); for (int i = 0; i < triggers.Count; i++) { string[] arrayDemoItems = triggers[i].DemographicsList.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); for (int j = 0; j < arrayDemoItems.Length; j++) { switch (arrayDemoItems[j].Split(',')[0]) { case "age": int val = PIn.Int(Regex.Match(arrayDemoItems[j], @"\d+").Value); if (arrayDemoItems[j].Contains("=")) //=, >=, or <= { if (val == pat.Age) { triggerNums.Add(triggers[i].EhrTriggerNum.ToString()); break; } } if (arrayDemoItems[j].Contains("<")) { if (pat.Age < val) { triggerNums.Add(triggers[i].EhrTriggerNum.ToString()); break; } } if (arrayDemoItems[j].Contains(">")) { if (pat.Age > val) { triggerNums.Add(triggers[i].EhrTriggerNum.ToString()); break; } } //should never happen, age element didn't contain a comparator break; case "gender": if (arrayDemoItems[j].Split(',')[0].StartsWith(pat.Gender.ToString())) { triggerNums.Add(triggers[i].EhrTriggerNum.ToString()); } break; default: break; //should never happen } } } triggerNums.Add("-1"); //to ensure the querry is valid. command = "SELECT * FROM ehrTrigger WHERE EhrTriggerNum IN (" + String.Join(",", triggerNums) + ")"; break; case "Vitalsign": List <string> trigNums = new List <string>(); vitalsign = (Vitalsign)triggerObject; command = "SELECT * FROM ehrtrigger WHERE VitalLoincList !=''"; List <EhrTrigger> triggersVit = Crud.EhrTriggerCrud.SelectMany(command); for (int i = 0; i < triggersVit.Count; i++) { string[] arrayVitalItems = triggersVit[i].VitalLoincList.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); for (int j = 0; j < arrayVitalItems.Length; j++) { double val = PIn.Double(Regex.Match(arrayVitalItems[j], @"\d+(.(\d+))*").Value); //decimal value w or w/o decimal. switch (arrayVitalItems[j].Split(',')[0]) { case "height": if (arrayVitalItems[j].Contains("=")) //=, >=, or <= { if (vitalsign.Height == val) { trigNums.Add(triggersVit[i].EhrTriggerNum.ToString()); break; } } if (arrayVitalItems[j].Contains("<")) { if (vitalsign.Height < val) { trigNums.Add(triggersVit[i].EhrTriggerNum.ToString()); break; } } if (arrayVitalItems[j].Contains(">")) { if (vitalsign.Height > val) { trigNums.Add(triggersVit[i].EhrTriggerNum.ToString()); break; } } //should never happen, Height element didn't contain a comparator break; case "weight": if (arrayVitalItems[j].Contains("=")) //=, >=, or <= { if (vitalsign.Weight == val) { trigNums.Add(triggersVit[i].EhrTriggerNum.ToString()); break; } } if (arrayVitalItems[j].Contains("<")) { if (vitalsign.Weight < val) { trigNums.Add(triggersVit[i].EhrTriggerNum.ToString()); break; } } if (arrayVitalItems[j].Contains(">")) { if (vitalsign.Weight > val) { trigNums.Add(triggersVit[i].EhrTriggerNum.ToString()); break; } } break; case "BMI": float BMI = Vitalsigns.CalcBMI(vitalsign.Weight, vitalsign.Height); if (arrayVitalItems[j].Contains("=")) //=, >=, or <= { if (BMI == val) { trigNums.Add(triggersVit[i].EhrTriggerNum.ToString()); break; } } if (arrayVitalItems[j].Contains("<")) { if (BMI < val) { trigNums.Add(triggersVit[i].EhrTriggerNum.ToString()); break; } } if (arrayVitalItems[j].Contains(">")) { if (BMI > val) { trigNums.Add(triggersVit[i].EhrTriggerNum.ToString()); break; } } break; case "BP": //TODO break; } //end switch } } //End Triggers Vit trigNums.Add("-1"); //to ensure the querry is valid. command = "SELECT * FROM ehrTrigger WHERE EhrTriggerNum IN (" + String.Join(",", trigNums) + ")"; break; default: //command="SELECT * FROM ehrtrigger WHERE false";//should not return any results. return(null); #if DEBUG throw new Exception(triggerObject.GetType().ToString() + " object not implemented as intervention trigger yet. Add to the list above to handle."); #endif //break; } List <EhrTrigger> listEhrTriggers = Crud.EhrTriggerCrud.SelectMany(command); if (listEhrTriggers.Count == 0) { return(null); //no triggers matched. } //Check for MatchCardinality.One type triggers.---------------------------------------------------------------------------- for (int i = 0; i < listEhrTriggers.Count; i++) { if (listEhrTriggers[i].Cardinality != MatchCardinality.One) { continue; } string triggerMessage = listEhrTriggers[i].Description + ":\r\n"; //Example:"Patient over 55:\r\n" triggerMessage += triggerObjectMessage; //Example:" -Patient Age 67\r\n" List <object> ListObjectMatches = new List <object>(); ListObjectMatches.Add(triggerObject); CDSIntervention cdsi = new CDSIntervention(); cdsi.EhrTrigger = listEhrTriggers[i]; cdsi.InterventionMessage = triggerMessage; cdsi.TriggerObjects = ListObjectMatches; retVal.Add(cdsi); } //Fill object lists to be checked------------------------------------------------------------------------------------------------- List <Allergy> ListAllergy = Allergies.GetAll(PatCur.PatNum, false); List <Disease> ListDisease = Diseases.Refresh(PatCur.PatNum, true); List <DiseaseDef> ListDiseaseDef = new List <DiseaseDef>(); List <EhrLab> ListEhrLab = EhrLabs.GetAllForPat(PatCur.PatNum); //List<EhrLabResult> ListEhrLabResults=null;//Lab results are stored in a list in the EhrLab object. List <MedicationPat> ListMedicationPat = MedicationPats.Refresh(PatCur.PatNum, false); List <AllergyDef> ListAllergyDef = new List <AllergyDef>(); for (int i = 0; i < ListAllergy.Count; i++) { ListAllergyDef.Add(AllergyDefs.GetOne(ListAllergy[i].AllergyDefNum)); } for (int i = 0; i < ListDisease.Count; i++) { ListDiseaseDef.Add(DiseaseDefs.GetItem(ListDisease[i].DiseaseDefNum)); } for (int i = 0; i < listEhrTriggers.Count; i++) { if (listEhrTriggers[i].Cardinality == MatchCardinality.One) { continue; //we handled these above. } string triggerMessage = listEhrTriggers[i].Description + ":\r\n"; triggerMessage += triggerObjectMessage; List <object> ListObjectMatches = new List <object>(); //Allergy, Disease, LabPanels, MedicationPat, Patient, VaccinePat ListObjectMatches.Add(triggerObject); //Allergy----------------------------------------------------------------------------------------------------------------------- //allergy.snomedreaction //allergy.AllergyDefNum>>AllergyDef.SnomedType //allergy.AllergyDefNum>>AllergyDef.SnomedAllergyTo //allergy.AllergyDefNum>>AllergyDef.MedicationNum>>Medication.RxCui //Disease----------------------------------------------------------------------------------------------------------------------- //Disease.DiseaseDefNum>>DiseaseDef.ICD9Code //Disease.DiseaseDefNum>>DiseaseDef.SnomedCode //Disease.DiseaseDefNum>>DiseaseDef.Icd10Code //LabPanels--------------------------------------------------------------------------------------------------------------------- //LabPanel.LabPanelNum<<LabResult.TestId (Loinc) //LabPanel.LabPanelNum<<LabResult.ObsValue (Loinc) //LabPanel.LabPanelNum<<LabResult.ObsRange (Loinc) //MedicationPat----------------------------------------------------------------------------------------------------------------- //MedicationPat.RxCui //MedicationPat.MedicationNum>>Medication.RxCui //Patient>>Demographics--------------------------------------------------------------------------------------------------------- //Patient.Gender //Patient.Birthdate (Loinc age?) //Patient.SmokingSnoMed //RxPat------------------------------------------------------------------------------------------------------------------------- //Do not check RxPat. It is useless. //VaccinePat-------------------------------------------------------------------------------------------------------------------- //VaccinePat.VaccineDefNum>>VaccineDef.CVXCode //VitalSign--------------------------------------------------------------------------------------------------------------------- //VitalSign.Height (Loinc) //VitalSign.Weight (Loinc) //VitalSign.BpSystolic (Loinc) //VitalSign.BpDiastolic (Loinc) //VitalSign.WeightCode (Snomed) //VitalSign.PregDiseaseNum (Snomed) //Use object matches to check if required conditions are met------------------------------------------------------------------------------- switch (listEhrTriggers[i].Cardinality) { case MatchCardinality.One: //should never get here, handled above. continue; case MatchCardinality.OneOfEachCategory: //falls through to two or more, but then branches at the end of the case statement. case MatchCardinality.TwoOrMore: //if(ListObjectMatches.Count<2) { // continue;//Must match at least two objects for this category. //} //Medication for (int m = 0; m < ListMedicationPat.Count; m++) { if (listEhrTriggers[i].MedicationNumList.Contains(" " + ListMedicationPat[m].MedicationNum + " ")) { ListObjectMatches.Add(ListMedicationPat[m]); continue; } if (ListMedicationPat[m].RxCui != 0 && listEhrTriggers[i].RxCuiList.Contains(" " + ListMedicationPat[m].RxCui + " ")) { ListObjectMatches.Add(ListMedicationPat[m]); continue; } } //Allergy for (int a = 0; a < ListAllergy.Count; a++) { if (listEhrTriggers[i].AllergyDefNumList.Contains(" " + ListAllergy[a].AllergyDefNum + " ")) { ListObjectMatches.Add(AllergyDefs.GetOne(ListAllergy[a].AllergyDefNum)); triggerMessage += " -(Allergy) " + AllergyDefs.GetOne(ListAllergy[a].AllergyDefNum).Description + "\r\n"; continue; } } //Problem for (int d = 0; d < ListDiseaseDef.Count; d++) { if (ListDiseaseDef[d].ICD9Code != "" && listEhrTriggers[i].ProblemIcd9List.Contains(" " + ListDiseaseDef[d].ICD9Code + " ")) { ListObjectMatches.Add(ListDiseaseDef[d]); triggerMessage += " -(ICD9) " + ICD9s.GetByCode(ListDiseaseDef[d].ICD9Code).Description + "\r\n"; continue; } if (ListDiseaseDef[d].Icd10Code != "" && listEhrTriggers[i].ProblemIcd10List.Contains(" " + ListDiseaseDef[d].Icd10Code + " ")) { ListObjectMatches.Add(ListDiseaseDef[d]); triggerMessage += " -(Icd10) " + Icd10s.GetByCode(ListDiseaseDef[d].Icd10Code).Description + "\r\n"; continue; } if (ListDiseaseDef[d].SnomedCode != "" && listEhrTriggers[i].ProblemSnomedList.Contains(" " + ListDiseaseDef[d].SnomedCode + " ")) { ListObjectMatches.Add(ListDiseaseDef[d]); triggerMessage += " -(Snomed) " + Snomeds.GetByCode(ListDiseaseDef[d].SnomedCode).Description + "\r\n"; continue; } if (listEhrTriggers[i].ProblemDefNumList.Contains(" " + ListDiseaseDef[d].DiseaseDefNum + " ")) { ListObjectMatches.Add(ListDiseaseDef[d]); triggerMessage += " -(Problem Def) " + ListDiseaseDef[d].DiseaseName + "\r\n"; continue; } } //Vital //TODO //Age //TODO //Gender //TODO //Lab Result for (int l = 0; l < ListEhrLab.Count; l++) { for (int r = 0; r < ListEhrLab[l].ListEhrLabResults.Count; r++) { if (listEhrTriggers[i].LabLoincList.Contains(" " + ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierID + " ") || listEhrTriggers[i].LabLoincList.Contains(" " + ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierIDAlt + " ")) { ListObjectMatches.Add(ListEhrLab[l].ListEhrLabResults[r]); if (ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierID != "") //should almost always be the case. { triggerMessage += " -(LOINC) " + Loincs.GetByCode(ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierID).NameShort + "\r\n"; } else if (ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierID != "") { triggerMessage += " -(LOINC) " + Loincs.GetByCode(ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierIDAlt).NameShort + "\r\n"; } else if (ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierText != "") { triggerMessage += " -(LOINC) " + ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierText + "\r\n"; } else if (ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierTextAlt != "") { triggerMessage += " -(LOINC) " + ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierTextAlt + "\r\n"; } else if (ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierID != "") { triggerMessage += " -(LOINC) " + ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierID + "\r\n"; } else if (ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierIDAlt != "") { triggerMessage += " -(LOINC) " + ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierIDAlt + "\r\n"; } else if (ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierTextOriginal != "") { triggerMessage += " -(LOINC) " + ListEhrLab[l].ListEhrLabResults[r].ObservationIdentifierTextOriginal + "\r\n"; } else { triggerMessage += " -(LOINC) Unknown code.\r\n"; //should never happen. } continue; } } } ListObjectMatches = RemoveDuplicateObjectsHelper(ListObjectMatches); if (listEhrTriggers[i].Cardinality == MatchCardinality.TwoOrMore && ListObjectMatches.Count < 2) { continue; //next trigger, do not add to retVal } if (listEhrTriggers[i].Cardinality == MatchCardinality.OneOfEachCategory && !OneOfEachCategoryHelper(listEhrTriggers[i], ListObjectMatches)) { continue; } break; case MatchCardinality.All: bool allConditionsMet = true; List <string> MatchedCodes = getCodesFromListHelper(ListObjectMatches); //new List<string>(); //Match all Icd9Codes------------------------------------------------------------------------------------------------------------------------------------------------- string[] arrayIcd9Codes = listEhrTriggers[i].ProblemIcd9List.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); for (int c = 0; c < arrayIcd9Codes.Length; c++) { if (MatchedCodes.Contains(arrayIcd9Codes[i])) { continue; //found required code } //required code not found, set allConditionsMet to false and continue to next trigger allConditionsMet = false; break; } if (!allConditionsMet) { continue; //next trigger } //Match all Icd10Codes------------------------------------------------------------------------------------------------------------------------------------------------ string[] arrayIcd10Codes = listEhrTriggers[i].ProblemIcd10List.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); for (int c = 0; c < arrayIcd10Codes.Length; c++) { if (MatchedCodes.Contains(arrayIcd10Codes[i])) { continue; //found required code } //required code not found, set allConditionsMet to false and continue to next trigger allConditionsMet = false; break; } if (!allConditionsMet) { continue; //next trigger } //Match all SnomedCodes----------------------------------------------------------------------------------------------------------------------------------------------- string[] arraySnomedCodes = listEhrTriggers[i].ProblemSnomedList.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); for (int c = 0; c < arraySnomedCodes.Length; c++) { if (MatchedCodes.Contains(arraySnomedCodes[i])) { continue; //found required code } //required code not found, set allConditionsMet to false and continue to next trigger allConditionsMet = false; break; } if (!allConditionsMet) { continue; //next trigger } //Match all CvxCodes-------------------------------------------------------------------------------------------------------------------------------------------------- string[] arrayCvxCodes = listEhrTriggers[i].CvxList.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); for (int c = 0; c < arrayCvxCodes.Length; c++) { if (MatchedCodes.Contains(arrayCvxCodes[i])) { continue; //found required code } //required code not found, set allConditionsMet to false and continue to next trigger allConditionsMet = false; break; } if (!allConditionsMet) { continue; //next trigger, do not add to retval } //Match all LoincCodes------------------------------------------------------------------------------------------------------------------------------------------------ string[] arrayLoincCodes = listEhrTriggers[i].LabLoincList.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); for (int c = 0; c < arrayLoincCodes.Length; c++) { if (MatchedCodes.Contains(arrayLoincCodes[i])) { continue; //found required code } //required code not found, set allConditionsMet to false and continue to next trigger allConditionsMet = false; break; } if (!allConditionsMet) { continue; //next trigger, do not add to retval } //TODO:with values //Match all Vitals---------------------------------------------------------------------------------------------------------------------------------------------------- //TODO:with values //Match all Demographics--------------------------------------------------------------------------------------------------------------------------------------------- //if(listEhrTriggers[i].DemographicAgeGreaterThan!=0){ // if(PatCur.Birthdate.Year>1880 && PatCur.Birthdate.AddYears(listEhrTriggers[i].DemographicAgeGreaterThan)>DateTime.Now){//patient too young // continue;//next trigger // } //} //if(listEhrTriggers[i].DemographicAgeLessThan!=0){ // if(PatCur.Birthdate.Year>1880 && PatCur.Birthdate.AddYears(listEhrTriggers[i].DemographicAgeGreaterThan)<DateTime.Now){//patient too old // continue;//next trigger // } //} //if(listEhrTriggers[i].DemographicGender!=""){ // if(!listEhrTriggers[i].DemographicGender.Contains(PatCur.Gender.ToString())){//Patient Gender not in gender list of trigger // continue;//next trigger // } //} //TODO: construct trigger message using all the codes in the trigger. break; } //end switch trigger cardinality //retVal.Add(triggerMessage,ListObjectMatches); CDSIntervention cdsi = new CDSIntervention(); cdsi.EhrTrigger = listEhrTriggers[i]; cdsi.InterventionMessage = triggerMessage; cdsi.TriggerObjects = ListObjectMatches; retVal.Add(cdsi); } //end triggers return(retVal); }
///<summary>Only called from Chart for now. No validation is performed here. Validate before calling. There are many validtion checks, including the NPI must be exactly 10 digits.</summary> public static string BuildNewCropClickThroughXml(Provider prov, Employee emp, Patient pat, out NCScript ncScript) { string deaNumDefault = ProviderClinics.GetDEANum(prov.ProvNum); ncScript = new NCScript(); ncScript.Credentials = new CredentialsType(); ncScript.Credentials.partnerName = NewCrop.NewCropPartnerName; ncScript.Credentials.name = NewCrop.NewCropAccountName; ncScript.Credentials.password = NewCrop.NewCropAccountPasssword; ncScript.Credentials.productName = NewCrop.NewCropProductName; ncScript.Credentials.productVersion = NewCrop.NewCropProductVersion; ncScript.UserRole = new UserRoleType(); bool isMidlevel = false; if (emp == null) //Provider { if (prov.IsSecondary) //Mid-level provider { isMidlevel = true; //Secondary (HYG) providers go accross to NewCrop as midlevel providers for now to satisfy the Ohio prescriber requirements. //HYG providers are not normally able to click through to NewCrop because they do not have an NPI number and an NPI is required. //In the future, instead of using the IsSecondary flag as as workaround, we should instead create a new field on the provider table //or perhaps the userod table to allow the user to select the type of provider. ncScript.UserRole.user = UserType.MidlevelPrescriber; ncScript.UserRole.role = RoleType.midlevelPrescriber; } else //Fully licensed provider { ncScript.UserRole.user = UserType.LicensedPrescriber; ncScript.UserRole.role = RoleType.doctor; } } else //Employee { ncScript.UserRole.user = UserType.Staff; ncScript.UserRole.role = RoleType.nurse; } ncScript.Destination = new DestinationType(); ncScript.Destination.requestedPage = RequestedPageType.compose; //This is the tab that the user will want 90% of the time. string practiceTitle = Tidy(PrefC.GetString(PrefName.PracticeTitle), 50); //May be blank. string practicePhone = PrefC.GetString(PrefName.PracticePhone); //Validated to be 10 digits within the chart. string practiceFax = PrefC.GetString(PrefName.PracticeFax); //Validated to be 10 digits within the chart. string practiceAddress = PrefC.GetString(PrefName.PracticeAddress); //Validated to exist in chart. string practiceAddress2 = PrefC.GetString(PrefName.PracticeAddress2); //May be blank. string practiceCity = PrefC.GetString(PrefName.PracticeCity); //Validated to exist in chart. string practiceState = PrefC.GetString(PrefName.PracticeST).ToUpper(); //Validated to be a US state code in chart. string practiceZip = Regex.Replace(PrefC.GetString(PrefName.PracticeZip), "[^0-9]*", ""); //Zip with all non-numeric characters removed. Validated to be 9 digits in chart. string practiceZip4 = practiceZip.Substring(5); //Last 4 digits of zip. practiceZip = practiceZip.Substring(0, 5); //First 5 digits of zip. string country = "US"; //Always United States for now. //if(CultureInfo.CurrentCulture.Name.Length>=2) { // country=CultureInfo.CurrentCulture.Name.Substring(CultureInfo.CurrentCulture.Name.Length-2); //} ncScript.Account = new AccountTypeRx(); //Each LicensedPrescriberID must be unique within an account. Since we send ProvNum for LicensedPrescriberID, each OD database must have a unique AccountID. ncScript.Account.ID = PrefC.GetString(PrefName.NewCropAccountId); //Customer account number then a dash then a random alpha-numeric string of 3 characters, followed by 2 digits. ncScript.Account.accountName = practiceTitle; //May be blank. ncScript.Account.siteID = "1"; //Always send 1. For each AccountID/SiteID pair, a separate database will be created in NewCrop. ncScript.Account.AccountAddress = new AddressType(); ncScript.Account.AccountAddress.address1 = practiceAddress; //Validated to exist in chart. ncScript.Account.AccountAddress.address2 = practiceAddress2; //May be blank. ncScript.Account.AccountAddress.city = practiceCity; //Validated to exist in chart. ncScript.Account.AccountAddress.state = practiceState; //Validated to be a US state code in chart. ncScript.Account.AccountAddress.zip = practiceZip; //Validated to be 9 digits in chart. First 5 digits go in this field. ncScript.Account.AccountAddress.zip4 = practiceZip4; //Validated to be 9 digits in chart. Last 4 digits go in this field. ncScript.Account.AccountAddress.country = country; //Validated above. ncScript.Account.accountPrimaryPhoneNumber = practicePhone; //Validated to be 10 digits within the chart. ncScript.Account.accountPrimaryFaxNumber = practiceFax; //Validated to be 10 digits within the chart. ncScript.Location = new LocationType(); ProviderClinic provClinic = ProviderClinics.GetOne(prov.ProvNum, 0); //Default providerclinic. This is needed for offices that don't use clinics. if (!PrefC.HasClinicsEnabled || (!PrefC.GetBool(PrefName.ElectronicRxClinicUseSelected) && pat.ClinicNum == 0) || (PrefC.GetBool(PrefName.ElectronicRxClinicUseSelected) && Clinics.ClinicNum == 0 && pat.ClinicNum == 0)) { //No clinic. ncScript.Location.ID = "0"; //Always 0, since clinicnums must be >= 1, will never overlap with a clinic if the office turns clinics on after first use. ncScript.Location.locationName = practiceTitle; //May be blank. ncScript.Location.LocationAddress = new AddressType(); ncScript.Location.LocationAddress.address1 = practiceAddress; //Validated to exist in chart. ncScript.Location.LocationAddress.address2 = practiceAddress2; //May be blank. ncScript.Location.LocationAddress.city = practiceCity; //Validated to exist in chart. ncScript.Location.LocationAddress.state = practiceState; //Validated to be a US state code in chart. ncScript.Location.LocationAddress.zip = practiceZip; //Validated to be 9 digits in chart. First 5 digits go in this field. ncScript.Location.LocationAddress.zip4 = practiceZip4; //Validated to be 9 digits in chart. Last 4 digits go in this field. ncScript.Location.LocationAddress.country = country; //Validated above. ncScript.Location.primaryPhoneNumber = practicePhone; //Validated to be 10 digits within the chart. ncScript.Location.primaryFaxNumber = practiceFax; //Validated to be 10 digits within the chart. ncScript.Location.pharmacyContactNumber = practicePhone; //Validated to be 10 digits within the chart. } else //Using clinics. { Clinic clinic = null; if (PrefC.GetBool(PrefName.ElectronicRxClinicUseSelected) && Clinics.ClinicNum != 0) { clinic = Clinics.GetClinic(Clinics.ClinicNum); } else { clinic = Clinics.GetClinic(pat.ClinicNum); } provClinic = ProviderClinics.GetOneOrDefault(prov.ProvNum, clinic.ClinicNum); ncScript.Location.ID = clinic.ClinicNum.ToString(); //A positive integer. ncScript.Location.locationName = clinic.Description; //May be blank. ncScript.Location.LocationAddress = new AddressType(); ncScript.Location.LocationAddress.address1 = clinic.Address; //Validated to exist in chart. ncScript.Location.LocationAddress.address2 = clinic.Address2; //May be blank. ncScript.Location.LocationAddress.city = clinic.City; //Validated to exist in chart. ncScript.Location.LocationAddress.state = clinic.State.ToUpper(); //Validated to be a US state code in chart. string clinicZip = Regex.Replace(clinic.Zip, "[^0-9]*", ""); //Zip with all non-numeric characters removed. Validated to be 9 digits in chart. string clinicZip4 = clinicZip.Substring(5); //Last 4 digits of zip. clinicZip = clinicZip.Substring(0, 5); //First 5 digits of zip. ncScript.Location.LocationAddress.zip = clinicZip; //Validated to be 9 digits in chart. First 5 digits go in this field. ncScript.Location.LocationAddress.zip4 = clinicZip4; //Validated to be 9 digits in chart. Last 4 digits go in this field. ncScript.Location.LocationAddress.country = country; //Validated above. ncScript.Location.primaryPhoneNumber = clinic.Phone; //Validated to be 10 digits within the chart. ncScript.Location.primaryFaxNumber = clinic.Fax; //Validated to be 10 digits within the chart. ncScript.Location.pharmacyContactNumber = clinic.Phone; //Validated to be 10 digits within the chart. } //Each unique provider ID sent to NewCrop will cause a billing charge. //Some customer databases have provider duplicates, because they have one provider record per clinic with matching NPIs. //We send NPI as the ID to prevent extra NewCrop charges. //Conversation with NewCrop: //Question: If one of our customers clicks through to NewCrop with 2 different LicensedPrescriber.ID values, // but with the same provider name and NPI, will Open Dental be billed twice or just one time for the NPI used? //Answer: "They would be billed twice. The IDs you send us should always be maintained and unique. // Users are always identified by LicensedPrescriber ID, since their name or credentials could potentially change." if (isMidlevel) { ncScript.MidlevelPrescriber = new MidlevelPrescriberType(); ncScript.MidlevelPrescriber.ID = prov.NationalProvID; //UPIN is obsolete ncScript.MidlevelPrescriber.LicensedPrescriberName = NewCrop.GetPersonNameForProvider(prov); if (deaNumDefault.ToLower() == "none") { ncScript.MidlevelPrescriber.dea = "NONE"; } else { ncScript.MidlevelPrescriber.dea = deaNumDefault; } if (provClinic != null) { if (provClinic.DEANum != deaNumDefault) { //Only set the locationDea if it is different than the default. ncScript.MidlevelPrescriber.locationDea = provClinic.DEANum; } ncScript.MidlevelPrescriber.licenseState = provClinic.StateWhereLicensed.ToUpper(); //Validated to be a US state code in the chart. ncScript.MidlevelPrescriber.licenseNumber = provClinic.StateLicense; //Validated to exist in chart. } ncScript.MidlevelPrescriber.npi = prov.NationalProvID; //Validated to be 10 digits in chart. } else //Licensed presriber { ncScript.LicensedPrescriber = new LicensedPrescriberType(); ncScript.LicensedPrescriber.ID = prov.NationalProvID; //UPIN is obsolete ncScript.LicensedPrescriber.LicensedPrescriberName = NewCrop.GetPersonNameForProvider(prov); if (deaNumDefault.ToLower() == "none") { ncScript.LicensedPrescriber.dea = "NONE"; } else { ncScript.LicensedPrescriber.dea = deaNumDefault; } if (provClinic != null) { if (provClinic.DEANum != deaNumDefault) { //Only set the locationDea if it is different than the default. ncScript.LicensedPrescriber.locationDea = provClinic.DEANum; } ncScript.LicensedPrescriber.licenseState = provClinic.StateWhereLicensed.ToUpper(); //Validated to be a US state code in the chart. ncScript.LicensedPrescriber.licenseNumber = provClinic.StateLicense; //Validated to exist in chart. } ncScript.LicensedPrescriber.npi = prov.NationalProvID; //Validated to be 10 digits in chart. //ncScript.LicensedPrescriber.freeformCredentials=;//This is where DDS and DMD should go, but we don't support this yet. Probably not necessary anyway. } if (emp != null) { ncScript.Staff = new StaffType(); ncScript.Staff.ID = "emp" + emp.EmployeeNum.ToString(); //A positive integer. Returned in the ExternalUserID field when retreiving prescriptions from NewCrop. Also, provider ID is returned in the same field if a provider created the prescription, so that we can create a distintion between employee IDs and provider IDs. ncScript.Staff.StaffName = new PersonNameType(); ncScript.Staff.StaffName.first = emp.FName; //First name or last name will not be blank. Validated in Chart. ncScript.Staff.StaffName.last = emp.LName; //First name or last name will not be blank. Validated in Chart. ncScript.Staff.StaffName.middle = emp.MiddleI; //May be blank. } ncScript.Patient = new PatientType(); ncScript.Patient.ID = pat.PatNum.ToString(); //A positive integer. ncScript.Patient.PatientName = new PersonNameType(); ncScript.Patient.PatientName.last = pat.LName; //Validated to exist in Patient Edit window. ncScript.Patient.PatientName.first = pat.FName; //May be blank. ncScript.Patient.PatientName.middle = pat.MiddleI; //May be blank. ncScript.Patient.medicalRecordNumber = pat.PatNum.ToString(); //A positive integer. //NewCrop specifically requested that we do not send SSN. //ncScript.Patient.socialSecurityNumber=Regex.Replace(pat.SSN,"[^0-9]*","");//Removes all non-numerical characters. ncScript.Patient.PatientAddress = new AddressOptionalType(); ncScript.Patient.PatientAddress.address1 = pat.Address; //May be blank. ncScript.Patient.PatientAddress.address2 = pat.Address2; //May be blank. ncScript.Patient.PatientAddress.city = pat.City; //May be blank. ncScript.Patient.PatientAddress.state = pat.State.ToUpper(); //May be blank. Validated in chart to be blank or to be a valid US state code. //For some reason, NewCrop will fail to load if a 9 digit zip code is sent. //One customer had all 9 digit zip codes entered for their patients, so we added code here to only send the first 5 digits of the zip. //Patient zip is validated in Chart to be blank, or #####, or #####-####, or #########. if (pat.Zip == "") { ncScript.Patient.PatientAddress.zip = ""; //Blank is allowed. } else //5 or 9 digit zip. Formats are #####, or #####-####, or #########. { ncScript.Patient.PatientAddress.zip = pat.Zip.Substring(0, 5); //First 5 digts only. } ncScript.Patient.PatientAddress.country = country; //Validated above. ncScript.Patient.PatientContact = new ContactType(); //ncScript.Patient.PatientContact.backOfficeFax=;//We do not have a field to pull this information from. //ncScript.Patient.PatientContact.backOfficeTelephone=;//We do not have a field to pull this information from. ncScript.Patient.PatientContact.cellularTelephone = pat.WirelessPhone.TrimStart('1'); //May be blank. Required to be 10 digits per NewCrop. ncScript.Patient.PatientContact.email = pat.Email; //May be blank, or may also contain multiple email addresses separated by commas. //ncScript.Patient.PatientContact.fax=;//We do not have a field to pull this information from. ncScript.Patient.PatientContact.homeTelephone = pat.HmPhone.TrimStart('1'); //May be blank. Required to be 10 digits per NewCrop. //ncScript.Patient.PatientContact.pagerTelephone=;//We do not have a field to pull this information from. ncScript.Patient.PatientContact.workTelephone = pat.WkPhone.TrimStart('1'); //May be blank. Required to be 10 digits per NewCrop. ncScript.Patient.PatientCharacteristics = new PatientCharacteristicsType(); ncScript.Patient.PatientCharacteristics.dob = pat.Birthdate.ToString("yyyyMMdd"); //DOB must be in CCYYMMDD format. if (pat.Gender == PatientGender.Male) { ncScript.Patient.PatientCharacteristics.gender = GenderType.M; } else if (pat.Gender == PatientGender.Female) { ncScript.Patient.PatientCharacteristics.gender = GenderType.F; } else { ncScript.Patient.PatientCharacteristics.gender = GenderType.U; } ncScript.Patient.PatientCharacteristics.genderSpecified = true; ncScript.Patient.PatientDiagnosis = GetPatDiagnoses(pat.PatNum); foreach (Vitalsign vitalsign in Vitalsigns.Refresh(pat.PatNum).OrderByDescending(x => x.DateTaken)) { if (vitalsign.Height != 0 && ncScript.Patient.PatientCharacteristics.height.IsNullOrEmpty()) //Only set once { ncScript.Patient.PatientCharacteristics.height = vitalsign.Height.ToString(); ncScript.Patient.PatientCharacteristics.heightUnits = "inches"; //No HeightUnitType field like the one for weight below } if (vitalsign.Weight != 0 && ncScript.Patient.PatientCharacteristics.weight.IsNullOrEmpty()) //Only set once { ncScript.Patient.PatientCharacteristics.weight = vitalsign.Weight.ToString(); ncScript.Patient.PatientCharacteristics.weightUnits = WeightUnitType.lbs1; ncScript.Patient.PatientCharacteristics.weightUnitsSpecified = true; } } //NewCrop programmer's comments regarding other fields we are not currently using (these fields are sent back when fetching prescriptions in the Chart): //ExternalPrescriptionId = your unique identifier for the prescription, only to be used if you are generating the prescription on your own UI. // This is referenced by NewCrop, and cannot be populated with any other value. //EncounterIdentifier = unique ID for the patient visit (e.g. John Doe, 11/11/2013). // This is used by NewCrop for reporting events against a visit, but otherwise does not impact the session. //EpisodeIdentifier = unique ID for the patient’s issue (e.g. John Doe’s broken leg) which may include multiple visits. // Currently not used by NewCrop except for being echoed back; it is possible this functionality would be expanded in the future based on its intent as noted. //ExternalSource = a codified field noting the origin of the prescription. This may not be used. //Serialize MemoryStream memoryStream = new MemoryStream(); XmlSerializer xmlSerializer = new XmlSerializer(typeof(NCScript)); xmlSerializer.Serialize(memoryStream, ncScript); byte[] memoryStreamInBytes = memoryStream.ToArray(); return(Encoding.UTF8.GetString(memoryStreamInBytes, 0, memoryStreamInBytes.Length)); }