'table-success',
'surprise' => 'table-primary',
'missed' => 'table-warning',
@@ -203,9 +392,9 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
= $isMale ? 'M' : 'F' ?>
- | = $s['fall_score'] !== null ? number_format($s['fall_score'], 1) : '—' ?> |
+ = $s['fall_score'] !== null ? number_format($s['fall_score'], 1) : '—' ?> |
= $s['spring_score'] !== null ? number_format($s['spring_score'], 1) : '—' ?> |
- = $s['year_score'] !== null ? number_format($s['year_score'], 1) : '—' ?> |
+ = $s['year_score'] !== null ? number_format($s['year_score'], 1) : '—' ?> |
= $s['predicted']
? 'Yes'
@@ -237,6 +426,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
@@ -253,6 +443,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
| Year ≥ |
+
@@ -271,6 +462,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
| Total |
@@ -292,7 +484,6 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
-
@@ -301,7 +492,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
-
+
@@ -312,6 +503,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
@@ -324,6 +516,93 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
+
+
+
+
+
+
+
+ Total Winners
+ = (int)$totalWinners ?>
+ Actual year-end trophy winners
+
+
+
+
+
+ Boys
+ = (int)$totalWinnerBoys ?>
+ = number_format($winnerBoysPct, 1) ?>%
+
+
+
+
+
+ Girls
+ = (int)$totalWinnerGirls ?>
+ = number_format($winnerGirlsPct, 1) ?>%
+
+
+
+
+
+ Unknown / Other
+ = (int)$totalWinnerOther ?>
+ = number_format($winnerOtherPct, 1) ?>%
+
+
+
+
+
+
+ 0): ?>
+
+ Boys = number_format($winnerBoysPct, 1) ?>%
+
+
+
+ Girls = number_format($winnerGirlsPct, 1) ?>%
+
+
+ 0): ?>
+
+ Other = number_format($winnerOtherPct, 1) ?>%
+
+
+
+
+ No winners
+
+
+
+
+
+
+
@@ -340,7 +619,6 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
-
Student Detail
@@ -357,6 +635,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
| Status |
+
0 ? round($totalConfirmed / $totalPredicted
foreach ($cls['students'] as $s):
if (!in_array($s['status'], ['confirmed', 'surprise'], true)) continue;
$rank++;
- $isMale = strtolower($s['gender'] ?? '') === 'male';
+ $genderNorm = strtolower(trim((string)($s['gender'] ?? '')));
+ $isMale = in_array($genderNorm, ['male', 'm', 'boy'], true);
$statusLabel = match ($s['status']) {
'confirmed' => '✓ Confirmed',
'surprise' => '↑ Surprise',
@@ -378,18 +658,17 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
= esc($cls['section_name']) ?> |
= esc($s['name']) ?> |
= $isMale ? 'M' : 'F' ?> |
- = $s['fall_score'] !== null ? number_format($s['fall_score'], 1) : '—' ?> |
+ = $s['fall_score'] !== null ? number_format($s['fall_score'], 1) : '—' ?> |
= $s['spring_score'] !== null ? number_format($s['spring_score'], 1) : '—' ?> |
- = $s['year_score'] !== null ? number_format($s['year_score'], 1) : '—' ?> |
+ = $s['year_score'] !== null ? number_format($s['year_score'], 1) : '—' ?> |
= $s['predicted'] ? 'Yes' : 'No' ?> |
- = $s['actual'] ? 'Yes' : 'No' ?> |
+ = $s['actual'] ? 'Yes' : 'No' ?> |
= $statusLabel ?> |
-
Prediction Accuracy Summary
@@ -407,6 +686,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
| Year ≥ |
+
@@ -423,6 +703,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
| Total |
@@ -438,17 +719,18 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
-
Charts
+
Trophy Counts per Class
- ![]()
+
+
Prediction Accuracy per Class
- ![]()
+
Overall Outcome Breakdown
@@ -456,8 +738,69 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
+ Winner Gender Breakdown
+
+
+
+
+ | Group |
+ Winners |
+ Percentage |
+
+
+
+
+
+ | Boys |
+ = (int)$totalWinnerBoys ?> |
+ = number_format($winnerBoysPct, 1) ?>% |
+
+
+ | Girls |
+ = (int)$totalWinnerGirls ?> |
+ = number_format($winnerGirlsPct, 1) ?>% |
+
+ 0): ?>
+
+ | Unknown / Other |
+ = (int)$totalWinnerOther ?> |
+ = number_format($winnerOtherPct, 1) ?>% |
+
+
+
+
+
+
+ | Total Winners |
+ = (int)$totalWinners ?> |
+ = $totalWinners > 0 ? '100.0%' : '0.0%' ?> |
+
+
+
+
+
+
+
+
+
+
+
+
+ = esc($winnerName) ?>
+
+
+
+
+
+
+
+
+
+
+
@@ -473,6 +816,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
$cSurprise = [];
$cMissed = [];
$cAccuracy = [];
+
foreach ($classResults as $cls) {
$cLabels[] = $cls['section_name'];
$cPredicted[] = $cls['predicted_count'];
@@ -483,94 +827,105 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
$cAccuracy[] = $cls['accuracy'];
}
?>
- var labels = = json_encode($cLabels) ?>;
+
+ var labels = = json_encode($cLabels) ?>;
var predicted = = json_encode($cPredicted) ?>;
- var actual = = json_encode($cActual) ?>;
+ var actual = = json_encode($cActual) ?>;
var confirmed = = json_encode($cConfirmed) ?>;
- var surprises = = json_encode($cSurprise) ?>;
- var missed = = json_encode($cMissed) ?>;
- var accuracy = = json_encode($cAccuracy) ?>;
+ var surprises = = json_encode($cSurprise) ?>;
+ var missed = = json_encode($cMissed) ?>;
+ var accuracy = = json_encode($cAccuracy) ?>;
- /* ── Chart 1: grouped bar – counts per class ── */
- new Chart(document.getElementById('chart-counts'), {
- type: 'bar',
- data: {
- labels: labels,
- datasets: [
- { label: 'Predicted', data: predicted, backgroundColor: '#4A90E2', borderRadius: 3 },
- { label: 'Actual', data: actual, backgroundColor: '#f0a500', borderRadius: 3 },
- { label: 'Confirmed', data: confirmed, backgroundColor: '#28a745', borderRadius: 3 },
- { label: 'Surprises', data: surprises, backgroundColor: '#17a2b8', borderRadius: 3 },
- { label: 'Missed', data: missed, backgroundColor: '#fd7e14', borderRadius: 3 },
- ]
- },
- options: {
- responsive: true,
- maintainAspectRatio: true,
- plugins: { legend: { position: 'bottom' } },
- scales: {
- x: { grid: { color: '#f0f0f0' } },
- y: { beginAtZero: true, ticks: { stepSize: 1 }, grid: { color: '#f0f0f0' } }
- }
- }
- });
-
- /* ── Chart 2: accuracy % per class ── */
- new Chart(document.getElementById('chart-accuracy'), {
- type: 'bar',
- data: {
- labels: labels,
- datasets: [{
- label: 'Accuracy %',
- data: accuracy,
- backgroundColor: accuracy.map(function(a) {
- return a >= 80 ? '#28a745' : a >= 50 ? '#f0a500' : '#dc3545';
- }),
- borderRadius: 3
- }]
- },
- options: {
- responsive: true,
- maintainAspectRatio: true,
- plugins: { legend: { display: false } },
- scales: {
- y: {
- beginAtZero: true, max: 100,
- ticks: { callback: function(v) { return v + '%'; } },
- grid: { color: '#f0f0f0' }
+ if (document.getElementById('chart-counts')) {
+ new Chart(document.getElementById('chart-counts'), {
+ type: 'bar',
+ data: {
+ labels: labels,
+ datasets: [
+ { label: 'Predicted', data: predicted, backgroundColor: '#4A90E2', borderRadius: 3 },
+ { label: 'Actual', data: actual, backgroundColor: '#f0a500', borderRadius: 3 },
+ { label: 'Confirmed', data: confirmed, backgroundColor: '#28a745', borderRadius: 3 },
+ { label: 'Surprises', data: surprises, backgroundColor: '#17a2b8', borderRadius: 3 },
+ { label: 'Missed', data: missed, backgroundColor: '#fd7e14', borderRadius: 3 },
+ ]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ plugins: { legend: { position: 'bottom' } },
+ scales: {
+ x: { grid: { color: '#f0f0f0' } },
+ y: { beginAtZero: true, ticks: { stepSize: 1 }, grid: { color: '#f0f0f0' } }
}
}
- }
- });
+ });
+ }
- /* ── Chart 3: doughnut overall outcome breakdown ── */
- new Chart(document.getElementById('chart-breakdown'), {
- type: 'doughnut',
- data: {
- labels: ['Confirmed', 'Surprises', 'Missed'],
- datasets: [{
- data: [= $totalConfirmed ?>, = $totalSurprise ?>, = $totalMissed ?>],
- backgroundColor: ['#28a745', '#17a2b8', '#fd7e14'],
- borderWidth: 2
- }]
- },
- options: {
- responsive: true,
- maintainAspectRatio: true,
- plugins: {
- legend: { position: 'bottom' },
- tooltip: {
- callbacks: {
- label: function(ctx) {
- var total = ctx.dataset.data.reduce(function(a, b) { return a + b; }, 0);
- var pct = total > 0 ? Math.round(ctx.parsed / total * 100) : 0;
- return ctx.label + ': ' + ctx.parsed + ' (' + pct + '%)';
+ if (document.getElementById('chart-accuracy')) {
+ new Chart(document.getElementById('chart-accuracy'), {
+ type: 'bar',
+ data: {
+ labels: labels,
+ datasets: [{
+ label: 'Accuracy %',
+ data: accuracy,
+ backgroundColor: accuracy.map(function(a) {
+ return a >= 80 ? '#28a745' : a >= 50 ? '#f0a500' : '#dc3545';
+ }),
+ borderRadius: 3
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ plugins: { legend: { display: false } },
+ scales: {
+ y: {
+ beginAtZero: true,
+ max: 100,
+ ticks: {
+ callback: function(v) {
+ return v + '%';
+ }
+ },
+ grid: { color: '#f0f0f0' }
+ }
+ }
+ }
+ });
+ }
+
+ if (document.getElementById('chart-breakdown')) {
+ new Chart(document.getElementById('chart-breakdown'), {
+ type: 'doughnut',
+ data: {
+ labels: ['Confirmed', 'Surprises', 'Missed'],
+ datasets: [{
+ data: [= $totalConfirmed ?>, = $totalSurprise ?>, = $totalMissed ?>],
+ backgroundColor: ['#28a745', '#17a2b8', '#fd7e14'],
+ borderWidth: 2
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ plugins: {
+ legend: { position: 'bottom' },
+ tooltip: {
+ callbacks: {
+ label: function(ctx) {
+ var total = ctx.dataset.data.reduce(function(a, b) {
+ return a + b;
+ }, 0);
+ var pct = total > 0 ? Math.round(ctx.parsed / total * 100) : 0;
+ return ctx.label + ': ' + ctx.parsed + ' (' + pct + '%)';
+ }
}
}
}
}
- }
- });
+ });
+ }
})();
function captureCharts() {
@@ -579,20 +934,41 @@ function captureCharts() {
'chart-accuracy': 'print-chart-accuracy',
'chart-breakdown': 'print-chart-breakdown',
};
+
Object.keys(map).forEach(function(canvasId) {
var canvas = document.getElementById(canvasId);
var img = document.getElementById(map[canvasId]);
- if (canvas && img) img.src = canvas.toDataURL('image/png');
+
+ if (canvas && img) {
+ img.src = canvas.toDataURL('image/png');
+ }
});
}
function printWithCharts() {
+ document.body.classList.remove('print-stickers-mode');
captureCharts();
window.print();
}
-window.addEventListener('beforeprint', captureCharts);
+function printWinnerStickers() {
+ document.body.classList.add('print-stickers-mode');
+
+ setTimeout(function () {
+ window.print();
+
+ setTimeout(function () {
+ document.body.classList.remove('print-stickers-mode');
+ }, 1000);
+ }, 250);
+}
+
+window.addEventListener('beforeprint', function () {
+ if (!document.body.classList.contains('print-stickers-mode')) {
+ captureCharts();
+ }
+});
= $this->endSection() ?>
-= $this->endSection() ?>
+= $this->endSection() ?>
\ No newline at end of file
|