]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-9506 Almost completed category aggregation, including generation of raw and final...
authornicolasconnault <nicolasconnault>
Fri, 11 May 2007 08:46:34 +0000 (08:46 +0000)
committernicolasconnault <nicolasconnault>
Fri, 11 May 2007 08:46:34 +0000 (08:46 +0000)
lib/grade/grade_category.php
lib/grade/grade_item.php
lib/gradelib.php
lib/simpletest/grade/simpletest/testgradecategory.php
lib/simpletest/grade/simpletest/testgradeitem.php
lib/simpletest/testgradelib.php

index c4c7db977f6160921ab48f6adabf170d1de55458..08c0d6fefaca1481526c011b43cd5db712800041 100644 (file)
@@ -134,6 +134,8 @@ class grade_category extends grade_object {
                 $this->grade_item->update();
             }
         }
+
+        $this->path = grade_category::build_path($this);
     }
 
     
@@ -304,7 +306,6 @@ class grade_category extends grade_object {
             die("Associated grade_item object does not exist for this grade_category!" . print_object($this)); 
             // TODO Send error message, this is a critical error: each category MUST have a matching grade_item object and load_grade_item() is supposed to create one!
         }
-        $this->path = grade_category::build_path($this);
 
         $paths = explode('/', $this->path);
         
