.. _custom_autograders:

Adding Custom Autograders
=========================

`e2xgrader` allows you to register custom autograders.

Writing Your Own Autograder
---------------------------

To write your own autograder, you need to inherit from `e2xgrader.graders.base.BaseGrader`.
This class exposes two methods:

* :code:`def cell_changed(self, cell: NotebookNode) -> bool:` 
* :code:`def determine_grade(self, cell: NotebookNode, log: logger = None) -> Tuple[Optional[float], float]:`

In most cases you can ignore the `cell_changed` method.
You always need to implement the `determine_grade` method. This method takes a cell and returns a tuple of `(points, max_points)`. 
Here `points` is the amount of points the autograder assigns to this cell and `max_points` is the maximum number of points the cell is worth.

Custom MultipleChoice Grader
----------------------------

Here is an example of a custom multiple choice grader that only assigns points if all answers are correctly selected:

.. code-block:: python

   from e2xgrader.graders import BaseGrader
   from e2xgrader.utils.extra_cells import get_choices, get_instructor_choices

   class CustomMultipleChoiceGrader(BaseGrader):

       def determine_grade(self, cell, log=None):
           # Determine the maximum number of points
           max_points = float(cell.metadata["nbgrader"]["points"])
           
           # Determine the correct solution
           reference_solution = get_instructor_choices(cell)

           # Determine what the student chose
           student_solution = get_choices(cell)

           # Return 0 points if the student did not select all correct answers
           for choice in reference_solution:
               if choice not in student_solution:
                   return 0, max_points
            
           # Return 0 points if the student did select an incorrect answer
           for choice in student_solution:
               if choice not in reference_solution:
                   return 0, max_points

           # The student did select only correct answers
           return max_points, max_points

Registering Your Autograder
---------------------------

To register your own autograder you need to add it via the `nbgrader_config.py` file.

Here is an example of how to do it for the example grader above:

.. code-block:: python

    # nbgrader_config.py
    from e2xgrader.config import configure_base

    c = get_config()
    configure_base(c)

    # Import your grader, here we assume it is in a package called mygrader
    # It can also be implemented directly in the config

    from mygrader import CustomMultipleChoiceGrader

    c.SaveAutoGrades.graders.update({
       "multiplechoice": CustomMultipleChoiceGrader
    })

Advanced Options
----------------

To find out which autograder to use, `e2xgrader` first determines the cell type and then looks in the dictionary :code:`SaveAutoGrades.graders` if there is an entry for the cell type.
If so it will call the grader for that entry. If not it will fall back to the standard `nbgrader` autograder.

The cell type is determined as follows:

If the cell has a metadata entry :code:`cell.metadata.extended_cell.type`, this value will be used (currently the options here are: `multiplechoice`, `singlechoice`, `diagram`, `attachments`). 
You can also add your own value here.
Otherwise the standard cell type :code:`cell.cell_type` will be used (`code`, `markdown`).