Skip to content

Autopopulate grade ranged rubric#192

Open
sumaiyamannan wants to merge 1 commit intoucl-isd:mainfrom
sumaiyamannan:autopopulategrade
Open

Autopopulate grade ranged rubric#192
sumaiyamannan wants to merge 1 commit intoucl-isd:mainfrom
sumaiyamannan:autopopulategrade

Conversation

@sumaiyamannan
Copy link
Contributor

@sumaiyamannan sumaiyamannan commented Dec 23, 2025

Summary

Introduce automatic averaging of ranged rubric grades at the final marking stage, with the ability for the final assessor to review and adjust individual rubric criteria before submission.

Use Case

When multiple assessors independently mark a submission using a Ranged Rubric, the final assessor should not need to re-enter grades from scratch. Instead, the system should pre-populate the final stage with the averaged results from earlier markers.

Configuration Requirements

mod_coursework activity settings

Marking workflow

Number of times each submission should initially be marked: 2
Automatic agreement of grades: No

Grade

Grading method: Ranged Rubric
Grading method for final stage: Same throughout all stages

Workflow

First assessor Logs in
-- Selects rubric criteria and assigns grades for each criterion
Second assessor Logs in
-- Selects rubric criteria and assigns grades independently
Final assessor Logs in
-- Sees each rubric criterion auto-populated with the average of the first and second assessor’s grades
-- Can adjust individual rubric criteria where required
-- Submits the final mark

Expected Behaviour

Login as the final assessor and see that the average criteria and grade from the previous two selections has been selected for the final assessor to change and submit

Each rubric criterion in the final stage is pre-selected using the average value of the prior assessors' selections.

The final assessor retains full control to override any averaged value.

No automatic agreement is enforced; the averaged values act as a starting point only.
Screenshot from 2025-12-23 14-25-07

Benefits

Reduces repetitive data entry for final assessors
Improves consistency and transparency in multi-marker workflows
Enhances usability of ranged rubric grading in Coursework

- Final assessor sees initial assessors selected in the criterion and grade
- Supports range rubric grading method.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces automatic population of ranged rubric grades during the final marking stage by averaging grades from initial assessors, allowing the final assessor to review and adjust criteria before submission.

  • Adds database query to calculate average grades and levels from initial assessors' rubric selections
  • Implements JavaScript module to pre-populate the final stage grading form with averaged values
  • Conditionally applies auto-population only when using ranged rubrics with manual agreement strategy

Reviewed changes

Copilot reviewed 3 out of 5 changed files in this pull request and generated 4 comments.

File Description
classes/models/coursework.php Adds method to retrieve averaged rubric grades and helper to detect ranged rubric usage
classes/controllers/feedback_controller.php Integrates auto-population logic into final stage feedback workflow
amd/src/rubric_ranges.js Implements client-side form population using averaged grade data

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

LEFT JOIN {grading_instances} gi ON cf.id = gi.itemid
LEFT JOIN {gradingform_rubric_ranges_f} rr ON gi.id = rr.instanceid
WHERE cf.submissionid = :submissionid AND cf.stageidentifier NOT LIKE 'final_agreed_1' AND cf.finalised = 1 AND gi.status = 1
GROUP BY rr.criterionid ORDER BY rr.criterionid ";
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double space in ORDER BY clause. Remove the extra space between 'ORDER' and 'BY' for consistency with SQL formatting standards.

Suggested change
GROUP BY rr.criterionid ORDER BY rr.criterionid ";
GROUP BY rr.criterionid ORDER BY rr.criterionid ";

