Greg Thompson commited on
Commit
172088f
1 Parent(s): db324cb

Update support for two different versions of the quiz system

Browse files
mathtext_fastapi/conversation_manager.py CHANGED
@@ -10,9 +10,13 @@ import requests
10
  from dotenv import load_dotenv
11
  from mathtext_fastapi.nlu import evaluate_message_with_nlu
12
  from mathtext_fastapi.math_quiz_fsm import MathQuizFSM
 
13
  from supabase import create_client
14
  from transitions import Machine
15
 
 
 
 
16
  load_dotenv()
17
 
18
  SUPA = create_client(
@@ -106,24 +110,27 @@ def pickle_and_encode_state_machine(state_machine):
106
  return dump_encoded
107
 
108
 
109
- def manage_math_quiz_fsm(user_message, contact_uuid):
110
  fsm_check = SUPA.table('state_machines').select("*").eq(
111
  "contact_uuid",
112
  contact_uuid
113
  ).execute()
114
 
115
  if fsm_check.data == []:
116
- math_quiz_state_machine = MathQuizFSM()
 
 
 
117
  messages = [math_quiz_state_machine.response_text]
118
  dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)
119
 
120
  SUPA.table('state_machines').insert({
121
  'contact_uuid': contact_uuid,
122
- 'addition3': dump_encoded
123
  }).execute()
124
  else:
125
  undump_encoded = base64.b64decode(
126
- fsm_check.data[0]['addition3'].encode('utf-8')
127
  )
128
  math_quiz_state_machine = pickle.loads(undump_encoded)
129
 
@@ -132,13 +139,61 @@ def manage_math_quiz_fsm(user_message, contact_uuid):
132
  messages = math_quiz_state_machine.validate_answer()
133
  dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)
134
  SUPA.table('state_machines').update({
135
- 'addition3': dump_encoded
136
  }).eq(
137
  "contact_uuid", contact_uuid
138
  ).execute()
139
  return messages
140
 
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  def return_next_conversational_state(context_data, user_message, contact_uuid):
143
  """ Evaluates the conversation's current state to determine the next state
144
 
@@ -159,12 +214,36 @@ def return_next_conversational_state(context_data, user_message, contact_uuid):
159
  elif context_data['state'] == 'addition-question-sequence' or \
160
  user_message == 'add':
161
 
162
- messages = manage_math_quiz_fsm(user_message, contact_uuid)
 
 
 
163
 
164
  if user_message == 'exit':
165
  state_label = 'exit'
166
  else:
167
  state_label = 'addition-question-sequence'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  input_prompt = messages.pop()
170
 
@@ -173,15 +252,15 @@ def return_next_conversational_state(context_data, user_message, contact_uuid):
173
  'input_prompt': input_prompt,
174
  'state': state_label
175
  }
176
- elif user_message == 'subtract':
177
- message_package = {
178
- 'messages': [
179
- "Time for some subtraction!",
180
- "Type your response as a number. For example, for '1 - 1', you'd write 0."
181
- ],
182
- 'input_prompt': "Here's the first one... What's 3-1?",
183
- 'state': "subtract-question-sequence"
184
- }
185
  elif context_data['state'] == 'exit' or user_message == 'exit':
186
  message_package = {
187
  'messages': [
@@ -198,8 +277,12 @@ def return_next_conversational_state(context_data, user_message, contact_uuid):
198
  'input_prompt': "Please type add or subtract to start a math activity.",
199
  'state': "reprompt-menu-options"
200
  }
 
201
  return message_package
202
 
 
 
 
203
 
204
  def manage_conversation_response(data_json):
205
  """ Calls functions necessary to determine message and context data to send
@@ -227,11 +310,18 @@ def manage_conversation_response(data_json):
227
  # TODO: Need to incorporate nlu_response into wormhole by checking answers against database (spreadsheet?)
228
  nlu_response = evaluate_message_with_nlu(message_data)
229
 
230
- message_package = return_next_conversational_state(
231
- context_data,
232
- user_message,
233
- contact_uuid
234
- )
 
 
 
 
 
 
 
235
 
236
  print("MESSAGE PACKAGE")
237
  print(message_package)
@@ -255,15 +345,32 @@ def manage_conversation_response(data_json):
255
  )
256
 
257
  # Update the context object with the new state of the conversation
258
- context = {
259
- "context":{
260
- "user": whatsapp_id,
261
- "state": message_package['state'],
262
- "bot_message": message_package['input_prompt'],
263
- "user_message": user_message,
264
- "type": 'ask'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  }
266
- }
267
 
