Greg Thompson commited on
Commit
8acf519
1 Parent(s): 48c823d

Update code for PEP8 formatting

Browse files
app.py CHANGED
@@ -12,7 +12,7 @@ from mathtext.text2int import text2int
12
  from pydantic import BaseModel
13
 
14
  from mathtext_fastapi.logging import prepare_message_data_for_logging
15
- from mathtext_fastapi.conversation_manager import manage_conversational_response
16
  from mathtext_fastapi.nlu import evaluate_message_with_nlu
17
 
18
  app = FastAPI()
@@ -54,10 +54,10 @@ def text2int_ep(content: Text = None):
54
  @app.post("/manager")
55
  async def programmatic_message_manager(request: Request):
56
  """
57
- Calls the conversation management function to determine what to send to the user based on the current state and user response
58
 
59
  Input
60
- request.body: dict - a json object of message data for the most recent user response
61
  {
62
  "author_id": "+47897891",
63
  "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09",
@@ -70,7 +70,7 @@ async def programmatic_message_manager(request: Request):
70
  }
71
 
72
  Output
73
- context: dict - a json object that holds the information for the current state
74
  {
75
  "user": "47897891",
76
  "state": "welcome-message-state",
@@ -86,13 +86,13 @@ async def programmatic_message_manager(request: Request):
86
 
87
  @app.post("/nlu")
88
  async def evaluate_user_message_with_nlu_api(request: Request):
89
- """ Calls the nlu evaluation function to run nlu functions and returns the nlu_response to Turn.io
90
-
91
  Input
92
- - request.body: a json object of message data for the most recent user response
93
-
94
  Output
95
- - int_data_dict or sent_data_dict: A dictionary telling the type of NLU run and the resulting data
96
  {'type':'integer', 'data': '8'}
97
  {'type':'sentiment', 'data': 'negative'}
98
  """
 
12
  from pydantic import BaseModel
13
 
14
  from mathtext_fastapi.logging import prepare_message_data_for_logging
15
+ from mathtext_fastapi.conversation_manager import manage_conversation_response
16
  from mathtext_fastapi.nlu import evaluate_message_with_nlu
17
 
18
  app = FastAPI()
 
54
  @app.post("/manager")
55
  async def programmatic_message_manager(request: Request):
56
  """
57
+ Calls conversation management function to determine the next state
58
 
59
  Input
60
+ request.body: dict - message data for the most recent user response
61
  {
62
  "author_id": "+47897891",
63
  "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09",
 
70
  }
71
 
72
  Output
