MDL-46139 Grades: Add a column to grade_grades to record how a grade is aggregated
authorDamyon Wiese <damyon@moodle.com>
Thu, 7 Aug 2014 08:33:09 +0000 (16:33 +0800)
committerAdrian Greeve <adrian@moodle.com>
Fri, 3 Oct 2014 05:47:42 +0000 (13:47 +0800)
grade/edit/tree/category_form.php
grade/edit/tree/lib.php
grade/lib.php
grade/report/user/lib.php
lib/db/install.xml [changed mode: 0644->0755]
lib/db/upgrade.php
lib/grade/grade_category.php
lib/grade/grade_grade.php

index f893091..51617ee 100644 (file)
@@ -37,15 +37,7 @@ class edit_category_form extends moodleform {
 
         $category = $this->_customdata['current'];
 
-        $this->aggregation_options = array(GRADE_AGGREGATE_MEAN            =>get_string('aggregatemean', 'grades'),
-                                           GRADE_AGGREGATE_WEIGHTED_MEAN   =>get_string('aggregateweightedmean', 'grades'),
-                                           GRADE_AGGREGATE_WEIGHTED_MEAN2  =>get_string('aggregateweightedmean2', 'grades'),
-                                           GRADE_AGGREGATE_EXTRACREDIT_MEAN=>get_string('aggregateextracreditmean', 'grades'),
-                                           GRADE_AGGREGATE_MEDIAN          =>get_string('aggregatemedian', 'grades'),
-                                           GRADE_AGGREGATE_MIN             =>get_string('aggregatemin', 'grades'),
-                                           GRADE_AGGREGATE_MAX             =>get_string('aggregatemax', 'grades'),
-                                           GRADE_AGGREGATE_MODE            =>get_string('aggregatemode', 'grades'),
-                                           GRADE_AGGREGATE_SUM             =>get_string('aggregatesum', 'grades'));
+        $this->aggregation_options = grade_helper::get_aggregation_strings();
 
         // visible elements
         $mform->addElement('header', 'headercategory', get_string('gradecategory', 'grades'));
index c483ff2..5fbb122 100644 (file)
@@ -661,15 +661,7 @@ class grade_edit_tree_column_aggregation extends grade_edit_tree_column_category
             throw new Exception('Array key (id) missing from 3rd param of grade_edit_tree_column_aggregation::get_category_cell($category, $levelclass, $params)');
         }
 
