/**
* 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
}
* 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');
}
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;
}
/**
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;
}
}
*/
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);
}
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++;
* @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;
}
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;
- }
+ }
}
?>
$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.
}
}
+/**
+ * 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;
+}
?>
$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;
+ }
}
?>
$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));
}
}
?>
$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();
$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;
}
$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();
$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));
+ }
}
?>