]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-20058 Grading report, aggregation of submission grades
authorDavid Mudrak <david.mudrak@gmail.com>
Mon, 4 Jan 2010 18:11:48 +0000 (18:11 +0000)
committerDavid Mudrak <david.mudrak@gmail.com>
Mon, 4 Jan 2010 18:11:48 +0000 (18:11 +0000)
Not finished yet

mod/workshop/aggregate.php [new file with mode: 0644]
mod/workshop/db/install.xml
mod/workshop/lang/en_utf8/workshop.php
mod/workshop/locallib.php
mod/workshop/version.php
mod/workshop/view.php

diff --git a/mod/workshop/aggregate.php b/mod/workshop/aggregate.php
new file mode 100644 (file)
index 0000000..459c89f
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Aggregates the grades for submission and grades for assessments and calculates the total grade for workshop
+ *
+ * @package   mod-workshop
+ * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+
+$cmid       = required_param('cmid', PARAM_INT);            // course module
+$confirm    = optional_param('confirm', false, PARAM_BOOL); // confirmation
+
+$cm         = get_coursemodule_from_id('workshop', $cmid, 0, false, MUST_EXIST);
+$course     = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+$workshop   = $DB->get_record('workshop', array('id' => $cm->instance), '*', MUST_EXIST);
+$workshop   = new workshop($workshop, $cm, $course);
+
+$PAGE->set_url(new moodle_url($workshop->aggregate_url(), array('cmid' => $cmid)));
+
+require_login($course, false, $cm);
+require_capability('mod/workshop:overridegrades', $PAGE->context);
+
+if ($confirm) {
+    if (!confirm_sesskey()) {
+        throw new moodle_exception('confirmsesskeybad');
+    }
+    $workshop->update_submission_grades();
+    $workshop->update_grading_grades();
+    redirect($workshop->view_url());
+}
+
+$PAGE->set_title($workshop->name);
+$PAGE->set_heading($course->fullname);
+$PAGE->navbar->add(get_string('aggregation', 'workshop'));
+
+//
+// Output starts here
+//
+echo $OUTPUT->header();
+echo $OUTPUT->confirm(get_string('aggregationinfo', 'workshop'),
+                        new moodle_url($PAGE->url, array('confirm' => 1)), $workshop->view_url());
+echo $OUTPUT->footer();
index 5e31da13678a21b045c42e3d5033a5498a2a01b9..75e29dffd10fea51f49a73d91e4f9a355c517c33 100644 (file)
         <KEY NAME="formfield_uk" TYPE="unique" FIELDS="assessmentid, strategy, dimensionid" COMMENT="The combination of assessmentid, strategy and dimensionid must be unique" PREVIOUS="assessment_fk"/>
       </KEYS>
     </TABLE>
-    <TABLE NAME="workshop_aggregations" COMMENT="Aggregated grade for submission, grade assessments and total grade calculated for workshop participants are stored here" PREVIOUS="workshop_grades">
+    <TABLE NAME="workshop_aggregations" COMMENT="Aggregated grade for assessment and total grade calculated for workshop participants are stored here. The aggregated grade for submission is stored in workshop_submissions" PREVIOUS="workshop_grades">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="workshopid"/>
         <FIELD NAME="workshopid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="the id of the workshop instance" PREVIOUS="id" NEXT="userid"/>
-        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="The id of the user which aggregated grades are calculated for" PREVIOUS="workshopid" NEXT="submissiongrade"/>
-        <FIELD NAME="submissiongrade" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" DECIMALS="5" COMMENT="The grade for submission" PREVIOUS="userid" NEXT="gradinggrade"/>
-        <FIELD NAME="gradinggrade" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" DECIMALS="5" COMMENT="The aggregated grade for all assessments made by this reviewer. The grade is a number from interval 0..100. If NULL then the grade for assessments has not been aggregated yet." PREVIOUS="submissiongrade" NEXT="totalgrade"/>
-        <FIELD NAME="totalgrade" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" DECIMALS="5" COMMENT="The total grade for this workshop to be pushed into the gradebook" PREVIOUS="gradinggrade" NEXT="timeaggregated"/>
-        <FIELD NAME="timeaggregated" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="The timestamp of when the participant's grades were recently aggregated. If there are some modifications of the assessment after this time, the aggregation shall be marked as possibly out-dated" PREVIOUS="totalgrade"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="The id of the user which aggregated grades are calculated for" PREVIOUS="workshopid" NEXT="gradinggrade"/>
+        <FIELD NAME="gradinggrade" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" DECIMALS="5" COMMENT="The aggregated grade for all assessments made by this reviewer. The grade is a number from interval 0..100. If NULL then the grade for assessments has not been aggregated yet." PREVIOUS="userid" NEXT="totalgrade"/>
+        <FIELD NAME="totalgrade" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" DECIMALS="5" COMMENT="The total grade for this workshop to be pushed into the gradebook" PREVIOUS="gradinggrade" NEXT="timetotalgraded"/>
+        <FIELD NAME="timetotalgraded" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="The timestamp of when the participant's totalgrade was recently aggregated. If there are some modifications of any assessment of his/her submission after this time, the aggregation shall be marked as possibly out-dated" PREVIOUS="totalgrade"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="workshop_fk"/>
index e985496a98e7dab45045010710591c8d8cb0ca5e..53a3a57496a5dc51531c83de8fef2998fbb2ed75 100644 (file)
@@ -28,6 +28,11 @@ defined('MOODLE_INTERNAL') || die();
 $string[''] = '';
 $string[''] = '';
 $string[''] = '';
+$string[''] = '';
+$string[''] = '';
+$string['aggregationinfo'] = 'During the aggregation process, the grades for submission, grades for assessment and total grades are re-calculated and stored into the Workshop database. This does not modify any manual overrides nor does not push the total grade into the gradebook.';
+$string['aggregation'] = 'Grades aggregation';
+$string['aggregategrades'] = 'Re-calculate grades';
 $string['nullgrade'] = '?';
 $string['formatpeergradeover'] = '$a->grade (<del>$a->gradinggrade</del> / <ins>$a->gradinggradeover</ins>)';
 $string['formatpeergrade'] = '$a->grade ($a->gradinggrade)';