268
  return context
269
 
 
10
  from dotenv import load_dotenv
11
  from mathtext_fastapi.nlu import evaluate_message_with_nlu
12
  from mathtext_fastapi.math_quiz_fsm import MathQuizFSM
13
+ from mathtext_fastapi.math_subtraction_fsm import MathSubtractionFSM
14
  from supabase import create_client
15
  from transitions import Machine
16
 
17
+ from scripts.quiz.generators import start_interactive_math
18
+ from scripts.quiz.hints import generate_hint
19
+
20
  load_dotenv()
21
 
22
  SUPA = create_client(
 
110
  return dump_encoded
111
 
112
 
113
+ def manage_math_quiz_fsm(user_message, contact_uuid, type):
114
  fsm_check = SUPA.table('state_machines').select("*").eq(
115
  "contact_uuid",
116
  contact_uuid
117
  ).execute()
118
 
119
  if fsm_check.data == []:
120
+ if type == 'addition':
121
+ math_quiz_state_machine = MathQuizFSM()
122
+ else:
123
+ math_quiz_state_machine = MathSubtractionFSM()
124
  messages = [math_quiz_state_machine.response_text]
125
  dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)
126
 
127
  SUPA.table('state_machines').insert({
128
  'contact_uuid': contact_uuid,
129
+ f'{type}': dump_encoded
130
  }).execute()
131
  else:
132
  undump_encoded = base64.b64decode(
133
+ fsm_check.data[0][type].encode('utf-8')
134
  )
135
  math_quiz_state_machine = pickle.loads(undump_encoded)
136
 
 
139
  messages = math_quiz_state_machine.validate_answer()
140
  dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)
141
  SUPA.table('state_machines').update({
142
+ f'{type}': dump_encoded
143
  }).eq(
144
  "contact_uuid", contact_uuid
145
  ).execute()
146
  return messages
147
 
148
 
149
+ def use_quiz_module_approach(user_message, context_data):
150
+ print("USER MESSAGE")
151
+ print(user_message)
152
+ print("=======================")
153
+ if user_message == 'add':
154
+ context_result = start_interactive_math()
155
+ message_package = {
156
+ 'messages': [
157
+ "Great, let's do some addition",
158
+ "First, we'll start with single digits.",
159
+ "Type your response as a number. For example, for '1 + 1', you'd write 2."
160
+ ],
161
+ 'input_prompt': context_result['text'],
162
+ 'state': "addition-question-sequence"
163
+ }
164
+
165
+ elif user_message == context_data.get('right_answer'):
166
+ context_result = start_interactive_math(
167
+ context_data['number_correct'],
168
+ context_data['number_incorrect'],
169
+ context_data['level']
170
+ )
171
+ message_package = {
172
+ 'messages': [
173
+ "That's right, great!",
174
+ ],
175
+ 'input_prompt': context_result['text'],
176
+ 'state': "addition-question-sequence"
177
+ }
178
+ else:
179
+ context_result = generate_hint(
180
+ context_data['question_numbers'],
181
+ context_data['right_answer'],
182
+ context_data['number_correct'],
183
+ context_data['number_incorrect'],
184
+ context_data['level'],
185
+ context_data['hints_used']
186
+ )
187
+ message_package = {
188
+ 'messages': [
189
+ context_result['text'],
190
+ ],
191
+ 'input_prompt': context_data['text'],
192
+ 'state': "addition-question-sequence"
193
+ }
194
+ return message_package, context_result
195
+
196
+
197
  def return_next_conversational_state(context_data, user_message, contact_uuid):