-        $options = array(GRADE_AGGREGATE_MEAN             => get_string('aggregatemean', 'grades'),
-                         GRADE_AGGREGATE_WEIGHTED_MEAN    => get_string('aggregateweightedmean', 'grades'),
-                         GRADE_AGGREGATE_WEIGHTED_MEAN2   => get_string('aggregateweightedmean2', 'grades'),
-                         GRADE_AGGREGATE_EXTRACREDIT_MEAN => get_string('aggregateextracreditmean', 'grades'),
-                         GRADE_AGGREGATE_MEDIAN           => get_string('aggregatemedian', 'grades'),
-                         GRADE_AGGREGATE_MIN              => get_string('aggregatemin', 'grades'),
-                         GRADE_AGGREGATE_MAX              => get_string('aggregatemax', 'grades'),
-                         GRADE_AGGREGATE_MODE             => get_string('aggregatemode', 'grades'),
-                         GRADE_AGGREGATE_SUM              => get_string('aggregatesum', 'grades'));
+        $options = grade_helper::get_aggregation_strings();
 
         $visible = explode(',', $CFG->grade_aggregations_visible);
         foreach ($options as $constant => $string) {
@@ -751,7 +743,7 @@ class grade_edit_tree_column_weight extends grade_edit_tree_column {
     public function get_header_cell() {
         global $OUTPUT;
         $headercell = clone($this->headercell);
-        $headercell->text = get_string('weightuc', 'grades').$OUTPUT->help_icon('aggregationcoefweight', 'grades');
+        $headercell->text = get_string('weights', 'grades').$OUTPUT->help_icon('aggregationcoefweight', 'grades');
         return $headercell;
     }
 
index d3f13ac..95c68a9 100644 (file)
@@ -1171,17 +1171,17 @@ class grade_structure {
 
                 } else if (($is_course or $is_category) and ($is_scale or $is_value)) {
                     if ($category = $element['object']->get_item_category()) {
+                        $aggrstrings = grade_helper::get_aggregation_strings();
+                        $stragg = $aggrstrings[$category->aggregation];
                         switch ($category->aggregation) {
                             case GRADE_AGGREGATE_MEAN:
                             case GRADE_AGGREGATE_MEDIAN:
                             case GRADE_AGGREGATE_WEIGHTED_MEAN:
                             case GRADE_AGGREGATE_WEIGHTED_MEAN2:
                             case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
-                                $stragg = get_string('aggregation', 'grades');
                                 return '<img src="'.$OUTPUT->pix_url('i/agg_mean') . '" ' .
                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
                             case GRADE_AGGREGATE_SUM:
-                                $stragg = get_string('aggregation', 'grades');
                                 return '<img src="'.$OUTPUT->pix_url('i/agg_sum') . '" ' .
                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
                         }
@@ -2449,6 +2449,11 @@ abstract class grade_helper {
      * @var array
      */
     protected static $pluginstrings = null;
+    /**
+     * Cached grade aggregation strings
+     * @var array
+     */
+    protected static $aggregationstrings = null;
 
     /**
      * Gets strings commonly used by the describe plugins
@@ -2481,6 +2486,29 @@ abstract class grade_helper {
         }
         return self::$pluginstrings;
     }
+
+    /**
+     * Gets strings describing the available aggregation methods.
+     *
+     * @return array
+     */
+    public static function get_aggregation_strings() {
+        if (self::$aggregationstrings === null) {
+            self::$aggregationstrings = array(
+                GRADE_AGGREGATE_MEAN             => get_string('aggregatemean', 'grades'),
+                GRADE_AGGREGATE_WEIGHTED_MEAN    => get_string('aggregateweightedmean', 'grades'),
+                GRADE_AGGREGATE_WEIGHTED_MEAN2   => get_string('aggregateweightedmean2', 'grades'),
+                GRADE_AGGREGATE_EXTRACREDIT_MEAN => get_string('aggregateextracreditmean', 'grades'),
+                GRADE_AGGREGATE_MEDIAN           => get_string('aggregatemedian', 'grades'),
+                GRADE_AGGREGATE_MIN              => get_string('aggregatemin', 'grades'),
+                GRADE_AGGREGATE_MAX              => get_string('aggregatemax', 'grades'),
+                GRADE_AGGREGATE_MODE             => get_string('aggregatemode', 'grades'),
+                GRADE_AGGREGATE_SUM              => get_string('aggregatesum', 'grades')
+            );
+        }
+        return self::$aggregationstrings;
+    }
+
     /**
      * Get grade_plugin_info object for managing settings if the user can
      *
index 695ecf3..b004e3e 100644 (file)
@@ -408,10 +408,12 @@ class grade_report_user extends grade_report {
 
             if (!$hide) {
                 /// Excluded Item
+                /**
                 if ($grade_grade->is_excluded()) {
                     $fullname .= ' ['.get_string('excluded', 'grades').']';
                     $excluded = ' excluded';
                 }
+                **/
 
                 /// Other class information
                 $class = "$hidden $excluded";
@@ -456,8 +458,14 @@ class grade_report_user extends grade_report {
                     $data['weight']['content'] = '-';
                     $data['weight']['headers'] = "$header_cat $header_row weight";
                     // has a weight assigned, might be extra credit
-                    if ($grade_object->aggregationcoef > 0 && $type <> 'courseitem') {
-                        $data['weight']['content'] = number_format($grade_object->aggregationcoef,2);
+
+                    $hints = $grade_grade->get_aggregation_hint($grade_object);
+                    if ($hints) {
+                        // This obliterates the weight because it provides a more informative description.
+                        if (intval($hints)) {
+                            $hints = format_float(intval($hints) / 100.0, 2) . ' %';
+                        }
+                        $data['weight']['content'] = $hints;
                     }
                 }
 
old mode 100644 (file)
new mode 100755 (executable)
index 5716152..10bd6f2
         <FIELD NAME="informationformat" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="format of information text"/>
         <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="the time this grade was first created"/>
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="the time this grade was last modified"/>
+        <FIELD NAME="usedinaggregation" TYPE="char" LENGTH="10" NOTNULL="true" DEFAULT="unknown" SEQUENCE="false" COMMENT="One of several values describing how this grade_grade was used when calculating the aggregation. Possible values are &quot;unknown&quot;, &quot;dropped&quot;, &quot;novalue&quot;, &quot;included&quot;"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index 2ae31fa..2e9866b 100644 (file)
@@ -3719,6 +3719,21 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2014072400.01);
     }
 
+    if ($oldversion < 2014080700.00) {
+
+        // Define field usedinaggregation to be added to grade_grades.
+        $table = new xmldb_table('grade_grades');
+        $field = new xmldb_field('usedinaggregation', XMLDB_TYPE_CHAR, '10', null, XMLDB_NOTNULL, null, 'unknown', 'timemodified');
+
+        // Conditionally launch add field usedinaggregation.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2014080700.00);
+    }
+
     if ($oldversion < 2014080801.00) {
 
         // Define index behaviour (not unique) to be added to question_attempts.
index 36dfb76..198b1d8 100644 (file)
@@ -526,6 +526,12 @@ class grade_category extends grade_object {
      */
     private function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded) {
         global $CFG;
+
+        // Remember these so we can set flags on them to describe how they were used in the aggregation.
+        $novalue = array();
+        $dropped = array();
+        $usedweights = array();
+
         if (empty($userid)) {
             //ignore first call
             return;
@@ -552,10 +558,10 @@ class grade_category extends grade_object {
         // can not use own final category grade in calculation
         unset($grade_values[$this->grade_item->id]);
 
-
         // sum is a special aggregation types - it adjusts the min max, does not use relative values
         if ($this->aggregation == GRADE_AGGREGATE_SUM) {
-            $this->sum_grades($grade, $oldfinalgrade, $items, $grade_values, $excluded);
+            $this->sum_grades($grade, $oldfinalgrade, $items, $grade_values, $excluded, $usedweights);
+            $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped);
             return;
         }
 
@@ -566,6 +572,8 @@ class grade_category extends grade_object {
             if (!is_null($oldfinalgrade)) {
                 $grade->update('aggregation');
             }
+            $dropped = $grade_values;
+            $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped);
             return;
         }
 
@@ -578,10 +586,12 @@ class grade_category extends grade_object {
             if (is_null($v)) {
                 // null means no grade
                 unset($grade_values[$itemid]);
+                $novalue[$itemid] = 0;
                 continue;
 
             } else if (in_array($itemid, $excluded)) {
                 unset($grade_values[$itemid]);
+                $dropped[$itemid] = 0;
                 continue;
             }
             // If grademin is hidden, set it to 0.
@@ -603,7 +613,13 @@ class grade_category extends grade_object {
         }
 
         // limit and sort
+        $allvalues = $grade_values;
         $this->apply_limit_rules($grade_values, $items);
+
+        $moredropped = array_diff($allvalues, $grade_values);
+        foreach ($moredropped as $drop => $unused) {
+            $dropped[$drop] = 0;
+        }
         asort($grade_values, SORT_NUMERIC);
 
         // let's see we have still enough grades to do any statistics
@@ -614,11 +630,12 @@ class grade_category extends grade_object {
             if (!is_null($oldfinalgrade)) {
                 $grade->update('aggregation');
             }
+            $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped);
             return;
         }
 
         // do the maths
-        $result = $this->aggregate_values_and_adjust_bounds($grade_values, $items);
+        $result = $this->aggregate_values_and_adjust_bounds($grade_values, $items, $usedweights);
         $agg_grade = $result['grade'];
 
         if (!$minvisible and $this->grade_item->gradetype != GRADE_TYPE_SCALE) {
@@ -635,9 +652,66 @@ class grade_category extends grade_object {
             $grade->update('aggregation');
         }
 
+        $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped);
+
         return;
     }
 
+    /**
+     * Set the flags on the grade_grade items to indicate how individual grades are used
+     * in the aggregation.
+     *
+     * @param int $userid The user we have aggregated the grades for.
+     * @param array $usedweights An array with keys for each of the grade_item columns included in the aggregation. The value are the relative weight.
+     * @param array $novalue An array with keys for each of the grade_item columns skipped because
+     *                       they had no value in the aggregation
+     * @param array $dropped An array with keys for each of the grade_item columns dropped
+     *                       because of any drop lowest/highest settings in the aggregation
+     */
+    private function set_usedinaggregation($userid, $usedweights, $novalue, $dropped) {
+        global $DB;
+
+        // Included.
+        if (!empty($usedweights)) {
+            // The usedweights items are updated individually to record the weights.
+            foreach ($usedweights as $gradeitemid => $contribution) {
+                // Convert contribution to a 4 digit integer so there are no localization problems.
+                $contribution = intval($contribution * 10000);
+                $DB->set_field_select('grade_grades',
+                                      'usedinaggregation',
+                                      $contribution,
+                                      "itemid = :itemid AND userid = :userid",
+                                      array('itemid'=>$gradeitemid, 'userid'=>$userid));
+            }
+        }
+
+        // No value.
+        if (!empty($novalue)) {
+            list($itemsql, $itemlist) = $DB->get_in_or_equal(array_keys($novalue), SQL_PARAMS_NAMED, 'g');
+
+            $itemlist['userid'] = $userid;
+
+            $DB->set_field_select('grade_grades',
+                                  'usedinaggregation',
+                                  'novalue',
+                                  "itemid $itemsql AND userid = :userid",
+                                  $itemlist);
+        }
+
+        // Dropped.
+        if (!empty($dropped)) {
+            list($itemsql, $itemlist) = $DB->get_in_or_equal(array_keys($dropped), SQL_PARAMS_NAMED, 'g');
+
+            $itemlist['userid'] = $userid;
+
+            $DB->set_field_select('grade_grades',
+                                  'usedinaggregation',
+                                  'dropped',
+                                  "itemid $itemsql AND userid = :userid",
+                                  $itemlist);
+        }
+    }
+
     /**
      * Internal function that calculates the aggregated grade and new min/max for this grade category
      *
@@ -646,12 +720,14 @@ class grade_category extends grade_object {
      * @param array $grade_values An array of values to be aggregated
      * @param array $items The array of grade_items
      * @since Moodle 2.6.5, 2.7.2
+     * @param array & $weights If provided, will be filled with the normalized weights
+     *                         for each grade_item as used in the aggregation.
      * @return array containing values for:
      *                'grade' => the new calculated grade
      *                'grademin' => the new calculated min grade for the category
      *                'grademax' => the new calculated max grade for the category
      */
-    public function aggregate_values_and_adjust_bounds($grade_values, $items) {
+    public function aggregate_values_and_adjust_bounds($grade_values, $items, & $weights = null) {
         $category_item = $this->get_grade_item();
         $grademin = $category_item->grademin;
         $grademax = $category_item->grademax;
@@ -668,17 +744,42 @@ class grade_category extends grade_object {
                 } else {
                     $agg_grade = $grades[intval(($num/2)-0.5)];
                 }
+
+                // Record the weights evenly.
+                if ($weights !== null && $num > 0) {
+                    foreach ($grade_values as $itemid=>$grade_value) {
+                        $weights[$itemid] = 1.0 / $num;
+                    }
+                }
                 break;
 
             case GRADE_AGGREGATE_MIN:
                 $agg_grade = reset($grade_values);
+                // Record the weights as used.
+                if ($weights !== null) {
+                    foreach ($grade_values as $itemid=>$grade_value) {
+                        $weights[$itemid] = 0;
+                    }
+                }
+                // Set the first item to 1.
+                $itemids = array_keys($grade_values);
+                $weights[reset($itemids)] = 1;
                 break;
 
             case GRADE_AGGREGATE_MAX:
-                $agg_grade = array_pop($grade_values);
+                // Record the weights as used.
+                if ($weights !== null) {
+                    foreach ($grade_values as $itemid=>$grade_value) {
+                        $weights[$itemid] = 0;
+                    }
+                }
+                // Set the last item to 1.
+                $itemids = array_keys($grade_values);
+                $weights[end($itemids)] = 1;
+                $agg_grade = end($grade_values);
                 break;
 
-            case GRADE_AGGREGATE_MODE:       // the most common value, average used if multimode
+            case GRADE_AGGREGATE_MODE:       // the most common value
                 // array_count_values only counts INT and STRING, so if grades are floats we must convert them to string
                 $converted_grade_values = array();
 
@@ -690,6 +791,9 @@ class grade_category extends grade_object {
                     } else {
                         $converted_grade_values[$k] = $gv;
                     }
+                    if ($weights !== null) {
+                        $weights[$k] = 0;
+                    }
                 }
 
                 $freq = array_count_values($converted_grade_values);
@@ -698,6 +802,14 @@ class grade_category extends grade_object {
                 $modes = array_keys($freq, $top);  // search for all modes (have the same highest count)
                 rsort($modes, SORT_NUMERIC);       // get highest mode
                 $agg_grade = reset($modes);
+                // Record the weights as used.
+                if ($weights !== null && $top > 0) {
+                    foreach ($grade_values as $k => $gv) {
+                        if ($gv == $agg_grade) {
+                            $weights[$k] = 1.0 / $top;
+                        }
+                    }
+                }
                 break;
 
             case GRADE_AGGREGATE_WEIGHTED_MEAN: // Weighted average of all existing final grades, weight specified in coef
@@ -711,13 +823,22 @@ class grade_category extends grade_object {
                     }
                     $weightsum += $items[$itemid]->aggregationcoef;
                     $sum       += $items[$itemid]->aggregationcoef * $grade_value;
+                    if ($weights !== null) {
+                        $weights[$itemid] = $items[$itemid]->aggregationcoef;
+                    }
                 }
-
                 if ($weightsum == 0) {
                     $agg_grade = null;
 
                 } else {
                     $agg_grade = $sum / $weightsum;
+                    if ($weights !== null) {
+                        // Normalise the weights.
+                        foreach ($weights as $itemid => $weight) {
+                            $weights[$itemid] = $weight / $weightsum;
+                        }
+                    }
+
                 }
                 break;
 
@@ -739,13 +860,23 @@ class grade_category extends grade_object {
                     }
                     $sum += $weight * $grade_value;
                 }
-
                 if ($weightsum == 0) {
                     $agg_grade = $sum; // only extra credits
 
                 } else {
                     $agg_grade = $sum / $weightsum;
                 }
+                // Record the weights as used.
+                if ($weights !== null) {
+                    foreach ($grade_values as $itemid=>$grade_value) {
+                        if ($items[$itemid]->aggregationcoef == 0 && $weightsum > 0) {
+                            $weight = $items[$itemid]->grademax - $items[$itemid]->grademin;
+                            $weights[$itemid] = ($items[$itemid]->grademax - $items[$itemid]->grademin) / $weightsum;
+                        } else {
+                            $weights[$itemid] = 0;
+                        }
+                    }
+                }
                 break;
 
             case GRADE_AGGREGATE_EXTRACREDIT_MEAN: // special average
@@ -757,9 +888,22 @@ class grade_category extends grade_object {
                     if ($items[$itemid]->aggregationcoef == 0) {
                         $num += 1;
                         $sum += $grade_value;
+                        if ($weights !== null) {
+                            $weights[$itemid] = 1;
+                        }
 
                     } else if ($items[$itemid]->aggregationcoef > 0) {
                         $sum += $items[$itemid]->aggregationcoef * $grade_value;
+                        if ($weights !== null) {
+                            $weights[$itemid] = 0;
+                        }
+                    }
+                }
+                if ($weights !== null && $num > 0) {
+                    foreach ($grade_values as $itemid=>$grade_value) {
+                        if ($weights[$itemid]) {
+                            $weights[$itemid] = 1.0 / $num;
+                        }
                     }
                 }
 
@@ -781,9 +925,11 @@ class grade_category extends grade_object {
                     $sum += $grade_value * ($items[$itemid]->grademax - $items[$itemid]->grademin);
                     $grademin += $items[$itemid]->grademin;
                     $grademax += $items[$itemid]->grademax;
+                    if ($weights !== null && $num > 0) {
+                        $weights[$itemid] = 1.0 / $num;
+                    }
                 }
 
-                $agg_grade = $sum / ($grademax - $grademin);
                 break;
 
             case GRADE_AGGREGATE_MEAN:    // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum)
@@ -791,6 +937,12 @@ class grade_category extends grade_object {
                 $num = count($grade_values);
                 $sum = array_sum($grade_values);
                 $agg_grade = $sum / $num;
+                // Record the weights evenly.
+                if ($weights !== null && $num > 0) {
+                    foreach ($grade_values as $itemid=>$grade_value) {
+                        $weights[$itemid] = 1.0 / $num;
+                    }
+                }
                 break;
         }
 
@@ -874,12 +1026,19 @@ class grade_category extends grade_object {
      * @param array $items Grade items
      * @param array $grade_values Grade values
      * @param array $excluded Excluded
+     * @param array & $weights For filling with the weights used in the aggregation.
      */
-    private function sum_grades(&$grade, $oldfinalgrade, $items, $grade_values, $excluded) {
+    private function sum_grades(&$grade, $oldfinalgrade, $items, $grade_values, $excluded, & $weights = null) {
         if (empty($items)) {
             return null;
         }
 
+        if ($weights) {
+            foreach ($grade_values as $itemid => $value) {
+                $weights[$itemid] = 0;
+            }
+        }
+
         // ungraded and excluded items are not used in aggregation
         foreach ($grade_values as $itemid=>$v) {
 
@@ -906,6 +1065,11 @@ class grade_category extends grade_object {
 
         $sum = array_sum($grade_values);
         $grade->finalgrade = $this->grade_item->bounded_grade($sum);
+        if ($weights !== null && $sum > 0) {
+            foreach ($grade_values as $itemid => $value) {
+                $weights[$itemid] = $value / $sum;
+            }
+        }
 
         // update in db if changed
         if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
index 279af7c..53ade2e 100644 (file)
@@ -49,7 +49,8 @@ class grade_grade extends grade_object {
      */
     public $required_fields = array('id', 'itemid', 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
                                  'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 'locked',
-                                 'locktime', 'exported', 'overridden', 'excluded', 'timecreated', 'timemodified');
+                                 'locktime', 'exported', 'overridden', 'excluded', 'timecreated',
+                                 'timemodified', 'usedinaggregation');
 
     /**
      * Array of optional fields with default values (these should match db defaults)
@@ -159,6 +160,12 @@ class grade_grade extends grade_object {
      */
     public $timemodified = null;
 
+    /**
+     * Used in aggregation flag. Can be one of 'unknown', 'dropped', 'novalue' or a specific weighting.
+     * @var string $usedinaggregation
+     */
+    public $usedinaggregation = 'unknown';
+
 
     /**
      * Returns array of grades for given grade_item+users
@@ -285,6 +292,26 @@ class grade_grade extends grade_object {
         return $this->timecreated;
     }
 
+    /**
+     * Returns the info on how this value was used in the aggregated grade
+     *
+     * @return string One of 'dropped', 'excluded', 'novalue' or a specific weighting
+     */
+    public function get_usedinaggregation() {
+        return $this->usedinaggregation;
+    }
+
+    /**
+     * Set usedinaggregation flag
+     *
+     * @param string $usedinaggregation
+     * @return void
+     */
+    public function set_usedinaggregation($usedinaggregation) {
+        $this->usedinaggregation = $usedinaggregation;
+        $this->update();
+    }
+
     /**
      * Returns timestamp when last graded, null if no grade present
      *
@@ -923,5 +950,54 @@ class grade_grade extends grade_object {
 
         // Pass information on to completion system
         $completion->inform_grade_changed($cm, $this->grade_item, $this, $deleted);
-     }
+    }
+
+    /**
+     * Get some useful information about how this grade_grade is reflected in the aggregation
+     * for the grade_category. For example this could be an extra credit item, and it could be
+     * dropped because it's in the X lowest or highest.
+     *
+     * @param grade_item $gradeitem An optional grade_item, saves having to load the grade_grade's grade_item
+     * @return string - A list of keywords that hint at how this grade_grade is reflected in the aggregation.
+     */
+    function get_aggregation_hint($gradeitem = null) {
+        $hint = '';
+
+        if ($this->is_excluded()) {
+            $hint = get_string('excluded', 'grades');
+        } else {
+            if (empty($grade_item)) {
+                if (!isset($this->grade_item)) {
+                    $this->load_grade_item();
+                }
+            } else {
+                $this->grade_item = $grade_item;
+                $this->itemid = $grade_item->id;
+            }
+            $item = $this->grade_item;
+
+            if (!$item->is_course_item()) {
+                $parent_category = $item->get_parent_category();
+                $parent_category->apply_forced_settings();
+                if ($parent_category->is_extracredit_used() && ($item->aggregationcoef > 0)) {
+                    $hint = get_string('aggregationcoefextra', 'grades');
+                }
+            }
+
+        }
+
+        // Is it dropped?
+        if ($hint == '') {
+            $aggr = $this->get_usedinaggregation();
+            if ($aggr == 'dropped') {
+                $hint = get_string('dropped', 'grades');
+            } else if ($aggr == 'novalue') {
+                $hint = '-';
+            } else if ($aggr != 'unknown') {
+                $hint = $aggr;
+            }
+        }
+
+        return $hint;
+    }
 }