argument('csv'); if (! is_file($csv)) { $this->error("File CSV tidak ditemukan: {$csv}"); return self::FAILURE; } if (! $faceid->isConfigured()) { $this->error('FaceID belum dikonfigurasi (FACEID_BASE_URL / FACEID_API_KEY di .env).'); return self::FAILURE; } $photosDir = $this->option('photos') ?: dirname($csv); $rows = array_map('str_getcsv', file($csv)); $header = array_shift($rows); if (! $header) { $this->error('CSV kosong / header tidak ditemukan.'); return self::FAILURE; } $idx = array_flip($header); $ok = 0; $fail = 0; foreach ($rows as $row) { if (count($row) < 2) { continue; } $studentId = trim($row[$idx['student_id'] ?? 0] ?? ''); $name = trim($row[$idx['name'] ?? 1] ?? ''); if ($studentId === '' || $name === '') { $fail++; continue; } $paths = []; foreach (['photo1', 'photo2', 'photo3'] as $col) { if (! isset($idx[$col])) { continue; } $p = trim($row[$idx[$col]] ?? ''); if ($p === '') { continue; } $full = $p[0] === '/' ? $p : rtrim($photosDir, '/').'/'.$p; if (is_file($full)) { $paths[] = $full; } } if (! $paths) { $this->warn(" [skip] {$studentId}: tidak ada foto valid"); $fail++; continue; } $twin = null; if (isset($idx['twin_group'])) { $twin = trim($row[$idx['twin_group']] ?? '') ?: null; } $res = $faceid->enroll($studentId, $name, $paths, $twin); if ($res['ok']) { $this->info(" [ok] {$studentId} ({$name}): ".$res['data']['photos_accepted'].' foto diterima'); $ok++; } else { $this->error(" [gagal] {$studentId} ({$name}): ".($res['error'] ?? 'unknown')); $fail++; } } $this->newLine(); $this->info("Selesai: {$ok} berhasil, {$fail} gagal/skip."); return $fail === 0 ? self::SUCCESS : self::FAILURE; } }