Wednesday, October 20, 2010

The new question engine - how it works

In my last blog post I promised more details of the new question engine "in a week or so". Unfortunately, things like work (mainly fixing minor bugs in the aforementioned question engine); rehearsing for a rather good concert that will take place this Friday; and buying curtains for my new flat, have been rather getting in the way. Now is the time to remedy that.

Last time I explained roughly what a question engine was, and that I had made big changes to the one in Moodle. I now want to say more about what the question engine has to do, and how the new one does it.

The key processes

There are three key code-paths:

To display a page of the quiz:

  1. Load the outline data about the student's attempt.
  2. Hence work out which questions are on this page.
  3. Load the details of this student's attempt at those questions within this quiz attempt.
  4. Get the display settings to use (should the marks, feedback, and so on be visible to this user).
  5. Taking note of the state of each question (from Step 3) update the display options. For example, if the student has not answered the question yet, we don't want to display the feedback now, even if we will later.
  6. Using the details of the current state of each question, and the relevant display options, output the HTML for the question.

The bits in italic are the bits done by the quiz. The rest is done by the question engine.

To start a new attempt at the quiz:

  1. From the list of questions in the quiz, work out the layout for this attempt. (This is only really interesting if you are shuffling the order of the questions, or selecting questions randomly.)
  2. Create an initial state for each question in the quiz attempt. (This is when things like the order of multiple choice options are randomised.)
  3. Write the initial states to the database.

To process a student's responses to a page of the quiz.

  1. From the submitted data, work out which attempt and questions are affected.
  2. Load the details of the current state of those question attempts.
  3. Sort all the submitted data, into the bits belonging to each question. (The bits of data have names like 'q188:1_answer'. The prefix before the '_' identifies this as data belonging to the first question in attempt 188, and the bit after the '_' identifies this as the answer to that question.)
  4. For each question, process its data to work out whether the state has changed and, if so, what the new state is. This is really the most important procedure, and I will talk more about it in the next section.
  5. Write any updated states to the database.
  6. Update the overall score for the attempt, if appropriate, and store it in the database.

These outlines are, of course, oversimplifications. If you really want to know what happens, you will have to read the code.

There are other processes which I will not cover in detail. These include finishing a quiz attempt, re-grading an attempt, fetching data in bulk for the quiz reports, and deleting attempts.

The most important procedure

This is the step where we take the data submitted by the student for one particular question and use it to update the state of that question.

Moodle has long had the concept of different questions types. It can handle short-answer questions, multiple-choices questions, matching questions, and so on. Naturally, what happens when updating the state of the question depends on the question type. That is true for both the old and new code.

Now, however, there is a new concept in addition to question types. The concept of 'question behaviours'.

In previous versions of Moodle, there was a rather cryptic setting for the quiz: Adaptive mode, Yes/No. That affected what happened as the student attempted the quiz. When adaptive mode was off, the student would go through the quiz entering their response to each question. Those responses were saved. At the end, they would submit the quiz and everything would be marked, all at once. Then the student could review their attempt (either immediately, or later, depending on the quiz settings) to see their marks and the feedback. When adaptive mode was on, the student could submit each question individually during the attempt. If they were right first time, they got full marks. If they were wrong, the got some feedback and could try again for reduced marks.

The problem with the previous version of Moodle was the way this was implemented. There was a single process_responses function that was full of code like "if adaptive mode, do this, else do that". It was a real tangle. It was very common to change the code to fix a bug in adaptive mode (for example), only to find that you had broken non-adaptive mode. Another problem was the essay question type, which has to be graded manually by the teacher. It did not really follow either adaptive or non-adaptive mode, but was still processed by the same code. That lead to bugs.

A very important realisations in the design of the new question engine was identifying this concept of a 'question behaviour' as something that could be isolated. There is now a behaviour called 'Deferred feedback' that works like the old non-adaptive mode; there is an 'Adaptive' behaviour; and there is a 'Manually graded' behaviour specially for the essay question type. Since these are now separate, you can alter one without risking breaking the others. Of course, the separate behaviours still share common functions like 'save responses' or 'grade responses'. We now also have a clean way to add new behaviours. I made a 'certainly-based marking' behaviour, and a behaviour called 'Interactive', which is a bit like the old Adaptive mode but modified to work exactly how the Open University wants.

It takes two to tango, and three to process a question

