Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 79 additions & 46 deletions packages/zambdas/src/patient/ai-interview/start/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Oystehr from '@oystehr/sdk';
import { APIGatewayProxyResult } from 'aws-lambda';
import { Appointment, Encounter, QuestionnaireResponse } from 'fhir/r4b';
import { Appointment, Encounter, Patient, QuestionnaireResponse } from 'fhir/r4b';
import { DateTime } from 'luxon';
import {
AI_QUESTIONNAIRE_ID,
createOystehrClient,
Expand All @@ -22,48 +23,56 @@ import { invokeChatbot } from '../../../shared/ai';

export const INTERVIEW_COMPLETED = 'Interview completed.';

const INITIAL_USER_MESSAGE_URGENT_CARE = `Perform a medical history intake session in the manner of a physician preparing me or my dependent for an urgent care visit, without using a fake name:
• Use a friendly and concerned physician's tone
• Determine who the patient is
• Ask only one question at a time.
• Ask no more than 20 questions in total.
• Don't number the questions.
• Cover all the major domains efficiently: chief complaint, history of present illness, past medical history, past surgical history, medications, allergies, family history, social history, hospitalizations, and relevant review of systems.
• Phrase questions in a clear, patient-friendly way that keeps the conversation moving quickly.
• If I give vague or incomplete answers, ask a brief follow-up before moving on.
• When you have gathered all useful information, end by saying: "No further questions, thanks for chatting. We've sent the information to your nurse or doctor to review. ${INTERVIEW_COMPLETED}"`;

const INITIAL_USER_MESSAGE_INJURY_JOB = `Perform a medical history intake session in the manner of a physician doing patient intake for a potential job related injury.
• Use a friendly and concerned physician's tone, but do not give yourself a name
• Determine who the patient is
• Ask only one question at a time.
• Ask no more than 15 questions in total.
• Don't number the questions.

Be sure to cover the following:
When did the injury happen?
How did the injury happen?
What body part(s) got injured?
Was this related to work or an auto accident?
Date and time of injury (very specific)
Location where injury occurred (workplace/job site)
Activity being performed at time of injury
Detailed description of what happened
Body part(s) affected
Symptoms noted at time of injury

If this was related to an auto-accident, cover these topics:
Patient's role (driver, passenger, pedestrian, cyclist)
Type of collision (rear-end, T-bone, head-on, rollover, etc.)
Speed estimate (if available)
Restraint use (seatbelt, airbag triggered)
Any loss of consciousness
Immediate symptoms

If time permits ask about past medical history, past surgical history, medications, allergies, family history, social history, hospitalizations, and relevant review of systems.
• Phrase questions in a clear, patient-friendly way that keeps the conversation moving quickly.
• If I give vague or incomplete answers, ask a brief follow-up before moving on.
• When you have gathered all useful information, end by saying: "No further questions, thanks for chatting. We've sent the information to your nurse or doctor to review. ${INTERVIEW_COMPLETED}"`;
function getInitialUserMessageUrgentCare(patientInfo: string): string {
return `Perform a medical history intake session in the manner of a physician preparing me or my dependent for an urgent care visit, without using a fake name:
• Use a friendly and concerned physician's tone
${patientInfo}
• Determine if you are communicating with the patient directly or a parent/guardian.
• Ask only one question at a time.
• Ask no more than 8 questions in total.
• Don't number the questions.
• Cover all the major domains efficiently: chief complaint, history of present illness, past medical history, past surgical history, medications, allergies, family history, social history, hospitalizations, and relevant review of systems.
• Phrase questions in a clear, patient-friendly way that keeps the conversation moving quickly.
• If I give vague or incomplete answers, ask a brief follow-up before moving on.
• When you have gathered all useful information, end by saying: "No further questions, thanks for chatting. We've sent the information to your nurse or doctor to review. ${INTERVIEW_COMPLETED}"
Begin taking the history by saying "I am an AI assistant who will ask you some questions to help your nurse or doctor prepare for your visit. Are you the patient or are you their parent or guardian?"`;
}

function getInitialUserMessageWorkerComp(patientInfo: string): string {
return `Perform a medical history intake session in the manner of a physician doing patient intake for a potential job related injury.
• Use a friendly and concerned physician's tone, but do not give yourself a name
${patientInfo}
• Determine if you are communicating with the patient directly or a parent/guardian.
• Ask only one question at a time.
• Ask no more than 8 questions in total.
• Don't number the questions.

Be sure to cover the following:
When did the injury happen?
How did the injury happen?
What body part(s) got injured?
Was this related to work or an auto accident?
Date and time of injury (very specific)
Location where injury occurred (workplace/job site)
Activity being performed at time of injury
Detailed description of what happened
Body part(s) affected
Symptoms noted at time of injury

If this was related to an auto-accident, cover these topics:
Patient's role (driver, passenger, pedestrian, cyclist)
Type of collision (rear-end, T-bone, head-on, rollover, etc.)
Speed estimate (if available)
Restraint use (seatbelt, airbag triggered)
Any loss of consciousness
Immediate symptoms

If time permits ask about past medical history, past surgical history, medications, allergies, family history, social history, hospitalizations, and relevant review of systems.
• Phrase questions in a clear, patient-friendly way that keeps the conversation moving quickly.
• If I give vague or incomplete answers, ask a brief follow-up before moving on.
• When you have gathered all useful information, end by saying: "No further questions, thanks for chatting. We've sent the information to your nurse or doctor to review. ${INTERVIEW_COMPLETED}"
Begin taking the history by saying "I am an AI assistant who will ask you some questions to help your nurse or doctor prepare for your visit. Are you the patient or are you their parent or guardian?"`;
}

let oystehrToken: string;

Expand All @@ -79,7 +88,7 @@ export const index = wrapHandler(ZAMBDA_NAME, async (input: ZambdaInput): Promis
const oystehr = await createOystehr(secrets);

const resources = (
await oystehr.fhir.search<Encounter | Appointment>({
await oystehr.fhir.search<Encounter | Appointment | Patient>({
resourceType: 'Encounter',
params: [
{
Expand All @@ -90,6 +99,10 @@ export const index = wrapHandler(ZAMBDA_NAME, async (input: ZambdaInput): Promis
name: '_include',
value: 'Encounter:appointment',
},
{
name: '_include:iterate',
value: 'Appointment:patient',
},
],
})
).unbundle();
Expand All @@ -103,9 +116,29 @@ export const index = wrapHandler(ZAMBDA_NAME, async (input: ZambdaInput): Promis
if (appointment == null) {
throw new Error(`Appointment for appointment ID ${appointmentId} not found`);
}

const patient = resources.find((resource) => resource.resourceType === 'Patient');
let questionnaireResponse: QuestionnaireResponse;
const existingQuestionnaireResponse = await findAIInterviewQuestionnaireResponse(encounterId, oystehr);
let prompt = INITIAL_USER_MESSAGE_URGENT_CARE;
const patientInfoDetails = [];
if (patient) {
if (patient.name && patient.name.length > 0) {
patientInfoDetails.push(`The patient name is: ${patient.name[0].given?.[0]} ${patient.name[0].family}`);
}
if (patient.birthDate) {
const birthDate = DateTime.fromISO(patient.birthDate);
const now = DateTime.now();
const patientAge = Math.floor(now.diff(birthDate, 'years').years);
patientInfoDetails.push(`Age: ${patientAge} year old`);
}
if (patient.gender) {
patientInfoDetails.push(`Sex: ${patient.gender}`);
}
if (appointment.description) {
patientInfoDetails.push(`Reason for visit: ${appointment.description}`);
}
}
let prompt = getInitialUserMessageUrgentCare(patientInfoDetails.join(', ') + '.');

if (
appointment.serviceCategory?.find(
Expand All @@ -116,7 +149,7 @@ export const index = wrapHandler(ZAMBDA_NAME, async (input: ZambdaInput): Promis
)
) {
console.log('Using workers compensation prompt');
prompt = INITIAL_USER_MESSAGE_INJURY_JOB;
prompt = getInitialUserMessageWorkerComp(patientInfoDetails.join(', ') + '.');
}

if (existingQuestionnaireResponse != null) {
Expand Down