init backend presensi
This commit is contained in:
105
app/Modules/Attendance/Controllers/FaceVerifyController.php
Normal file
105
app/Modules/Attendance/Controllers/FaceVerifyController.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Attendance\Controllers;
|
||||
|
||||
use App\Core\BaseApiController;
|
||||
use App\Modules\Face\Models\StudentFaceModel;
|
||||
use App\Modules\Face\Services\FaceService;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* FaceVerifyController
|
||||
*
|
||||
* POST /api/attendance/verify-face
|
||||
* Body: { "student_id": 123, "image": "data:image/jpeg;base64,..." }
|
||||
*
|
||||
* Strategi: Option 1 — bandingkan probe hanya dengan embedding milik student_id kandidat.
|
||||
*/
|
||||
class FaceVerifyController extends BaseApiController
|
||||
{
|
||||
public function verify(): ResponseInterface
|
||||
{
|
||||
$payload = $this->request->getJSON(true) ?? [];
|
||||
$studentId = (int) ($payload['student_id'] ?? 0);
|
||||
$imageData = (string) ($payload['image'] ?? '');
|
||||
|
||||
if ($studentId < 1) {
|
||||
return $this->errorResponse('student_id wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
if ($imageData === '') {
|
||||
return $this->errorResponse('image (base64) wajib diisi', null, null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'probe_');
|
||||
|
||||
try {
|
||||
$raw = $this->decodeBase64Image($imageData);
|
||||
if ($raw === null) {
|
||||
throw new \RuntimeException('Format gambar tidak valid (harus base64)');
|
||||
}
|
||||
file_put_contents($tmpFile, $raw);
|
||||
|
||||
$faceService = new FaceService();
|
||||
$probe = $faceService->extractEmbeddingWithQuality($tmpFile)['embedding'];
|
||||
|
||||
$faceModel = new StudentFaceModel();
|
||||
$rows = $faceModel->where('student_id', $studentId)->findAll();
|
||||
if (empty($rows)) {
|
||||
return $this->errorResponse('Belum ada embedding wajah untuk siswa ini', null, null, ResponseInterface::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$bestSim = -1.0;
|
||||
$bestSource = null;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$embedding = json_decode($row['embedding'] ?? '[]', true);
|
||||
if (! is_array($embedding) || $embedding === []) {
|
||||
continue;
|
||||
}
|
||||
$sim = $faceService->cosineSimilarity($probe, array_map('floatval', $embedding));
|
||||
if ($sim > $bestSim) {
|
||||
$bestSim = $sim;
|
||||
$bestSource = $row['source'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bestSim < 0) {
|
||||
return $this->errorResponse('Tidak ada embedding valid untuk siswa ini', null, null, ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
$threshold = $faceService->getDefaultThreshold();
|
||||
$status = $bestSim >= $threshold ? 'match' : 'no_match';
|
||||
|
||||
$data = [
|
||||
'student_id' => $studentId,
|
||||
'similarity' => $bestSim,
|
||||
'threshold' => $threshold,
|
||||
'matched_source'=> $bestSource,
|
||||
'status' => $status,
|
||||
];
|
||||
|
||||
return $this->successResponse($data, 'Face verification processed');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->errorResponse('Gagal verifikasi wajah: ' . $e->getMessage(), null, null, ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||||
} finally {
|
||||
if (is_file($tmpFile)) {
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function decodeBase64Image(string $input): ?string
|
||||
{
|
||||
$input = trim($input);
|
||||
if ($input === '') {
|
||||
return null;
|
||||
}
|
||||
if (strpos($input, 'base64,') !== false) {
|
||||
$parts = explode('base64,', $input, 2);
|
||||
$input = $parts[1];
|
||||
}
|
||||
$data = base64_decode($input, true);
|
||||
return $data === false ? null : $data;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user