sista_mif_ta/sista_mif/app/Http/Controllers/ClassificationTaskControlle...

196 lines
9.1 KiB
PHP

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\ClassificationResult;
use App\Models\KemendikRawData;
use App\Models\InternalRawData;
use App\Services\FastApiWorkerService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class ClassificationTaskController extends Controller
{
public function __construct(protected FastApiWorkerService $fastApi) {}
public function upload(Request $request) {
$request->validate(['file' => 'required|file|mimes:csv,xlsx,xls|max:20480']);
$result = $this->fastApi->classifyFile($request->file('file')->getRealPath(), $request->file('file')->getClientOriginalName());
if (!isset($result['results']) && isset($result['error'])) {
return back()->with('error', $result['error']);
}
if (empty($result['results'])) {
return back()->with('success', "Selesai. 0 data berhasil diproses.");
}
DB::transaction(function () use ($result) {
$kemendikUpsertData = [];
$internalResultsUpsertData = [];
$internalRawUpsertData = [];
foreach ($result['results'] as $row) {
if ($row['source_type'] === 'kemendik') {
$raw = $row['raw_data'] ?? [];
$kemendikUpsertData[] = [
'nimhsmsmh' => $row['nim'],
'source_type' => 'kemendik',
'nmmhsmsmh' => $row['nama'],
'tahun_lulus' => $row['tahun_lulus'] ?? null,
'f5c_jabatan_kode' => $raw['F5c'] ?? null,
'f5c_jabatan_text' => $this->mapF5c($raw['F5c'] ?? null),
'f1101_jenis_instansi_kode' => $raw['F1101'] ?? null,
'f1101_jenis_instansi_text' => $this->mapF1101($raw['F1101'] ?? null),
'f5a1_provinsi' => $raw['F5a1'] ?? null,
'raw_data' => json_encode($raw)
];
} else {
$internalResultsUpsertData[] = [
'nim' => $row['nim'],
'source_type' => 'internal_mif',
'nama' => $row['nama'],
'tahun_lulus' => $row['tahun_lulus'] ?? null,
'job_text_raw' => $row['job_text_raw'] ?? '',
'predicted_profile' => $row['predicted_profile'],
'confidence_score' => $row['confidence_score'] ?? 0,
'classification_method' => $row['classification_method'],
'status' => $row['status'],
'error_detail' => $row['error_detail'] ?? null
];
if (isset($row['raw_data'])) {
$mapped = ['nim' => $row['nim']];
$cols = [
'nama_lengkap', 'email', 'no_telepon', 'alamat_domisili', 'jurusan',
'program_studi', 'tahun_masuk', 'tahun_lulus', 'status_pekerjaan',
'klasifikasi_pekerjaan', 'nama_perusahaan', 'jenis_perusahaan', 'jabatan',
'lokasi_perusahaan', 'alamat_perusahaan', 'deskripsi_pekerjaan',
'tahun_mulai_kerja', 'masa_kerja_bulan', 'jumlah_lamaran_dikirim',
'jumlah_respons_lamaran', 'jumlah_undangan_wawancara', 'masa_tunggu_pra_lulus',
'masa_tunggu_pasca_lulus', 'total_masa_tunggu', 'instansi_terupdate',
'jabatan_terupdate', 'tahun_bekerja', 'status_pekerjaan_terupdate',
'linkedin_profile', 'sosmed_ig'
];
foreach ($cols as $col) {
$val = $row['raw_data'][$col] ?? null;
$mapped[$col] = $val === '' ? null : $val;
}
$internalRawUpsertData[] = $mapped;
}
}
}
if (!empty($kemendikUpsertData)) {
foreach (array_chunk($kemendikUpsertData, 500) as $chunk) {
KemendikRawData::upsert(
$chunk,
['nimhsmsmh', 'source_type'],
['nmmhsmsmh', 'tahun_lulus', 'f5c_jabatan_kode', 'f5c_jabatan_text', 'f1101_jenis_instansi_kode', 'f1101_jenis_instansi_text', 'f5a1_provinsi', 'raw_data']
);
}
}
if (!empty($internalResultsUpsertData)) {
foreach (array_chunk($internalResultsUpsertData, 500) as $chunk) {
ClassificationResult::upsert(
$chunk,
['nim', 'source_type'],
['nama', 'tahun_lulus', 'job_text_raw', 'predicted_profile', 'confidence_score', 'classification_method', 'status', 'error_detail']
);
}
}
if (!empty($internalRawUpsertData)) {
$cols = [
'nama_lengkap', 'email', 'no_telepon', 'alamat_domisili', 'jurusan',
'program_studi', 'tahun_masuk', 'tahun_lulus', 'status_pekerjaan',
'klasifikasi_pekerjaan', 'nama_perusahaan', 'jenis_perusahaan', 'jabatan',
'lokasi_perusahaan', 'alamat_perusahaan', 'deskripsi_pekerjaan',
'tahun_mulai_kerja', 'masa_kerja_bulan', 'jumlah_lamaran_dikirim',
'jumlah_respons_lamaran', 'jumlah_undangan_wawancara', 'masa_tunggu_pra_lulus',
'masa_tunggu_pasca_lulus', 'total_masa_tunggu', 'instansi_terupdate',
'jabatan_terupdate', 'tahun_bekerja', 'status_pekerjaan_terupdate',
'linkedin_profile', 'sosmed_ig'
];
foreach (array_chunk($internalRawUpsertData, 500) as $chunk) {
InternalRawData::upsert($chunk, ['nim'], $cols);
}
}
});
$isKemendik = $result['results'][0]['source_type'] === 'kemendik';
if ($isKemendik) {
$count = collect($result['results'])->where('source_type','kemendik')->count();
return back()->with('success', "Selesai. {$result['processed_rows']} baris diproses. {$count} data Kemendik berhasil masuk.");
} else {
$classified = collect($result['results'])->where('source_type','internal_mif')->where('status','auto_classified')->count();
return back()->with('success', "Selesai. {$result['processed_rows']} data diproses. {$classified} diklasifikasi otomatis (Internal MIF).");
}
}
private function mapF5c(?string $c): ?string { return ['1'=>'Founder','2'=>'Co-Founder','3'=>'Staff','4'=>'Freelance'][$c] ?? null; }
private function mapF1101(?string $c): ?string { return ['1'=>'Pemerintah','2'=>'Non-profit','3'=>'Swasta','4'=>'Wiraswasta','6'=>'BUMN','7'=>'Multilateral'][$c] ?? null; }
public function retrain(Request $request)
{
$overrideData = ClassificationResult::where('source_type', 'internal_mif')
->where('status', 'manual_override')
->whereNotNull('job_text_raw')
->whereNotNull('predicted_profile')
->get(['job_text_raw', 'predicted_profile']);
$csvPath = null;
if ($overrideData->count() > 0) {
$csvLines = ['job_text_raw;label'];
foreach ($overrideData as $row) {
$text = str_replace([';', "\n", "\r"], [',', ' ', ' '], $row->job_text_raw);
$label = $row->predicted_profile;
$csvLines[] = "{$text};{$label}";
}
$tmpDir = storage_path('app/temp');
if (!is_dir($tmpDir)) {
mkdir($tmpDir, 0755, true);
}
$csvPath = $tmpDir . '/manual_override_' . Str::uuid() . '.csv';
file_put_contents($csvPath, implode("\n", $csvLines));
Log::info("Retrain: Exported {$overrideData->count()} manual_override rows to {$csvPath}");
} else {
Log::info('Retrain: Tidak ada data manual_override, training hanya dengan corpus asli.');
}
$result = $this->fastApi->triggerRetrain($csvPath);
if ($csvPath && file_exists($csvPath)) {
try {
unlink($csvPath);
} catch (\Exception $e) {
Log::warning("Gagal menghapus temp CSV: " . $e->getMessage());
}
}
if (isset($result['error'])) {
return response()->json(['success' => false, 'message' => $result['error']], 422);
}
return response()->json([
'success' => true,
'message' => 'Re-training dimulai di background.',
'override_count' => $overrideData->count(),
]);
}
public function retrainStatus(Request $request)
{
$status = $this->fastApi->getRetrainStatus();
return response()->json($status);
}
}