436 lines
16 KiB
PHP
436 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Literacy;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Literacy\LiteracyAssessment;
|
|
use App\Models\Literacy\LiteracyQuestion;
|
|
use App\Models\Literacy\LiteracyMaterial;
|
|
use App\Models\Literacy\LiteracyOption;
|
|
use App\Models\Literacy\LiteracyAnswer;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Log;
|
|
use function similar_text;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
class LiteracyAssessmentController extends Controller
|
|
{
|
|
public function __construct()
|
|
{
|
|
// Middleware memastikan hanya student yang bisa mengakses
|
|
$this->middleware(function ($request, $next) {
|
|
if (Auth::user()->role !== 'student') {
|
|
abort(403, 'Unauthorized action.');
|
|
}
|
|
return $next($request);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Menampilkan daftar asesmen yang sedang berjalan atau sudah selesai untuk student yang login.
|
|
*/
|
|
public function index()
|
|
{
|
|
$assessments = LiteracyAssessment::where('user_id', Auth::id())
|
|
->orderBy('created_at', 'desc')
|
|
->get();
|
|
|
|
return view('literacy.student.assessments.index', compact('assessments'));
|
|
}
|
|
|
|
/**
|
|
* Memulai asesmen dan mengubah status menjadi "in_progress".
|
|
*/
|
|
public function start($id)
|
|
{
|
|
$assessment = LiteracyAssessment::where('id', $id)
|
|
->where('user_id', Auth::id())
|
|
->firstOrFail();
|
|
|
|
// Ubah status asesmen menjadi in_progress
|
|
$assessment->update(['status' => 'in_progress']);
|
|
|
|
// Ambil semua pertanyaan terkait asesmen ini
|
|
$questions = LiteracyQuestion::all(); // Sesuaikan jika ada filter
|
|
|
|
// Insert jawaban kosong jika belum ada
|
|
foreach ($questions as $question) {
|
|
LiteracyAnswer::updateOrInsert(
|
|
[
|
|
'assessment_id' => $assessment->id,
|
|
'question_id' => $question->id,
|
|
],
|
|
[
|
|
'option_id' => null,
|
|
'answer_text' => null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]
|
|
);
|
|
}
|
|
|
|
return redirect()->route('literacy_assessments_show', $id);
|
|
}
|
|
|
|
/**
|
|
* Menampilkan halaman asesmen dengan pertanyaan yang diurutkan.
|
|
*/
|
|
public function show($id)
|
|
{
|
|
$assessment = LiteracyAssessment::where('id', $id)
|
|
->where('user_id', Auth::id())
|
|
->firstOrFail();
|
|
|
|
// Ambil semua pertanyaan, urutkan pilihan ganda dulu baru essay
|
|
$questions = LiteracyQuestion::orderByRaw("FIELD(type, 'multiple_choice', 'essay')")
|
|
->with([
|
|
'answers' => function ($query) use ($id) {
|
|
$query->where('assessment_id', $id);
|
|
}
|
|
])
|
|
->get();
|
|
|
|
return view('literacy.student.assessments.assessment', compact('assessment', 'questions'));
|
|
}
|
|
|
|
/**
|
|
* Melanjutkan asesmen yang belum selesai.
|
|
*/
|
|
public function continue($id)
|
|
{
|
|
$assessment = LiteracyAssessment::where('id', $id)
|
|
->where('user_id', Auth::id())
|
|
->firstOrFail();
|
|
|
|
return redirect()->route('literacy_assessments_show', $id);
|
|
}
|
|
|
|
/**
|
|
* Menyimpan jawaban siswa ke database secara otomatis saat memilih atau mengisi jawaban.
|
|
*/
|
|
public function storeAnswer(Request $request, $assessmentId): JsonResponse
|
|
{
|
|
Log::info('Request Data:', $request->all());
|
|
|
|
$request->validate([
|
|
'question_id' => 'required|exists:literacy_questions,id',
|
|
'answer' => 'nullable|string',
|
|
]);
|
|
|
|
// Pastikan pengguna memiliki asesmen ini
|
|
$assessment = LiteracyAssessment::where('id', $assessmentId)
|
|
->where('user_id', Auth::id())
|
|
->first();
|
|
|
|
if (!$assessment) {
|
|
return response()->json(['error' => 'Asesmen tidak ditemukan'], 404);
|
|
}
|
|
|
|
$questionId = $request->question_id;
|
|
$answerValue = $request->answer;
|
|
|
|
$question = LiteracyQuestion::find($questionId);
|
|
if (!$question) {
|
|
return response()->json(['error' => 'Pertanyaan tidak ditemukan'], 404);
|
|
}
|
|
|
|
$answerData = ['option_id' => null, 'answer_text' => $answerValue];
|
|
|
|
if ($question->type === 'multiple_choice') {
|
|
$option = LiteracyOption::where('id', $answerValue)
|
|
->where('question_id', $questionId)
|
|
->first();
|
|
|
|
if (!$option) {
|
|
return response()->json(['error' => 'Jawaban tidak valid'], 400);
|
|
}
|
|
|
|
$answerData = ['option_id' => $option->id, 'answer_text' => null];
|
|
}
|
|
|
|
LiteracyAnswer::updateOrCreate(
|
|
[
|
|
'assessment_id' => $assessment->id,
|
|
'question_id' => $questionId,
|
|
],
|
|
$answerData
|
|
);
|
|
|
|
// Hitung jumlah pertanyaan dalam asesmen ini
|
|
$totalQuestions = $assessment->questions()->count();
|
|
|
|
// Hitung jumlah pertanyaan yang sudah dijawab
|
|
$answeredQuestions = LiteracyAnswer::where('assessment_id', $assessment->id)
|
|
->where(function ($query) {
|
|
$query->whereNotNull('option_id')
|
|
->orWhere('answer_text', '!=', '');
|
|
})
|
|
->count();
|
|
|
|
Log::info('Answered Questions:', ['answered' => $answeredQuestions, 'total' => $totalQuestions]);
|
|
|
|
// Jika semua pertanyaan sudah dijawab, update status ke `completed`
|
|
if ($answeredQuestions >= $totalQuestions) {
|
|
$assessment->update(['status' => 'completed']);
|
|
}
|
|
|
|
return response()->json(['success' => true]);
|
|
}
|
|
|
|
// public function submitAssessment($id)
|
|
// {
|
|
// try {
|
|
// $user = Auth::user();
|
|
// $assessment = LiteracyAssessment::where('id', $id)
|
|
// ->where('user_id', $user->id)
|
|
// ->firstOrFail();
|
|
|
|
// if ($assessment->status !== 'in_progress') {
|
|
// return response()->json(['error' => 'Asesmen tidak sedang berjalan.'], 400);
|
|
// }
|
|
|
|
// $answers = LiteracyAnswer::where('assessment_id', $id)->get();
|
|
|
|
// $correctWeight = 0;
|
|
// $totalWeight = 0;
|
|
// $essayThreshold = 60;
|
|
|
|
// $normalizeText = function ($text) {
|
|
// $text = strtolower($text);
|
|
// $text = preg_replace('/[^\p{L}\p{N}\s]/u', '', $text);
|
|
// $text = preg_replace('/\s+/', ' ', $text);
|
|
// return trim($text);
|
|
// };
|
|
|
|
// $feedback = [];
|
|
|
|
// foreach ($answers as $answer) {
|
|
// $question = LiteracyQuestion::find($answer->question_id);
|
|
// if (!$question)
|
|
// continue;
|
|
|
|
// $weight = $question->weight ?? ($question->type === 'essay' ? 5 : 1);
|
|
// $totalWeight += $weight;
|
|
|
|
// if ($question->type === 'multiple_choice') {
|
|
// if (
|
|
// $answer->option_id &&
|
|
// LiteracyOption::where('id', $answer->option_id)
|
|
// ->where('is_correct', true)
|
|
// ->exists()
|
|
// ) {
|
|
// $correctWeight += $weight;
|
|
// }
|
|
|
|
// } elseif ($question->type === 'essay') {
|
|
// $userAnswer = $normalizeText($answer->answer_text ?? '');
|
|
// $correctAnswerRaw = $question->essay_answer ?? '';
|
|
// $correctAnswers = array_map($normalizeText, preg_split('/\r\n|\r|\n/', $correctAnswerRaw));
|
|
// $maxMatchPercent = 0;
|
|
|
|
// foreach ($correctAnswers as $correctAnswer) {
|
|
// similar_text($userAnswer, $correctAnswer, $percent);
|
|
// $maxMatchPercent = max($maxMatchPercent, $percent);
|
|
// }
|
|
|
|
// if ($maxMatchPercent >= $essayThreshold) {
|
|
// $correctWeight += $weight;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // Hitung skor akhir berdasarkan bobot
|
|
// $score = $totalWeight > 0 ? ($correctWeight / $totalWeight) * 100 : 0;
|
|
|
|
// // Update asesmen yang sedang berjalan jadi completed
|
|
// $assessment->update([
|
|
// 'score' => round($score, 2),
|
|
// 'status' => 'completed',
|
|
// 'assessed_at' => now(),
|
|
// 'feedback' => json_encode($feedback), // Menyimpan feedback ke database
|
|
// ]);
|
|
|
|
// // Buat asesmen baru dengan status 'pending' untuk percakapan berikutnya
|
|
// LiteracyAssessment::create([
|
|
// 'user_id' => $user->id,
|
|
// 'status' => 'pending',
|
|
// 'score' => null,
|
|
// 'feedback' => '',
|
|
// 'assessed_at' => null,
|
|
// 'created_at' => now(),
|
|
// 'updated_at' => now(),
|
|
// ]);
|
|
|
|
// return response()->json([
|
|
// 'message' => 'Asesmen berhasil diselesaikan.',
|
|
// 'score' => round($score, 2),
|
|
// 'feedback' => $feedback, // Mengembalikan feedback dalam respons
|
|
// ], 200);
|
|
|
|
// } catch (\Exception $e) {
|
|
// return response()->json(['error' => 'Terjadi kesalahan: ' . $e->getMessage()], 500);
|
|
// }
|
|
// }
|
|
|
|
public function submitAssessment($id)
|
|
{
|
|
try {
|
|
$user = Auth::user();
|
|
$assessment = LiteracyAssessment::where('id', $id)
|
|
->where('user_id', $user->id)
|
|
->firstOrFail();
|
|
|
|
if ($assessment->status !== 'in_progress') {
|
|
return response()->json(['error' => 'Asesmen tidak sedang berjalan.'], 400);
|
|
}
|
|
|
|
$answers = LiteracyAnswer::where('assessment_id', $id)->get();
|
|
|
|
$correctWeight = 0;
|
|
$totalWeight = 0;
|
|
$essayThreshold = 60;
|
|
|
|
$normalizeText = function ($text) {
|
|
$text = strtolower($text);
|
|
$text = preg_replace('/[^\p{L}\p{N}\s]/u', '', $text);
|
|
$text = preg_replace('/\s+/', ' ', $text);
|
|
return trim($text);
|
|
};
|
|
|
|
$feedback = [];
|
|
|
|
foreach ($answers as $answer) {
|
|
$question = LiteracyQuestion::find($answer->question_id);
|
|
if (!$question)
|
|
continue;
|
|
|
|
$weight = $question->weight ?? ($question->type === 'essay' ? 5 : 1);
|
|
$totalWeight += $weight;
|
|
|
|
if ($question->type === 'multiple_choice') {
|
|
if (
|
|
$answer->option_id &&
|
|
LiteracyOption::where('id', $answer->option_id)
|
|
->where('is_correct', true)
|
|
->exists()
|
|
) {
|
|
$correctWeight += $weight;
|
|
}
|
|
|
|
} elseif ($question->type === 'essay') {
|
|
$userAnswer = $normalizeText($answer->answer_text ?? '');
|
|
$correctAnswerRaw = $question->essay_answer ?? '';
|
|
$correctAnswers = array_map($normalizeText, preg_split('/\r\n|\r|\n/', $correctAnswerRaw));
|
|
$maxMatchPercent = 0;
|
|
$bestMatch = '';
|
|
|
|
foreach ($correctAnswers as $correctAnswer) {
|
|
similar_text($userAnswer, $correctAnswer, $percent);
|
|
if ($percent > $maxMatchPercent) {
|
|
$maxMatchPercent = $percent;
|
|
$bestMatch = $correctAnswer;
|
|
}
|
|
}
|
|
|
|
if ($maxMatchPercent >= $essayThreshold) {
|
|
$correctWeight += $weight;
|
|
} else {
|
|
try {
|
|
$apiResponse = Http::timeout(10)->post('http://127.0.0.1:8001/generate-feedback/', [
|
|
'user_answer' => $answer->answer_text,
|
|
'expected_answer' => $bestMatch,
|
|
]);
|
|
|
|
if ($apiResponse->ok()) {
|
|
$aiFeedback = $apiResponse->json()['feedback'];
|
|
} else {
|
|
$aiFeedback = 'Feedback tidak tersedia saat ini.';
|
|
}
|
|
} catch (\Exception $e) {
|
|
$aiFeedback = 'Terjadi kesalahan saat menghubungi AI.';
|
|
}
|
|
|
|
// SIMPAN ke kolom feedback
|
|
$answer->feedback = $aiFeedback;
|
|
$answer->save();
|
|
|
|
$feedback[] = [
|
|
'question_id' => $question->id,
|
|
'feedback' => $aiFeedback,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hitung skor akhir berdasarkan bobot
|
|
$score = $totalWeight > 0 ? ($correctWeight / $totalWeight) * 100 : 0;
|
|
|
|
// Update asesmen yang sedang berjalan jadi completed
|
|
$assessment->update([
|
|
'score' => round($score, 2),
|
|
'status' => 'completed',
|
|
'assessed_at' => now(),
|
|
'feedback' => json_encode($feedback),
|
|
]);
|
|
|
|
// Buat asesmen baru dengan status 'pending' untuk percakapan berikutnya
|
|
LiteracyAssessment::create([
|
|
'user_id' => $user->id,
|
|
'status' => 'pending',
|
|
'score' => null,
|
|
'feedback' => '',
|
|
'assessed_at' => null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
return response()->json([
|
|
'message' => 'Asesmen berhasil diselesaikan.',
|
|
'score' => round($score, 2),
|
|
'feedback' => $feedback,
|
|
], 200);
|
|
|
|
} catch (\Exception $e) {
|
|
return response()->json(['error' => 'Terjadi kesalahan: ' . $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
public function viewResult($id)
|
|
{
|
|
// Ambil asesmen berdasarkan ID, pastikan asesmen milik user dan statusnya sudah 'completed'
|
|
$assessment = LiteracyAssessment::where('id', $id)
|
|
->where('user_id', Auth::id())
|
|
->where('status', 'completed')
|
|
->firstOrFail();
|
|
|
|
// Ambil semua pertanyaan beserta jawaban untuk asesmen ini
|
|
$questions = LiteracyQuestion::with([
|
|
'answers' => function ($query) use ($id) {
|
|
$query->where('assessment_id', $id);
|
|
},
|
|
'options'
|
|
])->get();
|
|
|
|
return view('literacy.student.assessments.result', compact('assessment', 'questions'));
|
|
}
|
|
|
|
public function result($id)
|
|
{
|
|
$assessment = LiteracyAssessment::where('id', $id)
|
|
->where('user_id', Auth::id())
|
|
->where('status', 'completed')
|
|
->firstOrFail();
|
|
|
|
$questions = LiteracyQuestion::with([
|
|
'answers' => function ($query) use ($id) {
|
|
$query->where('assessment_id', $id);
|
|
},
|
|
'options'
|
|
])->get();
|
|
|
|
return view('literacy.student.assessments.result', compact('assessment', 'questions'));
|
|
}
|
|
}
|