0, 'target_sizes' => [], 'sections' => [], 'assignments' => [], ]; } $sectionCount = $total <= $capacity ? 1 : (int) ceil($total / $capacity); $targetSizes = $this->targetSizes($total, $sectionCount); $sections = []; for ($i = 1; $i <= $sectionCount; $i++) { $sections[$i] = [ 'section_index' => $i, 'target_size' => $targetSizes[$i], 'total' => 0, 'bands' => ['A' => 0, 'B' => 0, 'C' => 0, 'D' => 0], 'students' => [], ]; } $grouped = ['A' => [], 'B' => [], 'C' => [], 'D' => []]; foreach ($students as $student) { $band = $student['score_band'] ?? 'D'; $grouped[$band][] = $student; } foreach (array_keys($grouped) as $band) { usort($grouped[$band], function (array $a, array $b): int { $scoreA = $a['final_score'] ?? $a['placement_score'] ?? -1; $scoreB = $b['final_score'] ?? $b['placement_score'] ?? -1; if ($scoreA !== $scoreB) { return $scoreB <=> $scoreA; } $name = strcmp((string) ($a['student_name'] ?? ''), (string) ($b['student_name'] ?? '')); return $name !== 0 ? $name : ((int) $a['student_id'] <=> (int) $b['student_id']); }); } $assignments = []; $order = 1; foreach (['A', 'B', 'C', 'D'] as $band) { foreach ($grouped[$band] as $student) { $sectionIndex = $this->selectSection($sections, $band); $sections[$sectionIndex]['total']++; $sections[$sectionIndex]['bands'][$band]++; $sections[$sectionIndex]['students'][] = (int) $student['student_id']; $student['planned_section_index'] = $sectionIndex; $student['assignment_order'] = $order++; $assignments[] = $student; } } return [ 'section_count' => $sectionCount, 'target_sizes' => $targetSizes, 'sections' => array_values($sections), 'assignments' => $assignments, ]; } private function targetSizes(int $total, int $sectionCount): array { $base = intdiv($total, $sectionCount); $remainder = $total % $sectionCount; $sizes = []; for ($i = 1; $i <= $sectionCount; $i++) { $sizes[$i] = $base + ($i <= $remainder ? 1 : 0); } return $sizes; } private function selectSection(array $sections, string $band): int { $eligible = array_filter($sections, fn ($s) => $s['total'] < $s['target_size']); if (empty($eligible)) { $eligible = $sections; } uasort($eligible, function (array $a, array $b) use ($band): int { $cmp = $a['total'] <=> $b['total']; if ($cmp !== 0) { return $cmp; } $cmp = ($a['bands'][$band] ?? 0) <=> ($b['bands'][$band] ?? 0); if ($cmp !== 0) { return $cmp; } return $a['section_index'] <=> $b['section_index']; }); return (int) array_key_first($eligible); } }