Skip to content

Commit

Permalink
Matrix: Reimplement get_possible_responses() for stats #746912
Browse files Browse the repository at this point in the history
  • Loading branch information
mkassaei authored and timhunt committed Feb 21, 2024
1 parent 64dd6f7 commit d8c365c
Show file tree
Hide file tree
Showing 7 changed files with 402 additions and 23 deletions.
2 changes: 1 addition & 1 deletion classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class provider implements
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection) : collection {
public static function get_metadata(collection $collection): collection {
$collection->add_user_preference('qtype_oumatrix_defaultmark', 'privacy:preference:defaultmark');
$collection->add_user_preference('qtype_oumatrix_penalty', 'privacy:preference:penalty');
$collection->add_user_preference('qtype_oumatrix_inputtype', 'privacy:preference:inputtype');
Expand Down
126 changes: 126 additions & 0 deletions question.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public function apply_attempt_state(question_attempt_step $step) {

/**
* Returns the roworder of the question being displayed.
*
* @param question_attempt $qa
* @return array|null
*/
Expand Down Expand Up @@ -182,6 +183,36 @@ public function has_specific_feedback(): bool {
}
return false;
}

/**
* Return a colun object.
*
* @param int $number
* @return object column|null
*/
protected function get_column_by_number(int $number): ?qtype_oumatrix\column {
foreach ($this->columns as $column) {
if ($column->number == $number) {
return $column;
}
}
return null;
}

/**
* Return a row object.
*
* @param int $rowid
* @return row|null
*/
protected function get_row_by_id(int $rowid): ?qtype_oumatrix\row {
foreach ($this->rows as $row) {
if ($row->id == $rowid) {
return $row;
}
}
return null;
}
}

/**
Expand Down Expand Up @@ -269,6 +300,56 @@ public function is_complete_response(array $response): bool {
return true;
}

public function classify_response(array $response): array {
$selectedchoices = [];
foreach ($this->roworder as $key => $rowid) {
$fieldname = $this->field($key);
if (array_key_exists($fieldname, $response) && $response[$fieldname]) {
$selectedchoices[$rowid] = $response[$fieldname];
} else {
$selectedchoices[$rowid] = 0;
}
}

$choices = [];
foreach ($selectedchoices as $rowid => $colnumber) {
if ($selectedchoices[$rowid] === 0) {
$choices[$rowid] = question_classified_response::no_response();
continue;
}
$column = $this->get_column_by_number($colnumber);
if (in_array($colnumber, array_keys($this->rows[$rowid]->correctanswers))) {
$fraction = 1;
} else {
$fraction = 0;
}
$choices[$rowid] = new question_classified_response($column->id, $column->name, $fraction);
}
return $choices;
}

public function prepare_simulated_post_data($simulatedresponse): array {
$postdata = [];
$subquestions = array_keys($simulatedresponse);
$answers = array_values($simulatedresponse);

foreach ($this->roworder as $key => $rowid) {
$row = $this->rows[$rowid];
if ($row->name !== $subquestions[$key]) {
continue;
}
if ($key === ($row->number - 1) && $row->name === $subquestions[$key]) {
foreach ($this->columns as $colid => $column) {
if ($column->name !== $answers[$key]) {
continue;
}
$postdata[$this->field($key)] = $column->number;
}
}
}
return $postdata;
}

public function grade_response(array $response): array {
// Retrieve the number of right responses and the total number of responses.
[$numrightparts, $total] = $this->get_num_parts_right($response);
Expand Down Expand Up @@ -388,6 +469,51 @@ public function is_complete_response(array $response): bool {
return true;
}

public function classify_response(array $response) {
$selectedchoices = [];
foreach ($response as $responsekey => $responsevalue) {
preg_match('/([\d+])_([\d+])/', $responsekey, $matches);
$rowid = $this->roworder[$matches[1]];
$colnumber = $matches[2];
$column = $this->get_column_by_number($colnumber);
$row = $this->get_row_by_id($rowid);
$rowcorrectanswers = $this->rows[$rowid]->correctanswers;
if ($responsevalue && in_array($colnumber, array_keys($rowcorrectanswers))) {
$fraction = 1 / count($rowcorrectanswers);
} else {
$fraction = 0;
}
$colid = $column->id;
$selectedchoices["$rowid-$colid"] = new question_classified_response(
$column->id, $row->name . ': ' . $column->name, $fraction);
}
return $selectedchoices;
}

public function prepare_simulated_post_data($simulatedresponse): array {
$postdata = [];
$subquestions = array_keys($simulatedresponse);
$answers = array_values($simulatedresponse);
foreach ($this->roworder as $key => $rowid) {
$row = $this->rows[$rowid];
$rowanswers = $answers[$key];
if ($key === ($row->number - 1) && $row->name === $subquestions[$key]) {
foreach ($this->columns as $colid => $column) {
// Set the field to '0' initially.
$postdata[$this->field($key, $column->number)] = '0';
foreach ($rowanswers as $colnumber => $colname) {
if ($row->name === $subquestions[$key] &&
$column->number === $colnumber && $column->name === $colname) {
// Set the field to '1' if it has been ticked..
$postdata[$this->field($key, $column->number)] = '1';
}
}
}
}
}
return $postdata;
}

public function grade_response(array $response): array {
// Retrieve the number of right responses and the total number of responses.
if ($this->grademethod == 'allnone') {
Expand Down
36 changes: 15 additions & 21 deletions questiontype.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,30 +316,24 @@ public function get_num_correct_choices(stdClass $questiondata): int {
}

public function get_possible_responses($questiondata) {
if ($questiondata->options->single) {
$responses = [];

// TODO: Sort out this funtion to work with rows and columns, etc.
foreach ($questiondata->options->answers as $aid => $answer) {
$responses[$aid] = new question_possible_response(
question_utils::to_plain_text($answer->answer, $answer->answerformat),
$answer->fraction);
$question = $this->make_question_instance($questiondata);
$this->initialise_question_instance($question, $questiondata);
$subqs = [];
$responses = [];
foreach ($question->rows as $rowid => $row) {
foreach ($question->columns as $colid => $col) {
$responseclass = $question->html_to_text($row->name . ': ' . $col->name, FORMAT_PLAIN);
if (in_array($col->number, array_keys($row->correctanswers))) {
$fraction = 1;
} else {
$fraction = 0;
}
$responses[$colid] = new question_possible_response($responseclass, $fraction);
}

$responses[null] = question_possible_response::no_response();
return [$questiondata->id => $responses];
} else {
$parts = [];

foreach ($questiondata->options->answers as $aid => $answer) {
$parts[$aid] = [
$aid => new question_possible_response(question_utils::to_plain_text(
$answer->answer, $answer->answerformat), $answer->fraction),
];
}

return $parts;
$subqs[$rowid - 1] = $responses;
}
return $subqs;
}

public function import_from_xml($data, $question, qformat_xml $format, $extra = null) {
Expand Down
2 changes: 1 addition & 1 deletion tests/helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function get_oumatrix_question_data_animals_single(): stdClass {
'name' => 'Birds',
],
14 => (object) [
'id' => 13,
'id' => 14,
'number' => 4,
'name' => 'Mammals',
],
Expand Down
107 changes: 107 additions & 0 deletions tests/question_multiple_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

use question_attempt_step;
use question_state;
use question_classified_response;

defined('MOODLE_INTERNAL') || die();

Expand Down Expand Up @@ -100,6 +101,112 @@ public function test_is_gradable_response(): void {
$this->assertTrue($question->is_gradable_response($response), $question->is_complete_response($response));
}

public function test_classify_response_multiple(): void {
$this->resetAfterTest();
$question = \test_question_maker::make_question('oumatrix', 'food_multiple');
$question->shuffleanswers = 0;
$question->start_attempt(new question_attempt_step(), 1);

$response = $question->prepare_simulated_post_data([
'Proteins' => [1 => 'Chicken breast', 3 => 'Salmon fillet', 6 => 'Steak'],
'Vegetables' => [2 => 'Carrot', 4 => 'Asparagus', 7 => 'Potato'],
'Fats' => [5 => 'Olive oil']]);
$this->assertEquals([
"21-21" => new question_classified_response(21, 'Proteins: Chicken breast', 1 / 3),
"21-22" => new question_classified_response(22, 'Proteins: Carrot', 0),
"21-23" => new question_classified_response(23, 'Proteins: Salmon fillet', 1 / 3),
"21-24" => new question_classified_response(24, 'Proteins: Asparagus', 0),
"21-25" => new question_classified_response(25, 'Proteins: Olive oil', 0),
"21-26" => new question_classified_response(26, 'Proteins: Steak', 1 / 3),
"21-27" => new question_classified_response(27, 'Proteins: Potato', 0),

"22-21" => new question_classified_response(21, 'Vegetables: Chicken breast', 0),
"22-22" => new question_classified_response(22, 'Vegetables: Carrot', 1 / 3),
"22-23" => new question_classified_response(23, 'Vegetables: Salmon fillet', 0),
"22-24" => new question_classified_response(24, 'Vegetables: Asparagus', 1 / 3),
"22-25" => new question_classified_response(25, 'Vegetables: Olive oil', 0),
"22-26" => new question_classified_response(26, 'Vegetables: Steak', 0),
"22-27" => new question_classified_response(27, 'Vegetables: Potato', 1 / 3),

"23-21" => new question_classified_response(21, 'Fats: Chicken breast', 0),
"23-22" => new question_classified_response(22, 'Fats: Carrot', 0),
"23-23" => new question_classified_response(23, 'Fats: Salmon fillet', 0),
"23-24" => new question_classified_response(24, 'Fats: Asparagus', 0),
"23-25" => new question_classified_response(25, 'Fats: Olive oil', 1),
"23-26" => new question_classified_response(26, 'Fats: Steak', 0),
"23-27" => new question_classified_response(27, 'Fats: Potato', 0),

], $question->classify_response($response));

$response = $question->prepare_simulated_post_data([
'Proteins' => [1 => 'Chicken breast', 3 => 'Salmon fillet'],
'Vegetables' => [2 => 'Carrot', 7 => 'Potato'],
'Fats' => [5 => 'Olive oil']]);
$this->assertEquals([
"21-21" => new question_classified_response(21, 'Proteins: Chicken breast', 1 / 3),
"21-22" => new question_classified_response(22, 'Proteins: Carrot', 0),
"21-23" => new question_classified_response(23, 'Proteins: Salmon fillet', 1 / 3),
"21-24" => new question_classified_response(24, 'Proteins: Asparagus', 0),
"21-25" => new question_classified_response(25, 'Proteins: Olive oil', 0),
"21-26" => new question_classified_response(26, 'Proteins: Steak', 0),
"21-27" => new question_classified_response(27, 'Proteins: Potato', 0),

"22-21" => new question_classified_response(21, 'Vegetables: Chicken breast', 0),
"22-22" => new question_classified_response(22, 'Vegetables: Carrot', 1 / 3),
"22-23" => new question_classified_response(23, 'Vegetables: Salmon fillet', 0),
"22-24" => new question_classified_response(24, 'Vegetables: Asparagus', 0),
"22-25" => new question_classified_response(25, 'Vegetables: Olive oil', 0),
"22-26" => new question_classified_response(26, 'Vegetables: Steak', 0),
"22-27" => new question_classified_response(27, 'Vegetables: Potato', 1 / 3),

"23-21" => new question_classified_response(21, 'Fats: Chicken breast', 0),
"23-22" => new question_classified_response(22, 'Fats: Carrot', 0),
"23-23" => new question_classified_response(23, 'Fats: Salmon fillet', 0),
"23-24" => new question_classified_response(24, 'Fats: Asparagus', 0),
"23-25" => new question_classified_response(25, 'Fats: Olive oil', 1),
"23-26" => new question_classified_response(26, 'Fats: Steak', 0),
"23-27" => new question_classified_response(27, 'Fats: Potato', 0),
], $question->classify_response($response));
}

public function test_prepare_simulated_post_data_multiple(): void {
$this->resetAfterTest();
$question = \test_question_maker::make_question('oumatrix', 'food_multiple');
$question->shuffleanswers = 0;
$question->start_attempt(new question_attempt_step(), 1);

$response = ['Proteins' => [1 => 'Chicken breast', 3 => 'Salmon fillet', 6 => 'Steak'],
'Vegetables' => [2 => 'Carrot', 4 => 'Asparagus', 7 => 'Potato'], 'Fats' => [5 => 'Olive oil']];

$expected = [
'rowanswers0_1' => '1',
'rowanswers0_2' => '0',
'rowanswers0_3' => '1',
'rowanswers0_4' => '0',
'rowanswers0_5' => '0',
'rowanswers0_6' => '1',
'rowanswers0_7' => '0',

'rowanswers1_1' => '0',
'rowanswers1_2' => '1',
'rowanswers1_3' => '0',
'rowanswers1_4' => '1',
'rowanswers1_5' => '0',
'rowanswers1_6' => '0',
'rowanswers1_7' => '1',

'rowanswers2_1' => '0',
'rowanswers2_2' => '0',
'rowanswers2_3' => '0',
'rowanswers2_4' => '0',
'rowanswers2_5' => '1',
'rowanswers2_6' => '0',
'rowanswers2_7' => '0',
];
$this->assertEquals($expected, $question->prepare_simulated_post_data($response));
}


public function test_is_same_response(): void {
$question = \test_question_maker::make_question('oumatrix', 'food_multiple');
$question->start_attempt(new question_attempt_step(), 1);
Expand Down
Loading

0 comments on commit d8c365c

Please sign in to comment.