def get(self, question_id): """ Get question subgraph --- tags: [question] parameters: - in: path name: question_id description: "question id" type: string required: true responses: 200: description: xxx schema: $ref: '#/definitions/Graph' 404: description: "invalid question key" type: string """ try: question = get_question_by_id(question_id) except Exception as err: return "Invalid question key.", 404 logger.debug(question.toJSON()) r = requests.post(f"http://{os.environ['RANKER_HOST']}:{os.environ['RANKER_PORT']}/api/subgraph", json=question.toJSON()) try: output = r.json() except Exception as err: raise ValueError("Response is not JSON.") return output, 200
def get(self, qa_id): """ Get feedback by answerset --- tags: [feedback] parameters: - in: path name: qa_id description: "<question_id>_<answerset_id>" type: string required: true responses: 200: description: "answer feedback" type: array items: $ref: '#/definitions/Feedback' 404: description: "invalid answerset/answer id" type: string """ try: question_id, answerset_id = qa_id.split('_') question = get_question_by_id(question_id) answerset = get_answerset_by_id(answerset_id) feedback = list_feedback_by_question_answerset(question, answerset) except Exception as err: return "Invalid answerset key", 404 return [f.toJSON() for f in feedback], 200
def get(self, question_id): """ Get question --- tags: [question] parameters: - in: path name: question_id description: "question id" type: string required: true example: A2T8TK8uxaEy responses: 200: description: question schema: $ref: '#/definitions/Question' 404: description: "invalid question key" """ try: question = get_question_by_id(question_id) except Exception as err: return "Invalid question key.", 404 answerset_list = question.answersets return {'question': question.toJSON(), 'owner': question.user.email, 'answerset_list': [a.toStandard(data=False) for a in answerset_list]}, 200
def post(self, question_id): """ Refresh KG for question --- tags: [cache] parameters: - in: path name: question_id description: "question id" type: string required: true responses: 202: description: "refreshing in progress" type: string 404: description: "invalid question key" type: string """ auth = request.authorization if auth: user_email = auth.username user = get_user_by_email(user_email) user_id = user.id else: user_id = current_user.id user_email = current_user.email try: question = get_question_by_id(question_id) except Exception as err: return "Invalid question key.", 404 # Update the knowledge graph for a question task = update_kg.apply_async(args=[question_id], kwargs={'user_email':user_email}) return {'task_id':task.id}, 202
def delete(self, question_id): """ Delete question --- tags: [question] parameters: - in: path name: question_id description: "question id" type: string required: true responses: 200: description: "question deleted" 401: description: "unauthorized" 404: description: "invalid question key" """ auth = request.authorization if auth: user_email = auth.username user = get_user_by_email(user_email) else: user = current_user logger.info('Deleting question %s', question_id) try: question = get_question_by_id(question_id) except Exception as err: return "Invalid question key.", 404 if not (user == question.user or user.has_role('admin')): return "UNAUTHORIZED", 401 # not authorized db.session.delete(question) db.session.commit() return "SUCCESS", 200
def get(self, qa_id): """ Get answerset --- tags: [answer] parameters: - in: path name: qa_id description: "<question_id>_<answerset_id>" type: string required: true responses: 200: description: "answerset data" type: object properties: answerset: schema: $ref: '#/definitions/Response' user: type: object question: type: object other_questions: type: array items: type: object other_answersets: type: array items: type: object feedback: type: array items: type: object 404: description: "invalid answerset id" type: string """ try: question_id, answerset_id = qa_id.split('_') question = get_question_by_id(question_id) answerset = get_answerset_by_id(answerset_id) answersets = question.answersets if not answerset in answersets: raise AssertionError() except Exception as err: return "Invalid answerset key.", 404 user = getAuthData() feedback = list_feedback_by_question_answerset(question, answerset) return {'question': question.toJSON(),\ 'answerset': answerset.toStandard(),\ 'feedback': [f.toJSON() for f in feedback],\ 'other_answersets': [], 'other_questions': []}, 200
def get(self, question_id): """ Get list of queued tasks for question --- tags: [tasks] parameters: - in: path name: question_id description: "question id" type: string required: true responses: 200: description: tasks type: object properties: answerers: type: array items: $ref: '#/definitions/Task' updaters: type: array items: $ref: '#/definitions/Task' 404: description: "invalid question key" type: string """ try: question = get_question_by_id(question_id) except Exception as err: return "Invalid question key.", 404 tasks = list(get_tasks().values()) # filter out the SUCCESS/FAILURE tasks tasks = [t for t in tasks if not (t['state'] == 'SUCCESS' or t['state'] == 'FAILURE' or t['state'] == 'REVOKED')] # filter out tasks for other questions question_tasks = [] for t in tasks: if not t['args']: continue match = re.match(r"[\[(]'(.*)',?[)\]]", t['args']) if match: if match.group(1) == question.id: question_tasks.append(t) # split into answer and update tasks answerers = [t for t in question_tasks if t['name'] == 'manager.tasks.answer_question'] updaters = [t for t in question_tasks if t['name'] == 'manager.tasks.update_kg'] return {'answerers': answerers, 'updaters': updaters}, 200
def update_kg(self, question_id, user_email=None): ''' Update the shared knowledge graph with respect to a question ''' self.update_state(state='UPDATING KG') question = get_question_by_id(question_id) logger.info(f"Updating the knowledge graph for '{question.name}'...") r = requests.post( f'http://{os.environ["BUILDER_HOST"]}:{os.environ["BUILDER_PORT"]}/api/', json=question.toJSON()) polling_url = f"http://{os.environ['BUILDER_HOST']}:{os.environ['BUILDER_PORT']}/api/task/{r.json()['task id']}" for _ in range(60 * 60 * 24): # wait up to 1 day r = requests.get(polling_url) if r.json()['state'] == 'FAILURE': raise RuntimeError('Builder failed.') if r.json()['state'] == 'REVOKED': raise RuntimeError('Task terminated by admin.') if r.json()['state'] == 'SUCCESS': break time.sleep(1) else: raise RuntimeError( "KG updating has not completed after 1 day. It will continue working, but we must return to the manager." ) try: if user_email: # send completion email question_url = f'http://{os.environ["ROBOKOP_HOST"]}/q/{question.id}' lines = [ f'We have finished gathering information for your question: <a href="{question_url}">"{question.natural_question}"</a>.' ] html = '<br />\n'.join(lines) with app.app_context(): msg = Message("ROBOKOP: Knowledge Graph Update Complete", sender=os.environ["ROBOKOP_DEFAULT_MAIL_SENDER"], recipients=[user_email], html=html) mail.send(msg) except Exception as err: logger.warning(f"Failed to send 'completed KG update' email: {err}") logger.info(f"Done updating for '{question.name}'.") return "You updated the KG!"
def answer_question(self, question_id, user_email=None): ''' Generate answerset for a question ''' self.update_state(state='ANSWERING') logger.info("Answering your question...") question = get_question_by_id(question_id) r = requests.post( f'http://{os.environ["RANKER_HOST"]}:{os.environ["RANKER_PORT"]}/api/', json=question.toJSON()) # wait here for response if r.status_code == 204: # found 0 answers raise NoAnswersException( "Question answering complete, found 0 answers.") self.update_state(state='ANSWERS FOUND') logger.info("Answers found.") try: answerset_json = r.json() except json.decoder.JSONDecodeError as err: raise ValueError(f"Response is not json: {r.text}") answerset = Answerset(answerset_json) question.answersets.append(answerset) db.session.commit() if user_email: try: with app.app_context(): question_url = f'http://{os.environ["ROBOKOP_HOST"]}/q/{question.id}' answerset_url = f'http://{os.environ["ROBOKOP_HOST"]}/a/{question_id}_{answerset.id}' lines = [ f'We have finished answering your question: <a href="{question_url}">"{question.natural_question}"</a>.' ] lines.append(f'<a href="{answerset_url}">ANSWERS</a>') html = '<br />\n'.join(lines) msg = Message("ROBOKOP: Answers Ready", sender=os.environ["ROBOKOP_DEFAULT_MAIL_SENDER"], recipients=[user_email], html=html) mail.send(msg) except Exception as err: logger.warning(f"Failed to send 'completed answer' email: {err}") logger.info("Done answering.") return answerset.id
def post(self, question_id): """ Edit question metadata --- tags: [question] parameters: - in: path name: question_id description: "question id" type: string required: true - in: body name: name description: "name of question" required: true - in: body name: natural_question description: "natural-language question" required: true - in: body name: notes description: "notes" required: true responses: 200: description: "question edited" 401: description: "unauthorized" 404: description: "invalid question key" """ auth = request.authorization if auth: user_email = auth.username user = get_user_by_email(user_email) else: user = current_user logger.info('Editing question %s', question_id) try: question = get_question_by_id(question_id) except Exception as err: return "Invalid question key.", 404 if not (user == question.user or user.has_role('admin')): return "UNAUTHORIZED", 401 # not authorized question.name = request.json['name'] question.notes = request.json['notes'] question.natural_question = request.json['natural_question'] db.session.commit() return "SUCCESS", 200
def get(self, question_id): """ Create new feedback --- tags: [feedback] parameters: - in: path name: question_id description: "question id" type: string required: true responses: 200: description: success 404: description: "invalid question key" """ try: question = get_question_by_id(question_id) feedback = list_feedback_by_question(question) except Exception as err: return "Invalid question id", 404 return feedback.toJSON(), 200
def get(self, qa_id, answer_id): """ Get answer --- tags: [answer] parameters: - in: path name: qa_id description: "<question_id>_<answerset_id>" type: string required: true - in: path name: answer_id description: "answer/result id" type: string required: true responses: 200: description: "answer data" type: object properties: answer: schema: $ref: '#/definitions/Result' answerset: schema: $ref: '#/definitions/Response' user: type: object question: type: object other_questions: type: array items: type: object other_answersets: type: array items: type: object feedback: type: array items: type: object 404: description: "invalid answerset/answer id" type: string """ try: question_id, answerset_id = qa_id.split('_') question = get_question_by_id(question_id) answerset = get_answerset_by_id(answerset_id) answersets = question.answersets if not answerset in answersets: raise AssertionError() answer = get_answer_by_id(answer_id) if not answer in answerset.answers: raise AssertionError() except Exception as err: return "Invalid answerset or answer key.", 404 questions = answerset.questions idx = questions.index(question) questions.pop(idx) idx = answersets.index(answerset) answersets.pop(idx) feedback = list_feedback_by_question_answer(question, answer) user = getAuthData() return {'user': user,\ 'answerset': answerset.toJSON(),\ 'answer': answer.toJSON(),\ 'feedback': [f.toJSON() for f in feedback],\ 'question': question.toJSON(),\ 'other_answersets': [aset.toJSON() for aset in answersets], 'other_questions': [q.toJSON() for q in questions]}, 200