Copilot uses AI. Check for mistakes.
/**
* Modify ranged rubric grading form.
*
* @author Sumaiya Javed <sumaiya.javed@catalyst.net.nz
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing angle bracket in email address. The author tag should end with '>' after the email address.

Suggested change
* @author Sumaiya Javed <sumaiya.javed@catalyst.net.nz
* @author Sumaiya Javed <sumaiya.javed@catalyst.net.nz>

Copilot uses AI. Check for mistakes.

/**
* @return grading_manager
* @return bool|gradingform_controller|null
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @return annotation specifies 'bool|gradingform_controller|null' but the function actually returns either an array of database records or null. Update the annotation to '@return array|false' to match the actual return type from get_records_sql().

Suggested change
* @return bool|gradingform_controller|null
* @return array|false|null

Copilot uses AI. Check for mistakes.

// Autopopulate average grade from initial assessors.
if ($coursework && $coursework->is_using_ranged_rubric() && $teacherfeedback->stageidentifier == 'final_agreed_1') {
if (str_contains($coursework->automaticagreementstrategy, 'none')) {
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using str_contains() with 'none' as a substring match is fragile and could match unintended values like 'nonesuch'. Consider using strict equality comparison (=== 'none') or checking against a defined constant if the strategy field contains exactly 'none'.

Suggested change
if (str_contains($coursework->automaticagreementstrategy, 'none')) {
if ($coursework->automaticagreementstrategy === 'none') {

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@leonstr leonstr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks broadly OK, the only serious issue is whether we need the if in get_advanced_grading_average_grade_range_rubric() and if we do surely some default return value is needed (false or null?).

We probably also need to consider if this new behaviour should be enabled by default. Hopefully my colleagues will be able to give some input into this decision.

FROM {coursework_feedbacks} cf
LEFT JOIN {grading_instances} gi ON cf.id = gi.itemid
LEFT JOIN {gradingform_rubric_ranges_f} rr ON gi.id = rr.instanceid
WHERE cf.submissionid = :submissionid AND cf.stageidentifier NOT LIKE 'final_agreed_1' AND cf.finalised = 1 AND gi.status = 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses NOT LIKE but the compared value doesn't contain a wildcard. Presumably this should be:

            WHERE cf.submissionid = :submissionid AND cf.stageidentifier <> 'final_agreed_1' AND cf.finalised = 1 AND gi.status = 1

// Autopopulate average grade from initial assessors.
if ($coursework && $coursework->is_using_ranged_rubric() && $teacherfeedback->stageidentifier == 'final_agreed_1') {
if (str_contains($coursework->automaticagreementstrategy, 'none')) {
$gdata = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed. Why bother initialising $gdata when it's assigned on the next line?

Comment on lines +140 to +141
if ($coursework && $coursework->is_using_ranged_rubric() && $teacherfeedback->stageidentifier == 'final_agreed_1') {
if (str_contains($coursework->automaticagreementstrategy, 'none')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than:

if (...) {
    if (...) {
        ⋮
    }
}

why not have a single if condition:

        if ($coursework && $coursework->is_using_ranged_rubric()
            && $teacherfeedback->stageidentifier == 'final_agreed_1'
            && $coursework->automaticagreementstrategy === 'none') {
        ) {

*/
public function get_advanced_grading_average_grade_range_rubric($submissionid) {
global $DB;
if ($submissionid) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check $submissionid here ? Could this actually be a falsy value? (Maybe it could but from a quick skim of the code I couldn't see how).

GROUP BY rr.criterionid ORDER BY rr.criterionid ";
return $DB->get_records_sql($sql, ['submissionid' => $submissionid]);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If if ($submissionid) {} is false then nothing is returned, won't that break the caller? Should we have return false here?

$urlparams['stageidentifier'] = $teacherfeedback->stageidentifier;
$PAGE->set_url('/mod/coursework/actions/feedbacks/new.php', $urlparams);

// Autopopulate average grade from initial assessors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this relates to non-core functionality I think this comment should explicitly mention the plugin in this comment, for example:

        // If using ranged rubric (gradingform_rubric_ranges) auto-populate average grade from initial assessors.

}

/**
* Check if ranged rubric is used in the current coursework.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, since this relates to non-core functionality I think this comment should explicitly mention the plugin. For example:

     * Check if ranged rubric (gradingform_rubric_ranges) is used in the
     * current coursework.

* Modify ranged rubric grading form.
*
* @author Sumaiya Javed <sumaiya.javed@catalyst.net.nz
* @copyright Catalyst IT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing year, presumably this should be:

 * @copyright 2025 Catalyst IT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants