*/ private const WINNER_TIERS = [ ['min' => 1, 'max' => 12, 'winners' => 2], ['min' => 13, 'max' => 16, 'winners' => 3], ['min' => 17, 'max' => 22, 'winners' => 4], ['min' => 23, 'max' => 27, 'winners' => 5], ['min' => 28, 'max' => null, 'winners' => 6], ]; public static function winnersForCount(int $count): int { if ($count <= 0) { return 0; } foreach (self::WINNER_TIERS as $tier) { $min = (int) $tier['min']; $max = $tier['max']; if ($count < $min) { continue; } if ($max === null || $count <= (int) $max) { return (int) $tier['winners']; } } return 0; } /** * @param array> $classSections * @param array $classCounts * @param array $overrides * @param array $questionCounts * @param array> $prizeMap * @return list> */ public static function buildClassRows( array $classSections, array $classCounts, array $overrides, array $questionCounts, array $prizeMap, int $limitClassId = 0 ): array { $rows = []; foreach ($classSections as $section) { $classId = (int) ($section['class_section_id'] ?? 0); if ($classId <= 0) { continue; } if ($limitClassId > 0 && $classId !== $limitClassId) { continue; } $count = $classCounts[$classId] ?? 0; if ($count <= 0) { continue; } $auto = self::winnersForCount($count); $override = isset($overrides[$classId]) ? (int) $overrides[$classId] : null; $questionCount = isset($questionCounts[$classId]) ? (int) $questionCounts[$classId] : null; $prizes = $prizeMap[$classId] ?? []; $rows[] = [ 'class_section_id' => $classId, 'class_section_name' => $section['class_section_name'] ?? (string) $classId, 'student_count' => $count, 'auto_winners' => $auto, 'override_winners' => $override, 'final_winners' => $override !== null ? $override : $auto, 'question_count' => $questionCount, 'prize_1' => $prizes[1] ?? null, 'prize_2' => $prizes[2] ?? null, 'prize_3' => $prizes[3] ?? null, 'prize_4' => $prizes[4] ?? null, 'prize_5' => $prizes[5] ?? null, 'prize_6' => $prizes[6] ?? null, ]; } return $rows; } /** * @param array> $classSections * @param array $classCounts * @return array> */ public static function filterSectionsWithStudents(array $classSections, array $classCounts): array { return array_values(array_filter($classSections, static function ($section) use ($classCounts) { $classId = (int) ($section['class_section_id'] ?? 0); if ($classId <= 0) { return false; } return ($classCounts[$classId] ?? 0) > 0; })); } /** * @param array $competition Row including optional class_section_id * @param array $classCounts * @param array $scoreCounts * @param array $overrides * @return array */ public static function getWinnersCountByClass( array $competition, array $classCounts, array $scoreCounts, array $overrides ): array { $classIds = array_unique(array_merge( array_keys($classCounts), array_keys($scoreCounts), array_keys($overrides) )); $limitClassId = (int) ($competition['class_section_id'] ?? 0); $map = []; foreach ($classIds as $classId) { $classId = (int) $classId; if ($classId <= 0) { continue; } if ($limitClassId > 0 && $classId !== $limitClassId) { continue; } $override = $overrides[$classId] ?? null; if ($override !== null) { $map[$classId] = (int) $override; continue; } $baseCount = $classCounts[$classId] ?? ($scoreCounts[$classId] ?? 0); if ($baseCount <= 0) { $map[$classId] = 0; continue; } $maxWinners = self::winnersForCount((int) $baseCount); $minWinners = 3; $map[$classId] = $maxWinners >= $minWinners ? $maxWinners : $minWinners; } return $map; } /** * @param array> $rows * @return array>> */ public static function groupScoresByClass(array $rows): array { $grouped = []; foreach ($rows as $row) { $classSectionId = (int) ($row['class_section_id'] ?? 0); if ($classSectionId <= 0) { continue; } $name = trim(($row['firstname'] ?? '').' '.($row['lastname'] ?? '')); $score = (float) ($row['score'] ?? 0); $grouped[$classSectionId][] = [ 'student_id' => $row['student_id'], 'name' => $name !== '' ? $name : ('Student #'.$row['student_id']), 'score' => $score, ]; } foreach ($grouped as &$rowsForClass) { usort($rowsForClass, static function ($a, $b) { return $b['score'] <=> $a['score']; }); } unset($rowsForClass); return $grouped; } /** * @param array>> $rankedByClass * @return array */ public static function getScoreCountsByClass(array $rankedByClass): array { $counts = []; foreach ($rankedByClass as $classId => $rows) { $counts[(int) $classId] = count($rows); } return $counts; } /** * @param array>> $rankedByClass * @param array $winnersCountByClass * @param array> $prizeMap * @param array $questionCounts * @return array>> */ public static function getTopWinnersByClass( array $rankedByClass, array $winnersCountByClass, array $prizeMap, array $questionCounts ): array { $out = []; foreach ($rankedByClass as $classSectionId => $rows) { $limit = (int) ($winnersCountByClass[$classSectionId] ?? 0); if ($limit <= 0) { continue; } $out[$classSectionId] = self::assignPrizesByScore( $rows, $prizeMap[$classSectionId] ?? [], $limit, $questionCounts[$classSectionId] ?? null ); } return $out; } /** * @param array $prizes * @return array */ public static function normalizePrizeInputs(array $prizes): array { $out = [1 => null, 2 => null, 3 => null, 4 => null, 5 => null, 6 => null]; foreach ($out as $rank => $_value) { $raw = trim((string) ($prizes[$rank] ?? '')); if ($raw === '') { $out[$rank] = null; } else { $out[$rank] = (float) $raw; } } return $out; } public static function castPrize(mixed $value): ?float { if ($value === null || $value === '') { return null; } return (float) $value; } /** * @param array $classPrizes */ public static function getPrizeForRank(array $classPrizes, int $rank): float { $val = $classPrizes[$rank] ?? null; return $val !== null ? (float) $val : 0.0; } /** * @param list> $rows * @param array $classPrizes * @return list> */ public static function assignPrizesByScore(array $rows, array $classPrizes, int $limit, ?int $questionCount = null): array { if ($limit <= 0) { return []; } usort($rows, static function ($a, $b) { return ($b['score'] ?? 0) <=> ($a['score'] ?? 0); }); $scoreGroups = []; $scoreOrder = []; foreach ($rows as $row) { $score = (float) ($row['score'] ?? 0); $scoreKey = number_format($score, 5, '.', ''); if (! array_key_exists($scoreKey, $scoreGroups)) { $scoreGroups[$scoreKey] = []; $scoreOrder[] = $scoreKey; } $scoreGroups[$scoreKey][] = $row; } $top = []; $rank = 1; foreach ($scoreOrder as $scoreKey) { if (count($top) >= $limit) { break; } foreach ($scoreGroups[$scoreKey] as $row) { $row['rank'] = $rank; $top[] = $row; } $rank++; } $sharedPrize = null; if ($questionCount !== null && $questionCount > 0 && ! empty($top)) { $allMax = true; foreach ($top as $row) { $score = (float) ($row['score'] ?? 0); if ($score < $questionCount) { $allMax = false; break; } } if ($allMax) { $sharedPrize = self::getPrizeForRank($classPrizes, 1); } } $out = []; foreach ($top as $row) { if ($sharedPrize !== null) { $prize = $sharedPrize; } else { $prize = self::getPrizeForRank($classPrizes, (int) ($row['rank'] ?? 1)); } $row['prize_amount'] = $prize; $out[] = $row; } return $out; } }