Commit 39ebacfe authored by Abbassy's avatar Abbassy
Browse files

Migrate overview cards and suggestion grade summary from assignfeedback_mojec

- Add test_summary_cards and suggestion_grade_summary output classes with Mustache templates
- Add grade_summary_helper for grade calculation logic
- Integrate new tables before Summary and Recommendations
- Rename 'Calculated grade' to 'Suggestion grade'
- Remove Summary table (replaced by Overview Cards)
- Move Competencies table next to Recommendations
- Update all table titles to consistent format
- Adjust spacing and layout for better visual consistency
parent b7babf3a
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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 <http://www.gnu.org/licenses/>.
/**
* Helper class for calculating grade summary from DTA result summary.
*
* @package assignsubmission_dta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignsubmission_dta;
defined('MOODLE_INTERNAL') || die();
use assignsubmission_dta\models\dta_result_summary;
/**
* Helper class for grade summary calculations.
*/
class grade_summary_helper {
/**
* Calculate grade summary from DTA result summary.
*
* @param \assignsubmission_dta\models\dta_result_summary|null $summary The DTA result summary
* @param float $maxgrade Maximum grade for the assignment
* @return array Summary array with keys: totaltests, successcount, failurecount, compilationerrors,
* test_score, competency_percent, has_competencies, finalgrade, maxgrade
*/
public static function calculate_summary(?\assignsubmission_dta\models\dta_result_summary $summary, float $maxgrade = 100.0): array {
$result = array(
'hasdata' => false,
'totaltests' => 0,
'successcount' => 0,
'failurecount' => 0,
'compilationerrors' => 0,
'unknowncount' => 0,
'successrate' => '?',
'successrate_percent' => 0.0,
'test_score' => 0.0,
'competency_percent' => 0.0,
'has_competencies' => false,
'finalgrade' => 0.0,
'maxgrade' => $maxgrade,
);
if ($summary === null || !is_object($summary)) {
return $result;
}
$result['hasdata'] = true;
$result['totaltests'] = $summary->assignsubmission_dta_result_count();
$result['successcount'] = $summary->assignsubmission_dta_successful_count();
$result['failurecount'] = $summary->assignsubmission_dta_failed_count();
$result['compilationerrors'] = $summary->assignsubmission_dta_compilation_error_count();
$result['unknowncount'] = $summary->assignsubmission_dta_unknown_count();
// Calculate success rate exactly like in summary table
if ($result['unknowncount'] == 0 && $result['compilationerrors'] == 0 && $result['totaltests'] > 0) {
$result['successrate_percent'] = round(($result['successcount'] / $result['totaltests']) * 100, 2);
$result['successrate'] = $result['successcount'] . '/' . $result['totaltests'] . ' (' . $result['successrate_percent'] . '%)';
} else {
// Format like summary table: "5/?" when there are compilation errors or unknown states
$result['successrate'] = $result['successcount'] . '/?';
$result['successrate_percent'] = 0.0;
}
// Only set grade to 0 if there are more than 1 compilation errors.
if ($result['compilationerrors'] > 1) {
$result['test_score'] = 0.0;
$result['finalgrade'] = 0.0;
return $result;
}
// Calculate test score (0-100%).
if ($result['totaltests'] > 0) {
$result['test_score'] = ($result['successcount'] / $result['totaltests']) * 100.0;
} else {
$result['test_score'] = 0.0;
}
// Calculate competency score from competency strings.
$result['competency_percent'] = self::calculate_competency_percent($summary);
$result['has_competencies'] = ($result['competency_percent'] > 0);
// Calculate combined final score: 80% test score + 20% competency score.
// If no competencies available, use test score only (weight 1.0).
$test_weight = $result['has_competencies'] ? 0.8 : 1.0;
$competency_weight = $result['has_competencies'] ? 0.2 : 0.0;
$final_percent = ($test_weight * $result['test_score']) + ($competency_weight * $result['competency_percent']);
if ($maxgrade > 0) {
$result['finalgrade'] = round(($final_percent / 100.0) * $maxgrade, 5);
$result['finalgrade'] = min(max($result['finalgrade'], 0), $maxgrade);
}
return $result;
}
/**
* Calculate competency percentage from competency strings.
*
* Parses successful_competencies and tested_competencies strings (semicolon-separated)
* and calculates the average percentage for each competency block.
*
* @param \assignsubmission_dta\models\dta_result_summary|null $summary Summary record with competency fields
* @return float Competency percentage (0-100)
*/
private static function calculate_competency_percent(?\assignsubmission_dta\models\dta_result_summary $summary): float {
if (empty($summary)) {
return 0.0;
}
// Use the property names that match the database structure
$successful = $summary->successful_competencies ?? $summary->successfultestcompetencies ?? '';
$tested = $summary->tested_competencies ?? $summary->overalltestcompetencies ?? '';
if (empty($successful) || empty($tested)) {
return 0.0;
}
// Parse semicolon-separated values.
$successful_array = array_filter(array_map('trim', explode(';', $successful)), function($v) {
return $v !== '' && is_numeric($v);
});
$tested_array = array_filter(array_map('trim', explode(';', $tested)), function($v) {
return $v !== '' && is_numeric($v);
});
if (empty($successful_array) || empty($tested_array) || count($successful_array) !== count($tested_array)) {
return 0.0;
}
// Calculate percentage for each competency block.
$percentages = array();
for ($i = 0; $i < count($successful_array); $i++) {
$successful_val = (float)$successful_array[$i];
$tested_val = (float)$tested_array[$i];
if ($tested_val > 0) {
$percentages[] = ($successful_val / $tested_val) * 100.0;
}
}
if (empty($percentages)) {
return 0.0;
}
// Return average of all competency percentages.
return round(array_sum($percentages) / count($percentages), 2);
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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 <http://www.gnu.org/licenses/>.
/**
* utility class for DTA submission plugin result display
*
* @package assignsubmission_dta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @copyright Gero Lueckemeyer and student project teams
*/
namespace assignsubmission_dta\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use templatable;
final class summary_table implements renderable, templatable {
/** @var array<int, array{label:string,value:string,cssclass?:string}> */
private array $rows;
private string $title;
/**
* @param array<int, array{label:string,value:string,cssclass?:string}> $rows
*/
public function __construct(array $rows, string $title = '') {
$this->rows = $rows;
$this->title = $title;
}
public function export_for_template(renderer_base $output): array {
return [
'title' => $this->title,
'rows' => $this->rows,
];
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// 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 <http://www.gnu.org/licenses/>.
/**
* Output class for rendering test summary cards.
*
* @package assignsubmission_dta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace assignsubmission_dta\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use templatable;
/**
* Renderable for test summary cards.
*/
class test_summary_cards implements renderable, templatable {
/** @var array Summary data */
private array $summary;
/**
* Constructor.
*
* @param array $summary Summary data with keys: totaltests, successcount, failurecount, compilationerrors,
* test_score, competency_percent, has_competencies, finalgrade, maxgrade
*/
public function __construct(array $summary) {
$this->summary = $summary;
}
/**
* Export data for template.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output): array {
$summary = $this->summary;
$successrate_percent = $summary['successrate_percent'] ?? 0.0;
$successrate_class = 'unknown';
$successrate_bgcolor = '#e9ecef';
$successrate_bordercolor = '#dee2e6';
$successrate_textcolor = '#495057';
// Format success rate exactly like in summary table: "5/11 (45.45%)" or "5/?"
$successrate_display = $summary['successrate'] ?? '?';
if ($summary['unknowncount'] == 0 && $summary['compilationerrors'] == 0 && $summary['totaltests'] > 0) {
if ($successrate_percent >= 75) {
$successrate_class = 'success';
$successrate_bgcolor = '#d4edda';
$successrate_bordercolor = '#c3e6cb';
$successrate_textcolor = '#155724';
} else if ($successrate_percent >= 50) {
$successrate_class = 'failure';
$successrate_bgcolor = '#fff3cd';
$successrate_bordercolor = '#ffeaa7';
$successrate_textcolor = '#856404';
} else {
$successrate_class = 'error';
$successrate_bgcolor = '#f8d7da';
$successrate_bordercolor = '#f5c6cb';
$successrate_textcolor = '#721c24';
}
}
return [
'hasdata' => $summary['hasdata'] ?? false,
'totaltests' => $summary['totaltests'],
'successcount' => $summary['successcount'],
'failurecount' => $summary['failurecount'],
'compilationerrors' => $summary['compilationerrors'],
'hascompilationerrors' => ($summary['compilationerrors'] > 0),
'unknowncount' => $summary['unknowncount'] ?? 0,
'hasunknown' => ($summary['unknowncount'] ?? 0) > 0,
'successrate' => $successrate_display,
'successrate_percent' => $successrate_percent,
'successrate_class' => $successrate_class,
'successrate_bgcolor' => $successrate_bgcolor,
'successrate_bordercolor' => $successrate_bordercolor,
'successrate_textcolor' => $successrate_textcolor,
];
}
}
/**
* Renderable for suggestion grade summary.
*/
class suggestion_grade_summary implements renderable, templatable {
/** @var array Summary data */
private array $summary;
/**
* Constructor.
*
* @param array $summary Summary data
*/
public function __construct(array $summary) {
$this->summary = $summary;
}
/**
* Export data for template.
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output): array {
$summary = $this->summary;
$testscore = $summary['test_score'] ?? 0.0;
$finalpercent = 0.0;
if ($summary['maxgrade'] > 0) {
$finalpercent = ($summary['finalgrade'] / $summary['maxgrade']) * 100;
}
$final_score = 0.0;
if (!empty($summary['has_competencies'])) {
$final_score = ($testscore * 0.8) + ($summary['competency_percent'] * 0.2);
} else {
$final_score = $testscore;
}
return [
'hasdata' => $summary['hasdata'] ?? false,
'totaltests' => $summary['totaltests'],
'successcount' => $summary['successcount'],
'test_score' => format_float($testscore, 2),
'competency_percent' => isset($summary['competency_percent']) ? format_float($summary['competency_percent'], 2) : '0.00',
'has_competencies' => $summary['has_competencies'] ?? false,
'final_score' => format_float($final_score, 2),
'finalgrade' => format_float($summary['finalgrade'], 2),
'maxgrade' => format_float($summary['maxgrade'], 2),
'finalpercent' => format_float($finalpercent, 1),
'hasmaxgrade' => ($summary['maxgrade'] > 0),
'hascompilationerror' => ($summary['compilationerrors'] > 1),
];
}
}
...@@ -27,10 +27,14 @@ require(__DIR__.'/../../../../../config.php'); ...@@ -27,10 +27,14 @@ require(__DIR__.'/../../../../../config.php');
require_once($CFG->dirroot.'/mod/assign/locallib.php'); require_once($CFG->dirroot.'/mod/assign/locallib.php');
require_once(__DIR__.'/dta_db_utils.php'); require_once(__DIR__.'/dta_db_utils.php');
require_once(__DIR__.'/models/dta_result.php'); require_once(__DIR__.'/models/dta_result.php');
require_once(__DIR__.'/grade_summary_helper.php');
require_once(__DIR__.'/output/test_summary_cards.php');
use assignsubmission_dta\output\summary_table;
use assignsubmission_dta\output\generic_table; use assignsubmission_dta\output\generic_table;
use assignsubmission_dta\output\sortable_table; use assignsubmission_dta\output\sortable_table;
use assignsubmission_dta\output\test_summary_cards;
use assignsubmission_dta\output\suggestion_grade_summary;
use assignsubmission_dta\grade_summary_helper;
// --- Prepare parameters & context --- // --- Prepare parameters & context ---
$cmid = optional_param('cmid', 0, PARAM_INT); $cmid = optional_param('cmid', 0, PARAM_INT);
...@@ -102,33 +106,53 @@ $PAGE->set_heading(format_string($SITE->fullname)); ...@@ -102,33 +106,53 @@ $PAGE->set_heading(format_string($SITE->fullname));
echo $OUTPUT->header(); echo $OUTPUT->header();
// --- Summary and Recommendations in one row --- // --- Calculate grade summary for test summary cards and suggestion grade ---
echo '<div class="row">'; $maxgrade = $assign->get_instance()->grade ?? 100.0;
$gradesummary = grade_summary_helper::calculate_summary($summary, $maxgrade);
// --- Render Overview Cards and Suggestion Grade Summary in one row ---
if ($summary !== null) {
echo '<div class="row">';
echo '<div class="col-md-6">';
echo $OUTPUT->render(new test_summary_cards($gradesummary));
echo '</div>'; // col-md-6
echo '<div class="col-md-6">';
echo $OUTPUT->render(new suggestion_grade_summary($gradesummary));
echo '</div>'; // col-md-6
echo '</div>'; // row
}
// --- Competencies and Recommendations in one row ---
echo '<div class="row" style="margin-top: 1.5rem;">';
echo '<div class="col-md-6">'; echo '<div class="col-md-6">';
// --- Summary Table (as in view.php) --- // --- Competencies Table (moved from bottom) ---
$successrate = "?"; $showncompetencies = explode(";", $summary->successfultestcompetencies);
if ($summary->assignsubmission_dta_unknown_count() == 0 && $summary->assignsubmission_dta_compilation_error_count() == 0 && $summary->assignsubmission_dta_result_count() > 0) { $overallcompetencies = explode(";", $summary->overalltestcompetencies);
$successrate = round(($summary->assignsubmission_dta_successful_count() / $summary->assignsubmission_dta_result_count()) * 100, 2);
$competencyrows = [];
for ($index = 0, $size = count($overallcompetencies); $index < $size; $index++) {
$comp = $overallcompetencies[$index];
$shown = $showncompetencies[$index];
// Only show competencies that have a non-zero value in overall competencies
if (!empty($comp) && $comp > 0) {
$percent = round(($shown / $comp) * 100, 0);
$competencyrows[] = [
get_string("comp" . $index, 'assignsubmission_dta'),
$percent . "% (" . $shown . "/" . $comp . ")",
get_string("comp_expl" . $index, 'assignsubmission_dta')
];
}
} }
$summaryrows = [ $competencyheadings = [
['label' => get_string('total_items', 'assignsubmission_dta'), 'value' => $summary->assignsubmission_dta_result_count()], get_string('competencies', 'assignsubmission_dta'),
['label' => get_string('tests_successful', 'assignsubmission_dta'), 'value' => $summary->assignsubmission_dta_successful_count(), 'cssclass' => 'dtaResultSuccess'], get_string('success_rate', 'assignsubmission_dta'),
['label' => get_string('failures', 'assignsubmission_dta'), 'value' => $summary->assignsubmission_dta_failed_count(), 'cssclass' => 'dtaResultFailure'], get_string('details', 'assignsubmission_dta')
['label' => get_string('compilation_errors', 'assignsubmission_dta'), 'value' => $summary->assignsubmission_dta_compilation_error_count(), 'cssclass' => 'dtaResultCompilationError'],
['label' => get_string('unknown_state', 'assignsubmission_dta'), 'value' => $summary->assignsubmission_dta_unknown_count(), 'cssclass' => 'dtaResultUnknown'],
['label' => get_string('success_rate', 'assignsubmission_dta'), 'value' =>
$summary->assignsubmission_dta_successful_count() . "/" .
(($summary->assignsubmission_dta_compilation_error_count() == 0 && $summary->assignsubmission_dta_unknown_count() == 0)
? $summary->assignsubmission_dta_result_count() . " (" . $successrate . "%)"
: "?"), 'cssclass' =>
(($summary->assignsubmission_dta_compilation_error_count() == 0 && $summary->assignsubmission_dta_unknown_count() == 0)
? ($successrate >= 75 ? 'dtaResultSuccess' : ($successrate >= 50 ? 'dtaResultFailure' : 'dtaResultCompilationError'))
: 'dtaResultUnknown')]
]; ];
echo $OUTPUT->render(new summary_table($summaryrows, get_string('summary', 'assignsubmission_dta'))); echo $OUTPUT->render(new generic_table($competencyheadings, $competencyrows, get_string('competencies', 'assignsubmission_dta')));
echo '</div>'; // col-md-6 echo '</div>'; // col-md-6
...@@ -202,44 +226,8 @@ echo '</div>'; // row ...@@ -202,44 +226,8 @@ echo '</div>'; // row
// --- Spacer --- // --- Spacer ---
echo html_writer::empty_tag("div", ["class" => "dtaSpacer"]); echo html_writer::empty_tag("div", ["class" => "dtaSpacer"]);
// --- Competencies Table (as in view.php) ---
$showncompetencies = explode(";", $summary->successfultestcompetencies);
$overallcompetencies = explode(";", $summary->overalltestcompetencies);
$competencyrows = [];
for ($index = 0, $size = count($overallcompetencies); $index < $size; $index++) {
$comp = $overallcompetencies[$index];
$shown = $showncompetencies[$index];
$compval = floatval($comp);
$shownval = floatval($shown);
// Only show competencies that have a non-zero value in overall competencies
if ($compval > 0) {
$pct = 100 * $shownval / $compval;
// Only show if percentage is greater than 0
if ($pct > 0) {
$competencyrows[] = [
get_string("comp" . $index, 'assignsubmission_dta'),
round($pct, 2) . "% (" . $shown . " / " . $comp . ")",
get_string("comp_expl" . $index, 'assignsubmission_dta')
];
}
}
}
$competencyheadings = [
get_string('competencies', 'assignsubmission_dta'),
'',
''
];
echo $OUTPUT->render(new generic_table($competencyheadings, $competencyrows, get_string('competencies', 'assignsubmission_dta')));
// --- Spacer ---
echo html_writer::empty_tag("div", ["class" => "dtaSpacer"]);
// --- Details Table (as in view.php) --- // --- Details Table (as in view.php) ---
echo '<div style="margin-top: 1.5rem;">';
$detailrows = []; $detailrows = [];
foreach ($summary->results as $r) { foreach ($summary->results as $r) {
// Package name row // Package name row
...@@ -310,10 +298,11 @@ foreach ($summary->results as $r) { ...@@ -310,10 +298,11 @@ foreach ($summary->results as $r) {
} }
$detailheadings = [ $detailheadings = [
get_string('details', 'assignsubmission_dta'), '',
'' ''
]; ];
echo $OUTPUT->render(new generic_table($detailheadings, $detailrows, get_string('details', 'assignsubmission_dta'))); echo $OUTPUT->render(new generic_table($detailheadings, $detailrows, get_string('details', 'assignsubmission_dta')));
echo '</div>'; // margin-top wrapper
echo $OUTPUT->footer(); echo $OUTPUT->footer();
...@@ -22,6 +22,8 @@ ...@@ -22,6 +22,8 @@
* @copyright Gero Lueckemeyer and student project teams * @copyright Gero Lueckemeyer and student project teams
*/ */
defined('MOODLE_INTERNAL') || die();
// General. // General.
$string["pluginname"] = "Dockerized Testing Agent (DTA)"; $string["pluginname"] = "Dockerized Testing Agent (DTA)";
$string["enabled"] = $string["pluginname"]; $string["enabled"] = $string["pluginname"];
...@@ -179,3 +181,15 @@ $string['url'] = 'URL'; ...@@ -179,3 +181,15 @@ $string['url'] = 'URL';
$string['difficulty'] = 'Difficulty'; $string['difficulty'] = 'Difficulty';
$string['score'] = 'Score'; $string['score'] = 'Score';
// Test summary cards and suggestion grade
$string['dta_report_heading'] = 'Dockerized Test Agent report Summary';
$string['tests_total'] = 'Total tests';
$string['suggestion_grade_summary'] = 'Suggestion grade';
$string['suggestion_grade_formula'] = 'Grade calculation formula';
$string['suggestion_grade_formula_test'] = 'Test score = (Successful tests / Total tests) × 100%';
$string['suggestion_grade_formula_competency'] = 'Competency score = Average of competency block percentages';
$string['suggestion_grade_formula_final'] = 'Final score = (Test score × 80%) + (Competency score × 20%)';
$string['suggestion_grade_formula_final_no_competency'] = 'Final score = Test score × 100%';
$string['suggestion_grade_formula_compilation'] = 'If more than 1 compilation error exists, final grade = 0';
$string['suggestion_grade_value'] = 'Suggestion grade';
<div class="card my-3"> <div class="card my-3" style="margin-top: 1rem; margin-bottom: 1rem; height: 100%;">
{{#title}}<div class="card-header">{{.}}</div>{{/title}} {{#title}}<div class="card-header"><h4 class="mb-0">{{.}}</h4></div>{{/title}}
<div class="card-body p-0"> <div class="card-body p-0" style="display: flex; flex-direction: column;">
<table class="generaltable table table-striped m-0"> <table class="generaltable table table-striped m-0">
{{#headings.0}} {{#headings.0}}
<thead> <thead>
......
<div class="card my-3"> <div class="card my-3" style="margin-top: 1rem; margin-bottom: 1rem; height: 100%;">
{{#title}}<div class="card-header">{{.}}</div>{{/title}} {{#title}}<div class="card-header"><h4 class="mb-0">{{.}}</h4></div>{{/title}}
<div class="card-body p-0"> <div class="card-body p-0" style="display: flex; flex-direction: column;">
<table class="generaltable table table-striped m-0"> <table class="generaltable table table-striped m-0">
{{#headings.0}} {{#headings.0}}
<thead> <thead>
......
{{#hasdata}}
<div class="card my-3" style="height: 100%; margin-top: 1rem; margin-bottom: 1rem;">
<div class="card-header">
<h4 class="mb-0">{{#str}}suggestion_grade_summary, assignsubmission_dta{{/str}}</h4>
</div>
<div class="card-body">
<h5 class="mt-3 mb-2">{{#str}}suggestion_grade_formula, assignsubmission_dta{{/str}}</h5>
<div class="mb-3" style="background-color: #ffffff; padding: 1rem; border: 1px solid #dee2e6; border-left: 3px solid #007bff; border-radius: 0.25rem;">
<!-- Test score formula -->
<div class="mb-2">
<strong>{{#str}}suggestion_grade_formula_test, assignsubmission_dta{{/str}}</strong><br/>
<span style="color: #666; font-size: 0.9em; margin-left: 1rem;">= ({{successcount}} / {{totaltests}}) × 100% = {{test_score}}%</span>
</div>
{{#has_competencies}}
<!-- Competency score formula -->
<div class="mb-2">
<strong>{{#str}}suggestion_grade_formula_competency, assignsubmission_dta{{/str}}</strong><br/>
<span style="color: #666; font-size: 0.9em; margin-left: 1rem;">= {{competency_percent}}%</span>
</div>
<!-- Final score formula with competencies -->
<div class="mb-2">
<strong>{{#str}}suggestion_grade_formula_final, assignsubmission_dta{{/str}}</strong><br/>
<span style="color: #666; font-size: 0.9em; margin-left: 1rem;">= ({{test_score}}% × 80%) + ({{competency_percent}}% × 20%) = {{final_score}}%</span>
</div>
{{/has_competencies}}
{{^has_competencies}}
<!-- Final score formula without competencies -->
<div class="mb-2">
<strong>{{#str}}suggestion_grade_formula_final_no_competency, assignsubmission_dta{{/str}}</strong><br/>
<span style="color: #666; font-size: 0.9em; margin-left: 1rem;">= {{test_score}}% × 100% = {{test_score}}%</span>
</div>
{{/has_competencies}}
{{#hascompilationerror}}
<!-- Compilation error rule -->
<div class="mb-2">
<strong style="color: #dc3545;">{{#str}}suggestion_grade_formula_compilation, assignsubmission_dta{{/str}}</strong>
</div>
{{/hascompilationerror}}
</div>
{{#hasmaxgrade}}
<!-- Final grade (highlighted) -->
<div class="mt-4 p-3" style="background-color: #e7f3ff; border: 1px solid #dee2e6; border-left: 4px solid #007bff; border-radius: 0.25rem;">
<strong style="font-size: 1.1em;">{{#str}}suggestion_grade_value, assignsubmission_dta{{/str}}: </strong>
<span style="font-size: 1.3em; font-weight: bold; color: #007bff;">{{finalgrade}} / {{maxgrade}} ({{finalpercent}}%)</span>
</div>
{{/hasmaxgrade}}
</div>
</div>
{{/hasdata}}
<div class="card my-3">
{{#title}}<div class="card-header">{{.}}</div>{{/title}}
<div class="card-body p-0">
<table class="generaltable table table-striped m-0">
<tbody>
{{#rows}}
<tr{{#cssclass}} class="{{.}}"{{/cssclass}}>
<th scope="row" class="w-50">{{label}}</th>
<td>{{value}}</td>
</tr>
{{/rows}}
{{^rows}}
<tr><td colspan="2">{{#str}} nothingtodisplay, core {{/str}}</td></tr>
{{/rows}}
</tbody>
</table>
</div>
</div>
{{#hasdata}}
<div class="card my-3" style="height: 100%; margin-top: 1rem; margin-bottom: 1rem;">
<div class="card-header">
<h4 class="mb-0">{{#str}}dta_report_heading, assignsubmission_dta{{/str}}</h4>
</div>
<div class="card-body">
<div class="row" style="display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; align-items: stretch;">
<!-- Total items card -->
<div class="card" style="flex: 0 0 calc(30% - 0.5rem); min-width: 140px; max-width: calc(30% - 0.5rem); border: 1px solid #dee2e6; border-radius: 0.25rem; box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); overflow: hidden; background-color: #ffffff; min-height: 120px; display: flex; flex-direction: column; justify-content: center; margin: 0;">
<div style="padding: 1.25rem; text-align: center; box-sizing: border-box;">
<div style="font-size: 2em; font-weight: bold;">{{totaltests}}</div>
<div style="font-size: 0.9em; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; margin-top: 0.5rem; line-height: 1.3; color: #666;">{{#str}}total_items, assignsubmission_dta{{/str}}</div>
</div>
</div>
<!-- Successful tests card (green) -->
<div class="card" style="flex: 0 0 calc(30% - 0.5rem); min-width: 140px; max-width: calc(30% - 0.5rem); border: 1px solid #dee2e6; border-radius: 0.25rem; box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); overflow: hidden; background-color: #d4edda; border-color: #c3e6cb; min-height: 120px; display: flex; flex-direction: column; justify-content: center; margin: 0;">
<div style="padding: 1.25rem; text-align: center; box-sizing: border-box;">
<div style="font-size: 2em; font-weight: bold; color: #155724;">{{successcount}}</div>
<div style="font-size: 0.9em; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; margin-top: 0.5rem; line-height: 1.3; color: #155724;">{{#str}}tests_successful, assignsubmission_dta{{/str}}</div>
</div>
</div>
<!-- Failed tests card (yellow/orange) -->
<div class="card" style="flex: 0 0 calc(30% - 0.5rem); min-width: 140px; max-width: calc(30% - 0.5rem); border: 1px solid #dee2e6; border-radius: 0.25rem; box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); overflow: hidden; background-color: #fff3cd; border-color: #ffeaa7; min-height: 120px; display: flex; flex-direction: column; justify-content: center; margin: 0;">
<div style="padding: 1.25rem; text-align: center; box-sizing: border-box;">
<div style="font-size: 2em; font-weight: bold; color: #856404;">{{failurecount}}</div>
<div style="font-size: 0.9em; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; margin-top: 0.5rem; line-height: 1.3; color: #856404;">{{#str}}failures, assignsubmission_dta{{/str}}</div>
</div>
</div>
<!-- Compilation errors card (red) - always shown -->
<div class="card" style="flex: 0 0 calc(30% - 0.5rem); min-width: 140px; max-width: calc(30% - 0.5rem); border: 1px solid #dee2e6; border-radius: 0.25rem; box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); overflow: hidden; background-color: #f8d7da; border-color: #f5c6cb; min-height: 120px; display: flex; flex-direction: column; justify-content: center; margin: 0;">
<div style="padding: 1.25rem; text-align: center; box-sizing: border-box;">
<div style="font-size: 2em; font-weight: bold; color: #721c24;">{{compilationerrors}}</div>
<div style="font-size: 0.9em; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; margin-top: 0.5rem; line-height: 1.3; color: #721c24;">{{#str}}compilation_errors, assignsubmission_dta{{/str}}</div>
</div>
</div>
<!-- Unknown state card (grey) - always shown -->
<div class="card" style="flex: 0 0 calc(30% - 0.5rem); min-width: 140px; max-width: calc(30% - 0.5rem); border: 1px solid #dee2e6; border-radius: 0.25rem; box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); overflow: hidden; background-color: #e9ecef; border-color: #dee2e6; min-height: 120px; display: flex; flex-direction: column; justify-content: center; margin: 0;">
<div style="padding: 1.25rem; text-align: center; box-sizing: border-box;">
<div style="font-size: 2em; font-weight: bold; color: #495057;">{{unknowncount}}</div>
<div style="font-size: 0.9em; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; margin-top: 0.5rem; line-height: 1.3; color: #495057;">{{#str}}unknown_state, assignsubmission_dta{{/str}}</div>
</div>
</div>
<!-- Success rate card (color based on percentage) -->
<div class="card" style="flex: 0 0 calc(30% - 0.5rem); min-width: 140px; max-width: calc(30% - 0.5rem); border: 1px solid {{successrate_bordercolor}}; border-radius: 0.25rem; box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); overflow: hidden; background-color: {{successrate_bgcolor}}; min-height: 120px; display: flex; flex-direction: column; justify-content: center; margin: 0;">
<div style="padding: 1.25rem; text-align: center; box-sizing: border-box;">
<div style="font-size: 1.5em; font-weight: bold; color: {{successrate_textcolor}};">{{successrate}}</div>
<div style="font-size: 0.9em; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; margin-top: 0.5rem; line-height: 1.3; color: {{successrate_textcolor}};">{{#str}}success_rate, assignsubmission_dta{{/str}}</div>
</div>
</div>
</div>
</div>
</div>
{{/hasdata}}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment