diff --git a/ClinicArrivals.Models/DoctorRoomLabelMapping.cs b/ClinicArrivals.Models/DoctorRoomLabelMapping.cs index cd47103..bec9688 100644 --- a/ClinicArrivals.Models/DoctorRoomLabelMapping.cs +++ b/ClinicArrivals.Models/DoctorRoomLabelMapping.cs @@ -1,14 +1,16 @@ -using PropertyChanged; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ClinicArrivals.Models -{ - [AddINotifyPropertyChangedInterface] - public class DoctorRoomLabelMapping +using PropertyChanged; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ClinicArrivals.Models +{ + // actually this does more than room label mappings, but the name is kept the same so as not to lose settings + + [AddINotifyPropertyChangedInterface] + public class DoctorRoomLabelMapping { public DoctorRoomLabelMapping(string id, string msg) { @@ -20,25 +22,29 @@ public DoctorRoomLabelMapping() { } - /// - /// FHIR ID of the Practitioner Record - /// - public string PractitionerFhirID { get; set; } - - /// - /// Name of the practitioner (cached from the FHIR resource) - /// - public string PractitionerName { get; set; } - - /// - /// e.g. Allocated room number - /// If we can do a mapping on this - probably going to be local file mapping? (prac to Room) - /// - public string LocationName { get; set; } - - /// - /// Any additional - /// - public string LocationDescription { get; set; } - } -} + /// + /// FHIR ID of the Practitioner Record + /// + public string PractitionerFhirID { get; set; } + + /// + /// Name of the practitioner (cached from the FHIR resource) + /// + public string PractitionerName { get; set; } + + /// + /// e.g. Allocated room number + /// If we can do a mapping on this - probably going to be local file mapping? (prac to Room) + /// + public string LocationName { get; set; } + + /// + /// Any additional + /// + public string LocationDescription { get; set; } + + public bool IgnoreThisDoctor { get; set; } + + public Boolean NoVideoForThisDoctor { get; set; } + } +} diff --git a/ClinicArrivals.Models/MessageTemplate.cs b/ClinicArrivals.Models/MessageTemplate.cs index 9fd40df..11d2f9c 100644 --- a/ClinicArrivals.Models/MessageTemplate.cs +++ b/ClinicArrivals.Models/MessageTemplate.cs @@ -14,6 +14,7 @@ public class MessageTemplate public const string MSG_TOO_MANY_APPOINTMENTS = "TooManyAppointments"; public const string MSG_UNEXPECTED = "Unexpected"; public const string MSG_SCREENING = "ConsiderTeleHealth"; + public const string MSG_SCREENING_NOVIDEO = "CantConsiderTeleHealth"; public const string MSG_SCREENING_YES = "ScreeningYesToVideo"; public const string MSG_SCREENING_NO = "ScreeningNoToVideo"; public const string MSG_DONT_UNDERSTAND_SCREENING = "ScreeningDontUnderstand"; @@ -31,6 +32,7 @@ public class MessageTemplate public const string DEF_MSG_TOO_MANY_APPOINTMENTS = "The robot processing this message couldn't figure out which appointment of multiple for this day that this message was about. Please phone [clinic number] for help"; public const string DEF_MSG_UNEXPECTED = "Patient {{Patient.name}} has an appointment with {{Practitioner.name}} at {{Appointment.start.time}} on {{Appointment.start.date}}, but this robot is not expecting a message right now"; public const string DEF_MSG_SCREENING = "Patient {{Patient.name}} does have an appointment with {{Practitioner.name}} at {{Appointment.start.time}} on {{Appointment.start.date}}. If you have symptoms of Covid-19, or exposure to a known case, you MUST choose to talk to the doctor by telephone/video, otherwise, you should choose to this unless you really need to come to the clinic. Respond to this message with YES to choose to telephone/video consultation, otherwise respond with NO"; + public const string DEF_MSG_SCREENING_NOVIDEO = "Patient {{Patient.name}} has an appointment with {{Practitioner.name}} at {{Appointment.start.time}} on {{Appointment.start.date}}. When you arrive at the clinic, stay in your car (or outside the clinic) and reply \"arrived\" to this message. If you have any potential symptoms of Covid-19, or exposure to a known case, you MUST advise the Doctor and staff by telephone in advance of your appointment."; public const string DEF_MSG_SCREENING_YES = "Thank you. Do not come to the doctor's clinic. Your doctor will call you for your appointment. When you are ready for your appointment, reply to this message with the word 'waiting'. If the doctor wants to see you by video, they will ask you to follow a link you will be sent before the appointment. You can join from any computer or smartphone. For instructions, see https://bit.ly/2vFGl2c"; public const string DEF_MSG_SCREENING_NO = "Thank you. When you arrive at the clinic, stay in your car (or outside the clinic) and reply \"arrived\" to this message"; public const string DEF_MSG_DONT_UNDERSTAND_SCREENING = "The robot processing this message didn't understand your response. Please answer yes or no, or phone [clinic number] for help"; diff --git a/ClinicArrivals.Models/MessagingEngine.cs b/ClinicArrivals.Models/MessagingEngine.cs index 35e3458..286ffc1 100644 --- a/ClinicArrivals.Models/MessagingEngine.cs +++ b/ClinicArrivals.Models/MessagingEngine.cs @@ -108,7 +108,7 @@ public int ProcessTodaysAppointments(List appointments) // 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))) + foreach (var appt in appointments.Where(n => IsUseablePhoneNumber(n.PatientMobilePhone) && IsToday(n.AppointmentStartTime) && isNotIgnoreDoctor(n.PractitionerFhirID))) { try { @@ -126,7 +126,17 @@ public int ProcessTodaysAppointments(List appointments) else if (appt.ArrivalStatus == AppointmentStatus.Booked && IsInTimeWindow(appt.AppointmentStartTime, MinutesBeforeScreening) && !appt.ExternalData.ScreeningMessageSent && !appt.IsVideoConsultation) { t++; - SmsMessage msg = new SmsMessage(NormalisePhoneNumber(appt.PatientMobilePhone), TemplateProcessor.processTemplate(MessageTemplate.MSG_SCREENING, appt, null)); + 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; @@ -193,6 +203,30 @@ public int ProcessTodaysAppointments(List appointments) return t; } + private bool isNotIgnoreDoctor(string id) + { + foreach (DoctorRoomLabelMapping dr in RoomMappings) + { + if (dr.PractitionerFhirID == id) + { + return !dr.IgnoreThisDoctor; + } + } + return false; + } + + private bool NoVideoForDoctor(string id) + { + foreach (DoctorRoomLabelMapping dr in RoomMappings) + { + if (dr.PractitionerFhirID == id) + { + return dr.NoVideoForThisDoctor; + } + } + return false; + } + private const bool IN = true; private const bool OUT = false; private const int MSG = 1; @@ -216,7 +250,7 @@ public int ProcessUpcomingAppointments(List appointments) // 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))) // we only send these messages 2-3 days in the future + 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 { diff --git a/ClinicArrivals/MainWindow.xaml b/ClinicArrivals/MainWindow.xaml index f5e8359..957e42d 100644 --- a/ClinicArrivals/MainWindow.xaml +++ b/ClinicArrivals/MainWindow.xaml @@ -164,7 +164,7 @@ - + @@ -192,6 +192,8 @@ + Ignore this Doctor + No Video for Doctor diff --git a/ClinicArrivals/ViewModel.cs b/ClinicArrivals/ViewModel.cs index 3fa5e56..d9d1c54 100644 --- a/ClinicArrivals/ViewModel.cs +++ b/ClinicArrivals/ViewModel.cs @@ -213,6 +213,7 @@ private void AddMissingTemplates() DefineDefaultTemplate(MessageTemplate.MSG_TOO_MANY_APPOINTMENTS, MessageTemplate.DEF_MSG_TOO_MANY_APPOINTMENTS); DefineDefaultTemplate(MessageTemplate.MSG_UNEXPECTED, MessageTemplate.DEF_MSG_UNEXPECTED); DefineDefaultTemplate(MessageTemplate.MSG_SCREENING, MessageTemplate.DEF_MSG_SCREENING); + DefineDefaultTemplate(MessageTemplate.MSG_SCREENING_NOVIDEO, MessageTemplate.DEF_MSG_SCREENING_NOVIDEO); DefineDefaultTemplate(MessageTemplate.MSG_SCREENING_YES, MessageTemplate.DEF_MSG_SCREENING_YES); DefineDefaultTemplate(MessageTemplate.MSG_SCREENING_NO, MessageTemplate.DEF_MSG_SCREENING_NO); DefineDefaultTemplate(MessageTemplate.MSG_DONT_UNDERSTAND_SCREENING, MessageTemplate.DEF_MSG_DONT_UNDERSTAND_SCREENING); diff --git a/Test.Models/MessageEngineTester.cs b/Test.Models/MessageEngineTester.cs index 9ea14ec..c251d77 100644 --- a/Test.Models/MessageEngineTester.cs +++ b/Test.Models/MessageEngineTester.cs @@ -111,6 +111,47 @@ public void testScreeningMsg() Assert.IsTrue(StorageOps[0].Appointment.ExternalData.ScreeningMessageSent); } + [TestMethod] + public void testScreeningMsgNoDoctor() + { + MessagingEngine engine = makeEngine(); + engine.TimeNow = new DateTime(2021, 1, 1, 10, 55, 0); + reset(); + engine.RoomMappings[0].IgnoreThisDoctor = true; + List appts = new List(); + // set it up: + appts.Add(appt10am()); + appts.Add(appt1pm()); + // run it + engine.ProcessTodaysAppointments(appts); + // inspect outputs: + Assert.AreEqual(0, OutputMsgs.Count); + Assert.AreEqual(0, StorageOps.Count); + } + + [TestMethod] + public void testScreeningMsgNoVideo() + { + MessagingEngine engine = makeEngine(); + engine.TimeNow = new DateTime(2021, 1, 1, 10, 55, 0); + reset(); + engine.RoomMappings[0].NoVideoForThisDoctor = true; + List appts = new List(); + // set it up: + appts.Add(appt10am()); + appts.Add(appt1pm()); + // run it + engine.ProcessTodaysAppointments(appts); + // inspect outputs: + Assert.AreEqual(1, OutputMsgs.Count); + Assert.AreEqual("+61411012345", OutputMsgs[0].phone); + Assert.AreEqual("Patient Test Patient #2 has an appointment with Dr Adam Ant at 01:00 PM on 1-Jan. When you arrive at the clinic, stay in your car (or outside the clinic) and reply \"arrived\" to this message. If you have any potential symptoms of Covid-19, or exposure to a known case, you MUST advise the Doctor and staff by telephone in advance of your appointment", OutputMsgs[0].message); + + Assert.AreEqual(1, StorageOps.Count); + Assert.AreEqual("1002", StorageOps[0].Appointment.AppointmentFhirID); + Assert.IsTrue(StorageOps[0].Appointment.ExternalData.ScreeningMessageSent); + } + [TestMethod] public void testScreeningMsgDone() { @@ -594,6 +635,8 @@ private void loadTestTemplates(TemplateProcessor tp) tp.Templates = new System.Collections.ObjectModel.ObservableCollection(); tp.Templates.Add(new MessageTemplate(MessageTemplate.MSG_REGISTRATION, "Patient {{Patient.name}} has an appointment with {{Practitioner.name}} at {{Appointment.start.time}} on {{Appointment.start.date}}. 3 hours prior to the appointment, you will be sent a COVID-19 screening check to decide whether you should do a video consultation rather than seeing the doctor in person")); tp.Templates.Add(new MessageTemplate(MessageTemplate.MSG_SCREENING, "Please consult the web page http://www.rcpa.org.au/xxx to determine whether you are eligible to meet with the doctor by phone/video. If you are, respond to this message with YES otherwise respond with NO")); + tp.Templates.Add(new MessageTemplate(MessageTemplate.MSG_SCREENING_NOVIDEO, "Patient {{Patient.name}} has an appointment with {{Practitioner.name}} at {{Appointment.start.time}} on {{Appointment.start.date}}. When you arrive at the clinic, stay in your car (or outside the clinic) and reply \"arrived\" to this message. If you have any potential symptoms of Covid-19, or exposure to a known case, you MUST advise the Doctor and staff by telephone in advance of your appointment")); + tp.Templates.Add(new MessageTemplate(MessageTemplate.MSG_SCREENING_YES, "Thank you. Do not come to the doctor's clinic. You will get an SMS message containing the URL for your video meeting a few minutes before your appointment. You can join from any computer or smartphone")); tp.Templates.Add(new MessageTemplate(MessageTemplate.MSG_SCREENING_NO, "Thank you. When you arrive at the clinic, stay in your car (or outside) and reply \"arrived\" to this message")); tp.Templates.Add(new MessageTemplate(MessageTemplate.MSG_VIDEO_INVITE, "Please start your video call at {{url}}. When you have started it, reply to this message with the word \"joined\"")); diff --git a/documentation/Templates.md b/documentation/Templates.md index 662869d..62f1b26 100644 --- a/documentation/Templates.md +++ b/documentation/Templates.md @@ -82,7 +82,7 @@ A typical example: ### ConsiderTeleHealth -This message is sent 2-3 hours in advance of the consultation to find out whether a video consulation is appropriate, or whether the patient should come in to the clinic. The exact wording of the message adapts to condition. Whatever the question is, the answer is "yes" for a video consultation, and "no" for a physical in person consultation. +This message is sent 2-3 hours in advance of the consultation to find out whether a video consultation is appropriate, or whether the patient should come in to the clinic. The exact wording of the message adapts to condition. Whatever the question is, the answer is "yes" for a video consultation, and "no" for a physical in person consultation. A typical example: @@ -114,6 +114,17 @@ A typical example: The robot processing this message didn't understand your response. Please answer yes or no, or phone [clinic number] for help + +### CantConsiderTeleHealth + +This message is sent 2-3 hours in advance of the consultation if a video consultation is known not be appropriate for a doctor (see [doctor settings](UserGuide.md)). There is no answer to this message (it shoud cover sending "arrive" when in the car park). + +A typical example: + + Patient {{Patient.name}} has an appointment with {{Practitioner.name}} at {{Appointment.start.time}} on {{Appointment.start.date}}. When you arrive at the clinic, stay in your car (or outside the clinic) and reply "arrived" to this message. If you have any potential symptoms of Covid-19, or exposure to a known case, you MUST advise the Doctor and staff by telephone in advance of your appointment. + +It is assumed that the appointment is not already marked as telehealth consultation in the PMS. + ### VideoInvite If the consultation has been marked as a video consultation by either the previous exchange or by the reception staff, then this work flow applies. This message is sent ten minutes or so before the appointment is due so the patient can get ready. diff --git a/documentation/UserGuide.md b/documentation/UserGuide.md index 5ea84c4..4d2fb07 100644 --- a/documentation/UserGuide.md +++ b/documentation/UserGuide.md @@ -24,7 +24,7 @@ The application has the following tabs: * **Unknown Incoming Messages**: List of SMS messages that have arrived that weren't understood (see note below) * **Sms Simulator**: Allows testing the application by simulating sending SMS messages to it (and seeing what has been sent) * **Pms Simulator**: Allows testing the application by creating appointments -* **Room Mappings**: List of instructions for finding the room for a particular doctor +* **Doctor Settings**: Settings for the Doctots in the clinic (including instructions for finding the room for a particular doctor) * **Message Templates**: Controls the actual text of the messages that get sent out to patients * **Settings**: Application configuration * **About**: Information about the program @@ -41,7 +41,7 @@ Patients are asked to respond to SMS messages that are sent to them with one wor See [Troubleshooting](Troubleshooting.md) for additional information. -## Managing the room configurations +## Doctor Settings If the patient is physically attending the practice, they wait out in the car park until they arrive, and then they are summoned into the practice by a message that the doctor is ready to see them. The message might read something like this: @@ -53,6 +53,12 @@ However the PMS systems do not track which room the doctor is in. So this inform Dr Adam Ant is ready to see you now +In addition, on a per doctor basis, you can specify that: + +* a doctor does not participate in messaging at all - this is typically used for special slots like nurse prep visits that are in person, and paired with other appointments so there's no point messaging about them + +* A doctor does not participate in video at all - all appointments are physical - this is typically used for special slots like nurse visits that must be in person. If a doctor is not using video, then a different welcoming message is sent out (CantConsiderTeleHealth) + ## Performing Initial Configuration This screen handles configuration when the program is first set up, and shouldn't require any additional management after that. diff --git a/documentation/Workflow.md b/documentation/Workflow.md index c2cbc4f..f352db7 100644 --- a/documentation/Workflow.md +++ b/documentation/Workflow.md @@ -8,17 +8,21 @@ As soon as an appointment is registered, the system sends the patient an SMS: "Thank you for making an appointment to see Dr X. [X] hours before the appointment, we will send you an SMS asking with you meet the criteria documented at http://www.rcpa.org.au/xxx, to decide whether you will talk to the doctor by telephone video, or physically come to the clinic. Please respond to this message to confirm you have seen it (or your appointment will be cancelled)" -### Error conditions +This message is only sent for appointments on the next day or the day after. It is not sent for appointments that are made on the same day. -If the patient replies with anything other than "arrived", then....? +### Error conditions -No response: cancel appointment? +If the patient replies with anything, then an error message is sent back to the patient advising them to talk to reception if anything is needed. ## Pre-appointment X hours (e.g. 2-3) before the appointment, send a message to the patient: -"Please consult the web page http://www.rcpa.org.au/xxx to determine whether you are eligable to meet with the doctor by phone/video. If you are, respond to this message with YES otherwise respond with NO" +"Please consult the web page http://www.rcpa.org.au/xxx to determine whether you are eligible to meet with the doctor by phone/video. If you are, respond to this message with YES otherwise respond with NO" + +Notes: +* the time is configurable in the application +* appointments that are made within the X hours time window - right up to now - will still cause this message to be sent. ## Teleconsulation pathway