From: David Mudrak Date: Mon, 4 Jan 2010 18:18:00 +0000 (+0000) Subject: Workshop total grade calculation support X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=1fed6ce39a18d677af5467ed72ee764e2d268b12;p=moodle.git Workshop total grade calculation support --- diff --git a/mod/workshop/lang/en_utf8/workshop.php b/mod/workshop/lang/en_utf8/workshop.php index 62b36af6a6..a6c97a27f1 100644 --- a/mod/workshop/lang/en_utf8/workshop.php +++ b/mod/workshop/lang/en_utf8/workshop.php @@ -28,9 +28,6 @@ defined('MOODLE_INTERNAL') || die(); $string[''] = ''; $string[''] = ''; $string[''] = ''; -$string[''] = ''; -$string['configgradedecimals'] = 'Default number of digits that should be shown after the decimal point when displaying grades.'; -$string['gradedecimals'] = 'Decimal places in grades'; $string['accesscontrol'] = 'Access control'; $string['aggregategrades'] = 'Re-calculate grades'; $string['aggregation'] = 'Grades aggregation'; @@ -60,6 +57,8 @@ $string['assignedassessments'] = 'Assigned submissions to assess'; $string['assignedassessmentsnone'] = 'You have no assigned submission to assess'; $string['backtoeditform'] = 'Back to editing form'; $string['byfullname'] = 'by url}\">{$a->name}'; +$string['calculatetotalgrades'] = 'Calculate total grades'; +$string['calculatetotalgradesdetails'] = 'expected: $a->expected
known: $a->known'; $string['comparisonhigh'] = 'High'; $string['comparisonlow'] = 'Low'; $string['comparisonnormal'] = 'Normal'; @@ -68,6 +67,7 @@ $string['comparisonverylow'] = 'Very low'; $string['configanonymity'] = 'Default anonymity mode in workshops'; $string['configassessmentcomps'] = 'Default value of the setting that influences the calculation of the grade for assessment.'; $string['configexamplesmode'] = 'Default mode of examples assessment in workshops'; +$string['configgradedecimals'] = 'Default number of digits that should be shown after the decimal point when displaying grades.'; $string['configgrade'] = 'Default maximum grade for submission in workshops'; $string['configgradinggrade'] = 'Default maximum grade for assessment in workshops'; $string['configmaxbytes'] = 'Default maximum submission file size for all workshops on the site (subject to course limits and other local settings)'; @@ -80,6 +80,7 @@ $string['editingassessmentform'] = 'Editing assessment form'; $string['editingsubmission'] = 'Editing submission'; $string['editsubmission'] = 'Edit submission'; $string['err_removegrademappings'] = 'Unable to remove the unused grade mappings'; +$string['evaluategradeswait'] = 'Please wait until the assessments are evaluated and total grades are calculated'; $string['examplesbeforeassessment'] = 'Examples are available after own submission and must be assessed before peer/self assessment phase'; $string['examplesbeforesubmission'] = 'Examples must be assessed before own submission'; $string['examplesmode'] = 'Mode of examples assessment'; @@ -88,6 +89,7 @@ $string['formatpeergrade'] = '$a->grade ($a->gradinggrade)'; $string['formatpeergradeover'] = '$a->grade ($a->gradinggrade / $a->gradinggradeover)'; $string['givengrade'] = 'Given grade: $a'; $string['givengrades'] = 'Given grades'; +$string['gradedecimals'] = 'Decimal places in grades'; $string['gradinggrade'] = 'Grade for assessment'; $string['gradinggradeof'] = 'Grade for assessment (of $a)'; $string['gradingsettings'] = 'Grading settings'; @@ -162,6 +164,7 @@ $string['taskinstructreviewers'] = 'Provide instructions for assessing'; $string['taskintro'] = 'Set the workshop introduction'; $string['tasksubmit'] = 'Submit your work'; $string['teacherweight'] = 'Weight of the teacher\'s assessments'; +$string['totalgradeof'] = 'Total grade (of $a)'; $string['totalgrade'] = 'Total grade'; $string['undersetup'] = 'The workshop is currently under setup. Please wait until it is switched to the next phase.'; $string['useexamplesdesc'] = 'Users practise evaluating on example submissions'; diff --git a/mod/workshop/locallib.php b/mod/workshop/locallib.php index 8501ef9753..47def608e1 100644 --- a/mod/workshop/locallib.php +++ b/mod/workshop/locallib.php @@ -165,7 +165,7 @@ class workshop { */ public function get_potential_authors($musthavesubmission=true) { $users = get_users_by_capability($this->context, 'mod/workshop:submit', - 'u.id,u.lastname,u.firstname', 'u.lastname,u.firstname,u.id', 0, 1000, '', '', false, false, true); + 'u.id,u.lastname,u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true); if ($musthavesubmission) { $users = array_intersect_key($users, $this->users_with_submission(array_keys($users))); } @@ -183,7 +183,7 @@ class workshop { */ public function get_potential_reviewers($musthavesubmission=false) { $users = get_users_by_capability($this->context, 'mod/workshop:peerassess', - 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname,u.id', 0, 1000, '', '', false, false, true); + 'u.id, u.lastname, u.firstname', 'u.lastname,u.firstname,u.id', '', '', '', '', false, false, true); if ($musthavesubmission) { // users without their own submission can not be reviewers $users = array_intersect_key($users, $this->users_with_submission(array_keys($users))); @@ -828,10 +828,36 @@ class workshop { } $phases[self::PHASE_ASSESSMENT] = $phase; - // Prepare tasks for the grading evaluation phase - todo + // Prepare tasks for the grading evaluation phase $phase = new stdClass(); $phase->title = get_string('phaseevaluation', 'workshop'); $phase->tasks = array(); + if (has_capability('mod/workshop:overridegrades', $this->context)) { + $authors = $this->get_potential_authors(false); + $reviewers = $this->get_potential_reviewers(false); + $expected = count($authors + $reviewers); + unset($authors); + unset($reviewers); + $known = $DB->count_records_select('workshop_aggregations', 'workshopid = ? AND totalgrade IS NOT NULL', + array($this->id)); + $task = new stdClass(); + $task->title = get_string('calculatetotalgrades', 'workshop'); + $a = new stdClass(); + $a->expected = $expected; + $a->known = $known; + $task->details = get_string('calculatetotalgradesdetails', 'workshop', $a); + if ($known >= $expected) { + $task->completed = true; + } elseif ($this->phase > self::PHASE_EVALUATION) { + $task->completed = false; + } + $phase->tasks['evaluateinfo'] = $task; + } else { + $task = new stdClass(); + $task->title = get_string('evaluategradeswait', 'workshop'); + $task->completed = 'info'; + $phase->tasks['evaluateinfo'] = $task; + } $phases[self::PHASE_EVALUATION] = $phase; // Prepare tasks for the "workshop closed" phase - todo @@ -1119,6 +1145,7 @@ class workshop { $data->totalcount = $numofparticipants; $data->maxgrade = $this->real_grade(100); $data->maxgradinggrade = $this->real_grading_grade(100); + $data->maxtotalgrade = $this->format_total_grade($data->maxgrade + $data->maxgradinggrade); return $data; } @@ -1150,7 +1177,7 @@ class workshop { if (is_null($raw)) { return null; } - return format_float($raw, $this->gradedecimals, $localized); + return format_float($raw, $this->gradedecimals, true); } /** @@ -1294,7 +1321,32 @@ class workshop { public function aggregate_total_grades($restrict=null) { global $DB; - // todo + // fetch a recordset with all assessments to process + $sql = 'SELECT s.grade, s.gradeover, s.authorid AS userid, + ag.id AS agid, ag.gradinggrade, ag.totalgrade + FROM {workshop_submissions} s + INNER JOIN {workshop_aggregations} ag ON (ag.userid = s.authorid) + 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 ag.userid $usql"; + $params = array_merge($params, $uparams); + } else { + throw new coding_exception('Empty value is not a valid parameter here'); + } + + $sql .= ' ORDER BY ag.userid'; // this is important for bulk processing + + $rs = $DB->get_recordset_sql($sql, $params); + + foreach ($rs as $current) { + $this->aggregate_total_grades_process($current); + } + $rs->close(); } //////////////////////////////////////////////////////////////////////////// @@ -1334,7 +1386,7 @@ class workshop { * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored. * * @param array $assessments of stdClass(->submissionid ->submissiongrade ->gradeover ->weight ->grade) - * @return null|float the aggregated grade rounded to numeric(10,5) + * @return void */ protected function aggregate_submission_grades_process(array $assessments) { global $DB; @@ -1387,7 +1439,7 @@ class workshop { * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored. * * @param array $assessments of stdClass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade) - * @return null|float the aggregated grade rounded to numeric(10,5) + * @return void */ protected function aggregate_grading_grades_process(array $assessments) { global $DB; @@ -1442,6 +1494,49 @@ class workshop { } } + /** + * Given an object with final grade for submission and final grade for assessment, updates the total grade in DB + * + * @param stdClass $data + * @return void + */ + protected function aggregate_total_grades_process(stdClass $data) { + global $DB; + + if (!is_null($data->gradeover)) { + $submissiongrade = $data->gradeover; + } else { + $submissiongrade = $data->grade; + } + + // If we do not have enough information to update totalgrade, do not do + // anything. Please note there may be a lot of reasons why the workshop + // participant does not have one of these grades - maybe she was ill or + // just did not reach the deadlines. Teacher has to fix grades in + // gradebook manually. + + if (is_null($submissiongrade) or (!empty($this->gradinggrade) and is_null($this->gradinggrade))) { + return; + } + + $totalgrade = $this->grade * $submissiongrade / 100 + $this->gradinggrade * $data->gradinggrade / 100; + + // check if the new total grade differs from the one stored in the database + if (grade_floats_different($totalgrade, $data->totalgrade)) { + // we need to save new calculation into the database + if (is_null($data->agid)) { + // no aggregation record yet + $record = new stdClass(); + $record->workshopid = $this->id; + $record->userid = $data->userid; + $record->totalgrade = $totalgrade; + $DB->insert_record('workshop_aggregations', $record); + } else { + $DB->set_field('workshop_aggregations', 'totalgrade', $totalgrade, array('id' => $data->agid)); + } + } + } + /** * Given a list of user ids, returns the filtered one containing just ids of users with own submission * diff --git a/mod/workshop/renderer.php b/mod/workshop/renderer.php index a9fa5fe6d8..2fceeb557a 100644 --- a/mod/workshop/renderer.php +++ b/mod/workshop/renderer.php @@ -434,7 +434,8 @@ class moodle_mod_workshop_renderer extends moodle_renderer_base { $this->sortable_heading(get_string('givengrades', 'workshop')), $this->sortable_heading(get_string('gradinggradeof', 'workshop', $data->maxgradinggrade), 'gradinggrade', $sortby, $sorthow), - $this->sortable_heading(get_string('totalgrade', 'workshop'), 'totalgrade', $sortby, $sorthow), + $this->sortable_heading(get_string('totalgradeof', 'workshop', $data->maxtotalgrade), + 'totalgrade', $sortby, $sorthow), ); $table->rowclasses = array(); $table->colclasses = array('reviewedby', 'peer', 'reviewerof'); @@ -490,7 +491,11 @@ class moodle_mod_workshop_renderer extends moodle_renderer_base { // column #4 - total grade for submission if ($tr == 0) { $cell = new html_table_cell(); - $cell->text = $participant->submissiongrade; + if (is_null($participant->submissiongrade)) { + $cell->text = get_string('nullgrade', 'workshop'); + } else { + $cell->text = $participant->submissiongrade; + } $cell->rowspan = $numoftrs; $row->cells[] = $cell; } @@ -505,7 +510,11 @@ class moodle_mod_workshop_renderer extends moodle_renderer_base { // column #6 - total grade for assessment if ($tr == 0) { $cell = new html_table_cell(); - $cell->text = $participant->gradinggrade; + if (is_null($participant->gradinggrade)) { + $cell->text = get_string('nullgrade', 'workshop'); + } else { + $cell->text = $participant->gradinggrade; + } $cell->rowspan = $numoftrs; $row->cells[] = $cell; } @@ -546,7 +555,7 @@ class moodle_mod_workshop_renderer extends moodle_renderer_base { if (!is_null($sortid)) { $iconasc = new moodle_action_icon(); - $iconasc->image->src = $this->old_icon_url('t/down'); + $iconasc->image->src = $this->old_icon_url('t/up'); $iconasc->image->alt = get_string('sortasc', 'workshop'); $iconasc->image->set_classes('sort asc'); $newurl = clone($PAGE->url); @@ -554,7 +563,7 @@ class moodle_mod_workshop_renderer extends moodle_renderer_base { $iconasc->link->url = new moodle_url($newurl); $icondesc = new moodle_action_icon(); - $icondesc->image->src = $this->old_icon_url('t/up'); + $icondesc->image->src = $this->old_icon_url('t/down'); $icondesc->image->alt = get_string('sortdesc', 'workshop'); $icondesc->image->set_classes('sort desc'); $newurl = clone($PAGE->url); diff --git a/mod/workshop/simpletest/testlocallib.php b/mod/workshop/simpletest/testlocallib.php index 4e9839450a..65ea02f734 100644 --- a/mod/workshop/simpletest/testlocallib.php +++ b/mod/workshop/simpletest/testlocallib.php @@ -49,6 +49,10 @@ class testable_workshop extends workshop { public function aggregate_grading_grades_process(array $assessments) { parent::aggregate_grading_grades_process($assessments); } + + public function aggregate_total_grades_process(stdClass $data) { + parent::aggregate_total_grades_process($data); + } } /** @@ -376,7 +380,123 @@ class workshop_internal_api_test extends UnitTestCase { $this->workshop->aggregate_grading_grades_process($batch); } + public function test_aggregate_total_grades_process_normal_new() { + global $DB; + + // fixture set-up + $this->workshop->grade = 85; + $this->workshop->gradinggrade = 15; + + $data = new stdClass(); + $data->agid = 1; + $data->grade = 95.00000; + $data->gradeover = null; + $data->gradinggrade = 67.34500; + $data->totalgrade = null; + + $expected = grade_floatval(0.95 * 85 + 0.67345 * 15); + $DB->expectOnce('set_field', array('workshop_aggregations', 'totalgrade', $expected, array('id' => 1))); + // excercise SUT + $this->workshop->aggregate_total_grades_process($data); + } + + public function test_aggregate_total_grades_process_overriden_new() { + global $DB; + + // fixture set-up + $this->workshop->grade = 80; + $this->workshop->gradinggrade = 20; + + $data = new stdClass(); + $data->agid = 2; + $data->grade = 95.00000; + $data->gradeover = 87.5; + $data->gradinggrade = 67.34500; + $data->totalgrade = null; + + $expected = grade_floatval(0.875 * 80 + 0.67345 * 20); + $DB->expectOnce('set_field', array('workshop_aggregations', 'totalgrade', $expected, array('id' => 2))); + // excercise SUT + $this->workshop->aggregate_total_grades_process($data); + } + + public function test_aggregate_total_grades_process_uptodate() { + global $DB; + + // fixture set-up + $this->workshop->grade = 100; + $this->workshop->gradinggrade = 100; + + $data = new stdClass(); + $data->agid = 3; + $data->grade = 45.00000; + $data->gradeover = null; + $data->gradinggrade = 40.00000; + $data->totalgrade = 85.00000; + + $DB->expectNever('set_field'); + // excercise SUT + $this->workshop->aggregate_total_grades_process($data); + } + + public function test_aggregate_total_grades_process_null_grade() { + global $DB; + // fixture set-up + $this->workshop->grade = 70; + $this->workshop->gradinggrade = 30; + + $data = new stdClass(); + $data->agid = 4; + $data->grade = null; + $data->gradeover = null; + $data->gradinggrade = 95.00000; + $data->totalgrade = null; + + $DB->expectNever('set_field'); + // excercise SUT + $this->workshop->aggregate_total_grades_process($data); + } + + /* + public function test_aggregate_total_grades_process_null_gradinggrade() { + global $DB; + + // fixture set-up + $this->workshop->grade = 70; + $this->workshop->gradinggrade = 30; + + $data = new stdClass(); + $data->agid = 5; + $data->grade = 12.12345; + $data->gradeover = null; + $data->gradinggrade = null; + $data->totalgrade = null; + + $DB->expectNever('set_field'); + // excercise SUT + $this->workshop->aggregate_total_grades_process($data); + } + + public function test_aggregate_total_grades_process_null_gradinggrade_of_zero() { + global $DB; + + // fixture set-up + $this->workshop->grade = 100; + $this->workshop->gradinggrade = 0; + + $data = new stdClass(); + $data->agid = 6; + $data->grade = 56.00000; + $data->gradeover = null; + $data->gradinggrade = null; + $data->totalgrade = null; + + $DB->expectOnce('set_field', array('workshop_aggregations', 'totalgrade', 56.00000, array('id' => 1))); + // excercise SUT + $this->workshop->aggregate_total_grades_process($data); + } + */ public function test_percent_to_value() { // fixture setup $total = 185; diff --git a/mod/workshop/styles.php b/mod/workshop/styles.php index 808a01af29..64c407f5ae 100644 --- a/mod/workshop/styles.php +++ b/mod/workshop/styles.php @@ -414,6 +414,17 @@ border: 1px solid #ddd; } +.mod-workshop .grading-report td.c3, +.mod-workshop .grading-report td.c5 { + text-align: center; + font-size: 160%; +} + +.mod-workshop .grading-report td.c6 { + text-align: center; + font-size: 220%; +} + /** * Edit assessment form */ diff --git a/mod/workshop/view.php b/mod/workshop/view.php index 9322fa1bdd..23cdd17e85 100644 --- a/mod/workshop/view.php +++ b/mod/workshop/view.php @@ -156,18 +156,18 @@ case workshop::PHASE_ASSESSMENT: } break; case workshop::PHASE_EVALUATION: - $page = optional_param('page', 0, PARAM_INT); - $sortby = optional_param('sortby', 'lastname', PARAM_ALPHA); - $sorthow = optional_param('sorthow', 'ASC', PARAM_ALPHA); - $perpage = 10; // todo let the user modify this - $groups = ''; // todo let the user choose the group - $PAGE->set_url(new moodle_url($PAGE->url, compact('sortby', 'sorthow', 'page'))); - $data = $workshop->prepare_grading_report($USER->id, $groups, $page, $perpage, $sortby, $sorthow); - if ($data) { - $showauthornames = has_capability('mod/workshop:viewauthornames', $PAGE->context); - $showreviewernames = has_capability('mod/workshop:viewreviewernames', $PAGE->context); + if (has_capability('mod/workshop:overridegrades', $PAGE->context)) { + $page = optional_param('page', 0, PARAM_INT); + $sortby = optional_param('sortby', 'lastname', PARAM_ALPHA); + $sorthow = optional_param('sorthow', 'ASC', PARAM_ALPHA); + $perpage = 10; // todo let the user modify this + $groups = ''; // todo let the user choose the group + $PAGE->set_url(new moodle_url($PAGE->url, compact('sortby', 'sorthow', 'page'))); + $data = $workshop->prepare_grading_report($USER->id, $groups, $page, $perpage, $sortby, $sorthow); + if ($data) { + $showauthornames = has_capability('mod/workshop:viewauthornames', $PAGE->context); + $showreviewernames = has_capability('mod/workshop:viewreviewernames', $PAGE->context); - if (has_capability('mod/workshop:overridegrades', $PAGE->context)) { $form = new html_form(); $form->url = new moodle_url($workshop->aggregate_url(), compact('sortby', 'sorthow', 'page')); $form->button = new html_button(); @@ -182,7 +182,32 @@ case workshop::PHASE_EVALUATION: echo $OUTPUT->box_start('buttonsbar'); echo $OUTPUT->box($OUTPUT->button($form) . $OUTPUT->help_icon($helpicon), 'buttonwithhelp'); echo $OUTPUT->box_end(); + + // prepare paging bar + $pagingbar = new moodle_paging_bar(); + $pagingbar->totalcount = $data->totalcount; + $pagingbar->page = $page; + $pagingbar->perpage = $perpage; + $pagingbar->baseurl = $PAGE->url; + $pagingbar->pagevar = 'page'; + + echo $OUTPUT->paging_bar($pagingbar); + echo $wsoutput->grading_report($data, $showauthornames, $showreviewernames, $sortby, $sorthow); + echo $OUTPUT->paging_bar($pagingbar); } + } + break; +case workshop::PHASE_CLOSED: + $page = optional_param('page', 0, PARAM_INT); + $sortby = optional_param('sortby', 'lastname', PARAM_ALPHA); + $sorthow = optional_param('sorthow', 'ASC', PARAM_ALPHA); + $perpage = 10; // todo let the user modify this + $groups = ''; // todo let the user choose the group + $PAGE->set_url(new moodle_url($PAGE->url, compact('sortby', 'sorthow', 'page'))); + $data = $workshop->prepare_grading_report($USER->id, $groups, $page, $perpage, $sortby, $sorthow); + if ($data) { + $showauthornames = has_capability('mod/workshop:viewauthornames', $PAGE->context); + $showreviewernames = has_capability('mod/workshop:viewreviewernames', $PAGE->context); // prepare paging bar $pagingbar = new moodle_paging_bar(); @@ -197,7 +222,6 @@ case workshop::PHASE_EVALUATION: echo $OUTPUT->paging_bar($pagingbar); } break; -case workshop::PHASE_CLOSED: default: }