198
  """ Evaluates the conversation's current state to determine the next state
199
 
 
214
  elif context_data['state'] == 'addition-question-sequence' or \
215
  user_message == 'add':
216
 
217
+ # Used in FSM
218
+ # messages = manage_math_quiz_fsm(user_message, contact_uuid)
219
+
220
+ message_package, context_result = use_quiz_module_approach(user_message, context_data)
221
 
222
  if user_message == 'exit':
223
  state_label = 'exit'
224
  else:
225
  state_label = 'addition-question-sequence'
226
+ # Used in FSM
227
+ # input_prompt = messages.pop()
228
+ # message_package = {
229
+ # 'messages': messages,
230
+ # 'input_prompt': input_prompt,
231
+ # 'state': state_label
232
+ # }
233
+
234
+ print("MESSAGE PACKAGE")
235
+ print(message_package)
236
+ context_data = context_result
237
+ message_package['state'] = state_label
238
+
239
+ elif context_data['state'] == 'subtraction-question-sequence' or \
240
+ user_message == 'subtract':
241
+ messages = manage_math_quiz_fsm(user_message, contact_uuid, 'subtraction')
242
+
243
+ if user_message == 'exit':
244
+ state_label = 'exit'
245
+ else:
246
+ state_label = 'subtraction-question-sequence'
247
 
248
  input_prompt = messages.pop()
249
 
 
252
  'input_prompt': input_prompt,
253
  'state': state_label
254
  }
255
+
256
+ # message_package = {
257
+ # 'messages': [
258
+ # "Time for some subtraction!",
259
+ # "Type your response as a number. For example, for '1 - 1', you'd write 0."
260
+ # ],
261
+ # 'input_prompt': "Here's the first one... What's 3-1?",
262
+ # 'state': "subtract-question-sequence"
263
+ # }
264
  elif context_data['state'] == 'exit' or user_message == 'exit':
265
  message_package = {
266
  'messages': [
 
277
  'input_prompt': "Please type add or subtract to start a math activity.",
278
  'state': "reprompt-menu-options"
279
  }
280
+ # Used in FSM
281
  return message_package
282
 
283
+ # Used in quiz folder approach
284
+ # return context_result, message_package
285
+
286
 
287
  def manage_conversation_response(data_json):
288
  """ Calls functions necessary to determine message and context data to send
 
310
  # TODO: Need to incorporate nlu_response into wormhole by checking answers against database (spreadsheet?)
311
  nlu_response = evaluate_message_with_nlu(message_data)
312
 
313
+ if context_data['state'] == 'addition':
314
+ context_result, message_package = return_next_conversational_state(
315
+ context_data,
316
+ user_message,
317
+ contact_uuid
318
+ )
319
+ else:
320
+ message_package = return_next_conversational_state(
321
+ context_data,
322
+ user_message,
323
+ contact_uuid
324
+ )
325
 
326
  print("MESSAGE PACKAGE")
327
  print(message_package)
 
345
  )
346
 
347
  # Update the context object with the new state of the conversation
348
+ if context_data['state'] == 'addition':
349
+ context = {
350
+ "context": {
351
+ "user": whatsapp_id,
352
+ "state": message_package['state'],
353
+ "bot_message": message_package['input_prompt'],
354
+ "user_message": user_message,
355
+ "type": 'ask',
356
+ # Necessary for quiz folder approach
357
+ "text": context_result.get('text'),
358
+ "question_numbers": context_result.get('question_numbers'),
359
+ "right_answer": context_result.get('right_answer'),
360
+ "number_correct": context_result.get('number_correct'),
361
+ "hints_used": context_result.get('hints_used'),
362
+ }
363
+ }
364
+ else:
365
+ context = {
366
+ "context": {
367
+ "user": whatsapp_id,
368
+ "state": message_package['state'],
369
+ "bot_message": message_package['input_prompt'],
370
+ "user_message": user_message,
371
+ "type": 'ask',
372
+ }
373
  }
 
374
 
375
  return context
376
 
mathtext_fastapi/math_subtraction_fsm.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from transitions import Machine
3
+
4
+
5
+ class MathSubtractionFSM(object):
6
+ states = [
7
+ 'quiz_start',
8
+ 'quiz_question',
9
+ 'quiz_end'
10
+ ]
11
+
12
+ transitions = [
13
+ ['ask_second_question', 'quiz_start', 'quiz_question'],
14
+ ['ask_next_question', 'quiz_question', 'quiz_question'],
15
+ ['exit', 'quiz_start', 'quiz_end'],
16
+ ['exit', 'quiz_question', 'quiz_end'],
17
+ ]
18
+
19
+ def __init__(
20
+ self,
21
+ initial_state='quiz_start',
22
+ question_nums=[4, 3],
23
+ initial_student_answer=0,
24
+ ):
25
+ # Instantiate the FSM
26
+ self.machine = Machine(
27
+ model=self,
28
+ states=MathSubtractionFSM.states,
29
+ transitions=MathSubtractionFSM.transitions,
30
+ initial=initial_state
31
+ )
32
+
33
+ # Instantiate variables necessary for tracking activity
34
+ self.question_nums = question_nums
35
+ self.correct_answer = self.question_nums[0] - self.question_nums[1]
36
+ self.student_answer = initial_student_answer
37
+ self.is_correct_answer = False
38
+ self.response_text = f"What is {self.question_nums[0]} - {self.question_nums[1]}?"
39
+
40
+ # Define functions to run on transitions
41
+ self.machine.on_enter_quiz_question('generate_math_problem')
42
+ self.machine.on_exit_quiz_question('validate_answer')
43
+
44
+ def validate_answer(self):
45
+ if self.student_answer == 'exit':
46
+ self.machine.set_state('quiz_end')
47
+ return ["Come back any time!"]
48
+ elif self.correct_answer == self.student_answer:
49
+ self.machine.set_state('quiz_question')
50
+ self.generate_math_problem()
51
+ return ['Great job!', self.response_text]
52
+ else:
53
+ return ["That's not quite right. Try again.", self.response_text]
54
+
55
+ def generate_math_problem(self):
56
+ self.question_nums = random.sample(range(1, 100), 2)
57
+ self.response_text = f"What is {self.question_nums[0]} - {self.question_nums[1]}"
58
+ self.correct_answer = self.question_nums[0] - self.question_nums[1]
scripts/make_request.py CHANGED
@@ -20,13 +20,25 @@ def add_message_text_to_sample_object(message_text):
20
 
