MDL-9506 Stuck on grade_category->generate_grades. I cannot figure out how to recursively generate raw grades for each category's associated grade_item based on that category's children categories and items. Heaps of other changes with this commit, including a new grade_object::update_from_db() method, which uses the state of the record in DB to update the current object with a matching id (useful when you insert an incomplete object in the DB and want to get the default values as set up in the DB).

This commit is contained in:
nicolasconnault
2007-05-08 08:01:55 +00:00
parent 33cf55bf96
commit 2c72af1f08
8 changed files with 216 additions and 40 deletions
+1 -2
View 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
}
+102 -9
View 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;
}
}
+22 -24
View 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;
}
}
}
?>
+21
View 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.
+17
View 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;
}
?>
@@ -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;
}
}
?>
@@ -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));
}
}
?>
+16 -3
View 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));
}
}
?>