Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions client/protocols/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class ResearchProtocol(Protocol):
CS61A_ID = '61a'
C88C_ID = '88c'
UNKNOWN_COURSE = '<unknown course>'
UNKNOWN_EMAIL = '<unknown email from CLI>'

GET_CONSENT = True
CONSENT_CACHE = '.ok_consent'
Expand Down
101 changes: 79 additions & 22 deletions client/protocols/followup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
import os
import logging
import json
import re
import requests
import hmac
import pickle

from urllib.parse import urlencode

from client.utils.printer import print_error

Expand All @@ -20,9 +26,10 @@
class FollowupProtocol(models.ResearchProtocol, UnlockProtocol):

PROTOCOL_NAME = 'followup'
FOLLOWUP_ENDPOINT = models.ResearchProtocol.SERVER + '/questions'
FOLLOWUP_ENDPOINT = models.ResearchProtocol.SERVER + '/successQuestions?'
RESPONSE_ENDPOINT = models.ResearchProtocol.SERVER + '/successQuestionsResponse'
GET_CONSENT = True
FOLLOWUPS_FILE = 'followups.json'
FOLLOWUP_CACHE = '.ok_followups'

def run(self, messages):
config = config_utils._get_config(self.args.config)
Expand All @@ -31,21 +38,20 @@ def run(self, messages):
return

check_solved = self._check_solved(messages)
failed, active_function = check_solved['failed'], check_solved['active_function']
failed, _ = check_solved['failed'], check_solved['active_function']
if failed:
return

if self.FOLLOWUPS_FILE not in os.listdir():
followup_data = []
else:
followup_data = json.loads(open(self.FOLLOWUPS_FILE).read())
email = messages.get('email') or self.UNKNOWN_EMAIL
responded_followups = self._get_followups(email)
followup_queue = []
for entry in followup_data:
if entry['name'] == active_function:
for followup in entry['followups']:
if not followup['response']:
followup_queue.append(followup)
for question_id, analytic in messages.get('grading', {}).items():
if analytic['failed'] == 0 and question_id not in responded_followups:
followup_queue.append(question_id)

consent = None
if len(followup_queue) > 0:
consent = self._get_consent(email)
format.print_line('~')
print('Follow-up questions')
print()
Expand All @@ -54,21 +60,44 @@ def run(self, messages):
self.PROMPT))
print('Type {} to quit'.format(self.EXIT_INPUTS[0]))
print()


for followup in followup_queue:
response = self._ask_followup(followup)
followup['response'] = response

with open(self.FOLLOWUPS_FILE, 'w') as f:
f.write(json.dumps(followup_data, indent=2))
filename = config['src'][0]
hw_id = str(int(re.findall(r'hw(\d+)\.(py|scm|sql)', filename)[0][0]))
for q_id in followup_queue:
params = {
'hwId': hw_id,
'questionId': q_id,
}
server_response = requests.get(self.FOLLOWUP_ENDPOINT + urlencode(params))
if server_response.status_code == 200:
followup = server_response.json()
response_data = self._ask_followup(followup)
if response_data:
payload = {
'email': email,
'consent': consent,
'hwId': hw_id,
'activeFunction': q_id,
'responseIndex': response_data.get('response_index', None),
'responseText': response_data.get('response_text', None)
}
try:
server_response = requests.post(self.RESPONSE_ENDPOINT, json=payload).json()
if server_response.get('status', '') != 'ok':
print_error("Error reaching 61a-bot server. Please inform the course staff on Ed and try again later.")
else:
self._append_followups(email, q_id)
except Exception as e:
print_error("Error reaching 61a-bot server. Please inform the course staff on Ed and try again later.")
else:
break

def _ask_followup(self, followup):
question, choices = followup['question'], followup['choices']
print(question)
print()
for c in choices:
print(c)
for i, c in enumerate(choices):
print(f"{chr(ord('A') + i)}. {c}")
print()
valid_responses = [chr(ord('A') + i) for i in range(len(choices))] + [chr(ord('a') + i) for i in range(len(choices))] + list(self.EXIT_INPUTS)
response = None
Expand All @@ -79,6 +108,34 @@ def _ask_followup(self, followup):

if response not in self.EXIT_INPUTS:
print(f'LOG: received {response.upper()} from student')
return response.upper()
response_index = ord(response.upper()) - ord('A')
response_text = choices[response_index]
return {
'response_index': response_index,
'response_text': response_text
}
return {}

def _get_followups(self, email):
if self.FOLLOWUP_CACHE in os.listdir():
try:
with open(self.FOLLOWUP_CACHE, 'rb') as f:
data = pickle.load(f)
if not hmac.compare_digest(data.get('mac'), self._mac(email, data.get('followups', []))):
os.remove(self.FOLLOWUP_CACHE)
return self._get_context(email)
return data.get('followups', [])

except:
os.remove(self.FOLLOWUP_CACHE)
return self._get_context(email)
else:
return []

def _append_followups(self, email, responded):
followups = self._get_followups(email)
followups.append(responded)
with open(self.FOLLOWUP_CACHE, 'wb') as f:
pickle.dump({'followups': followups, 'mac': self._mac(email, followups)}, f, protocol=pickle.HIGHEST_PROTOCOL)

protocol = FollowupProtocol
2 changes: 0 additions & 2 deletions client/protocols/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ class HelpProtocol(models.ResearchProtocol):
CONTEXT_CACHE = '.ok_context'
CONTEXT_LENGTH = 3
DISABLED_CACHE = '.ok_disabled'
UNKNOWN_EMAIL = '<unknown from CLI>'
BOT_PREFIX = '[61A-bot]: '
HELP_TYPE_PROMPT = BOT_PREFIX + "Would you like to receive debugging help (d) or help understanding the problem (p)? You can also type a specific question.\nPress return/enter to receive no help. Type \"never\" to turn off 61a-bot for this assignment."
NO_HELP_TYPE_PROMPT = BOT_PREFIX + "Would you like to receive 61A-bot feedback on your code (y/N/never)? "
Expand Down Expand Up @@ -120,7 +119,6 @@ def animate():
try:
help_response = requests.post(self.HELP_ENDPOINT, json=help_payload).json()
except Exception as e:
# print(requests.post(self.HELP_ENDPOINT, json=help_payload))
print_error("Error generating hint. Please try again later.")
return
if 'output' not in help_response:
Expand Down