index d5655a73c421348fd6376a271c9e3671b15f9e3b..64899a6177c4380168e6dc349ac7cd2170d98563 100644 (file)
@@ -30,8 +30,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-require_once(dirname(__FILE__).'/lib.php');      // we extend this library here
-require_once($CFG->libdir . '/gradelib.php');
+require_once(dirname(__FILE__).'/lib.php');     // we extend this library here
+require_once($CFG->libdir . '/gradelib.php');   // we use some rounding and comparing routines here
 
 /**
  * Full-featured workshop API
@@ -587,6 +587,14 @@ class workshop {
         return new moodle_url($CFG->wwwroot . '/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode));
     }
 
+    /**
+     * @return moodle_url to the aggregation page
+     */
+    public function aggregate_url() {
+        global $CFG;
+        return new moodle_url($CFG->wwwroot . '/mod/workshop/aggregate.php', array('cmid' => $this->cm->id));
+    }
+
     /**
      * Returns an object containing all data to display the user's full name and picture
      *
@@ -960,10 +968,10 @@ class workshop {
         $params['workshopid'] = $this->id;
         $sqlsort = $sortby . ' ' . $sorthow . ',u.lastname,u.firstname,u.id';
         $sql = "SELECT u.id AS userid,u.firstname,u.lastname,u.picture,u.imagealt,
-                       s.title AS submissiontitle, a.submissiongrade, a.gradinggrade, a.totalgrade
+                       s.title AS submissiontitle, s.grade AS submissiongrade, ag.gradinggrade, ag.totalgrade
                   FROM {user} u
              LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id)
-             LEFT JOIN {workshop_aggregations} a ON (a.userid = u.id)
+             LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = s.workshopid)
                  WHERE s.workshopid = :workshopid AND s.example = 0 AND u.id $participantids
               ORDER BY $sqlsort";
         $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
@@ -1120,6 +1128,106 @@ class workshop {
         return grade_floatval((float)$grade + (float)$gradinggrade);
     }
 
+    /**
+     * Calculates grades for submission for the given participant(s)
+     *
+     * Grade for submission is calculated as a weighted mean of all given grades
+     *
+     * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s)
+     * @return void
+     */
+    public function update_submission_grades($restrict=null) {
+        global $DB;
+
+        // fetch a recordset with all assessments to process
+        $sql = 'SELECT s.id AS submissionid, s.authorid, s.grade AS submissiongrade, s.gradeover, s.gradeoverby,
+                       a.weight, a.grade
+                  FROM {workshop_submissions} s
+             LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id)
+                 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
+        $params = array('workshopid' => $this->id);
+
+        if (is_null($restrict)) {
+            // update all users - no more conditions
+        } elseif (!empty($restrict)) {
+            list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
+            $sql .= " AND s.authorid $usql";
+            $params = array_merge($params, $uparams);
+        } else {
+            throw new coding_exception('Empty value is not a valid parameter here');
+        }
+
+        $sql .= ' ORDER BY s.id'; // this is important for bulk processing
+        $rs = $DB->get_recordset_sql($sql, $params);
+
+        $previous   = null;
+        foreach ($rs as $current) {
+            if (is_null($previous)) {
+                // we are processing the very first record in the recordset
+                $previous   = $current;
+                $sumgrades  = 0;
+                $sumweights = 0;
+            }
+            if (is_null($current->grade)) {
+                // this was not assessed yet
+                continue;
+            }
+            if ($current->weight == 0) {
+                // this does not influence the calculation
+                continue;
+            }
+            if ($current->submissionid != $previous->submissionid) {
+                // firstly finish the calculation for the previous submission as we now have all its data
+                if ($sumweights > 0) {
+                    // there is a chance that the aggregated grade has changed
+                    $finalgrade = $sumgrades / $sumweights;
+                    if (grade_floats_different($finalgrade, $previous->submissiongrade)) {
+                        // we need to save new calculation into the database
+                        $DB->set_field('workshop_submissions', 'grade', $finalgrade, array('id' => $previous->submissionid));
+                    }
+                }
+                // and then start to process another submission
+                $previous = $current;
+                if (is_null($current->grade)) {
+                    $sumgrades = 0;
+                } else {
+                    $sumgrades  = $current->grade;
+                }
+                $sumweights = $current->weight;
+                continue;
+            } else {
+                // we are still processing the current submission
+                $sumgrades  += $current->grade * $current->weight;
+                $sumweights += $current->weight;
+                continue;
+            }
+        }
+        // finally we must calculate the last submission's grade as it was not done in the previous loop
+        if ($sumweights > 0) {
+            // there is a chance that the aggregated grade has changed
+            $finalgrade = $sumgrades / $sumweights;
+            if (grade_floats_different($finalgrade, $current->submissiongrade)) {
+                // we need to save new calculation into the database
+                $DB->set_field('workshop_submissions', 'grade', $finalgrade, array('id' => $current->submissionid));
+            }
+        }
+        $rs->close();
+    }
+
+    /**
+     * Calculates grades for assessment for the given participant(s)
+     *
+     * Grade for submission is calculated as a weighted mean of all given grades
+     *
+     * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s)
+     * @return void
+     */
+    public function update_grading_grades($restrict=null) {
+        global $DB;
+
+        // todo
+    }
+
     ////////////////////////////////////////////////////////////////////////////////
     // Internal methods (implementation details)                                  //
     ////////////////////////////////////////////////////////////////////////////////
index 58d7bac5053beb69f7d66666298fa17e8e0d1616..374539592112921565967a575a6329a15e0ab3fb 100644 (file)
@@ -28,6 +28,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version  = 2009092900;
+$module->version  = 2009092901;
 $module->requires = 2009090400;  // Requires this Moodle version
 $module->cron     = 60;
index 066980687ec8ad4c0ed5a8420631043618cb3453..035a0ea1c67cd8c4e9a7242bdfee7d9b74fbe6fc 100644 (file)
@@ -160,7 +160,7 @@ case workshop::PHASE_EVALUATION:
     $page       = optional_param($pagingvar, 0, PARAM_INT);
     $perpage    = 10;           // todo let the user modify this
     $groups     = '';           // todo let the user choose the group
-    $sortby     = 'totalgrade';   // todo let the user choose the column to sort by
+    $sortby     = 'submissiongrade';   // todo let the user choose the column to sort by
     $sorthow    = 'DESC';        // todo detto
 
     $data = $workshop->prepare_grading_report($USER->id, $groups, $page, $perpage, $sortby, $sorthow);