private void ProcessScreeningResponse(PmsAppointment appt, SmsMessage msg) { // the patient should respond with "yes" or "no" but they may not bother and just respond with "arrived" // of course they might respond with anything else that we can't understand, so we'll explain apologetically if they do if (MessageMatches(msg.message, "yes", "y")) { // twilio: SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_SCREENING_YES, appt, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "process screening response 'yes'", appt); if (IsDoingVideo) { // PMS: appt.IsVideoConsultation = true; if (VideoManager.AsksForVideoUrl()) { AppointmentUpdater.SaveAppointmentAsVideoMeeting(appt, null, null); } else { var details = VideoManager.getConferenceDetails(appt, false); AppointmentUpdater.SaveAppointmentAsVideoMeeting(appt, "Video URL: " + details, details); } } // local storage appt.ExternalData.ScreeningMessageResponse = true; if (IsDoingVideo) { appt.ExternalData.IsVideoConsultation = true; } else { appt.IsVideoConsultation = false; } Storage.SaveAppointmentStatus(appt); } else if (MessageMatches(msg.message, "no", "n")) { SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_SCREENING_NO, appt, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "process screening response 'no'", appt); appt.ExternalData.ScreeningMessageResponse = true; appt.IsVideoConsultation = false; Storage.SaveAppointmentStatus(appt); } else if (MessageMatches(msg.message, "arrived", "here", "a")) { ProcessArrivalMessage(appt, msg); } else { // we haven't understood it SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_DONT_UNDERSTAND_SCREENING, appt, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "fail to process screening response", appt); UnprocessableMessages.Add(msg); } }
private void ProcessArrivalMessage(PmsAppointment appt, SmsMessage msg) { if (MessageMatches(msg.message, "arrived", "here", "a")) { // twilio: SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_ARRIVED_THX, appt, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "process arrival message", appt); // PMS: appt.ArrivalStatus = AppointmentStatus.Arrived; AppointmentUpdater.SaveAppointmentStatusValue(appt); // local storage appt.ExternalData.ScreeningMessageResponse = true; appt.ExternalData.ArrivalStatus = AppointmentStatus.Arrived; Storage.SaveAppointmentStatus(appt); } else { // we haven't understood it SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_DONT_UNDERSTAND_ARRIVING, appt, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "fail to process arrival message", appt); UnprocessableMessages.Add(msg); } }
/// <summary> /// This method is called every X minutes to process any changes to the future appointments on the PMS side. /// Typically, this covers the next 2 days in the future (not including today, since we don't send the registration message if the appointment is made today) /// /// </summary> /// <param name="stored">The view of the appointments we already had (important, because it remembers what messages we already sent)</param> /// <param name="incoming">The current information from the PMS</param> public int ProcessUpcomingAppointments(List <PmsAppointment> appointments) { // pseudo code // for each incoming appointment // is it new - send the pre-registration message, and add it to stored int t = 0; foreach (var appt in appointments.Where(n => IsUseablePhoneNumber(n.PatientMobilePhone) && IsNearFuture(n.AppointmentStartTime) && isNotIgnoreDoctor(n.PractitionerFhirID))) // we only send these messages 2-3 days in the future { try { if (!appt.ExternalData.PostRegistrationMessageSent) { t++; SmsMessage msg = new SmsMessage(NormalisePhoneNumber(appt.PatientMobilePhone), TemplateProcessor.processTemplate(MessageTemplate.MSG_REGISTRATION, appt, null)); SmsSender.SendMessage(msg); LogMsg(OUT, msg, "send registration message", appt); appt.ExternalData.PostRegistrationMessageSent = true; Storage.SaveAppointmentStatus(appt); } } catch (Exception e) { Logger.Log(ERR, "Exception processing " + appt.AppointmentFhirID + ": " + e.Message); } } return(t); }
private PmsAppointment ChooseRelevantAppointment(List <PmsAppointment> candidates, SmsMessage msg) { if (candidates.Count > 2) { SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_TOO_MANY_APPOINTMENTS, candidates[0], null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "Too many candidates", candidates[0]); UnprocessableMessages.Add(msg); } else { // pseudo code: // if the two appointments are at the same time, we only care about the first one // otherwise, in principle we are interested in the first one, unless the message response belongs to an earlier cycle ("yes" / "no") and we're waiting for that var appt1 = candidates[0].AppointmentStartTime < candidates[1].AppointmentStartTime ? candidates[0] : candidates[1]; var appt2 = candidates[0].AppointmentStartTime < candidates[1].AppointmentStartTime ? candidates[1] : candidates[0]; if (MessageMatches(msg.message, "yes", "no")) { foreach (var appt in candidates) { if (appt.ExternalData.ScreeningMessageSent && !appt.ExternalData.ScreeningMessageResponse) { return(appt); } } return(null); } else if (MessageMatches(msg.message, "joined", "ready")) { foreach (var appt in candidates) { if (appt.ExternalData.VideoInviteSent) { return(appt); } } return(null); } else if (MessageMatches(msg.message, "arrived")) { foreach (var appt in candidates) { if (appt.ArrivalStatus == AppointmentStatus.Booked) { return(appt); } } return(null); } else { SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_TOO_MANY_APPOINTMENTS, candidates[0], null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "can't choose appointment", appt1); UnprocessableMessages.Add(msg); } } return(null); }
private void ProcessUnexpectedResponse(PmsAppointment appt, SmsMessage msg) { SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_UNEXPECTED, appt, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "unexpected message", appt); UnprocessableMessages.Add(msg); }
private void HandleUnknownMessage(SmsMessage msg) { // a future possible enhancement is to ask the user which patient the appointment is for; this will smooth the work flow, but the response // processing might be complicated. can it be just a Medicare number and date? SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_UNKNOWN_PH, null, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "handle unknown message", null); UnprocessableMessages.Add(msg); }
private void ProcessVideoInviteResponse(PmsAppointment appt, SmsMessage msg) { if (MessageMatches(msg.message, "joined", "ok", "j")) { // twilio: SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_VIDEO_THX, appt, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "accept video response", appt); // PMS: appt.ArrivalStatus = AppointmentStatus.Arrived; AppointmentUpdater.SaveAppointmentStatusValue(appt); // local storage: appt.ExternalData.ArrivalStatus = appt.ArrivalStatus; Storage.SaveAppointmentStatus(appt); } else { // we haven't understood it SmsMessage rmsg = new SmsMessage(msg.phone, TemplateProcessor.processTemplate(MessageTemplate.MSG_DONT_UNDERSTAND_VIDEO, appt, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "fail to process video response", appt); UnprocessableMessages.Add(msg); } }
/// <summary> /// This method is called every X seconds to process any changes to the appointments on the PMS side /// </summary> /// <param name="stored">The view of the appointments we already had (important, because it remembers what messages we already sent)</param> /// <param name="incoming">The current information from the PMS</param> public int ProcessTodaysAppointments(List <PmsAppointment> appointments) { // pseudo code // for each incoming appointment // is it new?- add it to the stored list // has the status changed from arrived to fulfilled? - send the invite message if it's not a TeleHealth consultation // if the appointment is within 3 hours, and the screening message hasn't been sent, send it // if the appointment is within 10 minutes a TeleHealth consultation, and the setup message hasn't been sent, send it int t = 0; foreach (var appt in appointments.Where(n => IsUseablePhoneNumber(n.PatientMobilePhone) && IsToday(n.AppointmentStartTime) && isNotIgnoreDoctor(n.PractitionerFhirID))) { try { if (appt.ExternalData.ArrivalStatus == AppointmentStatus.Arrived && appt.ArrivalStatus == AppointmentStatus.Fulfilled) { t++; Dictionary <string, string> vars = new Dictionary <string, string>(); vars.Add("room", FindRoomNote(appt.PractitionerFhirID)); SmsMessage msg = new SmsMessage(NormalisePhoneNumber(appt.PatientMobilePhone), TemplateProcessor.processTemplate(MessageTemplate.MSG_APPT_READY, appt, vars)); SmsSender.SendMessage(msg); LogMsg(OUT, msg, "invite patient to come in", appt); appt.ExternalData.ArrivalStatus = appt.ArrivalStatus; Storage.SaveAppointmentStatus(appt); } else if (appt.ArrivalStatus == AppointmentStatus.Booked && IsInTimeWindow(appt.AppointmentStartTime, MinutesBeforeScreening) && !appt.ExternalData.ScreeningMessageSent && !appt.IsVideoConsultation) { t++; SmsMessage msg; if (NoVideoForDoctor(appt.PractitionerFhirID)) { msg = new SmsMessage(NormalisePhoneNumber(appt.PatientMobilePhone), TemplateProcessor.processTemplate(MessageTemplate.MSG_SCREENING_NOVIDEO, appt, null)); // this one doesn't ask for a yes/no so we say that we have already received the appt response appt.ExternalData.ScreeningMessageResponse = true; } else { msg = new SmsMessage(NormalisePhoneNumber(appt.PatientMobilePhone), TemplateProcessor.processTemplate(MessageTemplate.MSG_SCREENING, appt, null)); } SmsSender.SendMessage(msg); LogMsg(OUT, msg, "send out screening message", appt); appt.ExternalData.ScreeningMessageSent = true; Storage.SaveAppointmentStatus(appt); } else if (appt.ArrivalStatus == AppointmentStatus.Booked && IsInTimeWindow(appt.AppointmentStartTime, MinutesBeforeScreening) && !appt.ExternalData.ScreeningMessageSent && appt.IsVideoConsultation) { //t++; // it was made as as telehealth consultation manually // twilio: if (IsDoingVideo) { SmsMessage rmsg = new SmsMessage(appt.PatientMobilePhone, TemplateProcessor.processTemplate(MessageTemplate.MSG_VIDEO_WELCOME, appt, null)); SmsSender.SendMessage(rmsg); LogMsg(OUT, rmsg, "start video sequence", appt); // PMS: appt.IsVideoConsultation = true; if (VideoManager.AsksForVideoUrl()) { AppointmentUpdater.SaveAppointmentAsVideoMeeting(appt, null, null); } else { var details = VideoManager.getConferenceDetails(appt, false); AppointmentUpdater.SaveAppointmentAsVideoMeeting(appt, "Video URL: " + details, details); } } // local storage appt.ExternalData.ScreeningMessageSent = true; appt.ExternalData.ScreeningMessageResponse = true; appt.ExternalData.IsVideoConsultation = true; Storage.SaveAppointmentStatus(appt); } else if (appt.ArrivalStatus == AppointmentStatus.Booked && (appt.IsVideoConsultation || appt.ExternalData.IsVideoConsultation) && IsInTimeWindow(appt.AppointmentStartTime, MinutesBeforeVideoInvite) && !appt.ExternalData.VideoInviteSent) { t++; Dictionary <string, string> vars = new Dictionary <string, string>(); var details = VideoManager.getConferenceDetails(appt, true); vars.Add("url", details); SmsMessage msg = new SmsMessage(NormalisePhoneNumber(appt.PatientMobilePhone), TemplateProcessor.processTemplate(MessageTemplate.MSG_VIDEO_INVITE, appt, vars)); SmsSender.SendMessage(msg); LogMsg(OUT, msg, "invite to video", appt); appt.ExternalData.VideoInviteSent = true; Storage.SaveAppointmentStatus(appt); } else if (appt.ArrivalStatus == AppointmentStatus.Booked && (appt.IsVideoConsultation || appt.ExternalData.IsVideoConsultation) && appt.ExternalData.VideoInviteSent && !String.IsNullOrEmpty(appt.ExternalData.VideoSessionId) && VideoManager.canKnowIfJoined()) { if (VideoManager.hasSomeoneJoined(appt.ExternalData.VideoSessionId)) { // PMS: appt.ArrivalStatus = AppointmentStatus.Arrived; AppointmentUpdater.SaveAppointmentStatusValue(appt); // Storage appt.ExternalData.ArrivalStatus = appt.ArrivalStatus; Storage.SaveAppointmentStatus(appt); } } } catch (Exception e) { Logger.Log(ERR, "Exception processing " + appt.AppointmentFhirID + ": " + e.Message); } } return(t); }
/// <summary> /// This method is called every X seconds to process any changes to the appointments on the PMS side /// </summary> /// <param name="stored">The view of the appointments we already had (important, because it remembers what messages we already sent)</param> /// <param name="incoming">The current information from the PMS</param> public int ProcessTodaysAppointments(List <PmsAppointment> appointments) { // pseudo code // for each incoming appointment // is it new?- add it to the stored list // has the status changed from arrived to fulfilled? - send the invite message if it's not a TeleHealth consultation // if the appointment is within 3 hours, and the screening message hasn't been sent, send it // if the appointment is within 10 minutes a TeleHealth consultation, and the setup message hasn't been sent, send it int t = 0; foreach (var appt in appointments.Where(n => IsUseablePhoneNumber(n.PatientMobilePhone) && IsToday(n.AppointmentStartTime))) { try { if (appt.ExternalData.ArrivalStatus == AppointmentStatus.Arrived && appt.ArrivalStatus == AppointmentStatus.Fulfilled) { t++; Dictionary <string, string> vars = new Dictionary <string, string>(); vars.Add("room", FindRoomNote(appt.PractitionerFhirID)); SmsMessage msg = new SmsMessage(NormalisePhoneNumber(appt.PatientMobilePhone), TemplateProcessor.processTemplate(MessageTemplate.MSG_APPT_READY, appt, vars)); SmsSender.SendMessage(msg); LogMsg(OUT, msg, "invite patient to come in", appt); appt.ExternalData.ArrivalStatus = appt.ArrivalStatus; Storage.SaveAppointmentStatus(appt); } else if (appt.ArrivalStatus == AppointmentStatus.Booked && IsInTimeWindow(appt.AppointmentStartTime, 180) && !appt.ExternalData.ScreeningMessageSent) { t++; SmsMessage msg = new SmsMessage(NormalisePhoneNumber(appt.PatientMobilePhone), TemplateProcessor.processTemplate(MessageTemplate.MSG_SCREENING, appt, null)); SmsSender.SendMessage(msg); LogMsg(OUT, msg, "send out screening message", appt); appt.ExternalData.ScreeningMessageSent = true; Storage.SaveAppointmentStatus(appt); } else if (appt.ArrivalStatus == AppointmentStatus.Booked && (appt.IsVideoConsultation || appt.ExternalData.IsVideoConsultation) && IsInTimeWindow(appt.AppointmentStartTime, VideoManager.getNotificationMinutes()) && !appt.ExternalData.VideoInviteSent) { t++; Dictionary <string, string> vars = new Dictionary <string, string>(); var details = VideoManager.getConferenceDetails(appt, true); vars.Add("url", details); SmsMessage msg = new SmsMessage(NormalisePhoneNumber(appt.PatientMobilePhone), TemplateProcessor.processTemplate(MessageTemplate.MSG_VIDEO_INVITE, appt, vars)); SmsSender.SendMessage(msg); LogMsg(OUT, msg, "invite to video", appt); appt.ExternalData.VideoInviteSent = true; Storage.SaveAppointmentStatus(appt); } else if (appt.ArrivalStatus == AppointmentStatus.Booked && (appt.IsVideoConsultation || appt.ExternalData.IsVideoConsultation) && appt.ExternalData.VideoInviteSent && !String.IsNullOrEmpty(appt.ExternalData.VideoSessionId) && VideoManager.canKnowIfJoined()) { if (VideoManager.hasSomeoneJoined(appt.ExternalData.VideoSessionId)) { // PMS: appt.ArrivalStatus = AppointmentStatus.Arrived; AppointmentUpdater.SaveAppointmentStatusValue(appt); // Storage appt.ExternalData.ArrivalStatus = appt.ArrivalStatus; Storage.SaveAppointmentStatus(appt); } } } catch (Exception e) { Logger.Log(ERR, "Exception processing " + appt.AppointmentFhirID + ": " + e.Message); } } return(t); }