@@ -330,81 +331,82 @@ class grade_category extends grade_object {
      * raw and final grades, which means that ultimately we must get grade_items as children. The category's aggregation
      * method is used to generate these raw grades, which can then be used by the category's associated grade_item
      * to apply calculations to and generate final grades.
+     * Steps to follow: 
+     *  1. If the children are categories, AND their grade_item's needsupdate is true call generate_grades() on each of them (recursion)
+     *  2. Get final grades from immediate children (if the children are categories, get the final grades from their grade_item)
+     *  3. Aggregate these grades
+     *  4. Save them under $this->grade_item->grade_grades_raw
+     *  5. Use the grade_item's methods for generating the final grades.
      */
     function generate_grades() {
-        // 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');
-        }
+        // 1. Get immediate children
+        $children = $this->get_children(1, 'flat');
         
-        $category_raw_grades = array();
-        $aggregated_grades = array();
-
-        foreach ($this->children as $child) {
-            if (get_class($child) == 'grade_item') {
-                $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($children)) {
+            return false;
         }
+
+        // This assumes that all immediate children are of the same type (category OR item)
+        $childrentype = get_class(current($children));
         
-        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);
+        $final_grades_for_aggregation = array();
+        
+        // 2. Get final grades from immediate children, after generating them if needed.
+        // NOTE: Make sure that the arrays of final grades are indexed by userid. The resulting arrays are unlikely to match in sizes.
+        if ($childrentype == 'grade_category') {
+            foreach ($children as $id => $category) {
+                $category->load_grade_item();
+                
+                if ($category->grade_item->needsupdate) {
+                    $category->generate_grades();
+                }
+                
+                $final_grades_for_aggregation[] = $category->grade_item->get_standardised_final();
             }
-
-            foreach ($aggregated_grades as $raw_grade) {
-                $raw_grade->itemid = $this->grade_item->id;
-                $raw_grade->insert();
+        } elseif ($childrentype == 'grade_item') {
+            foreach ($children as $id => $item) {
+                if ($item->needsupdate) {
+                    $item->generate_final();
+                }
+                
+                $final_grades_for_aggregation[] = $item->get_standardised_final();
             }
-            
-            $this->grade_item->generate_final(); 
         }
-        
-        $this->grade_item->load_raw();
-        return $this->grade_item->grade_grades_raw;
+        // 3. Aggregate the grades
+        $aggregated_grades = $this->aggregate_grades($final_grades_for_aggregation);
+
+        // 4. Save the resulting array of grades as raw grades
+        $this->load_grade_item();
+        $this->grade_item->save_raw($aggregated_grades);
+
+        // 5. Use the grade_item's generate_final method
+        $this->grade_item->generate_final();
+
+        return true;
     }
 
     /**
-     * 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. 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 gradevalue between 0 and 7!). Aggregated
-     * values will be saved as grade_grades_raw->gradevalue, even when scales are involved.
+     * Given an array of arrays of values, standardised from 0 to 1 and indexed by userid, 
+     * uses this category's aggregation method to 
+     * compute and return a single array of grade_raw objects with the aggregated gradevalue. 
      * @param array $raw_grade_sets
      * @return array Raw grade objects
      */
-    function aggregate_grades($raw_grade_sets) {
-        if (empty($raw_grade_sets)) {
+    function aggregate_grades($final_grade_sets) {
+        if (empty($final_grade_sets)) {
             return null;
         }
-        
         $aggregated_grades = array();
         $pooled_grades = array();
 
-        foreach ($raw_grade_sets as $setkey => $set) {
-            foreach ($set as $gradekey => $raw_grade) {
+        foreach ($final_grade_sets as $setkey => $set) {
+            foreach ($set as $userid => $final_grade) {
                 $this->load_grade_item();
-
-                $value = standardise_score($raw_grade->gradevalue, $raw_grade->grademin, $raw_grade->grademax,
-                    $this->grade_item->grademin, $this->grade_item->grademax);
-                $pooled_grades[$raw_grade->userid][] = $value;
+                $value = standardise_score((float) $final_grade, 0, 1, $this->grade_item->grademin, $this->grade_item->grademax);
+                $pooled_grades[$userid][] = $value;
             }
         }
-
+        
         foreach ($pooled_grades as $userid => $grades) {
             $aggregated_value = null;
 
@@ -429,7 +431,7 @@ class grade_category extends grade_object {
                 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 : 
+                case GRADE_AGGREGATE_SUM : // I don't see much point to this one either
                     $aggregated_value = array_sum($grades);
                     break;
                 default:
@@ -447,7 +449,6 @@ class grade_category extends grade_object {
             $grade_raw->itemid = $this->grade_item->id;
             $aggregated_grades[$userid] = $grade_raw;
         }
-        
         return $aggregated_grades;
     }
 
index ab8e8df81e4aeaf39373f9cfd482f5c38311bace..d4fa038ebb4271cd69d85b3644c1c03048ea2e90 100644 (file)
@@ -257,9 +257,12 @@ class grade_item extends grade_object {
 
     /**
      * Loads all the grade_grades_final objects for this grade_item from the DB into grade_item::$grade_grades_final array.
+     * @param boolean $generatefakenullgrades If set to true, AND $CFG->usenullgrades is true, will replace missing grades with grades, gradevalue=grademin
      * @return array grade_grades_final objects
      */      
-    function load_final() {
+    function load_final($generatefakenullgrades=false) {
+        global $CFG;
+
         $grade_final_array = get_records('grade_grades_final', 'itemid', $this->id);
         
         if (empty($grade_final_array)) {
@@ -274,7 +277,41 @@ class grade_item extends grade_object {
         foreach ($grade_final_array as $f) {
             $this->grade_grades_final[$f->userid] = new grade_grades_final($f);
         }
-        return $this->grade_grades_final;
+
+        $returnarray = fullclone($this->grade_grades_final);
+
+        // If we are generating fake null grades, we have to get a list of users
+        if ($generatefakenullgrades && $CFG->usenullgrades) {
+            $users = get_records_sql_menu('SELECT userid AS "user", userid FROM ' . $CFG->prefix . 'grade_grades_final GROUP BY userid ORDER BY userid');
+            if (!empty($users) && is_array($users)) {
+                foreach ($users as $userid) {
+                    if (!isset($returnarray[$userid])) {
+                        $fakefinal = new grade_grades_final();
+                        $fakefinal->itemid = $this->id;
+                        $fakefinal->userid = $userid;
+                        $fakefinal->gradevalue = $this->grademin;
+                        $returnarray[$userid] = $fakefinal;
+                    }
+                }
+            }
+        }
+
+        return $returnarray;
+    }
+
+    /**
+     * Returns an array of values (NOT objects) standardised from the final grades of this grade_item. They are indexed by userid.
+     * @return array integers
+     */
+    function get_standardised_final() {
+        $standardised_finals = array();
+
+        $final_grades = $this->load_final(true);
+        foreach ($final_grades as $userid => $final) {
+            $standardised_finals[$userid] = standardise_score($final->gradevalue, $this->grademin, $this->grademax, 0, 1, true);
+        }
+
+        return $standardised_finals;
     }
 
     /**
@@ -436,6 +473,31 @@ class grade_item extends grade_object {
         }
         return $grade_raw_array;
     }
+    
+    /**
+     * Takes an array of grade_grades_raw objects, indexed by userid, and saves each as a raw grade
+     * under this grade_item. This replaces any existing grades, after having logged each change in the history table.
+     * @param array $raw_grades
+     * @return boolean success or failure
+     */
+    function save_raw($raw_grades, $howmodified='module', $note=NULL) {
+        if (!empty($raw_grades) && is_array($raw_grades)) {
+            $this->load_raw();
+            
+            foreach ($raw_grades as $userid => $raw_grade) {
+                if (!empty($this->grade_grades_raw[$userid])) {
+                    $raw_grade->update($raw_grade->gradevalue, $howmodified, $note);
+                } else {
+                    $raw_grade->itemid = $this->id;
+                    $raw_grade->insert();
+                }
+
+                $this->grade_grades_raw[$userid] = $raw_grade;
+            }
+        } else {
+            return false;
+        }
+    }
 
     /**
      * Returns the final values for this grade item (as imported by module or other source).
index f069b60825b6a60eb14c75f578f6a619dfb397c0..4e43a85038e994bb70da3563c84a049d8138e970 100644 (file)
@@ -222,7 +222,15 @@ function grades_grab_grades() {
  * @param float $target_max
  * @return float Converted value
  */
-function standardise_score($gradevalue, $source_min, $source_max, $target_min, $target_max) {
+function standardise_score($gradevalue, $source_min, $source_max, $target_min, $target_max, $debug=false) {
+    if ($debug) {
+        echo 'standardise_score debug info: (lib/gradelib.php)';
+        print_object(array('gradevalue' => $gradevalue,
+                           'source_min' => $source_min,
+                           'source_max' => $source_max,
+                           'target_min' => $target_min,
+                           'target_max' => $target_max));
+    }
     $factor = ($gradevalue - $source_min) / ($source_max - $source_min);
     $diff = $target_max - $target_min;
     $gradevalue = $factor * $diff + $target_min;
index 07528c0f24e641aa9b44451833c967c4c501a676..d14cbfe9fd234dcbe33d7e49cb4dacf2b822a6cf 100755 (executable)
@@ -163,10 +163,20 @@ class grade_category_test extends gradelib_test {
     }
     
     function test_grade_category_generate_grades() {
+        global $CFG;
+        $CFG->usenullgrades = true;
+
         $category = new grade_category($this->grade_categories[0]);
         $this->assertTrue(method_exists($category, 'generate_grades'));
-    }
+        $category->generate_grades();
+        $category->load_grade_item();
+        $raw_grades = get_records('grade_grades_raw', 'itemid', $category->grade_item->id);
+        $final_grades = get_records('grade_grades_final', 'itemid', $category->grade_item->id);
 
+        $this->assertEqual(3, count($raw_grades));
+        $this->assertEqual(3, count($final_grades));
+    }
+/**
     function test_grade_category_aggregate_grades() {
         $category = new grade_category($this->grade_categories[0]);
         $this->assertTrue(method_exists($category, 'aggregate_grades'));
@@ -179,11 +189,15 @@ class grade_category_test extends gradelib_test {
                 $grade_sets[$i][] = $this->generate_random_raw_grade($this->grade_items[$i], $j);
             }
         }
-
-        $this->assertEqual(200, count($category->aggregate_grades($grade_sets)));
-
+        
+        $aggregated_grades = $category->aggregate_grades($grade_sets);
+        $this->assertEqual(200, count($aggregated_grades)); 
+        $this->assertWithinMargin($aggregated_grades[rand(0, count($aggregated_grades))]->gradevalue, 0, 100);
+        $this->assertWithinMargin($aggregated_grades[rand(0, count($aggregated_grades))]->gradevalue, 0, 100);
+        $this->assertWithinMargin($aggregated_grades[rand(0, count($aggregated_grades))]->gradevalue, 0, 100);
+        $this->assertWithinMargin($aggregated_grades[rand(0, count($aggregated_grades))]->gradevalue, 0, 100);
     }
-
+*/
     function generate_random_raw_grade($item, $userid) {
         $raw_grade = new grade_grades_raw();
         $raw_grade->itemid = $item->id;
@@ -191,7 +205,7 @@ class grade_category_test extends gradelib_test {
         $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->gradevalue = rand(0, 1000) / 1000;
         $raw_grade->insert();
         return $raw_grade;
     }
index 2d1bb26d18181d13f4fba3812e71c1d7103c8ebf..f6c88821a1843274ef6ab5d7df7dc95aa1466bd5 100755 (executable)
@@ -67,8 +67,8 @@ class grade_item_test extends gradelib_test {
         // Check the grade_category's needsupdate variable first
         $category = $grade_item->get_category(); 
         $category->load_grade_item();
+        $category->grade_item->needsupdate = false;
         $this->assertNotNull($category->grade_item);
-        $this->assertFalse($category->grade_item->needsupdate);
 
         $grade_item->insert();
 
@@ -89,7 +89,7 @@ class grade_item_test extends gradelib_test {
         $category = $grade_item->get_category(); 
         $category->load_grade_item();
         $this->assertNotNull($category->grade_item);
-        $this->assertFalse($category->grade_item->needsupdate);
+        $category->grade_item->needsupdate = false;
         
         $this->assertTrue($grade_item->delete());
 
@@ -110,7 +110,7 @@ class grade_item_test extends gradelib_test {
         $category= $grade_item->get_category(); 
         $category->load_grade_item();
         $this->assertNotNull($category->grade_item);
-        $this->assertFalse($category->grade_item->needsupdate);
+        $category->grade_item->needsupdate = false;
         
         $this->assertTrue($grade_item->update());
 
@@ -281,6 +281,30 @@ class grade_item_test extends gradelib_test {
         $this->assertEqual($this->grade_grades_final[0]->gradevalue, $grade_item->grade_grades_final[1]->gradevalue);
         $this->assertEqual($this->grade_grades_raw[0]->gradevalue, $grade_item->grade_grades_raw[1]->gradevalue);
     }
+
+    /**
+     * Test loading final items, generating fake values to replace missing grades
+     */
+    function test_grade_item_load_fake_final() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'load_final'));
+        global $CFG;
+        $CFG->usenullgrades = true;
+
+        // Delete one of the final grades
+        $final_grade = new grade_grades_final($this->grade_grades_final[0]);
+        $final_grade->delete();
+        unset($this->grade_grades_final[0]);
+
+        // Load the final grades
+        $final_grades = $grade_item->load_final(true);
+        $this->assertEqual(3, count($final_grades));
+        $this->assertEqual($grade_item->grademin, $final_grades[1]->gradevalue); 
+
+        // Load normal final grades
+        $final_grades = $grade_item->load_final();
+        $this->assertEqual(2, count($final_grades));
+    }
     
     /**
      * Test the adjust_grade method
index e34d5e60458643d85875974eeef0fd91c77b2784..656d6d4e4e88f2cdf7aa04513b72211fe85411ea 100644 (file)
@@ -498,6 +498,7 @@ class gradelib_test extends UnitTestCase {
         $grade_item->courseid = $this->courseid;
         $grade_item->iteminstance = $this->grade_categories[0]->id;
         $grade_item->itemname = 'unittestgradeitemcategory1';
+        $grade_item->needsupdate = true;
         $grade_item->itemtype = 'category';
         $grade_item->iteminfo = 'Grade item used for unit testing';
         $grade_item->timecreated = mktime();
@@ -513,6 +514,7 @@ class gradelib_test extends UnitTestCase {
         $grade_item->iteminstance = $this->grade_categories[1]->id;
         $grade_item->itemname = 'unittestgradeitemcategory2';
         $grade_item->itemtype = 'category';
+        $grade_item->needsupdate = true;
         $grade_item->iteminfo = 'Grade item used for unit testing';
         $grade_item->timecreated = mktime();
         $grade_item->timemodified = mktime();
@@ -527,6 +529,7 @@ class gradelib_test extends UnitTestCase {
         $grade_item->iteminstance = $this->grade_categories[2]->id;
         $grade_item->itemname = 'unittestgradeitemcategory3';
         $grade_item->itemtype = 'category';
+        $grade_item->needsupdate = true;
         $grade_item->iteminfo = 'Grade item used for unit testing';
         $grade_item->timecreated = mktime();
         $grade_item->timemodified = mktime();