microservices/assessment_service/src/routes/submitted_assessment.py (744 lines of code) (raw):

""" Submitted Assessment endpoints """ import traceback from datetime import datetime from requests.exceptions import ConnectTimeout from fastapi import APIRouter, Query, Request from typing import List, Union, Optional from typing_extensions import Literal from common.models import (SubmittedAssessment, Assessment, Learner, User, LearningExperience, Rubric) from common.utils.errors import (ResourceNotFoundException, ValidationError, PreconditionFailedError) from common.utils.http_exceptions import (InternalServerError, BadRequest, ResourceNotFound, ConnectionTimeout, PreconditionFailed) from common.utils.logging_handler import Logger from common.utils.assessor_handler import ( traverse_down, remove_assessor_for_submitted_assessments, replace_assessor_of_submitted_assessments, filter_submitted_assessments ) from services.submitted_assessment import ( traverse_up, traverse_up_uuid, submit_assessment, get_latest_submission, get_all_submission, get_submitted_assessment_data, instructor_handler, staff_to_learner_handler) from schemas.submitted_assessment_schema import ( SubmittedAssessmentRequestModel, SubmittedAssessmentResponseModel, AllSubmittedAssessmentResponseModel, UpdateSubmittedAssessmentModel, DeleteSubmittedAssessment, SubmittedAssessmentUniqueResponseModel, SubmittedAssessmentAssessorResponseModel, ManualEvaluationResponseModel, AllSubmittedAssessmentAssessorResponseModel, UpdateAssessorIdRequestModel, ReplaceAssessorofSubmittedAssessmentsResponseModel) from schemas.error_schema import (NotFoundErrorResponseModel, ConnectionTimeoutResponseModel, PreconditionFailedResponseModel) from config import ERROR_RESPONSES router = APIRouter(tags=["Submitted Assessment"], responses=ERROR_RESPONSES) # pylint: disable = broad-except,redefined-builtin,broad-exception-raised,too-many-function-args @router.post( "/submitted-assessment", response_model=SubmittedAssessmentResponseModel, responses={ 412: { "model": PreconditionFailedResponseModel }, 404: { "model": NotFoundErrorResponseModel }, 408: { "model": ConnectionTimeoutResponseModel } }) def create_submitted_assessment( request: Request, input_submitted_assessment: SubmittedAssessmentRequestModel): """ The create_submitted_assessment endpoint will add the submitted_assessment to firestore. ### Args: - input_submitted_assessment (SubmittedAssessmentSchema): input submitted_assessment to be inserted ### Raises: - ResourceNotFoundException: If the assessment or learner does not exist - PreconditionFailed: If max_attempt is exceeded - ConnectTimeout: If external service call fails - Exception: 500 Internal Server Error if something went wrong ### Returns: - SubmittedAssessmentResponseModel: SubmittedAssessment object """ try: submitted_assessment_dict = {**input_submitted_assessment.dict()} submitted_assessment_fields = submit_assessment(submitted_assessment_dict, request) return { "success": True, "message": "Successfully created the submitted assessment.", "data": submitted_assessment_fields } except ConnectTimeout as e: Logger.error(e) Logger.error(traceback.print_exc()) raise ConnectionTimeout(str(e)) from e except PreconditionFailedError as e: Logger.error(e) Logger.info(traceback.print_exc()) raise PreconditionFailed(str(e)) from e except ResourceNotFoundException as e: Logger.error(e) Logger.error(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) raise InternalServerError(str(e)) from e @router.get( "/submitted-assessment/{uuid}/learner/all-submissions", response_model=AllSubmittedAssessmentAssessorResponseModel, responses={404: { "model": NotFoundErrorResponseModel }}) def get_all_submitted_assessment(req: Request, uuid: str, skip: int = Query(0, ge=0, le=2000), limit: int = Query(10, ge=1, le=100)): """ The get_all_submitted_assessment endpoint will fetch all the submitted_assessment from firestore for the learner and the assessment of the given submitted_assessment uuid ### Args: - uuid (str): Unique identifier for submitted_assessment - skip (int): Number of objects to be skipped - limit (int): Size of submitted assessment array to be returned ### Raises: - ResourceNotFoundException: If the learner_id/assessment_id does not exist - Exception: 500 Internal Server Error if something went wrong ### Returns: - AllSubmittedAssessmentAssessorResponseModel: List of SubmittedAssessment """ try: header = {"Authorization": req.headers.get("authorization")} all_submissions = get_all_submission(uuid, skip, limit, header) count = 10000 response = {"records": all_submissions, "total_count": count} return { "success": True, "message": "Successfully fetched the submitted assessments.", "data": response } except ResourceNotFoundException as e: Logger.error(e) Logger.error(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) raise InternalServerError(str(e)) from e @router.get( "/submitted-assessment/{uuid}/learner/latest-submission", response_model=SubmittedAssessmentAssessorResponseModel, responses={404: { "model": NotFoundErrorResponseModel }}) def get_latest_submitted_assessment(req: Request, uuid: str): """ The get_latest_submitted_assessment endpoint will fetch the latest submitted_assessment from firestore for the learner and the assessment of the given submitted_assessment uuid. ### Args: - uuid (str): Unique identifier for submitted_assessment ### Raises: - ResourceNotFoundException: If the submitted_assessment does not exist - Exception: 500 Internal Server Error if something went wrong ### Returns: - SubmittedAssessmentAssessorResponseModel: SubmittedAssessment object """ try: header = {"Authorization": req.headers.get("authorization")} submitted_assessment = SubmittedAssessment.find_by_uuid(uuid) assessment_id = submitted_assessment.assessment_id learner_id = submitted_assessment.learner_id latest_submission = get_latest_submission( learner_id, assessment_id, True, header) return { "success": True, "message": "Successfully fetched the submitted assessment.", "data": latest_submission } except ResourceNotFoundException as e: Logger.error(e) Logger.error(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) raise InternalServerError(str(e)) from e @router.get( "/submitted-assessment/{uuid}", response_model=SubmittedAssessmentResponseModel, responses={404: { "model": NotFoundErrorResponseModel }}) def get_submitted_assessment(uuid: str): """ The get submitted_assessment endpoint will return the submitted_assessment from firestore of which uuid is provided ### Args: - uuid (str): Unique identifier for submitted_assessment ### Raises: - ResourceNotFoundException: If the submitted_assessment does not exist - Exception: 500 Internal Server Error if something went wrong ### Returns: - SubmittedAssessmentResponseModel: SubmittedAssessment Object """ try: submitted_assessment = SubmittedAssessment.find_by_uuid(uuid) submitted_assessment_fields = submitted_assessment.get_fields( reformat_datetime=True) submitted_assessment_fields["timer_start_time"] = str( submitted_assessment_fields["timer_start_time"]) return { "success": True, "message": "Successfully fetched the submitted assessment.", "data": submitted_assessment_fields } except ResourceNotFoundException as e: Logger.error(e) Logger.error(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) raise InternalServerError(str(e)) from e @router.put( "/submitted-assessment/{uuid}", name="Update Submitted Assessment", response_model=SubmittedAssessmentResponseModel, responses={404: { "model": NotFoundErrorResponseModel }}) def update_assessment_item( uuid: str, input_submitted_assessment: UpdateSubmittedAssessmentModel): """ Update a submitted_assessment ### Args: - input_submitted_assessment (UpdateSubmittedAssessmentModel): Required body of submitted_assessment ### Raises: - ResourceNotFoundException: If the submitted_assessment does not exist - Exception: 500 Internal Server Error if something went wrong ### Returns: - SubmittedAssessmentResponseModel: SubmittedAssessment Object """ try: submitted_assessment = SubmittedAssessment.find_by_uuid(uuid) submitted_assessment_dict = {**input_submitted_assessment.dict()} submitted_assessment_fields = submitted_assessment.get_fields() # Update status, pass_status, and result based on submitted_rubrics evaluated = False if submitted_assessment_fields.get("type") in \ ["srl", "static_srl", "cognitive_wrapper"] and \ submitted_assessment_fields.get("is_autogradable", False): # Always pass SRL assessments pass_status = True result = "Pass" status = "completed" evaluated = True else: submitted_rubrics = submitted_assessment_dict.get("submitted_rubrics", None) if submitted_rubrics is not None: assessment_id = submitted_assessment.assessment_id assessment = Assessment.find_by_uuid(assessment_id) if assessment.child_nodes and "rubrics" in assessment.child_nodes: # FIXME: Assumption: only one rubric for each assessment rubric_id = assessment.child_nodes["rubrics"][0] rubric = Rubric.find_by_uuid(rubric_id) criteria = rubric.evaluation_criteria criteria = {v: k for k, v in criteria.items()} if len(submitted_rubrics): pass_status = True result = "Exemplary" for r_criterion in submitted_assessment_dict["submitted_rubrics"]: if criteria[r_criterion["result"]] > criteria[result]: result = r_criterion["result"] if criteria[result] > criteria["Proficient"]: pass_status = False status = "evaluated" else: status = "completed" evaluated = True if evaluated: submitted_assessment_dict["status"] = status submitted_assessment_dict["result"] = result submitted_assessment_dict["pass_status"] = pass_status for key, value in submitted_assessment_dict.items(): if key == "comments" and value is not None: if value.get("created_time") is None: value["created_time"] = str(datetime.now()) if submitted_assessment_fields.get("comments"): value = [value] + submitted_assessment_fields.get( "comments", []) else: value = [value] submitted_assessment_fields[key] = value for key, value in submitted_assessment_fields.items(): setattr(submitted_assessment, key, value) submitted_assessment.update() submitted_assessment_fields = submitted_assessment.get_fields( reformat_datetime=True) submitted_assessment_fields["timer_start_time"] = str( submitted_assessment_fields["timer_start_time"]) return { "success": True, "message": "Successfully updated the submitted assessment.", "data": submitted_assessment_fields } except ResourceNotFoundException as e: Logger.error(e) Logger.error(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) raise InternalServerError(str(e)) from e @router.delete( "/submitted-assessment/{uuid}", response_model=DeleteSubmittedAssessment, responses={404: { "model": NotFoundErrorResponseModel }}) def delete_submitted_assessment(uuid: str): """ Delete a submitted_assessment from firestore ### Args: - uuid (str): Unique id of the submitted_assessment ### Raises: - ResourceNotFoundException: If the submitted_assessment does not exist - Exception: 500 Internal Server Error if something went wrong ### Returns: - JSON: Success/Fail Message """ try: SubmittedAssessment.delete_by_uuid(uuid) return { "success": True, "message": "Successfully deleted the submitted assessment." } except ResourceNotFoundException as e: Logger.error(e) Logger.error(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) raise InternalServerError(str(e)) from e @router.get( "/learner/{learner_id}/submitted-assessments", name="Get all Submitted Assessments for Learner", response_model=AllSubmittedAssessmentResponseModel, responses={404: { "model": NotFoundErrorResponseModel }}) def get_all_submitted_assessment_for_learner(req: Request, learner_id: str, assessment_id: Optional[str] = None, skip: int = Query(0, ge=0, le=2000), limit: int = Query(10, ge=1, le=100)): """ The get_all_submitted_assessment_for_learner endpoint will fetch all the submitted_assessments for a learner from firestore. ### Args: - learner_id (str): uuid of learner - assessment_id (str): optional uuid of the assessment - skip (int): Number of objects to be skipped - limit (int): Size of array to be returned ### Raises: - ResourceNotFoundException: If the learner_id does not exist - Exception: 500 Internal Server Error if something went wrong ### Returns: - SubmittedAssessmentResponseModel: SubmittedAssessment object """ try: # Check to validate if Learner with given ID exists or not header = {"Authorization": req.headers.get("authorization")} _ = Learner.find_by_uuid(learner_id) collection_manager = SubmittedAssessment.collection collection_manager = collection_manager.filter("is_deleted", "==", False) collection_manager = collection_manager.filter("learner_id", "==", learner_id) if assessment_id: collection_manager = collection_manager.filter("assessment_id", "==", assessment_id) submitted_assessments = collection_manager.order("-created_time").offset( skip).fetch(limit) if submitted_assessments: submitted_assessments = [ get_submitted_assessment_data(submitted_assessment, True, header) for submitted_assessment in submitted_assessments ] for assessment in submitted_assessments: assessment["timer_start_time"] = str(assessment["timer_start_time"]) count = 10000 response = {"records": submitted_assessments, "total_count": count} return { "success": True, "message": "Successfully fetched the submitted assessments", "data": response } except ResourceNotFoundException as e: raise ResourceNotFound(str(e)) from e except Exception as e: raise InternalServerError(str(e)) from e @router.get( "/submitted-assessments", response_model=AllSubmittedAssessmentAssessorResponseModel, name="Get filtered/searched Submitted Assessments") def get_filtered_submitted_assessments( req: Request, sort_by: Optional[Literal["learner_name", "unit_name", "result", "attempt_no", "timer_start_time"]] = "timer_start_time", sort_order: Optional[Literal["ascending", "descending"]] = "ascending", name: Optional[str] = None, assessor_id: Optional[str] = None, is_autogradable: Union[bool, None] = Query(default=None), discipline_name: Union[List[str], None] = Query(default=None), unit_name: Union[List[str], None] = Query(default=None), status: Union[List[str], None] = Query(default=None), type: Union[List[str], None] = Query(default=None), result: Union[List[str], None] = Query(default=None), is_flagged: Union[bool, None] = Query(default=None), skip: int = Query(0, ge=0, le=2000), limit: int = Query(10, ge=1, le=100)): """ The get filtered submitted assessments endpoint will return an array of submitted assessments from firestore ### Args: - sort_by (str): sort submitted assessment based on this parameter value - sort_order (str): ascending or descending sort - name (str): search submitted assessment based on assessment name keyword - assessor_id (str): uuid of assessor - is_autogradable (bool): to return autogradable or human gradable or both type of assessments - discipline_name (list): Pathway disciplines to filter submitted assessment - unit_name (list): Pathway units to filter submitted assessments on - type (list): type to filter submitted assessments on - status (list): status of submitted assessment to filter on - result (list): result to filter submitted assessments on - is_flagged (bool): return flagged assessments or not or both - skip (int): Number of objects to be skipped - limit (int): Size of array to be returned ### Raises: - ValidationError: If filters are incorrect - Exception: 500 Internal Server Error if something went wrong ### Returns: - AllSubmittedAssessmentAssessorResponseModel: List of SubmittedAssessments """ try: header = {"Authorization": req.headers.get("authorization")} collection_manager = SubmittedAssessment.collection collection_manager = collection_manager.filter("is_deleted", "==", False) in_query_used = False status_filter_applied = True type_filter_applied = True learner_filter_applied = True # assessor_id is a string because an assessor can either view only the # submissions assigned to him or all the submissions if assessor_id: assessor = User.find_by_user_id(assessor_id) if assessor.user_type == "assessor": collection_manager = collection_manager.filter("assessor_id", "==", assessor_id) elif assessor.user_type in ["coach", "instructor"]: ## TODO: Store user_id of the learner instead of user_id learner_ids = staff_to_learner_handler(header, assessor_id, assessor.user_type) if not learner_ids: ## FIXME: return statement should be executed in the end only return { "success": True, "message": "Successfully fetched the submitted assessments.", "data": [] } elif len(learner_ids)<=30: in_query_used = True collection_manager = collection_manager.filter("learner_id", "in", learner_ids) else: learner_filter_applied = False else: raise ResourceNotFoundException(f"User of type {assessor.user_type} "\ "not found.") if is_flagged is not None: collection_manager = collection_manager.filter("is_flagged", "==", is_flagged) if is_autogradable is not None: collection_manager = collection_manager.filter("is_autogradable", "==", is_autogradable) if result: if len(result) == 1: collection_manager = collection_manager.filter("result", "==", result[0]) else: in_query_used = True collection_manager = collection_manager.filter("result", "in", result) if status: if len(status) == 1: collection_manager = collection_manager.filter("status", "==", status[0]) elif not in_query_used: in_query_used = True collection_manager = collection_manager.filter("status", "in", status) else: status_filter_applied = False if type: if len(type) == 1: collection_manager = collection_manager.filter("type", "==", type[0]) elif not in_query_used: in_query_used = True collection_manager = collection_manager.filter("type", "in", type) else: type_filter_applied = False # check if the collection can be directly sorted if the field # to sort on is a field in the data model of SubmittedAssessment if sort_by in ["result", "attempt_no", "timer_start_time"]: sorted_collection = True if sort_order == "descending": sort_by = "-" + sort_by submitted_assessments = collection_manager.order(sort_by).fetch() else: sorted_collection = False submitted_assessments = collection_manager.fetch() filtered_submitted_assessments = [] le_map = {} discipline_map = {} instructor_map = {} assessment_map = {} assessor_map = {} # iterate through all the submitted assessments for submitted_assessment in submitted_assessments: assessment_id = submitted_assessment.assessment_id # if the submitted assessments are already sorted, then we need to fetch # only limit number of values if not limit: break # if status filter is not applied above and the submitted_assessment # status does not lie in the status query, then do not add this submission if not status_filter_applied and \ submitted_assessment.status not in status: continue # if type filter is not applied above and the submitted_assessment # type does not lie in the type query, then do not add this submission elif not type_filter_applied and \ submitted_assessment.type not in type: continue # if filter by learner is not applied above and the submitted_assessment # learner_id does not lie in the list of learner_ids query, then do not # add this submission elif not learner_filter_applied and \ submitted_assessment.learner_id not in learner_ids: continue if assessment_id in assessment_map: assessment_node = assessment_map[assessment_id] else: assessment_node = Assessment.find_by_uuid(assessment_id) assessment_map[assessment_id] = assessment_node assessment_data = assessment_node.get_fields(reformat_datetime=True) assessment_name = assessment_data["name"] # 1. If search query (name) is None, or # 2. if it is not None, then it exists in assessment name # Then the submitted assessment document can be added keyword_exists = not name or \ (name and name.lower() in assessment_name.lower()) if keyword_exists: try: if assessment_id in le_map: le_data = le_map[assessment_id] else: le_node = traverse_up_uuid(assessment_id, "assessments", "learning_experience") le_data = le_node.get_fields() le_map[assessment_id] = { "name": le_data.get("name", ""), "uuid": le_data.get("uuid") } except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) Logger.info("Passing Empty Dict to Get Success Response") le_data = {} try: le_id = le_data.get("uuid") if le_id in discipline_map: discipline_data = discipline_map[le_id] else: discipline_data = traverse_up_uuid(le_id, "learning_experiences", "discipline").get_fields() discipline_map[le_id] = { "name": discipline_data.get("name", ""), "uuid": discipline_data.get("uuid") } except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) Logger.info("Passing Empty Dict to Get Success Response") discipline_data = {} # 1a. If unit_name query filter is None, OR # 1b. if it is not None, then the unit pathway's name lies in any # unit_name, AND # 2a. If discipline_name query filter is None, OR # 2b. if it is not None, then the pathway discipline's name lies in # any discipline_name # Then the submitted assessment document can be added if (not unit_name or le_data.get("name", "") in unit_name) and (not discipline_name or discipline_data.get("name", "") in discipline_name): # 1. if the submitted assessment collection is already sorted # 2. if the number of assessments to be skipped is not yet complete # Then skip this submitted assessment and reduce skip by 1 # else add the submitted assessment if sorted_collection and skip: skip -= 1 else: submitted_assessment_data = get_submitted_assessment_data( submitted_assessment, False, None, assessment_node, assessor_map) submitted_assessment_data["unit_name"] = le_data.get("name", "") submitted_assessment_data["discipline_name"] = discipline_data.get( "name", "") # get instructor data try: discipline_id = discipline_data.get("uuid") if discipline_id in instructor_map: instructor_data = instructor_map[discipline_id] elif assessor and assessor.user_type == "instructor": instructor_id = assessor.user_id instructor_data = User.find_by_user_id( instructor_id).get_fields() instructor_map[discipline_id] = { "first_name": instructor_data.get("first_name", ""), "last_name": instructor_data.get("last_name", ""), "user_id": instructor_data.get("user_id") } else: instructor_id = instructor_handler( header, discipline_data["uuid"]) instructor_data = User.find_by_user_id( instructor_id).get_fields() instructor_map[discipline_id] = { "first_name": instructor_data.get("first_name", ""), "last_name": instructor_data.get("last_name", ""), "user_id": instructor_data.get("user_id") } submitted_assessment_data["instructor_id"] = instructor_data.get( "user_id", "") submitted_assessment_data["instructor_name"] = \ (instructor_data.get("first_name", "") + " " + instructor_data.get("last_name", "")).lstrip() except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) submitted_assessment_data["instructor_id"] = "" submitted_assessment_data["instructor_name"] = "Unassigned" filtered_submitted_assessments.append(submitted_assessment_data) if sorted_collection: limit -= 1 # if the submitted assessments are not sorted, then sort it and return the # only those submissions defined by skip and limit if not sorted_collection: if sort_by == "ascending": filtered_submitted_assessments = sorted( filtered_submitted_assessments, key=lambda i: i[sort_by]) else: filtered_submitted_assessments = sorted( filtered_submitted_assessments, key=lambda i: i[sort_by], reverse=True) filtered_submitted_assessments = \ filtered_submitted_assessments[skip:skip+limit] count = 10000 response = {"records": filtered_submitted_assessments, "total_count": count} return { "success": True, "message": "Successfully fetched the submitted assessments.", "data": response } except ResourceNotFoundException as e: Logger.error(e) Logger.error(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except ValidationError as e: Logger.error(e) Logger.error(traceback.print_exc()) raise BadRequest(str(e), data=e.data) from e except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) raise InternalServerError(str(e)) from e @router.get( "/submitted-assessments/unique", response_model=SubmittedAssessmentUniqueResponseModel, name="Get Unique Values for Submitted Assessment Filters") def get_unique_values_submitted_assessments( assessor_id: Optional[str] = None, is_autogradable: Union[bool, None] = Query(default=None), status: Union[List[str], None] = Query(default=None) ): """ The get_unique_values_submitted_assessments endpoint will return an array of unique values for competency, type and result ### Args: - assessor_id (str): filter submitted assessments based on assessor id - is_autogradable (bool): to return autogradable or human gradable or both type of assessments - status (list): status of submitted assessment to filter on ### Raises: - ResourceNotFoundException: If the assessor does not exist - Exception: 500 Internal Server Error if something went wrong ### Returns: - SubmittedAssessmentUniqueResponseModel: Dictionary of unique values of required SubmittedAssessment fields. """ try: collection_manager = SubmittedAssessment.collection if assessor_id: user = User.find_by_user_id(assessor_id) if user.user_type != "assessor": raise ResourceNotFoundException( f"Assessor with uuid {assessor_id} not found") collection_manager = collection_manager.filter("assessor_id", "==", assessor_id) if is_autogradable: collection_manager.filter("is_autogradable", "==", is_autogradable) if status: if len(status) == 1: collection_manager = collection_manager.filter("status", "==", status[0]) else: collection_manager = collection_manager.filter("status", "in", status) submitted_assessments = collection_manager.order("timer_start_time").fetch() unique_discipline_names = set() unique_unit_names = set() unique_types = set() unique_results = set() for submitted_assessment in submitted_assessments: # adding unique type if submitted_assessment.type: unique_types.add(submitted_assessment.type) # adding unique result if submitted_assessment.result: unique_results.add(submitted_assessment.result) assessment_node = Assessment.find_by_uuid( submitted_assessment.assessment_id) try: le_node = traverse_up(assessment_node, "assessments", "learning_experience") le_data = le_node.get_fields(reformat_datetime=True) unique_unit_names.add(le_data.get("name", "")) discipline_data = traverse_up(le_node, "learning_experiences", "discipline").get_fields(reformat_datetime=True) unique_discipline_names.add(discipline_data.get("name", "")) except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) pass unique_discipline_names = sorted(list(unique_discipline_names)) unique_unit_names = sorted(list(unique_unit_names)) unique_types = sorted(list(unique_types)) unique_results = sorted(list(unique_results)) return { "success": True, "message": "Successfully fetched the unique " + \ "values for submitted assessments.", "data": { "discipline_names": unique_discipline_names, "unit_names": unique_unit_names, "types": unique_types, "results": unique_results } } except ResourceNotFoundException as e: Logger.error(e) Logger.error(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except ValidationError as e: Logger.error(e) Logger.error(traceback.print_exc()) raise BadRequest(str(e), data=e.data) from e except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) raise InternalServerError(str(e)) from e @router.get( "/learner/{learner_id}/learning-experience/{le_id}/submitted-assessments/" "manual-evaluation", name="Get Human Graded Assessments of an Unit for a Learner", response_model=ManualEvaluationResponseModel, responses={404: { "model": NotFoundErrorResponseModel }}) def get_all_manual_evaluation_submitted_assessments_for_learner( req: Request, learner_id: str, le_id: str, skip: int = Query(0, ge=0, le=2000), limit: int = Query(10, ge=1, le=100)): """ This endpoint will fetch all the submitted_assessments of a learner for a given unit from firestore, that can get feedback from the Assessor. ### Args: - learner_id : uuid of learner - le_id: uuid of learning_experience (unit) - skip (int): Number of objects to be skipped. Default 0. - limit (int): Size of array to be returned. Default 10. ### Raises: - ResourceNotFoundException: If the learner_id or le_id does not exist - Exception: 500 Internal Server Error if something went wrong ### Returns: - SubmittedAssessmentResponseModel: SubmittedAssessment object """ try: header = {"Authorization": req.headers.get("authorization")} # Check to validate if Learner and learning_experience # with given ID exists or not Learner.find_by_uuid(learner_id) LearningExperience.find_by_uuid(le_id) assessor_map = {} collection_manager = SubmittedAssessment.collection collection_manager = collection_manager.filter( "learner_id", "==", learner_id) collection_manager = collection_manager.filter( "is_autogradable", "==", False) submitted_assessments = collection_manager.order("-created_time").offset( skip).fetch(limit) response = [] for submitted_assessment in submitted_assessments: assessment_node = Assessment.find_by_uuid( submitted_assessment.assessment_id) try: lo_node = traverse_up(assessment_node, "assessments", "module") le_node = traverse_up(lo_node, "learning_objects", "learning_experience") le_data = le_node.get_fields() except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) Logger.info("Passing Empty Dict to Get Success Response") le_data = {} if le_id == le_data.get("uuid", ""): try: discipline_data = traverse_up(le_node, "learning_experiences", "discipline").get_fields() except Exception as e: Logger.error(e) Logger.error(traceback.print_exc()) Logger.info("Passing Empty Dict to Get Success Response") discipline_data = {} submitted_assessment_data = get_submitted_assessment_data( submitted_assessment, False, None, assessment_node, assessor_map) submitted_assessment_data["unit_name"] = le_data.get("name", "") submitted_assessment_data["discipline_name"] = discipline_data.get( "name", "") if discipline_data: instructor_id = instructor_handler(header, discipline_data["uuid"]) instructor_data = User.find_by_user_id(instructor_id).get_fields() submitted_assessment_data["instructor_id"] = instructor_data.get( "user_id", "") submitted_assessment_data["instructor_name"] = \ (instructor_data.get("first_name", "") + " " + instructor_data.get("last_name", "")).lstrip() else: submitted_assessment_data["instructor_id"] = "" submitted_assessment_data["instructor_name"] = "Unassigned" found = False for item in response: if item.get("learning_object", "") == lo_node.uuid: found = True item["submitted_assessments"].append(submitted_assessment_data) if not found: response.append({ "learning_object": lo_node.uuid, "learning_object_name": lo_node.name, "submitted_assessments": [submitted_assessment_data] }) count = 10000 final_response = {"records": response, "total_count": count} return { "success": True, "message": "Successfully fetched the submitted assessments", "data": final_response } except ResourceNotFoundException as e: print(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except Exception as e: print(traceback.print_exc()) raise InternalServerError(str(e)) from e @router.put( "/discipline/{discipline_id}/submitted-assessments"\ "/update-assessor", include_in_schema=False, response_model=ReplaceAssessorofSubmittedAssessmentsResponseModel, responses={404: { "model": NotFoundErrorResponseModel }}) def update_assessor_of_submitted_assessments_of_a_discipline( req: Request, discipline_id: str, input_assessor: Optional[UpdateAssessorIdRequestModel] = None): """ Unassign and assign another assessor for all non-evaluated submitted assessments related to a discipline ### Args: - discipline_id (str): Unique identifier of a curriculum_pathway of alias discipline - assessor_id (str): Unique identifier of user(assessor) ### Raises: - ResourceNotFoundException: If the discipline_id does not exist - Exception: 500 Internal Server Error if something went wrong ### Returns: - JSON: Success/Fail Message """ try: assessor_id = None if input_assessor: input_assessor= {**input_assessor.dict()} assessor_id = input_assessor.get("assessor_id") assessment_ids = [] assessment_ids = traverse_down( discipline_id, "curriculum_pathways", "assessments",assessment_ids) assessment_ids = list(set(assessment_ids)) response_msg= "No assessments found for discipline "\ f"with uuid {discipline_id}" if assessment_ids: submitted_assessments = filter_submitted_assessments( assessment_ids, assessor_id) if assessor_id: response_msg = f"Successfully replaced assessor {assessor_id} "\ f"for submitted assessments of discipline with uuid {discipline_id}." replace_assessor_of_submitted_assessments(discipline_id,req, submitted_assessments,assessment_ids,assessor_id) else: remove_assessor_for_submitted_assessments(submitted_assessments) response_msg = "Successfully unassigned assessor for all evaluation "\ f"pending submitted assessments of discipline with uuid {discipline_id}" return { "success": True, "message": response_msg } except ResourceNotFoundException as e: print(traceback.print_exc()) raise ResourceNotFound(str(e)) from e except Exception as e: print(traceback.print_exc()) raise InternalServerError(str(e)) from e