21
  """
22
  message_data = '{' + f'"author_id": "+57787919091", "author_type": "OWNER", "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09", "message_body": "{message_text}", "message_direction": "inbound", "message_id": "4kl209sd0-a7b8-2hj3-8563-3hu4a89b32", "message_inserted_at": "2023-01-10T02:37:28.477940Z", "message_updated_at": "2023-01-10T02:37:28.487319Z"' + '}'
 
 
23
  context_data = '{' + '"user":"", "state":"start-conversation", "bot_message":"", "user_message":"{message_text}"' + '}'
24
 
 
 
25
  json_string = '{' + f'"context_data": {context_data}, "message_data": {message_data}' + '}'
26
  b_string = json_string.encode("utf-8")
27
 
28
  return b_string
29
 
 
 
 
 
 
 
 
 
30
 
31
  def run_simulated_request(endpoint, sample_answer, context=None):
32
  print(f"Case: {sample_answer}")
@@ -58,7 +70,7 @@ def run_simulated_request(endpoint, sample_answer, context=None):
58
  # run_simulated_request('nlu', 'IDK 5?')
59
  # run_simulated_request('manager', '')
60
  run_simulated_request('manager', 'add')
61
- # run_simulated_request('manager', 'subtract')
62
  # run_simulated_request('manager', 'exit')
63
 
64
 
 
20
 
21
  """
22
  message_data = '{' + f'"author_id": "+57787919091", "author_type": "OWNER", "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09", "message_body": "{message_text}", "message_direction": "inbound", "message_id": "4kl209sd0-a7b8-2hj3-8563-3hu4a89b32", "message_inserted_at": "2023-01-10T02:37:28.477940Z", "message_updated_at": "2023-01-10T02:37:28.487319Z"' + '}'
23
+ # context_data = '{' + '"user":"", "state":"addition-question-sequence", "bot_message":"", "user_message":"{message_text}"' + '}'
24
+
25
  context_data = '{' + '"user":"", "state":"start-conversation", "bot_message":"", "user_message":"{message_text}"' + '}'
26
 
27
+ # context_data = '{' + '"user":"", "state":"addition-question-sequence", "bot_message":"", "user_message":"{message_text}","text": "What is 2+3?","question_numbers": [4,3],"right_answer": 7,"number_correct": 2, "number_incorrect": 0, "hints_used": 0, "level": "easy"' + '}'
28
+
29
  json_string = '{' + f'"context_data": {context_data}, "message_data": {message_data}' + '}'
30
  b_string = json_string.encode("utf-8")
31
 
32
  return b_string
33
 
34
+ # """
35
+ # "text": "What is 2+3?",
36
+ # "question_numbers": [2,3],
37
+ # "right_answer": 5,
38
+ # "number_correct": 2,
39
+ # "hints_used": 0,
40
+ # """
41
+
42
 
43
  def run_simulated_request(endpoint, sample_answer, context=None):
44
  print(f"Case: {sample_answer}")
 
70
  # run_simulated_request('nlu', 'IDK 5?')
71
  # run_simulated_request('manager', '')
72
  run_simulated_request('manager', 'add')
73
+ run_simulated_request('manager', 'subtract')
74
  # run_simulated_request('manager', 'exit')
75
 
76