import logging from data_access.user_profile import get_current_time_for_user from lex_bot_handler import LexBotHandler from common.lex_config import LOG_LEVEL, SLOT_MED_TIME, SLOT_MED_TIME_OF_DAY, INTENT_MEDICATION_TIME, \ INTENT_YES_MEDICATION, INTENT_NO_MEDICATION, BOT_MEDICATION_NAME from common import lex_helper as helper from common import msg_strings from data_access import medication_diary as med_diary from data_access.survey_completion import update_survey_completion, get_next_survey_bot from data_access.data_config import format_only_time_to_str from collections import namedtuple import pytz from datetime import datetime from data_access.send_sms import send_sms logging.basicConfig() logger = logging.getLogger("MedicationDiaryBot") logger.setLevel(LOG_LEVEL) CARETAKER_MED_MISSING_MESSAGE = "Trial Assistant: Patient {} has not taken their medication as of {} today. " PERIOD_TO_AMPM = { 'MO': 'AM', 'AF': 'PM', 'EV': 'PM', 'NI': 'PM' } TimeDetail = namedtuple('TimeDetail', ['time_str', 'time_of_day', 'finished']) def parse_time_input(time_val, time_of_day_val, time_val_alts=[]): if time_val is None: if time_val_alts: # e.g. ["05:00", "17:00" ] ambiguous AM or PM time_val_alts.sort() # pick the smaller one if time_of_day_val is None: time_val = time_val_alts[0] return TimeDetail(time_str=time_val, time_of_day=None, finished=False) elif time_of_day_val == 'AM': time_val = time_val_alts[0] return TimeDetail(time_str=time_val, time_of_day=time_of_day_val, finished=True) else: # PM time_val = time_val_alts[1] return TimeDetail(time_str=time_val, time_of_day=time_of_day_val, finished=True) else: # did not get time value return TimeDetail(time_str=None, time_of_day=time_of_day_val, finished=False) else: # case: time_val is one of "NI", "MO", "AF", "EV" if time_val in PERIOD_TO_AMPM: time_of_day_val = PERIOD_TO_AMPM[time_val] return TimeDetail(time_str=None, time_of_day=time_of_day_val, finished=False) elif time_of_day_val is not None: if time_of_day_val == 'PM': hour = int(time_val.split(':')[0]) if hour < 12: hour += 12 time_val = str(hour) + time_val[2:] return TimeDetail(time_str=time_val, time_of_day=time_of_day_val, finished=True) else: hour = int(time_val.split(':')[0]) logger.info(f'hour: {hour}') time_of_day_val = 'AM' if hour < 12 else 'PM' return TimeDetail(time_str=time_val, time_of_day=time_of_day_val, finished=True) def medication_time(intent_request): """ Handler for the medication time intent :param intent_request: lex intent request :return: """ session_attributes = helper.get_attribute(intent_request, 'sessionAttributes') current_intent = helper.get_attribute(intent_request, 'currentIntent') slots = helper.get_attribute(current_intent, 'slots') slot_details = helper.get_attribute(current_intent, 'slotDetails') intent_name = helper.get_attribute(current_intent, 'name') if helper.is_validation_request(intent_request): return validate_medication_time(intent_name, session_attributes, slot_details, slots) med_taken_time = slots[SLOT_MED_TIME] hh = int(med_taken_time.split(':')[0]) mm = int(med_taken_time.split(':')[1]) user = helper.lookup_user(session_attributes) local_time_reported = get_current_time_for_user(user) now_with_no_timezone = datetime.now() med_taken_datetime = now_with_no_timezone.replace(hour=hh, minute=mm, second=0) local_med_time = pytz.timezone(user.timezone).localize(med_taken_datetime) # TODO: for production, handle cases when user reported the same info multiple times. med_diary.log_med(user.uid, time_reported=local_time_reported, med_taken=True, time_taken=local_med_time) update_survey_completion(user.uid, local_time_reported, BOT_MEDICATION_NAME) session_attributes['NextBot'] = get_next_survey_bot(user.uid, local_time_reported) return helper.close(session_attributes, helper.FulfillmentState.FULFILLED, message_content=msg_strings.get('FINISH_MED_DIARY')) def validate_medication_time(intent_name, session_attributes, slot_details, slots): time_slot_val = slots.get(SLOT_MED_TIME, None) time_of_day_slot_val = slots.get(SLOT_MED_TIME_OF_DAY, None) time_val_resolutions = slot_details.get(SLOT_MED_TIME, {}).get('resolutions', []) if slot_details.get( SLOT_MED_TIME, {}) else [] time_detail = parse_time_input(time_slot_val, time_of_day_slot_val, time_val_alts=[res['value'] for res in time_val_resolutions]) slots[SLOT_MED_TIME] = time_detail.time_str slots[SLOT_MED_TIME_OF_DAY] = time_detail.time_of_day if time_detail.finished: return helper.delegate(session_attributes, slots) else: if time_detail.time_str is not None: return helper.elicit_slot(session_attributes, intent_name, slots, SLOT_MED_TIME_OF_DAY, f'Do you mean ' f'{time_detail.time_str} ' f'AM, or PM?', message_type='SSML') elif time_detail.time_of_day: return helper.elicit_slot(session_attributes, intent_name, slots, SLOT_MED_TIME, f'Can you tell the exact time in the ' f'{time_detail.time_of_day}?', message_type='SSML') else: return helper.elicit_slot(session_attributes, intent_name, slots, SLOT_MED_TIME, 'What time did you take your medication?', message_type='SSML') def yes_med(intent_request): session_attributes = helper.get_attribute(intent_request, 'sessionAttributes') current_intent = helper.get_attribute(intent_request, 'currentIntent') slots = helper.get_attribute(current_intent, 'slots') return helper.elicit_slot(session_attributes, INTENT_MEDICATION_TIME, slots, SLOT_MED_TIME, 'Great. When did you take your medication today?', message_type='SSML') def no_med(intent_request): session_attributes = helper.get_attribute(intent_request, 'sessionAttributes') user = helper.lookup_user(session_attributes) current_time = get_current_time_for_user(user) if user.caretaker_num: logger.info(f'Will send notification to care taker: {user.caretaker_num}') time_str = format_only_time_to_str(current_time) msg = CARETAKER_MED_MISSING_MESSAGE.format(user.uid, time_str) send_sms(user.caretaker_num, msg) else: logger.info('No caretaker to notify.') med_diary.log_med(user.uid, time_reported=current_time, med_taken=False) update_survey_completion(user.uid, current_time, BOT_MEDICATION_NAME) session_attributes['NextBot'] = get_next_survey_bot(user.uid, current_time) return helper.close(session_attributes, helper.FulfillmentState.FULFILLED, message_content=msg_strings.get('DID_NOT_TAKE_MED')) def lambda_handler(event, context): bot_handler = LexBotHandler() bot_handler.register_intent(INTENT_MEDICATION_TIME, medication_time) bot_handler.register_intent(INTENT_YES_MEDICATION, yes_med) bot_handler.register_intent(INTENT_NO_MEDICATION, no_med) return bot_handler.handle_lambda(event, context)