moodle_local_treestudyplan/classes/local/aggregators/competency_aggregator.php
2024-06-02 23:23:32 +02:00

294 lines
11 KiB
PHP

<?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/**
* Aggregate course results with moodle course completion
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local\aggregators;
use core_competency\api as c_api;
use local_treestudyplan\courseinfo;
use local_treestudyplan\gradeinfo;
use local_treestudyplan\studyitem;
use local_treestudyplan\completion;
use local_treestudyplan\coursecompetencyinfo;
/**
* Aggregate course results with moodle course completion
*/
class competency_aggregator extends \local_treestudyplan\aggregator {
/** @var bool */
public const DEPRECATED = false;
/** @var stdClass */
private $agcfg = null;
/**
* Retrieve or initialize current config object
* @return stdClass
*/
private function cfg() {
if (empty($this->agcfg)) {
$this->agcfg = (object)[
'thresh_completed' => 0.66, // Minimum fraction that must be completed to aggregate as completed.
'use_failed' => true, // Support failed completion yes/no.
];
}
return $this->agcfg;
}
/**
* Create new instance of aggregation method
* @param string $configstr Aggregation configuration string
*/
public function __construct($configstr) {
// Allow public constructor for testing purposes.
$this->initialize($configstr);
}
/**
* Initialize the aggregation method
* @param string $configstr Aggregation configuration string
*/
protected function initialize($configstr) {
// First initialize with the defaults.
foreach (["thresh_completed" ] as $key) {
$val = intval(get_config('local_treestudyplan', "competency_{$key}"));
if ($val >= 0 && $val <= 100) {
$this->cfg()->$key = floatval($val) / 100;
}
}
foreach (["use_failed" ] as $key) {
$this->cfg()->$key = boolval(get_config('local_treestudyplan', "competency_{$key}"));
}
// Next, decode json.
$config = \json_decode($configstr, true);
if (is_array($config)) {
// Copy all valid config settings to this item.
foreach (["thresh_completed" ] as $key) {
if (array_key_exists($key, $config)) {
$val = $config[$key];
if ($val >= 0 && $val <= 100) {
$this->cfg()->$key = floatval($val) / 100;
}
}
}
foreach (["use_failed" ] as $key) {
if (array_key_exists($key, $config)) {
$this->cfg()->$key = boolval($config[$key]);
}
}
}
}
/**
* Return the current configuration string.
* @return string Configuration string
*/
public function config_string() {
return json_encode([
"thresh_completed" => 100 * $this->cfg()->thresh_completed,
"use_failed" => $this->cfg()->use_failed,
]);
}
/**
* Determine if aggregation method wants to select gradables
* @return bool True if aggregation method needs gradables to be selected
*/
public function select_gradables() {
return false;
}
/**
* Determine if aggregation method is deprecated
* @return bool True if aggregation method is deprecated
*/
public function deprecated() {
return self::DEPRECATED;
}
/**
* Determine if Aggregation method makes use of "required grades" in a course/module.
* @return bool True if Aggregation method makes use of "required grades" in a course/module.
*/
public function use_required_grades() {
return true;
}
/**
* Determine if aggregation method makes use of required grades
* @return bool True if aggregation method makes use of
*/
public function use_item_conditions() {
return false;
}
/**
* Determine if the aggregation method uses course competencies,
* @return bool True if the aggregation method uses course competencies,
*/
public function use_coursecompetencies() {
return true;
}
/**
* Return course completion information based on the core completion infromation
* Possible states:
* completion::EXCELLENT - Completed with excellent results
* completion::GOOD - Completed with good results
* completion::COMPLETED - Completed
* completion::PROGRESS - Started, but not completed yey
* completion::FAILED - Failed
* completion::INCOMPLETE - Not yet started
* @param courseinfo $courseinfo Courseinfo object for the course to check
* @param studyitem $studyitem Studyitem object for the course to check
* @param int $userid Id of user to check this course for
* @return int Aggregated completion as completion class constant
*/
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
// Retrieve the course competencies.
$course = $courseinfo->course();
$cci = new coursecompetencyinfo($course, $studyitem);
$competencies = $cci->course_competencies();
if (count($competencies) == 0) {
return completion::INCOMPLETE;
}
$count = 0;
$courseproficient = 0;
$proficient = 0;
$requiredmet = 0;
$requiredcount = 0;
foreach ($competencies as $c) {
$count += 1;
$p = $cci->proficiency($c, $userid);
if ($p->proficient) {
$proficient += 1;
}
if ($p->courseproficient) {
$courseproficient += 1;
}
if ($cci->is_required(($c))) {
$requiredcount += 1;
if ($p->proficient) {
$requiredmet += 1;
}
}
}
// Determine minimum for.
$limit = $this->cfg()->thresh_completed * $count;
$coursefinished = ($course->enddate) ? ($course->enddate < time()) : false;
if ($proficient >= $count && $requiredmet >= $requiredcount) {
if ($limit < $count) {
return completion::EXCELLENT;
} else {
return completion::COMPLETED;
}
} else if ($proficient > $limit && $requiredmet >= $requiredcount) {
return completion::COMPLETED;
} else if ($proficient > 0) {
if ($this->cfg()->use_failed && $coursefinished) {
return completion::FAILED;
} else {
return completion::PROGRESS;
}
} else {
if ($this->cfg()->use_failed && $coursefinished) {
return completion::FAILED;
} else {
return completion::INCOMPLETE;
}
}
}
/**
* Aggregate juncton/filter inputs into one final junction outcome
* @param int[] $completion List of completion inputs
* @param studyitem $studyitem Studyitem object for the junction
* @param int $userid Id of user to check completion for
* @return int Aggregated completion as completion class constant
*/
public function aggregate_junction(array $completion, studyitem $studyitem, $userid = 0) {
// Aggregate multiple incoming states into one junction or finish.
// Possible states:.
// - completion::EXCELLENT - All incoming states are excellent.
// - completion::GOOD - All incoming states are at least good.
// - completion::COMPLETED - All incoming states are at least completed.
// - completion::FAILED - All incoming states are failed.
// - completion::INCOMPLETE - All incoming states are incomplete.
// - completion::PROGRESS - All other states.
$method = strtoupper($studyitem->conditions()); // One of ANY or ALL.
// First count all states.
$statecount = completion::count_states($completion);
$total = count($completion);
if ($method == "ANY") {
if ($statecount[completion::EXCELLENT] >= 1) {
return completion::EXCELLENT;
} else if ($statecount[completion::GOOD] >= 1) {
return completion::GOOD;
} else if ($statecount[completion::COMPLETED] >= 1) {
return completion::COMPLETED;
} else if ($statecount[completion::PROGRESS] >= 1) {
return completion::PROGRESS;
} else if ($statecount[completion::FAILED] >= 1) {
return completion::FAILED;
} else {
return completion::INCOMPLETE;
}
} else { /* default value of ALL */
if ($total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT;
} else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD]) ) {
return completion::GOOD;
} else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD]
+ $statecount[completion::COMPLETED]) ) {
return completion::COMPLETED;
} else if ($statecount[completion::FAILED]) {
return completion::FAILED;
} else if ($total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE;
} else {
return completion::PROGRESS;
}
}
}
/**
* Determine completion for a single grade and user
* @param gradeinfo $gradeinfo Gradeinfo object for grade to check
* @param mixed $userid Id of user to check completion for
* @return int Aggregated completion as completion class constant
*/
public function grade_completion(gradeinfo $gradeinfo, $userid) {
// COURSE COMPETENCIES DOESN'T REALLY USE THIS FUNCTION.
}
}