156 lines
6.0 KiB
PHP
156 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace App\Commands;
|
|
|
|
use App\Support\Enrollment\DeliberationDecision;
|
|
use CodeIgniter\CLI\BaseCommand;
|
|
use CodeIgniter\CLI\CLI;
|
|
use CodeIgniter\Database\BaseConnection;
|
|
|
|
class EnrollmentDataAuditCommand extends BaseCommand
|
|
{
|
|
protected $group = 'Registration';
|
|
protected $name = 'registration:enrollment-data-audit';
|
|
protected $description = 'Audit withdrawn terminology normalization and duplicate enrollment rows per student/year.';
|
|
protected $usage = 'php spark registration:enrollment-data-audit [--school-year=2026-2027] [--json]';
|
|
protected $options = [
|
|
'--school-year' => '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<array<string, mixed>>
|
|
*/
|
|
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<array<string, mixed>>
|
|
*/
|
|
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;
|
|
}
|
|
}
|