'Optional school year filter for duplicate enrollment detection.', '--json' => 'Print machine-readable JSON.', ]; private BaseConnection $db; public function run(array $params) { $this->db = \Config\Database::connect(); $schoolYear = trim((string) (CLI::getOption('school-year') ?? '')); $report = [ 'generated_at' => date('Y-m-d H:i:s'), 'school_year_filter' => $schoolYear !== '' ? $schoolYear : null, 'withdrawn_normalization' => $this->auditWithdrawnValues(), 'duplicate_enrollments' => $this->auditDuplicateEnrollments($schoolYear), ]; if (CLI::getOption('json') !== null) { CLI::write(json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); return; } CLI::write('Enrollment Data Audit', 'green'); CLI::newLine(); CLI::write('Withdrawn / misspelling findings: ' . count($report['withdrawn_normalization']), 'yellow'); foreach ($report['withdrawn_normalization'] as $row) { CLI::write(sprintf( ' [%s] %s.%s = %s', $row['table'], $row['column'], $row['id'] ?? $row['student_id'] ?? '?', $row['value'] )); } CLI::newLine(); CLI::write('Duplicate enrollment groups: ' . count($report['duplicate_enrollments']), 'yellow'); foreach ($report['duplicate_enrollments'] as $group) { CLI::write(sprintf( ' student=%d year=%s rows=%d ids=[%s]', $group['student_id'], $group['school_year'], $group['row_count'], implode(',', $group['enrollment_ids']) )); } } /** * @return list> */ private function auditWithdrawnValues(): array { $findings = []; $patterns = ['widthrawan', 'widthrwan', 'withdraw']; if ($this->db->tableExists('enrollments')) { $rows = $this->db->table('enrollments') ->select('id, student_id, school_year, enrollment_status') ->get() ->getResultArray(); foreach ($rows as $row) { $status = strtolower(trim((string) ($row['enrollment_status'] ?? ''))); foreach ($patterns as $pattern) { if ($status === $pattern || str_contains($status, $pattern)) { $findings[] = [ 'table' => 'enrollments', 'column' => 'enrollment_status', 'id' => (int) ($row['id'] ?? 0), 'student_id' => (int) ($row['student_id'] ?? 0), 'school_year' => (string) ($row['school_year'] ?? ''), 'value' => (string) ($row['enrollment_status'] ?? ''), 'recommended' => 'withdrawn', ]; } } } } if ($this->db->tableExists('student_decisions')) { $rows = $this->db->table('student_decisions') ->select('id, student_id, school_year, decision, deliberation_decision_standard') ->get() ->getResultArray(); foreach ($rows as $row) { foreach (['decision', 'deliberation_decision_standard'] as $column) { $raw = (string) ($row[$column] ?? ''); $normalized = DeliberationDecision::normalize($raw); if ($raw !== '' && $normalized === DeliberationDecision::WITHDRAWN && strtoupper($raw) !== DeliberationDecision::WITHDRAWN) { $findings[] = [ 'table' => 'student_decisions', 'column' => $column, 'id' => (int) ($row['id'] ?? 0), 'student_id' => (int) ($row['student_id'] ?? 0), 'school_year' => (string) ($row['school_year'] ?? ''), 'value' => $raw, 'recommended' => DeliberationDecision::WITHDRAWN, ]; } } } } return $findings; } /** * @return list> */ private function auditDuplicateEnrollments(string $schoolYear): array { if (! $this->db->tableExists('enrollments')) { return []; } $builder = $this->db->table('enrollments') ->select('student_id, school_year, COUNT(*) AS row_count, GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) AS enrollment_ids', false) ->groupBy('student_id, school_year') ->having('row_count >', 1); if ($schoolYear !== '') { $builder->where('school_year', $schoolYear); } $groups = []; foreach ($builder->get()->getResultArray() as $row) { $ids = array_values(array_filter(array_map('intval', explode(',', (string) ($row['enrollment_ids'] ?? ''))))); $groups[] = [ 'student_id' => (int) ($row['student_id'] ?? 0), 'school_year' => (string) ($row['school_year'] ?? ''), 'row_count' => (int) ($row['row_count'] ?? 0), 'enrollment_ids' => $ids, ]; } return $groups; } }