moodle_local_treestudyplan/classes/local/gradegenerator.php
2023-08-25 10:41:56 +02:00

296 lines
12 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/>.
/**
*
* @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;
use Exception;
class gradegenerator {
private $table = [];
private static $loremipsum = [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Etiam scelerisque ligula porttitor velit sollicitudin blandit.",
"Praesent laoreet nisi id lacus laoreet volutpat.",
"Donec rutrum tortor tempus lectus malesuada, ut pretium eros vehicula.",
"Phasellus vulputate tortor vehicula mauris porta ultricies.",
"Ut et lacus sit amet nisl facilisis elementum.",
"Vestibulum ut mauris ac justo tincidunt hendrerit.",
"Fusce congue nulla quis elit facilisis malesuada.",
"Aenean ornare eros placerat ipsum fringilla, sed imperdiet felis imperdiet.",
"Ut malesuada risus ultricies arcu dapibus, quis lobortis eros maximus.",
"Nam ullamcorper dolor ac est tristique, vel blandit tortor tristique.",
"Quisque quis lorem vitae leo lobortis posuere.",
"Nulla ac enim consectetur, rhoncus eros sed, malesuada enim.",
"Vestibulum lobortis lacus ac dolor pulvinar, gravida tincidunt dolor bibendum.",
"Maecenas fringilla urna eget sem bibendum, non lacinia lorem tempus.",
"Nullam quis metus sagittis, pharetra orci eget, ultrices nunc.",
"Morbi et ante at ipsum sodales porta.",
"Morbi vel neque in urna vestibulum vestibulum eu quis lectus.",
"Nam consequat dolor at enim vestibulum, ac gravida nisl consequat.",
"Phasellus ac libero vestibulum, vulputate tellus at, viverra dui.",
"Vivamus venenatis magna a nunc cursus, eget laoreet velit malesuada.",
"Cras fermentum velit vitae tellus sodales, vulputate semper purus porta.",
"Cras ultricies orci in est elementum, at laoreet erat tempus.",
"In non magna et lorem sagittis sollicitudin sit amet et est.",
"Etiam vitae augue ac turpis volutpat iaculis a vitae enim.",
"Integer pharetra quam ac tortor porta dignissim.",
"Pellentesque ullamcorper neque vitae ligula rhoncus accumsan.",
"Nullam in lectus sit amet est faucibus elementum vitae vel risus.",
"Aenean vehicula libero ut convallis blandit.",
"Aenean id mi facilisis, tristique enim vel, egestas lorem.",
"Mauris suscipit dui eget neque gravida, vel pellentesque leo gravida.",
"Quisque quis elit at velit maximus viverra ultricies in nisi.",
"Vivamus et orci nec magna hendrerit egestas sed quis arcu.",
"Suspendisse semper tortor sed justo iaculis volutpat.",
"Praesent interdum dolor nec ultricies imperdiet.",
"Vivamus tristique justo quis tellus commodo, at faucibus justo auctor.",
"Praesent pharetra tellus vel nunc mattis pharetra.",
"Cras a dui quis arcu rutrum ullamcorper sit amet et sem.",
"Aenean porttitor risus ac enim tempor posuere.",
"Mauris bibendum augue ac vehicula mattis.",
"Vestibulum nec justo vehicula, euismod enim sed, convallis magna.",
"Praesent ultrices elit vitae velit dignissim dignissim.",
"Curabitur vehicula velit vitae tortor consequat consectetur sit amet at leo.",
"Sed lobortis neque a magna facilisis aliquam.",
"Phasellus a libero in sem aliquam varius.",
"Mauris tincidunt ligula a risus efficitur euismod.",
"Sed pharetra diam ac neque tempus convallis.",
"Donec at ipsum elementum ex hendrerit laoreet mollis non elit.",
"Praesent eu arcu sollicitudin, fermentum tellus at, blandit dolor.",
"Curabitur in lectus consequat, bibendum ligula vitae, semper lacus.",
"Aenean eu risus non sem pretium dictum.",
"Praesent nec risus vestibulum quam venenatis tempor.",
"Nullam rhoncus ex a quam egestas, eu auctor enim lobortis.",
"Nam luctus ante id lacus scelerisque, quis blandit ante elementum.",
];
private function generatedfeedback() {
if (file_exists("/usr/games/fortune")) {
// Get a fortune if it is available.
return shell_exec("/usr/games/fortune -n 160 -e disclaimer literature science pratchett wisdom education");
} else {
// Get a random loremipsum string.
return self::$loremipsum[rand(0, count(self::$loremipsum)-1)];
}
}
public function __construct() {
}
public function addstudent(string $student) {
if (!array_key_exists($student, $this->table)) {
$this->table[$student] = [
"intelligence" => rand(70, 100),
"endurance" => rand(70, 100),
"skills" => [],
];
}
}
public function addskill(string $student, string $skill) {
$this->addstudent($student);
if (!array_key_exists($skill, $this->table[$student]["skills"])) {
$int = $this->table[$student]["intelligence"];
$end = $this->table[$student]["endurance"];
$this->table[$student]["skills"][$skill] = [
"intelligence" => min(100, $int + rand(-30, 30)),
"endurance" => min(100, $end + rand(-10, 10)),
];
}
}
// Below is mostly just for reference in the json file.
public function addUserNameInfo(string $student, $firstname, $lastname) {
$this->addstudent($student);
$this->table[$student]["firstname"] = $firstname;
$this->table[$student]["lastname"] = $lastname;
}
public function generateraw($student, $skill, $count ) {
$this->addskill($student, $skill);
$int = $this->table[$student]["skills"][$skill]["intelligence"];
$end = $this->table[$student]["skills"][$skill]["endurance"];
$results = [];
$gaveup = false;
for ($i=0; $i < $count; $i++) {
$r = new \stdClass;
if ($gaveup) {
$r->done = !$gaveup;
} else {
$r->done = (rand(0, $end) > 20); // Determine if the assignment was done.
}
if ($r->done) {
$score = rand(0, $int);
$r->result = ($score > 20); // Determine if the assignment was successful.
if (!$r->result) {
$r->failed = !($score > 10);
}
} else {
$r->result = false; // Make sure a result property is always there.
$r->failed = true;
}
// Aways generate a little feedback.
$r->fb = $this->generatedfeedback();
$results[] = $r;
if (!$gaveup && $i >= 3) {
// There is a slight chance the students with low endurance for this course will stop with this course's work entirely.
$gaveup = (rand(0, $end) < 15);
}
}
return $results;
}
public function generate($student, $skill, array $gradeinfos ) {
global $DB;
$table ="local_treestudyplan_gradecfg";
$rlist = [];
$gen = $this->generateraw($student, $skill, count($gradeinfos));
for ($i=0; $i < count($gradeinfos); $i++) {
$g = $gradeinfos[$i];
$gi = $g->getGradeitem();
$gr = $gen[$i];
// First get the configured interpretation for this scale or grade.
$scale = $gi->load_scale();
if ( isset($scale)) {
$gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]);
} else if ($gi->grademin == 0) {
$gradecfg = $DB->get_record($table, ["grade_points" => $gi->grademax]);
} else {
$gradecfg = null;
}
// Next generate the grade.
if ($gradecfg) {
if (!$gr->done) {
// INCOMPLETE.
// Fair chance of teacher forgetting to set incomplete to "no evidence".
$grade = 0;
$r = (object)["gi" => $g, "grade" => $grade, "fb" => "" ];
} else if (!$gr->result) {
$grade = rand($gradecfg->min_progress, $gradecfg->min_completed -1 );
$r = (object)["gi" => $g, "grade" => $grade, "fb" => $gr->fb ];
} else {
// COMPLETED.
$r = (object)["gi" => $g, "grade" => rand( $gradecfg->min_completed, $gi->grademax ), "fb" => $gr->fb ];
}
$r->gradetext = $r->grade;
if ( isset($scale)) {
$scaleitems = $scale->load_items();
if ($r->grade > 0) {
$r->gradetext = trim($scale->get_nearest_item($r->grade));
} else {
$r->gradetext = "-";
}
}
} else if ($gi->gradepass > 0) {
if (!$gr->done) {
// INCOMPLETe or FAILED.
$grade = rand(0, $gi->gradepass/2);
$r = (object)["gi" => $g, "grade" => $grade, "fb" =>($grade > 0) ? $gr->fb : "" ];
} else if (!$gr->result) {
//PROGRESS.
$r = (object)["gi" => $g, "grade" => rand( round($gi->gradepass/2), $gi->gradepass -1 ), "fb" => $gr->fb ];
} else {
// COMPLETED.
$r = (object)["gi" => $g, "grade" => rand( $gi->gradepass, $gi->grademax ), "fb" => $gr->fb ];
}
$r->gradetext = $r->grade;
} else {
// Blind assumptions if nothing is provided.
// Over 55% of range is completed.
// Under 35% is not done.
$range = floatval($gi->grademax - $gi->grademin);
if (!$gr->done) {
// INCOMPLETe or FAILED.
$grade = rand(0, round($range * 0.35) - 1);
$r = (object)["gi" => $g, "grade" => $gi->grademin+$grade, "fb" =>($grade > 0) ? $gr->fb : "" ];
} else if (!$gr->result) {
//PROGRESS.
$r = (object)["gi" => $g, "grade" => $gi->grademin+rand(round($range * 0.35), round($range * 0.55) - 1 ), "fb" => $gr->fb ];
} else {
// COMPLETED.
$r = (object)["gi" => $g, "grade" => $gi->grademin+rand(round($range * 0.55) , $range ), "fb" => $gr->fb ];
}
$r->gradetext = $r->grade;
}
$rlist[] = $r;
}
return $rlist;
}
public function getstats($student) {
return $this->table[$student];
}
public function serialize(): ?string{
return json_encode([
"table" => $this->table], JSON_PRETTY_PRINT);
}
public function unserialize(string $data): void {
$o = json_decode($data, true);
$this->table = $o["table"];
}
public function toFile(string $filename) {
$filename = self::expand_tilde($filename);
file_put_contents($filename, $this->serialize());
}
public function fromFile(string $filename) {
$filename = self::expand_tilde($filename);
if (file_exists($filename)) {
try {
$json = file_get_contents($filename);
$this->unserialize($json);
} catch (Exception $x) {
cli_problem("ERROR loading from file");
throw $x; // Throw X up again to show the output.
}
}
}
private static function expand_tilde($path) {
if (function_exists('posix_getuid') && strpos($path, '~') !== false) {
$info = posix_getpwuid(posix_getuid());
$path = str_replace('~', $info['dir'], $path);
}
return $path;
}
}