Skip to content

Commit

Permalink
Merge pull request #45 from grahamegrieve/ignore_doctor
Browse files Browse the repository at this point in the history
Allow to flag a doctor as "ignore" and not send messages (intended fo…
  • Loading branch information
grahamegrieve authored Apr 1, 2020
2 parents e6b9877 + b4f4f0e commit d130427
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 44 deletions.
72 changes: 39 additions & 33 deletions ClinicArrivals.Models/DoctorRoomLabelMapping.cs
Original file line number Diff line number Diff line change
@@ -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)
{
Expand All @@ -20,25 +22,29 @@ public DoctorRoomLabelMapping()
{
}

/// <summary>
/// FHIR ID of the Practitioner Record
/// </summary>
public string PractitionerFhirID { get; set; }

/// <summary>
/// Name of the practitioner (cached from the FHIR resource)
/// </summary>
public string PractitionerName { get; set; }

/// <summary>
/// e.g. Allocated room number
/// If we can do a mapping on this - probably going to be local file mapping? (prac to Room)
/// </summary>
public string LocationName { get; set; }

/// <summary>
/// Any additional
/// </summary>
public string LocationDescription { get; set; }
}
}
/// <summary>
/// FHIR ID of the Practitioner Record
/// </summary>
public string PractitionerFhirID { get; set; }

/// <summary>
/// Name of the practitioner (cached from the FHIR resource)
/// </summary>
public string PractitionerName { get; set; }

/// <summary>
/// e.g. Allocated room number
/// If we can do a mapping on this - probably going to be local file mapping? (prac to Room)
/// </summary>
public string LocationName { get; set; }

/// <summary>
/// Any additional
/// </summary>
public string LocationDescription { get; set; }

public bool IgnoreThisDoctor { get; set; }

public Boolean NoVideoForThisDoctor { get; set; }
}
}
2 changes: 2 additions & 0 deletions ClinicArrivals.Models/MessageTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down
40 changes: 37 additions & 3 deletions ClinicArrivals.Models/MessagingEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public int ProcessTodaysAppointments(List<PmsAppointment> 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
{
Expand All @@ -126,7 +126,17 @@ public int ProcessTodaysAppointments(List<PmsAppointment> 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;
Expand Down Expand Up @@ -193,6 +203,30 @@ public int ProcessTodaysAppointments(List<PmsAppointment> 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;
Expand All @@ -216,7 +250,7 @@ public int ProcessUpcomingAppointments(List<PmsAppointment> 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
{
Expand Down
4 changes: 3 additions & 1 deletion ClinicArrivals/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
<TabItem Header="Pms Simulator">
<local:SimulatePmsControl DataContext="{Binding Path=PmsSimulator, Mode=OneWay}"></local:SimulatePmsControl>
</TabItem>
<TabItem Header="Room Mappings">
<TabItem Header="Doctor Settings">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
Expand Down Expand Up @@ -192,6 +192,8 @@
<DataTemplate>
<StackPanel Margin="12" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Path=PractitionerName}" TextWrapping="Wrap" FontSize="16"/>
<CheckBox IsChecked="{Binding Path=IgnoreThisDoctor}">Ignore this Doctor</CheckBox>
<CheckBox IsChecked="{Binding Path=NoVideoForThisDoctor}">No Video for Doctor</CheckBox>
<TextBox Text="{Binding Path=LocationName, Mode=TwoWay}" TextWrapping="Wrap" FontSize="16" SpellCheck.IsEnabled="True"/>
<TextBox Text="{Binding Path=LocationDescription, Mode=TwoWay}" TextWrapping="Wrap" FontWeight="Normal" FontSize="16" AcceptsReturn="True" Height="60" Padding="4" SpellCheck.IsEnabled="True"/>
</StackPanel>
Expand Down
1 change: 1 addition & 0 deletions ClinicArrivals/ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
43 changes: 43 additions & 0 deletions Test.Models/MessageEngineTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PmsAppointment> appts = new List<PmsAppointment>();
// 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<PmsAppointment> appts = new List<PmsAppointment>();
// 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()
{
Expand Down Expand Up @@ -594,6 +635,8 @@ private void loadTestTemplates(TemplateProcessor tp)
tp.Templates = new System.Collections.ObjectModel.ObservableCollection<MessageTemplate>();
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\""));
Expand Down
13 changes: 12 additions & 1 deletion documentation/Templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions documentation/UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:

Expand All @@ -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.
Expand Down
Loading

0 comments on commit d130427

Please sign in to comment.