In order to do anything, there now has to be a three-way dance between the core of the question engine, the behaviour and the question type. Does this just replace the old tangle with a new tangle (of feet)? Fortunately there is a consistent logic. The request arrives at the question engine. The question engine inspects it, and passes it on to the appropriate behaviour. The behaviour inspects it in more detail, to work out exactly what need to be done. For example is the student just saving a response, or are they submitting something for grading. The behaviour then asks the question type to do that specific thing. All this leads to a new state that is passed back to the question engine.

So, the flow of control is question engine -> behaviour -> question type except, critically, in one place. When we start a new attempt, we have to choose which behaviour to use for each question. At this point the question engine directly asks the question type to decide. Normally, the question type will just say "use whatever behaviour the quiz settings ask for", but certain question types, like the essay, can instead say "I don't care about the quiz settings, I demand the manual grading behaviour."

If you like software design patterns, you can think of this as a double application of the strategy pattern. The question engine uses a behaviour strategy, which uses a question type strategy (with the subtlety that the choice of behaviour strategy is made by the question type).

Summary

So that is roughly how it works. A clear separation of responsibility between three separate components. Each component focussing on doing one aspect of the processing accurately, which makes the system highly extensible, robust and maintainable. Of course, everyone says that about the software they design, but based on my experiences over the last year, first of building all the parts of the system, and then of fixing the bugs that were found during testing, I say it with some confidence.

8 comments:

  1. Sounds like a big step forward!

    Will be interesting to explore the impact of this on our own Opaque questions for STACK when we get a chance.

    ReplyDelete
  2. "These outlines are, of course, oversimplifications. If you really want to know what happens, you will have to read the code."

    ... or the developer documentation ;-)

    Seriously, it all sounds like a great advance on what was there before.

    As an aside... we made extensive use of the pattern book when I was a Bell Labs developer. Your mention of patterns in your post makes me very nostalgic.

    Keep up the good work!

    Ian.

    ReplyDelete
  3. Simon, I look forwards to a future version of Stack working with the standard version of Opaque question type in Moodle 2.1.

    Ian, code is documentation. At least, I hope that all my code is human-readable. It also contains extensive PHPdocumentor comments. However, finishing the developer documentation at http://docs.moodle.org/en/Development:Overview_of_the_Moodle_question_engine is on my to do list.

    ReplyDelete
  4. I totally agree with "code is documentation" approach. Detailed API description is what proprietary libraries need. In open source world, the code talks. Adding a page with overall design and the developer's intention is useful (as in this nice blog post for example) but reading the source is the way.

    ReplyDelete
  5. Inspiring work Tim,

    When so many people feel very good about just adding features, you have gone ahead and worked for a year on improving the internals. Hope someday it will be possible to reuse the question engine for lesson activity as well.

    Was wondering how difficult or easy would it be port to Moodle 1.9 as I am sure Moodle 2.0 will take at least a year to dislodge Moodle 1.9 from being the version of choice.

    I am hoping to build a good assessment engine, perhaps as a new activity for Moodle 1.9 based upon your question engine. Proposed features are difficulty levels, page marking by students, next question being served on the basis of previous question like GMAT, quiz sub-parts, attempt capability, a feature to club existing questions (e.g. a passage of English followed by some comprehension questions), category-wise reporting.

    Handling of randomisation using a new question type irks me and while I am about it, would like to get rid of it.

    I recall I was able to backport a part of activity completion in 1.9.

    Looking forward to your next blog entry and your comment about backporting the Question Engine 2.0 to 1.9.

    ReplyDelete
  6. Using the question bank and question engine in the lesson activity is tentatively planned for Moodle 2.1, I think.

    At the moment, the code only exists in a form that works with Moodle 1.9: https://github.com/timhunt/Moodle-Question-Engine-2. However, you have to realise the huge risks in using that code. Basically, when you want to move to Moodle 2.1 including the new question engine, you would have to handle upgrading the database yourself. Also, you would have to handle the upgrade from your existing Moodle site to the new question engine. Those are both really hard problems.

    On the other hand, I would like people to look at the new code, and give me feedback on how it works. For example, handling of random questions is now much saner, I think.

    I am intending to write more, but currently I am really busy.

    ReplyDelete
  7. hi tim,
    Please provide me flow of question type how it works. I want to know how different files in question type works and their order of execution...

    ReplyDelete
  8. 1. See the docs at http://docs.moodle.org/dev/Developing_a_Question_Type

    2. This sort of question is better asked at http://moodle.org/mod/forum/view.php?id=737

    ReplyDelete