>> */ public function forStudents(array $studentIds): array { $studentIds = array_values(array_unique(array_filter( array_map('intval', $studentIds), static fn(int $id): bool => $id > 0 ))); if ($studentIds === []) { return []; } $history = []; $semesterRows = $this->latestSemesterRows($studentIds); foreach ($semesterRows as $row) { $studentId = (int) ($row['student_id'] ?? 0); $schoolYear = trim((string) ($row['school_year'] ?? '')); $semester = $this->normalizeSemester($row['semester'] ?? ''); if ($studentId <= 0 || $schoolYear === '' || !in_array($semester, ['fall', 'spring'], true)) { continue; } $history[$studentId][$schoolYear] ??= $this->emptyYear($schoolYear); if ($semester === 'fall') { $history[$studentId][$schoolYear]['midterm_s1'] = $this->score($row['midterm_exam_score'] ?? null); $history[$studentId][$schoolYear]['ptap_s1'] = $this->score($row['ptap_score'] ?? null); $history[$studentId][$schoolYear]['attendance_s1'] = $this->score($row['attendance_score'] ?? null); $history[$studentId][$schoolYear]['_semester_score_s1'] = $this->score($row['semester_score'] ?? null); } else { $finalScore = $this->score($row['final_exam_score'] ?? null); if ($finalScore === null) { // Some older Spring rows stored the final in this legacy column. $finalScore = $this->score($row['midterm_exam_score'] ?? null); } $history[$studentId][$schoolYear]['final_s2'] = $finalScore; $history[$studentId][$schoolYear]['ptap_s2'] = $this->score($row['ptap_score'] ?? null); $history[$studentId][$schoolYear]['attendance_s2'] = $this->score($row['attendance_score'] ?? null); $history[$studentId][$schoolYear]['_semester_score_s2'] = $this->score($row['semester_score'] ?? null); } } foreach ($this->commentRows($studentIds) as $row) { $studentId = (int) ($row['student_id'] ?? 0); $schoolYear = trim((string) ($row['school_year'] ?? '')); $semester = $this->normalizeSemester($row['semester'] ?? ''); $type = $this->normalizeCommentType($row['score_type'] ?? ''); if ($studentId <= 0 || $schoolYear === '') { continue; } $history[$studentId][$schoolYear] ??= $this->emptyYear($schoolYear); $field = match ($semester . ':' . $type) { 'fall:midterm' => 'midterm_comment_s1', 'fall:ptap' => 'ptap_comment_s1', 'fall:attendance' => 'attendance_comment_s1', 'spring:final' => 'final_comment_s2', 'spring:ptap' => 'ptap_comment_s2', 'spring:attendance' => 'attendance_comment_s2', default => null, }; if ($field === null || $history[$studentId][$schoolYear][$field] !== '') { continue; } $comment = trim((string) ($row['comment'] ?? '')); $review = trim((string) ($row['comment_review'] ?? '')); $history[$studentId][$schoolYear][$field] = $type === 'attendance' ? ($comment !== '' ? $comment : $review) : ($review !== '' ? $review : $comment); } foreach ($this->decisionRows($studentIds) as $row) { $studentId = (int) ($row['student_id'] ?? 0); $schoolYear = trim((string) ($row['school_year'] ?? '')); if ($studentId <= 0 || $schoolYear === '') { continue; } $history[$studentId][$schoolYear] ??= $this->emptyYear($schoolYear); if ($history[$studentId][$schoolYear]['year_score'] === null) { $history[$studentId][$schoolYear]['year_score'] = $this->score($row['year_score'] ?? null); } } foreach ($history as &$studentHistory) { foreach ($studentHistory as &$year) { if ($year['year_score'] === null) { $semesterScores = array_values(array_filter( [$year['_semester_score_s1'], $year['_semester_score_s2']], static fn($score): bool => $score !== null )); if ($semesterScores !== []) { $year['year_score'] = round(array_sum($semesterScores) / count($semesterScores), 1); } } unset($year['_semester_score_s1'], $year['_semester_score_s2']); } unset($year); krsort($studentHistory, SORT_STRING); $studentHistory = array_values($studentHistory); } unset($studentHistory); return $history; } private function latestSemesterRows(array $studentIds): array { if (!$this->db->tableExists('semester_scores')) { return []; } $rows = $this->db->table('semester_scores') ->select('id, student_id, school_year, semester, midterm_exam_score, final_exam_score, ptap_score, attendance_score, semester_score, updated_at') ->whereIn('student_id', $studentIds) ->orderBy('school_year', 'DESC') ->orderBy('updated_at', 'DESC') ->orderBy('id', 'DESC') ->get() ->getResultArray(); $latest = []; foreach ($rows as $row) { $key = (int) ($row['student_id'] ?? 0) . '|' . trim((string) ($row['school_year'] ?? '')) . '|' . $this->normalizeSemester($row['semester'] ?? ''); $latest[$key] ??= $row; } return array_values($latest); } private function commentRows(array $studentIds): array { if (!$this->db->tableExists('score_comments')) { return []; } $builder = $this->db->table('score_comments') ->select('id, student_id, school_year, semester, score_type, comment, comment_review') ->whereIn('student_id', $studentIds); if ($this->db->fieldExists('updated_at', 'score_comments')) { $builder->orderBy('updated_at', 'DESC'); } elseif ($this->db->fieldExists('created_at', 'score_comments')) { $builder->orderBy('created_at', 'DESC'); } return $builder->orderBy('id', 'DESC')->get()->getResultArray(); } private function decisionRows(array $studentIds): array { if (!$this->db->tableExists('student_decisions')) { return []; } return $this->db->table('student_decisions') ->select('id, student_id, school_year, year_score') ->whereIn('student_id', $studentIds) ->orderBy('school_year', 'DESC') ->orderBy('updated_at', 'DESC') ->orderBy('id', 'DESC') ->get() ->getResultArray(); } private function emptyYear(string $schoolYear): array { return [ 'school_year' => $schoolYear, 'midterm_s1' => null, 'midterm_comment_s1' => '', 'ptap_s1' => null, 'ptap_comment_s1' => '', 'attendance_s1' => null, 'attendance_comment_s1' => '', 'final_s2' => null, 'final_comment_s2' => '', 'ptap_s2' => null, 'ptap_comment_s2' => '', 'attendance_s2' => null, 'attendance_comment_s2' => '', 'year_score' => null, '_semester_score_s1' => null, '_semester_score_s2' => null, ]; } private function normalizeSemester($value): string { $semester = strtolower(trim((string) $value)); return match ($semester) { 'fall', 'first', 'first semester', 'semester 1', '1' => 'fall', 'spring', 'second', 'second semester', 'semester 2', '2' => 'spring', default => $semester, }; } private function normalizeCommentType($value): string { $type = strtolower(trim((string) $value)); $type = trim((string) preg_replace('/[^a-z0-9]+/', '_', $type), '_'); return match ($type) { 'midterm_comment', 'midterm_comments' => 'midterm', 'final_comment', 'final_comments' => 'final', 'ptap_comment', 'ptap_comments' => 'ptap', 'attendance_comment', 'attendance_comments', 'attendence', 'attendence_comment' => 'attendance', default => $type, }; } private function score($value): ?float { return $value !== null && $value !== '' && is_numeric($value) ? round((float) $value, 2) : null; } }