TangibleAI/mathtext-fastapi
1
1import base642import copy3import dill4import os5import json6import jsonpickle7import pickle8import random9import requests10import mathtext_fastapi.global_state_manager as gsm11 12from dotenv import load_dotenv13from mathtext_fastapi.nlu import evaluate_message_with_nlu14from mathtext_fastapi.math_quiz_fsm import MathQuizFSM15from mathtext_fastapi.math_subtraction_fsm import MathSubtractionFSM16from supabase import create_client17from transitions import Machine18 19from mathactive.generators import start_interactive_math20from mathactive.hints import generate_hint21from mathactive.microlessons import num_one22 23load_dotenv()24 25SUPA = create_client(26 os.environ.get('SUPABASE_URL'),27 os.environ.get('SUPABASE_KEY')28)29 30 31def pickle_and_encode_state_machine(state_machine):32 dump = pickle.dumps(state_machine)33 dump_encoded = base64.b64encode(dump).decode('utf-8')34 return dump_encoded35 36 37def manage_math_quiz_fsm(user_message, contact_uuid, type):38 fsm_check = SUPA.table('state_machines').select("*").eq(39 "contact_uuid",40 contact_uuid41 ).execute()42 43 # This doesn't allow for when one FSM is present and the other is empty44 """45 146 data=[] count=None47 48 249 data=[{'id': 29, 'contact_uuid': 'j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09', 'addition3': None, 'subtraction': None, 'addition': 50 51 - but problem is there is no subtraction , but it's assuming there's a subtration52 53 Cases54 - make a completely new record55 - update an existing record with an existing FSM56 - update an existing record without an existing FSM57 """58 print("MATH QUIZ FSM ACTIVITY")59 print("user_message")60 print(user_message)61 # Make a completely new entry62 if fsm_check.data == []:63 if type == 'addition':64 math_quiz_state_machine = MathQuizFSM()65 else:66 math_quiz_state_machine = MathSubtractionFSM()67 messages = [math_quiz_state_machine.response_text]68 dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)69 70 SUPA.table('state_machines').insert({71 'contact_uuid': contact_uuid,72 f'{type}': dump_encoded73 }).execute()74 # Update an existing record with a new state machine75 elif not fsm_check.data[0][type]:76 if type == 'addition':77 math_quiz_state_machine = MathQuizFSM()78 else:79 math_quiz_state_machine = MathSubtractionFSM()80 messages = [math_quiz_state_machine.response_text]81 dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)82 83 SUPA.table('state_machines').update({84 f'{type}': dump_encoded85 }).eq(86 "contact_uuid", contact_uuid87 ).execute() 88 # Update an existing record with an existing state machine89 elif fsm_check.data[0][type]:90 undump_encoded = base64.b64decode(91 fsm_check.data[0][type].encode('utf-8')92 )93 math_quiz_state_machine = pickle.loads(undump_encoded)94 95 math_quiz_state_machine.student_answer = user_message96 math_quiz_state_machine.correct_answer = str(math_quiz_state_machine.correct_answer)97 messages = math_quiz_state_machine.validate_answer()98 dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine) 99 SUPA.table('state_machines').update({100 f'{type}': dump_encoded101 }).eq(102 "contact_uuid", contact_uuid103 ).execute()104 return messages105 106 107def retrieve_microlesson_content(context_data, user_message, microlesson, contact_uuid):108 # TODO: This is being filtered by both the local and global states, so not changing109 if microlesson == 'addition':110 messages = manage_math_quiz_fsm(user_message, contact_uuid, 'addition')111 112 if user_message == 'exit':113 state_label = 'exit'114 else:115 state_label = 'addition-question-sequence'116 117 input_prompt = messages.pop()118 message_package = {119 'messages': messages,120 'input_prompt': input_prompt,121 'state': state_label122 }123 elif context_data['local_state'] == 'addition2' or microlesson == 'addition2':124 if user_message == 'harder' or user_message == 'easier':125 user_message = ''126 message_package = num_one.process_user_message(contact_uuid, user_message)127 message_package['state'] = 'addition2'128 message_package['input_prompt'] = '?'129 130 elif context_data['local_state'] == 'subtraction-question-sequence' or \131 user_message == 'subtract' or \132 microlesson == 'subtraction':133 messages = manage_math_quiz_fsm(user_message, contact_uuid, 'subtraction')134 135 if user_message == 'exit':136 state_label = 'exit'137 else:138 state_label = 'subtraction-question-sequence'139 140 input_prompt = messages.pop()141 142 message_package = {143 'messages': messages,144 'input_prompt': input_prompt,145 'state': state_label146 }147 print("MICROLESSON CONTENT RESPONSE")148 print(message_package)149 return message_package150 151 152curriculum_lookup_table = {153 'N1.1.1_G1': 'addition',154 'N1.1.1_G2': 'addition2',155 'N1.1.2_G1': 'subtraction'156}157 158 159def lookup_local_state(next_state):160 microlesson = curriculum_lookup_table[next_state]161 return microlesson162 163 164def create_text_message(message_text, whatsapp_id):165 """ Fills a template with input values to send a text message to Whatsapp166 167 Inputs168 - message_text: str - the content that the message should display169 - whatsapp_id: str - the message recipient's phone number170 171 Outputs172 - message_data: dict - a preformatted template filled with inputs173 """174 message_data = {175 "preview_url": False,176 "recipient_type": "individual",177 "to": whatsapp_id,178 "type": "text",179 "text": {180 "body": message_text181 }182 }183 return message_data184 185 186def manage_conversation_response(data_json):187 """ Calls functions necessary to determine message and context data """188 print("V2 ENDPOINT")189 190 # whatsapp_id = data_json['author_id']191 message_data = data_json['message_data']192 context_data = data_json['context_data']193 whatsapp_id = message_data['author_id']194 user_message = message_data['message_body']195 print("MESSAGE DATA")196 print(message_data)197 print("CONTEXT DATA")198 print(context_data)199 print("=================")200 201 # nlu_response = evaluate_message_with_nlu(message_data)202 203 # context_data = {204 # 'contact_uuid': 'abcdefg',205 # 'current_state': 'N1.1.1_G2',206 # 'user_message': '1',207 # 'local_state': ''208 # }209 print("STEP 1")210 print(data_json)211 print(f"1: {context_data['current_state']}")212 if not context_data['current_state']:213 context_data['current_state'] = 'N1.1.1_G1'214 print(f"2: {context_data['current_state']}")215 216 curriculum_copy = copy.deepcopy(gsm.curriculum)217 curriculum_copy.state = context_data['current_state']218 print("STEP 2")219 if user_message == 'easier':220 curriculum_copy.left()221 next_state = curriculum_copy.state222 elif user_message == 'harder':223 curriculum_copy.right()224 next_state = curriculum_copy.state225 else:226 next_state = context_data['current_state']227 print("next_state")228 print(next_state)229 230 print("STEP 3")231 microlesson = lookup_local_state(next_state)232 233 print("microlesson")234 print(microlesson)235 236 microlesson_content = retrieve_microlesson_content(context_data, user_message, microlesson, context_data['contact_uuid'])237 238 headers = {239 'Authorization': f"Bearer {os.environ.get('TURN_AUTHENTICATION_TOKEN')}",240 'Content-Type': 'application/json'241 }242 243 # Send all messages for the current state before a user input prompt (text/button input request)244 for message in microlesson_content['messages']:245 data = create_text_message(message, whatsapp_id)246 247 # print("data")248 # print(data)249 250 r = requests.post(251 f'https://whatsapp.turn.io/v1/messages',252 data=json.dumps(data),253 headers=headers254 )255 256 print("STEP 4")257 # combine microlesson content and context_data object258 259 updated_context = {260 "context": {261 "contact_id": whatsapp_id,262 "contact_uuid": context_data['contact_uuid'],263 "current_state": next_state,264 "local_state": microlesson_content['state'],265 "bot_message": microlesson_content['input_prompt'],266 "user_message": user_message,267 "type": 'ask'268 }269 }270 print(updated_context)271 return updated_context272 