- LaTeX template improved: submission count, challenge fill and skip counts added
- "List best results only" option added to each report generation functionality
This commit is contained in:
parent
1fff851fb5
commit
144c62f9ad
@ -7,7 +7,7 @@ require_once "common_func.php";
|
||||
|
||||
class ReportBuilder
|
||||
{
|
||||
static public function getStatsByFilters(int $gameid, string $filter, string $groups, string $ordering)
|
||||
static public function getStatsByFilters(int $gameid, string $filter, string $groups, bool $bestOnly)
|
||||
{
|
||||
$groupMgr = new GroupMgr();
|
||||
$testMgr = new TestMgr();
|
||||
@ -18,7 +18,7 @@ class ReportBuilder
|
||||
$groupFilter = ["nickname", "IN", $nicknames];
|
||||
|
||||
// get IDs
|
||||
$tests = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true, $groupFilter);
|
||||
$tests = $testMgr->getResultsByGameId($gameid, $filter, "", true, $bestOnly, $groupFilter);
|
||||
$ids = array_map(fn($test) => $test["_id"], $tests);
|
||||
|
||||
// generate stats
|
||||
@ -71,12 +71,25 @@ class Answer
|
||||
{
|
||||
return "\\answer" . $this->type . "{" . $this->ratio . "}{" . $this->text . "}\n";
|
||||
}
|
||||
|
||||
// Get answer ratio.
|
||||
public function getRatio(): float
|
||||
{
|
||||
return $this->ratio;
|
||||
}
|
||||
}
|
||||
|
||||
class Challenge
|
||||
{
|
||||
private string $question;
|
||||
private array $answers;
|
||||
private int $fillCount;
|
||||
private int $skipCount;
|
||||
|
||||
// Sort answers by ratio.
|
||||
private function sortAnswers() : void {
|
||||
usort($this->answers, function($a, $b) {return $a->getRatio() < $b->getRatio();});
|
||||
}
|
||||
|
||||
function __construct(array $data)
|
||||
{
|
||||
@ -87,6 +100,10 @@ class Challenge
|
||||
$type = $answer === $data["correct_answer"] ? Answer::CORRECT : Answer::INCORRECT;
|
||||
$this->answers[] = new Answer($type, $answer, $ratio);
|
||||
}
|
||||
$this->fillCount = array_sum($data["player_answers"]); // get fill count
|
||||
$this->skipCount = $data["skipped"];
|
||||
|
||||
$this->sortAnswers(); // sort answers by fill ratio
|
||||
}
|
||||
|
||||
public function getQuestion(): string
|
||||
@ -99,10 +116,22 @@ class Challenge
|
||||
return $this->answers;
|
||||
}
|
||||
|
||||
public function getFillCount(): int {
|
||||
return $this->fillCount;
|
||||
}
|
||||
|
||||
public function getSkipCount(): int {
|
||||
return $this->skipCount;
|
||||
}
|
||||
|
||||
public function getSubmissionCount() : int {
|
||||
return $this->fillCount + $this->skipCount;
|
||||
}
|
||||
|
||||
// Generate TeX representation.
|
||||
public function genTeX(): string
|
||||
{
|
||||
$tex = "\\begin{question}{" . $this->question . "}\n";
|
||||
$tex = "\\begin{question}{" . $this->question . "}{" . $this->fillCount . "}\n";
|
||||
foreach ($this->answers as &$answer) {
|
||||
$tex .= $answer->genTeX();
|
||||
}
|
||||
@ -116,6 +145,10 @@ class ReportSection
|
||||
private string $title;
|
||||
private array $challenges;
|
||||
|
||||
private function getNumberOfSubmissions() : int {
|
||||
return count($this->challenges) > 0 ? $this->challenges[0]->getSubmissionCount() : 0;
|
||||
}
|
||||
|
||||
function __construct(string $title, array $challenges)
|
||||
{
|
||||
$this->title = $title;
|
||||
@ -135,7 +168,7 @@ class ReportSection
|
||||
// Generate TeX representation of this report.
|
||||
function genTeX(): string
|
||||
{
|
||||
$tex = "\\begin{quiz}{" . $this->title . "}\n";
|
||||
$tex = "\\begin{quiz}{" . $this->title . "}{" . $this->getMaxFillingCount() . "}\n";
|
||||
foreach ($this->challenges as $challenge) {
|
||||
$tex .= $challenge->genTeX();
|
||||
}
|
||||
|
@ -406,7 +406,7 @@ class TestMgr
|
||||
}
|
||||
|
||||
// Get test results by game ID.
|
||||
function getResultsByGameId(string $gameid, string $filter, string $orderby, bool $exclude_challenge_data, array ...$furtherFilters): array
|
||||
function getResultsByGameId(string $gameid, string $filter, string $orderby, bool $exclude_challenge_data, bool $best_ones_only, array ...$furtherFilters): array
|
||||
{
|
||||
$qb = $this->db->createQueryBuilder();
|
||||
$qb = $qb->where(["gameid", "=", (int)$gameid]);
|
||||
@ -448,6 +448,34 @@ class TestMgr
|
||||
}
|
||||
|
||||
$test_data_array = $qb->getQuery()->fetch();
|
||||
|
||||
// if only the best results should be included, then...
|
||||
if ($best_ones_only) {
|
||||
// filter out ongoing ones
|
||||
$tests = array_filter($test_data_array, fn($test) => $test["state"] === Test::TEST_CONCLUDED);
|
||||
|
||||
// sort by result
|
||||
usort($tests, fn($a, $b) => $a["summary"]["percentage"] > $b["summary"]["percentage"]);
|
||||
|
||||
// gather best tests by username here
|
||||
$best_test_ids = [];
|
||||
foreach ($tests as $test) {
|
||||
$nickname = $test["nickname"];
|
||||
if (!in_array($nickname, $best_test_ids)) {
|
||||
$best_tests[$nickname] = $test["_id"];
|
||||
}
|
||||
}
|
||||
|
||||
// just keep values, drop the keys (nicknames)
|
||||
$best_tests = array_values($best_tests);
|
||||
|
||||
// remove non-best results
|
||||
$test_data_array = array_filter($test_data_array, fn($test) => in_array($test["_id"], $best_tests));
|
||||
|
||||
// renumber results
|
||||
$test_data_array = array_values($test_data_array);
|
||||
}
|
||||
|
||||
return $test_data_array;
|
||||
}
|
||||
|
||||
@ -487,6 +515,7 @@ class TestMgr
|
||||
"correct_answer" => $correct_answer,
|
||||
"player_answers" => array_fill(0, count($challenge["answers"]), 0),
|
||||
"answer_count" => count($challenge["answers"]),
|
||||
"skipped" => 0
|
||||
];
|
||||
$aggregated[$challenge_indices[$idhash]] = $challenge_info; // insert challenge info
|
||||
}
|
||||
@ -495,9 +524,12 @@ class TestMgr
|
||||
$challenge_idx = $challenge_indices[$idhash];
|
||||
|
||||
// add up player answer
|
||||
if (trim($challenge["player_answer"]) !== "") {
|
||||
$player_answer = trim($challenge["player_answer"]);
|
||||
if (($player_answer !== "") && ($player_answer != -1)) { // player answered
|
||||
$answer_idx = array_search($challenge["answers"][$challenge["player_answer"]], $aggregated[$challenge_idx]["answers"]); // transform player answer index to report answer index
|
||||
$aggregated[$challenge_idx]["player_answers"][(int)$answer_idx]++;
|
||||
} else { // player has not answered or provided an unprocessable answer
|
||||
$aggregated[$challenge_idx]["skipped"]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,10 @@ if (!get_autologin_state() || (($user_data["privilege"] !== PRIVILEGE_CREATOR) &
|
||||
<select id="report_output_type">
|
||||
<option value="pdf">PDF</option>
|
||||
<option value="tex">TeX</option>
|
||||
</select>
|
||||
</select><br>
|
||||
<input type="checkbox" id="best_only"><label for="best_only" style="font-size: 12px">Csak legjobb eredmények felhasználónként</label>
|
||||
<br>
|
||||
<br>
|
||||
<input type="button" value="Előállítás" onclick="generate_game_report_by_groups()">
|
||||
<input type="button" value="Bezárás" onclick="hide('report_generator')">
|
||||
</section>
|
||||
|
@ -485,6 +485,7 @@ function get_results_by_gameid(ReqHandler &$rh, array $params): array
|
||||
$filter = trim($params["filter"] ?? "");
|
||||
$ordering = trim($params["orderby"] ?? "");
|
||||
$groups = explode_list(trim($params["groups"] ?? ""));
|
||||
$best_only = trim($params["best_only"] ?? "false") === "true";
|
||||
|
||||
$game = $gameMgr->getGame($gameid);
|
||||
|
||||
@ -516,9 +517,9 @@ function get_results_by_gameid(ReqHandler &$rh, array $params): array
|
||||
// execute filtering
|
||||
$game_results = null;
|
||||
if ($group_filter !== []) {
|
||||
$game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true, $group_filter);
|
||||
$game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true, $best_only, $group_filter);
|
||||
} else {
|
||||
$game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true);
|
||||
$game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true, $best_only);
|
||||
}
|
||||
$result = $game_results;
|
||||
}
|
||||
@ -534,6 +535,7 @@ function generate_report_by_groups(ReqHandler &$rh, array $params): string
|
||||
$gameid = trim($params["gameid"]);
|
||||
$filter = trim($params["filter"] ?? "");
|
||||
$groups = explode_list(trim($params["groups"]));
|
||||
$best_only = trim($params["best_only"] ?? "false") === "true";
|
||||
$outtype = trim($params["outtype"] ?? "pdf");
|
||||
|
||||
// only PDF and TEX are valid
|
||||
@ -565,7 +567,7 @@ function generate_report_by_groups(ReqHandler &$rh, array $params): string
|
||||
// assemble report
|
||||
$report = new Report($game->getName());
|
||||
foreach ($groups as $groupname) {
|
||||
$stats = ReportBuilder::getStatsByFilters($gameid, $filter, $groupname, "");
|
||||
$stats = ReportBuilder::getStatsByFilters($gameid, $filter, $groupname, $best_only);
|
||||
$section = new ReportSection($groupname, $stats);
|
||||
$report->addSection($section);
|
||||
}
|
||||
|
@ -373,12 +373,13 @@ function list_results_by_game(game) {
|
||||
|
||||
function generate_game_report_by_groups() {
|
||||
let gameid = EDITED_GAME["_id"];
|
||||
let filter = document.getElementById("report_filter").value.trim()
|
||||
let groups = document.getElementById("report_groups").value.trim()
|
||||
let filter = document.getElementById("report_filter").value.trim();
|
||||
let groups = document.getElementById("report_groups").value.trim();
|
||||
let best_only = document.getElementById("best_only").checked ? "true" : "false";
|
||||
let outtype = document.getElementById("report_output_type").value;
|
||||
|
||||
let report_url = `interface.php?action=generate_report_by_groups&gameid=${gameid}&groups=${groups}&filter=${filter}&outtype=${outtype}`;
|
||||
report_url = report_url.replace("#", "%23");
|
||||
let report_url = `interface.php?action=generate_report_by_groups&gameid=${gameid}&groups=${groups}&filter=${filter}&outtype=${outtype}&best_only=${best_only}`;
|
||||
report_url = report_url.replaceAll("#", "%23");
|
||||
window.open(report_url, "_blank");
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ function fetch_results() {
|
||||
let filterF = document.getElementById("filter");
|
||||
let orderbyF = document.getElementById("orderby");
|
||||
let groupsF = document.getElementById("groups");
|
||||
let best_onlyChk = document.getElementById("best_only");
|
||||
|
||||
let filter = autoconvert_datetime(filterF.value.trim());
|
||||
|
||||
@ -46,7 +47,8 @@ function fetch_results() {
|
||||
gameid: GAMEID,
|
||||
filter: filter.trim(),
|
||||
orderby: orderbyF.value.trim(),
|
||||
groups: groupsF.value.trim()
|
||||
groups: groupsF.value.trim(),
|
||||
best_only: best_onlyChk.checked ? "true" : "false"
|
||||
};
|
||||
|
||||
request(req).then(resp => {
|
||||
|
@ -1,12 +1,17 @@
|
||||
% !TeX spellcheck = hu_HU
|
||||
% !TeX encoding = UTF-8
|
||||
% !TeX program = xelatex
|
||||
|
||||
\documentclass[10pt]{article}
|
||||
|
||||
|
||||
%% Importing packages
|
||||
% General things
|
||||
\usepackage[magyar]{babel}
|
||||
\usepackage[a4paper,margin=1in]{geometry}
|
||||
\usepackage[a4paper,margin=10mm,footskip=5mm]{geometry}
|
||||
\usepackage{fontspec}
|
||||
\usepackage[fontsize=7pt]{fontsize}
|
||||
\usepackage{titlesec}
|
||||
|
||||
% For frame layout and content
|
||||
\usepackage{xcolor}
|
||||
@ -28,6 +33,10 @@
|
||||
\definecolor{colpbb}{HTML}{A0A0A0} % Progressbar background
|
||||
\definecolor{colanc}{HTML}{176767} % Correct answer background
|
||||
\definecolor{colani}{HTML}{D3E5E5} % Incorrect answer background
|
||||
\definecolor{colsbg}{HTML}{AA8A7D} % Group title background
|
||||
\definecolor{colsfg}{HTML}{EFEFEF} % Group title text color
|
||||
\definecolor{colqsb}{HTML}{176767} % Quiz sequence number background
|
||||
\definecolor{colqsf}{HTML}{EFEFEF} % Quiz sequence number text color
|
||||
|
||||
% Setting up outer frames
|
||||
\mdfsetup{
|
||||
@ -53,7 +62,7 @@
|
||||
% Replacing ToC text
|
||||
\addto\captionsmagyar{%
|
||||
\renewcommand{\contentsname}%
|
||||
{\huge Kurzusok}%
|
||||
{\huge Csoportok}%
|
||||
}
|
||||
|
||||
% Add dotfill to ToC
|
||||
@ -83,39 +92,75 @@
|
||||
\newcommand{\unnumsec}[1]{\section*{#1}%
|
||||
\addcontentsline{toc}{section}{#1}}
|
||||
|
||||
% Set section title style
|
||||
\titleformat{\unnumsec}{\Large\bfseries}{\thesection}{1em}{}
|
||||
|
||||
% Macros for answers
|
||||
\newcommand{\answerCorrect}[2]{\progressbar{#1} & \begin{minipage}{0.8\columnwidth}\begin{mdframed}[backgroundcolor=colanc]\color{coltxl}{#2}\end{mdframed}\end{minipage}\\}
|
||||
\newcommand{\answerIncorrect}[2]{\progressbar[filledcolor=colpbs]{#1} & \begin{minipage}{0.8\columnwidth}\begin{mdframed}[backgroundcolor=colani]\color{coltxd}{#2}\end{mdframed}\end{minipage}\\}
|
||||
|
||||
% Macros for questions.
|
||||
\newmdenv[backgroundcolor=colqsb,align=left,innerleftmargin=0.2em,innerrightmargin=0.2em]{qsn} % Question sequence number
|
||||
|
||||
% Environment for questions. The parameter is the question itself, the contents of the environment should consist of \answer*{}{} macros and nothing else
|
||||
\newenvironment{question}[1]{
|
||||
\begin{center}
|
||||
\newenvironment{question}[2]{
|
||||
\begin{center}
|
||||
\begin{mdframed}
|
||||
\raisebox{0.05em} {
|
||||
\begin{minipage}{0.6cm}
|
||||
\begin{qsn}[userdefinedwidth=0.5cm]
|
||||
\begin{flushright}
|
||||
\color{colqsf}\arabic{qc}.
|
||||
\end{flushright}
|
||||
\end{qsn}
|
||||
\end{minipage}
|
||||
}
|
||||
%
|
||||
{\color{coltit}\large #1\par}\medskip
|
||||
%
|
||||
{\hspace{1em}\small #2~kitöltő}\vspace{0.6em}\par
|
||||
|
||||
\stepcounter{qc}
|
||||
|
||||
\begin{tabular}{c c}
|
||||
}{
|
||||
}{
|
||||
\end{tabular}
|
||||
|
||||
\end{mdframed}
|
||||
\end{center}
|
||||
\end{center}
|
||||
}
|
||||
|
||||
% An environment to create a quiz containing questions. The parameter is either the title of the quiz, or the name of the course
|
||||
\newenvironment{quiz}[1]{
|
||||
\newenvironment{quiz}[2]{
|
||||
% Create highlighted group title
|
||||
\begin{mdframed}[backgroundcolor=colsbg]
|
||||
{
|
||||
\color{colsfg}
|
||||
\unnumsec{#1}
|
||||
}{
|
||||
{\small #2~kitöltő}
|
||||
\vspace{0.4em}
|
||||
}
|
||||
\end{mdframed}
|
||||
|
||||
% Reset question counter
|
||||
\setcounter{qc}{1}
|
||||
}{
|
||||
\clearpage
|
||||
}
|
||||
|
||||
\begin{document}
|
||||
\begin{center}
|
||||
\Huge \IfFileExists{stats/title.tex}{\input{title.tex}}{Kvíz eredmények}
|
||||
\end{center}
|
||||
\begin{center}
|
||||
\Huge \IfFileExists{title.tex}{\input{title.tex}}{Kvíz eredmények}
|
||||
\end{center}
|
||||
|
||||
\Large
|
||||
\tableofcontents
|
||||
\normalsize
|
||||
\clearpage
|
||||
\Large
|
||||
\tableofcontents
|
||||
\normalsize
|
||||
\clearpage
|
||||
|
||||
\inputIfFileExists{content.tex}
|
||||
% Create question counter
|
||||
\newcounter{qc}
|
||||
|
||||
\inputIfFileExists{content.tex}
|
||||
|
||||
\end{document}
|
||||
|
@ -52,7 +52,8 @@ if (!$gameMgr->getGame($game_id)->isUserContributorOrOwner($user_data["nickname"
|
||||
<input type="text" placeholder="Rendezés" id="orderby" style="font-family: 'Monaco', monospace; width: 30em;">
|
||||
<input type="button" value="Szűrés" onclick="fetch_results()">
|
||||
<input type="button" value="Jelentés előállítása" onclick="generate_report()">
|
||||
<input type="button" value="Kijelöltek törlése" onclick="delete_tests()">
|
||||
<input type="button" value="Kijelöltek törlése" onclick="delete_tests()"><br>
|
||||
<input type="checkbox" id="best_only"><label for="best_only" style="font-size: 12px">Csak legjobb eredmények felhasználónként</label>
|
||||
</section>
|
||||
<section>
|
||||
<section id="table_section">
|
||||
|
Loading…
x
Reference in New Issue
Block a user