73
+ context: dict - the information for the current state
74
  {
75
  "user": "47897891",
76
  "state": "welcome-message-state",
 
86
 
87
  @app.post("/nlu")
88
  async def evaluate_user_message_with_nlu_api(request: Request):
89
+ """ Calls nlu evaluation and returns the nlu_response
90
+
91
  Input
92
+ - request.body: json - message data for the most recent user response
93
+
94
  Output
95
+ - int_data_dict or sent_data_dict: dict - the type of NLU run and result
96
  {'type':'integer', 'data': '8'}
97
  {'type':'sentiment', 'data': 'negative'}
98
  """
mathtext_fastapi/conversation_manager.py CHANGED
@@ -11,14 +11,14 @@ load_dotenv()
11
 
12
 
13
  def create_text_message(message_text, whatsapp_id):
14
- """ Fills a template with the necessary information to send a text message to Whatsapp
15
 
16
  Inputs
17
  - message_text: str - the content that the message should display
18
  - whatsapp_id: str - the message recipient's phone number
19
 
20
  Outputs
21
- - message_data: dict - a preformatted template with the inputs' values included
22
  """
23
  message_data = {
24
  "preview_url": False,
@@ -38,7 +38,7 @@ def create_button_objects(button_options):
38
  - button_options: list - a list of text to be displayed in buttons
39
 
40
  Output
41
- - button_arr: list - a list of button objects that use a template filled with the input values
42
 
43
  NOTE: Not fully implemented and tested
44
  """
@@ -56,11 +56,13 @@ def create_button_objects(button_options):
56
 
57
 
58
  def create_interactive_message(message_text, button_options, whatsapp_id):
59
- """ Fills a template with the necessary information to send a message with button options to Whatsapp
60
 
61
  * NOTE: Not fully implemented and tested
62
- * NOTE/TODO: It is possible to create other kinds of messages with the 'interactive message' template
63
- * Documentation: https://whatsapp.turn.io/docs/api/messages#interactive-messages
 
 
64
 
65
  Inputs
66
  - message_text: str - the content that the message should display
@@ -88,17 +90,18 @@ def create_interactive_message(message_text, button_options, whatsapp_id):
88
 
89
 
90
  def return_next_conversational_state(context_data, user_message):
91
- """ Evaluates the current state of the conversation to determine resources for the next state of the conversation
92
 
93
  Input
94
- - context_data: dict - data about the conversation's current state received from the Turn.io stack
95
  - user_message: str - the message the user sent in response to the state
96
 
97
  Output
98
- - message_package: dict - a series of messages and user input to send the user
99
  """
100
- if context_data['user_message'] == '' and context_data['state'] == 'start-conversation':
101
- message_package = {
 
102
  'messages': [],
103
  'input_prompt': "Welcome to our math practice. What would you like to try? Type add or subtract.",
104
  'state': "welcome-sequence"
@@ -141,11 +144,11 @@ def return_next_conversational_state(context_data, user_message):
141
  return message_package
142
 
143
 
144
- def manage_conversational_response(data_json):
145
- """ Parses message data, determines how to respond to user message at a given conversational state, builds/sends messages, and updates/sends context
146
 
147
  Input
148
- - data_json: dict - the data for a message the user sent to Turn.io/Whatsapp
149
 
150
  Output
151
  - context: dict - a record of the state at a given point a conversation
@@ -166,7 +169,10 @@ def manage_conversational_response(data_json):
166
  # TODO: Need to incorporate nlu_response into wormhole by checking answers against database (spreadsheet?)
167
  nlu_response = evaluate_message_with_nlu(message_data)
168
 
169
- message_package = return_next_conversational_state(context_data, user_message)
 
 
 
170
 
171
  headers = {
172
  'Authorization': f"Bearer {os.environ.get('TURN_AUTHENTICATION_TOKEN')}",
@@ -176,7 +182,11 @@ def manage_conversational_response(data_json):
176
  # Send all messages for the current state before a user input prompt (text/button input request)
177
  for message in message_package['messages']:
178
  data = create_text_message(message, whatsapp_id)
179
- r = requests.post(f'https://whatsapp.turn.io/v1/messages', data=json.dumps(data), headers=headers)
 
 
 
 
180
 
181
  # Update the context object with the new state of the conversation
182
  context = {
@@ -191,8 +201,6 @@ def manage_conversational_response(data_json):
191
 
192
  return context
193
 
194
-
195
-
196
  # data = {
197
  # "to": whatsapp_id,
198
  # "type": "interactive",
 
11
 
12
 
13
  def create_text_message(message_text, whatsapp_id):
14
+ """ Fills a template with input values to send a text message to Whatsapp
15
 
16
  Inputs
17
  - message_text: str - the content that the message should display
18
  - whatsapp_id: str - the message recipient's phone number
19
 
20
  Outputs
21
+ - message_data: dict - a preformatted template filled with inputs
22
  """
23
  message_data = {
24
  "preview_url": False,
 
38
  - button_options: list - a list of text to be displayed in buttons
39
 
40
  Output
41
+ - button_arr: list - preformatted button objects filled with the inputs
42
 
43
  NOTE: Not fully implemented and tested
44
  """
 
56
 
57
 
58
  def create_interactive_message(message_text, button_options, whatsapp_id):
59
+ """ Fills a template to create a button message for Whatsapp
60
 
61
  * NOTE: Not fully implemented and tested
62
+ * NOTE/TODO: It is possible to create other kinds of messages
63
+ with the 'interactive message' template
64
+ * Documentation:
65
+ https://whatsapp.turn.io/docs/api/messages#interactive-messages
66
 
67
  Inputs
68
  - message_text: str - the content that the message should display
 
90
 
91
 
92
  def return_next_conversational_state(context_data, user_message):
93
+ """ Evaluates the conversation's current state to determine the next state
94
 
95
  Input
96
+ - context_data: dict - data about the conversation's current state
97
  - user_message: str - the message the user sent in response to the state
98
 
99
  Output
100
+ - message_package: dict - a series of messages and prompt to send
101
  """
102
+ if context_data['user_message'] == '' and \
103
+ context_data['state'] == 'start-conversation':
104
+ message_package = {
105
  'messages': [],
106
  'input_prompt': "Welcome to our math practice. What would you like to try? Type add or subtract.",
107
  'state': "welcome-sequence"
 
144
  return message_package
145
 
146
 
147
+ def manage_conversation_response(data_json):
148
+ """ Calls functions necessary to determine message and context data to send
149
 
150
  Input
151
+ - data_json: dict - message data from Turn.io/Whatsapp
152
 
153
  Output
154
  - context: dict - a record of the state at a given point a conversation
 
169
  # TODO: Need to incorporate nlu_response into wormhole by checking answers against database (spreadsheet?)
170
  nlu_response = evaluate_message_with_nlu(message_data)
171
 
172
+ message_package = return_next_conversational_state(
173
+ context_data,
174
+ user_message
175
+ )
176
 
177
  headers = {
178
  'Authorization': f"Bearer {os.environ.get('TURN_AUTHENTICATION_TOKEN')}",
 
182
  # Send all messages for the current state before a user input prompt (text/button input request)
183
  for message in message_package['messages']:
184
  data = create_text_message(message, whatsapp_id)
185
+ r = requests.post(
186
+ f'https://whatsapp.turn.io/v1/messages',
187
+ data=json.dumps(data),
188
+ headers=headers
189
+ )
190
 
191
  # Update the context object with the new state of the conversation
192
  context = {
 
201
 
202
  return context
203
 
 
 
204
  # data = {
205
  # "to": whatsapp_id,
206
  # "type": "interactive",
mathtext_fastapi/logging.py CHANGED
@@ -6,7 +6,10 @@ from supabase import create_client
6
 
7
  load_dotenv()
8
 
9
- SUPA = create_client(os.environ.get('SUPABASE_URL'), os.environ.get('SUPABASE_KEY'))
 
 
 
10
 
11
 
12
  def log_message_data_through_supabase_api(table_name, log_data):
@@ -18,32 +21,43 @@ def format_datetime_in_isoformat(dt):
18
 
19
 
20
  def get_or_create_supabase_entry(table_name, insert_data, check_variable=None):
21
- """ Checks whether a project or contact exists in the database and adds if one is not found
22
 
23
  Input:
24
  - table_name: str- the name of the table in Supabase that is being examined
25
  - insert_data: json - the data to insert
26
- - check_variable: str/None - the specific field to examine for existing matches
27
 
28
  Result
29
  - logged_data - an object with the Supabase data
30
 
31
  """
32
  if table_name == 'contact':
33
- resp = SUPA.table('contact').select("*").eq("original_contact_id", insert_data['original_contact_id']).eq("project", insert_data['project']).execute()
 
 
 
 
 
 
34
  else:
35
- resp = SUPA.table(table_name).select("*").eq(check_variable, insert_data[check_variable]).execute()
 
 
 
36
 
37
  if len(resp.data) == 0:
38
- logged_data = log_message_data_through_supabase_api(table_name, insert_data)
 
 
 
39
  else:
40
  logged_data = resp
41
  return logged_data
42
 
43
 
44
-
45
  def prepare_message_data_for_logging(message_data, nlu_response):
46
- """ Builds the message data for each table and ensures it's logged to the database
47
 
48
  Input:
49
  - message_data: an object with the full message data from Turn.io/Whatsapp
@@ -52,7 +66,11 @@ def prepare_message_data_for_logging(message_data, nlu_response):
52
  'name': "Rori",
53
  # Autogenerated fields: id, created_at, modified_at
54
  }
55
- project_data_log = get_or_create_supabase_entry('project', project_data, 'name')
 
 
 
 
56
 
57
  contact_data = {
58
  'project': project_data_log.data[0]['id'], # FK
@@ -64,7 +82,6 @@ def prepare_message_data_for_logging(message_data, nlu_response):
64
  }
65
  contact_data_log = get_or_create_supabase_entry('contact', contact_data)
66
 
67
-
68
  del message_data['author_id']
69
 
70
  message_data = {
@@ -81,4 +98,7 @@ def prepare_message_data_for_logging(message_data, nlu_response):
81
  'request_object': message_data
82
  # Autogenerated fields: created_at, modified_at
83
  }
84
- message_data_log = log_message_data_through_supabase_api('message', message_data)
 
 
 
 
6
 
7
  load_dotenv()
8
 
9
+ SUPA = create_client(
10
+ os.environ.get('SUPABASE_URL'),
11
+ os.environ.get('SUPABASE_KEY')
12
+ )
13
 
14
 
15
  def log_message_data_through_supabase_api(table_name, log_data):
 
21
 
22
 
23
  def get_or_create_supabase_entry(table_name, insert_data, check_variable=None):
24
+ """ Checks if project or contact exists and adds entry if not found
25
 
26
  Input:
27
  - table_name: str- the name of the table in Supabase that is being examined
28
  - insert_data: json - the data to insert
29
+ - check_variable: str/None - the specific field to check for existing match
30
 
31
  Result
32
  - logged_data - an object with the Supabase data
33
 
34
  """
35
  if table_name == 'contact':
36
+ resp = SUPA.table('contact').select("*").eq(
37
+ "original_contact_id",
38
+ insert_data['original_contact_id']
39
+ ).eq(
40
+ "project",
41
+ insert_data['project']
42
+ ).execute()
43
  else:
44
+ resp = SUPA.table(table_name).select("*").eq(
45
+ check_variable,
46
+ insert_data[check_variable]
47
+ ).execute()
48
 
49
  if len(resp.data) == 0:
50
+ logged_data = log_message_data_through_supabase_api(
51
+ table_name,
52
+ insert_data
53
+ )
54
  else:
55
  logged_data = resp
56
  return logged_data
57
 
58
 
 
59
  def prepare_message_data_for_logging(message_data, nlu_response):
60
+ """ Builds objects for each table and logs them to the database
61
 
62
  Input:
63
  - message_data: an object with the full message data from Turn.io/Whatsapp
 
66
  'name': "Rori",
67
  # Autogenerated fields: id, created_at, modified_at
68
  }
69
+ project_data_log = get_or_create_supabase_entry(
70
+ 'project',
71
+ project_data,
72
+ 'name'
73
+ )
74
 
75
  contact_data = {
76
  'project': project_data_log.data[0]['id'], # FK
 
82
  }
83
  contact_data_log = get_or_create_supabase_entry('contact', contact_data)
84
 
 
85
  del message_data['author_id']
86
 
87
  message_data = {
 
98
  'request_object': message_data
99
  # Autogenerated fields: created_at, modified_at
100
  }
101
+ message_data_log = log_message_data_through_supabase_api(
102
+ 'message',
103
+ message_data
104
+ )
mathtext_fastapi/nlu.py CHANGED
@@ -5,11 +5,11 @@ import re
5
 
6
 
7
  def build_nlu_response_object(type, data, confidence):
8
- """ Builds a json object from the result of nlu functions to send back to Turn.io
9
  Inputs
10
  - type: str - the type of nlu run (integer or sentiment-analysis)
11
  - data: str - the student message
12
- - confidence: - the nlu confidence score. Integer is ''. Sentiment analysis is a float
13
  """
14
  return {'type': type, 'data': data, 'confidence': confidence}
15
 
@@ -25,19 +25,23 @@ def test_for_float_or_int(message_data, message_text):
25
  def test_for_number_sequence(message_text_arr, message_data, message_text):
26
  nlu_response = {}
27
  if all(ele.isdigit() for ele in message_text_arr):
28
- nlu_response = build_nlu_response_object('integer', ','.join(message_text_arr), '')
 
 
 
 
29
  prepare_message_data_for_logging(message_data, nlu_response)
30
  return nlu_response
31
 
32
 
33
  def run_text2int_on_each_list_item(message_text_arr):
34
- """ Checks each item in an array to see if it can be converted to an integer
35
 
36
  Input
37
  - message_text_arr: list - a set of text extracted from the student message
38
 
39
  Output
40
- - student_response_arr: list - a set of integers derived from the nlu function
41
  """
42
  student_response_arr = []
43
  for student_response in message_text_arr:
@@ -74,12 +78,24 @@ def evaluate_message_with_nlu(message_data):
74
  student_response_arr = run_text2int_on_each_list_item(message_text_arr)
75
  if 32202 in student_response_arr:
76
  sentiment_api_resp = sentiment(message_text)
77
- nlu_response = build_nlu_response_object('sentiment', sentiment_api_resp[0]['label'], sentiment_api_resp[0]['score'])
 
 
 
 
78
  else:
79
  if len(student_response_arr) > 1:
80
- nlu_response = build_nlu_response_object('integer', ','.join(str(num) for num in student_response_arr), '' )
 
 
 
 
81
  else:
82
- nlu_response = build_nlu_response_object('integer', student_response_arr[0], '')
 
 
 
 
83
 
84
  prepare_message_data_for_logging(message_data, nlu_response)
85
  return nlu_response
 
5
 
6
 
7
  def build_nlu_response_object(type, data, confidence):
8
+ """ Turns nlu results into an object to send back to Turn.io
9
  Inputs
10
  - type: str - the type of nlu run (integer or sentiment-analysis)
11
  - data: str - the student message
12
+ - confidence: - the nlu confidence score (sentiment) or '' (integer)
13
  """
14
  return {'type': type, 'data': data, 'confidence': confidence}
15
 
 
25
  def test_for_number_sequence(message_text_arr, message_data, message_text):
26
  nlu_response = {}
27
  if all(ele.isdigit() for ele in message_text_arr):
28
+ nlu_response = build_nlu_response_object(
29
+ 'integer',
30
+ ','.join(message_text_arr),
31
+ ''
32
+ )
33
  prepare_message_data_for_logging(message_data, nlu_response)
34
  return nlu_response
35
 
36
 
37
  def run_text2int_on_each_list_item(message_text_arr):
38
+ """ Attempts to convert each list item to an integer
39
 
40
  Input
41
  - message_text_arr: list - a set of text extracted from the student message
42
 
43
  Output
44
+ - student_response_arr: list - a set of integers (32202 for error code)
45
  """
46
  student_response_arr = []
47
  for student_response in message_text_arr:
 
78
  student_response_arr = run_text2int_on_each_list_item(message_text_arr)
79
  if 32202 in student_response_arr:
80
  sentiment_api_resp = sentiment(message_text)
81
+ nlu_response = build_nlu_response_object(
82
+ 'sentiment',
83
+ sentiment_api_resp[0]['label'],
84
+ sentiment_api_resp[0]['score']
85
+ )
86
  else:
87
  if len(student_response_arr) > 1:
88
+ nlu_response = build_nlu_response_object(
89
+ 'integer',
90
+ ','.join(str(num) for num in student_response_arr),
91
+ ''
92
+ )
93
  else:
94
+ nlu_response = build_nlu_response_object(
95
+ 'integer',
96
+ student_response_arr[0],
97
+ ''
98
+ )
99
 
100
  prepare_message_data_for_logging(message_data, nlu_response)
101
  return nlu_response