]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-9506 Stuck on grade_category->generate_grades. I cannot figure out how to recursi...
authornicolasconnault <nicolasconnault>
Tue, 8 May 2007 08:01:55 +0000 (08:01 +0000)
committernicolasconnault <nicolasconnault>
Tue, 8 May 2007 08:01:55 +0000 (08:01 +0000)
lib/grade/grade_calculation.php
lib/grade/grade_category.php
lib/grade/grade_item.php
lib/grade/grade_object.php
lib/gradelib.php
lib/simpletest/grade/simpletest/testgradecategory.php
lib/simpletest/grade/simpletest/testgradeitem.php
lib/simpletest/testgradelib.php

index 8fec2e23d3f8562ccab812e83ff8fb1135a5ac76..1508dc89a2188cc16960a95ef3f9de260c1082fd 100644 (file)
@@ -70,10 +70,9 @@ class grade_calculation extends grade_object {
     /**
      * Applies the formula represented by this object to the value given, and returns the result.
      * @param float $oldvalue
-     * @param string $valuetype Either 'gradevalue' or 'gradescale'
      * @return float result
      */
-    function compute($oldvalue, $valuetype = 'gradevalue') {
+    function compute($oldvalue) {
         return $oldvalue; // TODO implement computation using parser
     }
 
index e05f223e1b4bb4abb04191a29eea39de6f7ce5a0..b161019c7960087a2d6e7008bd37067c1dc40cf4 100644 (file)
@@ -219,7 +219,7 @@ class grade_category extends grade_object {
      * to apply calculations to and generate final grades.
      */
     function generate_grades() {
-        // Check that the children have final grades. If not, call their generate_raw_grades method (recursion)
+        // Check that the children have final grades. If not, call their generate_grades method (recursion)
         if (empty($this->children)) {
             $this->children = $this->get_children(1, 'flat');
         }
@@ -229,34 +229,118 @@ class grade_category extends grade_object {
 
         foreach ($this->children as $child) {
             if (get_class($child) == 'grade_item') {
-                $category_raw_grades[$child->id] = $child->load_final();
-            } elseif ($get_class($child) == 'grade_category') {
-                $category_raw_grades[$child->id] = $child->load_final();
-                if (empty($category_raw_grades)) {
-                    $category_raw_grades[$child->id] = $child->generate_grades();
+                $category_raw_grades[$child->id] = $child->load_raw();
+            } elseif (get_class($child) == 'grade_category') {
+                $child->load_grade_item();
+                $raw_grades = $child->grade_item->load_raw();
+                
+                if (empty($raw_grades)) {
+                    $child->generate_grades(); 
+                    $category_raw_grades[$child->id] = $child->grade_item->load_raw();
+                } else {
+                    $category_raw_grades[$child->id] = $raw_grades;
                 } 
             }
         }
-
+        
         if (empty($category_raw_grades)) {
             return null;
         } else {
             $aggregated_grades = $this->aggregate_grades($category_raw_grades);
+            
+            if (count($category_raw_grades) == 1) {
+                $aggregated_grades = current($category_raw_grades);
+            }
+
             foreach ($aggregated_grades as $raw_grade) {
+                $raw_grade->itemid = $this->grade_item->id;
                 $raw_grade->insert();
             }
-            $this->grade_item->generate_final();
+            $this->load_grade_item();
+            $this->grade_item->generate_final(); 
         }
+        
+        $this->grade_item->load_raw();
+        return $this->grade_item->grade_grades_raw;
     }
 
     /**
      * Given an array of arrays of grade objects (raw or final), uses this category's aggregation method to 
-     * compute and return a single array of grade_raw objects with the aggregated gradevalue.
+     * compute and return a single array of grade_raw objects with the aggregated gradevalue. This method
+     * must also standardise all the scores (which have different mins and maxs) so that their values can
+     * be meaningfully aggregated (it would make no sense to perform MEAN(239, 5) on a grade_item with a 
+     * gradevalue between 20 and 250 and another grade_item with a gradescale between 0 and 7!). Aggregated
+     * values will be saved as grade_grades_raw->gradevalue, even when scales are involved.
      * @param array $raw_grade_sets
      * @return array Raw grade objects
      */
     function aggregate_grades($raw_grade_sets) {
+        if (empty($raw_grade_sets)) {
+            return null;
+        }
         
+        $aggregated_grades = array();
+        $pooled_grades = array();
+
+        foreach ($raw_grade_sets as $setkey => $set) {
+            foreach ($set as $gradekey => $raw_grade) {
+                $valuetype = 'gradevalue';
+                
+                if (!empty($raw_grade->gradescale)) {
+                    $valuetype = 'gradescale';
+                }
+                $this->load_grade_item();
+
+                $value = standardise_score($raw_grade->$valuetype, $raw_grade->grademin, $raw_grade->grademax,
+                    $this->grade_item->grademin, $this->grade_item->grademax);
+                $pooled_grades[$raw_grade->userid][] = $value;
+            }
+        }
+
+        foreach ($pooled_grades as $userid => $grades) {
+            $aggregated_value = null;
+
+            switch ($this->aggregation) {
+                case GRADE_AGGREGATE_MEAN : // Arithmetic average
+                    $num = count($grades);
+                    $sum = array_sum($grades);
+                    $aggregated_value = $sum / $num;
+                    break;
+                case GRADE_AGGREGATE_MEDIAN : // Middle point value in the set: ignores frequencies
+                    sort($grades);
+                    $num = count($grades);
+                    $halfpoint = intval($num / 2);
+                    
+                    if($num % 2 == 0) { 
+                        $aggregated_value = ($grades[ceil($halfpoint)] + $grades[floor($halfpoint)]) / 2; 
+                    } else { 
+                        $aggregated_value = $grades[$halfpoint]; 
+                    }
+
+                    break;
+                case GRADE_AGGREGATE_MODE : // Value that occurs most frequently. Not always useful (all values are likely to be different)
+                    // TODO implement or reject
+                    break;
+                case GRADE_AGGREGATE_SUM : 
+                    $aggregated_value = array_sum($grades);
+                    break;
+                default:
+                    $num = count($grades);
+                    $sum = array_sum($grades);
+                    $aggregated_value = $sum / $num; 
+                    break;
+            }
+
+            $grade_raw = new grade_grades_raw();
+            $grade_raw->userid = $userid;
+            $grade_raw->gradevalue = $aggregated_value;
+            $grade_raw->grademin = $this->grade_item->grademin;
+            $grade_raw->grademax = $this->grade_item->grademax;
+            $grade_raw->itemid = $this->grade_item->id;
+            $aggregated_grades[$userid] = $grade_raw;
+        }
+        
+        return $aggregated_grades;
     }
 
     /**
@@ -414,6 +498,15 @@ class grade_category extends grade_object {
     function load_grade_item() {
         $params = get_record('grade_items', 'categoryid', $this->id, 'itemtype', 'category');
         $this->grade_item = new grade_item($params);
+        
+        // If the associated grade_item isn't yet created, do it now
+        if (empty($this->grade_item->id)) {
+            $this->grade_item->iteminstance = $this->id;
+            $this->grade_item->itemtype = 'category';
+            $this->grade_item->insert();
+            $this->grade_item->update_from_db();
+        }
+
         return $this->grade_item;
     }
 }
index c559469c5d42fba9a2e0f4910ad19b73ec3ccae7..27862020ff7a4e605614f016a8384fc1f496b22f 100644 (file)
@@ -296,6 +296,11 @@ class grade_item extends grade_object {
      */      
     function load_raw() {
         $grade_raw_array = get_records('grade_grades_raw', 'itemid', $this->id);
+
+        if (empty($grade_raw_array)) {
+            return null;
+        }
+
         foreach ($grade_raw_array as $r) {
             $this->grade_grades_raw[$r->userid] = new grade_grades_raw($r);
         }
@@ -567,24 +572,21 @@ class grade_item extends grade_object {
 
         foreach ($grade_raw_array as $userid => $raw) {
             // the value could be gradevalue or gradescale
-            $valuetype = null;
-            
-            if (!empty($raw->gradevalue)) {
-                $valuetype = 'gradevalue';
-            } elseif (!empty($raw->gradescale)) {
-                $valuetype = 'gradescale';
+            $valuetype = "grade$this->gradetype";
+            if (empty($raw->$valuetype)) {
+                return 'ERROR! The raw grade has no value for ' . $valuetype . ' (or the grade_item has no gradetype.)';
             }
-
+            
             $newgradevalue = $raw->$valuetype;
             
             if (!empty($this->calculation)) {
                 $this->upgrade_calculation_to_object();
-                $newgradevalue = $this->calculation->compute($raw->$valuetype, $valuetype);
+                $newgradevalue = $this->calculation->compute($raw->$valuetype);
             }
             
             $final = $this->grade_grades_final[$userid];
 
-            $final->$valuetype = $this->adjust_grade($raw, $newgradevalue, $valuetype);
+            $final->$valuetype = $this->adjust_grade($raw, $newgradevalue);
             
             if ($final->update()) {
                 $count++;
@@ -612,19 +614,18 @@ class grade_item extends grade_object {
      * @param object $grade_raw The raw object to compare with this grade_item's rules
      * @param mixed $gradevalue The new gradevalue (after calculations are performed).
      *                          If null, the raw_grade's gradevalue or gradescale will be used.
-     * @param string $valuetype Either 'gradevalue' or 'gradescale'
      * @return mixed 
      */
-    function adjust_grade($grade_raw, $gradevalue=NULL, $valuetype='gradevalue') {
+    function adjust_grade($grade_raw, $gradevalue=NULL) {
         $raw_offset = 0;
         $item_offset = 0;
-        
-        if ($valuetype == 'gradevalue') { // Dealing with numerical grade
+
+        if ($this->gradetype == 'value') { // Dealing with numerical grade
             if (empty($gradevalue)) {
                 $gradevalue = $grade_raw->gradevalue;
             }
 
-        } elseif($valuetype == 'gradescale') { // Dealing with a scale value
+        } elseif($this->gradetype == 'scale') { // Dealing with a scale value
             if (empty($gradevalue)) {
                 $gradevalue = $grade_raw->gradescale;
             }
@@ -647,23 +648,20 @@ class grade_item extends grade_object {
             return false;
         }
         
-        /**
-         * Darlene's formula
-         */
-        $factor = ($gradevalue - $grade_raw->grademin) / ($grade_raw->grademax - $grade_raw->grademin);
-        $diff = $this->grademax - $this->grademin;
-        $gradevalue = $factor * $diff + $this->grademin;
+        // Standardise score to the new grade range
+        $gradevalue = standardise_score($gradevalue, $grade_raw->grademin, 
+                $grade_raw->grademax, $this->grademin, $this->grademax);
 
         // Apply rounding or factors, depending on whether it's a scale or value
-        if ($valuetype == 'gradevalue') {
+        if ($this->gradetype == 'value') {
             // Apply other grade_item factors
             $gradevalue *= $this->multfactor;
             $gradevalue += $this->plusfactor;
-        } elseif ($valuetype == 'gradescale') {
+        } elseif ($this->gradetype == 'scale') {
             $gradevalue = (int) round($gradevalue);
         }
-
+        
         return $gradevalue;
-    }
+    } 
 }
 ?>
index 8ef6e192dffce7a99020d9c23e2ddfbba04976af..d234079d011a869a0a6013e7b03469b4da8e5b64 100644 (file)
@@ -121,6 +121,27 @@ class grade_object {
         $this->id = insert_record($this->table, $clone, true);
         return $this->id;
     }
+
+    /**
+     * Using this object's id field, fetches the matching record in the DB, and looks at
+     * each variable in turn. If the DB has different data, the db's data is used to update
+     * the object. This is different from the update() function, which acts on the DB record
+     * based on the object.
+     */
+    function update_from_db() {
+        if (empty($this->id)) {
+            return false;
+        } else {
+            $class = get_class($this);
+            $object = new $class(array('id' => $this->id));
+            foreach ($object as $var => $val) {
+                if ($this->$var != $val) {
+                    $this->$var = $val;
+                }
+            }
+        }
+        return true;
+    }
     
     /**
      * Uses the variables of this object to retrieve all matching objects from the DB.
index 259da1153a19a9104f3ac91c67f4e4ae96cfc483..799dee06ac24259ab42c501f21de1475a1a25d51 100644 (file)
@@ -208,4 +208,21 @@ function grades_grab_grades() {
     }
 }
 
+/**
+ * Given a float value situated between a source minimum and a source maximum, converts it to the
+ * corresponding value situated between a target minimum and a target maximum. Thanks to Darlene
+ * for the formula :-)
+ * @param float $gradevalue
+ * @param float $source_min
+ * @param float $source_max
+ * @param float $target_min
+ * @param float $target_max
+ * @return float Converted value
+ */
+function standardise_score($gradevalue, $source_min, $source_max, $target_min, $target_max) {
+    $factor = ($gradevalue - $source_min) / ($source_max - $source_min);
+    $diff = $target_max - $target_min;
+    $gradevalue = $factor * $diff + $target_min;
+    return $gradevalue; 
+}
 ?>
index a89a56e6603911dacec1610bdfb0bb58f83ff978..2c44cdab1739674b9f11f65436f3956e7318f6a2 100755 (executable)
@@ -159,6 +159,41 @@ class grade_category_test extends gradelib_test {
         $category = new grade_category();
         $this->assertFalse($category->has_children()); 
     }
+    
+    function test_grade_category_generate_grades() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'generate_grades'));
+        $raw_grades = $category->generate_grades();
+        $this->assertEqual(3, count($raw_grades));
+    }
 
+    function test_grade_category_aggregate_grades() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'aggregate_grades'));
+        
+        // Generate 3 random data sets
+        $grade_sets = array();
+        
+        for ($i = 0; $i < 3; $i++) {
+            for ($j = 0; $j < 200; $j++) {
+                $grade_sets[$i][] = $this->generate_random_raw_grade($this->grade_items[$i], $j);
+            }
+        }
+
+        $this->assertEqual(200, count($category->aggregate_grades($grade_sets)));
+
+    }
+
+    function generate_random_raw_grade($item, $userid) {
+        $raw_grade = new grade_grades_raw();
+        $raw_grade->itemid = $item->id;
+        $raw_grade->userid = $userid;
+        $raw_grade->grademin = $item->grademin;
+        $raw_grade->grademax = $item->grademax;
+        $valuetype = "grade$item->gradetype";
+        $raw_grade->$valuetype = rand($raw_grade->grademin, $raw_grade->grademax);
+        $raw_grade->insert();
+        return $raw_grade;
+    }
 } 
 ?>
index c6cf4a102cf7f9ee4aba8e8c5993d7f803adf5c9..ee03453393fb8cde428f3d86d93754c89ac69969 100755 (executable)
@@ -401,8 +401,8 @@ class grade_item_test extends gradelib_test {
         $this->assertEqual(3, count($grade_item->grade_grades_raw));
 
         $grade_item->generate_final();
-        $this->assertEqual(3, count($grade_item->grade_grades_final));
-
+        $grade_item->load_final();
+        $this->assertEqual(3, count($grade_item->grade_grades_final)); 
     }
 } 
 ?>
index a065957f5306662a67683c58c2b2bafac9dffceb..4966cbe2f74880a20c638774de60e53f31d078ee 100644 (file)
@@ -174,7 +174,9 @@ class gradelib_test extends UnitTestCase {
         $grade_item->itemtype = 'mod';
         $grade_item->itemmodule = 'quiz';
         $grade_item->iteminstance = 1;
-        $grade_item->grademax = 137;
+        $grade_item->gradetype = 'value';
+        $grade_item->grademin = 30;
+        $grade_item->grademax = 140;
         $grade_item->itemnumber = 1;
         $grade_item->iteminfo = 'Grade item used for unit testing';
         $grade_item->timecreated = mktime();
@@ -191,13 +193,16 @@ class gradelib_test extends UnitTestCase {
         $grade_item->itemname = 'unittestgradeitem2';
         $grade_item->itemtype = 'import';
         $grade_item->itemmodule = 'assignment';
+        $grade_item->gradetype = 'value';
         $grade_item->iteminstance = 2;
         $grade_item->itemnumber = null;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 100;
         $grade_item->iteminfo = 'Grade item used for unit testing';
         $grade_item->locked = mktime() + 240000;
         $grade_item->timecreated = mktime();
         $grade_item->timemodified = mktime();
-
+        
         if ($grade_item->id = insert_record('grade_items', $grade_item)) {
             $this->grade_items[] = $grade_item;
         }
@@ -210,7 +215,10 @@ class gradelib_test extends UnitTestCase {
         $grade_item->itemtype = 'mod';
         $grade_item->itemmodule = 'forum';
         $grade_item->iteminstance = 3;
-        $grade_item->itemnumber = 3;
+        $grade_item->gradetype = 'scale';
+        $grade_item->scaleid = 1;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 7;
         $grade_item->iteminfo = 'Grade item used for unit testing';
         $grade_item->timecreated = mktime();
         $grade_item->timemodified = mktime();
@@ -742,6 +750,11 @@ class gradelib_test extends UnitTestCase {
             $this->assertTrue(grade_is_locked($grade_item->itemtype, $grade_item->itemmodule, $grade_item->iteminstance, $grade_item->itemnumber)); 
         }
     }
+
+    function test_grade_standardise_score() {
+        $this->assertEqual(4, round(standardise_score(6, 0, 7, 0, 5)));
+        $this->assertEqual(40, standardise_score(50, 30, 80, 0, 100));
+    }
 }
 
 ?>