fix: populate questions and options tables, scale AI chapters/checkpoints to video duration, open student lesson discovery

This commit is contained in:
Hamza-Ayed
2026-08-28 04:14:16 +03:00
parent dc2ebb108c
commit 17a22c6985
5 changed files with 75 additions and 57 deletions
+46 -39
View File
@@ -42,8 +42,11 @@ class AiVideoAnalyzerService
);
}
// 2. Clear old checkpoints for this lesson if regenerating
Database::query("DELETE FROM exams WHERE lesson_id = ? AND scope = 'in_video_checkpoint'", [$lessonId]);
// 2. Clear old checkpoints and their questions for this lesson
$oldExams = Database::select("SELECT id FROM exams WHERE lesson_id = ? AND scope = 'in_video_checkpoint'", [$lessonId]);
foreach ($oldExams as $oe) {
Database::query("DELETE FROM exams WHERE id = ?", [$oe['id']]);
}
// 3. Save Socratic Checkpoints into exams, questions, question_options
if (!empty($analysisResult['socratic_checkpoints'])) {
@@ -57,7 +60,7 @@ class AiVideoAnalyzerService
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
$examId = Database::insert(
$examId = (int)Database::insert(
"INSERT INTO exams (uuid, course_id, lesson_id, creator_type, scope, title, timestamp_seconds, rewind_on_fail_seconds, passing_percentage, total_points, is_mandatory, is_published)
VALUES (?, ?, ?, 'ai_adaptive', 'in_video_checkpoint', ?, ?, ?, 100.00, 10, 1, 1)",
[
@@ -78,7 +81,7 @@ class AiVideoAnalyzerService
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
$qId = Database::insert(
$qId = (int)Database::insert(
"INSERT INTO questions (uuid, exam_id, question_text, question_type, bloom_taxonomy, explanation_text, points)
VALUES (?, ?, ?, 'multiple_choice', 'comprehension', ?, 10)",
[
@@ -90,11 +93,13 @@ class AiVideoAnalyzerService
);
$correctIdx = (int)($cp['correct_index'] ?? 0);
foreach ($cp['options'] as $idx => $optText) {
Database::insert(
"INSERT INTO question_options (question_id, option_text, is_correct) VALUES (?, ?, ?)",
[$qId, $optText, ($idx === $correctIdx) ? 1 : 0]
);
if (!empty($cp['options']) && is_array($cp['options'])) {
foreach ($cp['options'] as $idx => $optText) {
Database::insert(
"INSERT INTO question_options (question_id, option_text, is_correct) VALUES (?, ?, ?)",
[$qId, (string)$optText, ($idx === $correctIdx) ? 1 : 0]
);
}
}
} catch (\Throwable $e) {
error_log("Checkpoint insert error: " . $e->getMessage());
@@ -199,38 +204,48 @@ class AiVideoAnalyzerService
private static function buildGroundedCurriculumAnalysis(string $title, int $duration, array $curriculum): array
{
$isMilitary = ($curriculum['subject'] === 'التربية الوطنية والثقافة العسكرية');
$duration = max(5, $duration);
// Dynamic Time Segmentation based on real duration
if ($duration <= 45) {
// Short Video (e.g. 10s AI generated video)
$c1 = max(1, (int)round($duration * 0.3));
$c2 = max(2, (int)round($duration * 0.7));
$cp1 = max(1, (int)round($duration * 0.3));
$cp2 = max(2, (int)round($duration * 0.7));
$rewind = 3;
} else {
// Full Length Standard Lesson (e.g. 10 - 45 minutes)
$c1 = max(60, (int)round($duration * 0.25));
$c2 = max(120, (int)round($duration * 0.60));
$cp1 = max(60, (int)round($duration * 0.25));
$cp2 = max(120, (int)round($duration * 0.60));
$rewind = 45;
}
if ($isMilitary) {
return [
'timeline_chapters' => [
['start_seconds' => 0, 'end_seconds' => 150, 'title' => 'النشأة والتأسيس التاريخي', 'summary' => 'مراحل تشكيل القوات المسلحة الأردنية — الجيش العربي منذ عام 1921.'],
['start_seconds' => 150, 'end_seconds' => 360, 'title' => 'القرار التاريخي لتعريب القيادة (1956)', 'summary' => 'الرؤية الوطنية للملك الحسين بن طلال وإنهاء الانتداب البريطاني.'],
['start_seconds' => 360, 'end_seconds' => $duration, 'title' => 'معركة الكرامة (1968) والأدوار التنموية', 'summary' => 'تحطيم أسطورة العدو، والمستشفيات الميدانية الإنسانية.']
['start_seconds' => 0, 'end_seconds' => $c1, 'title' => 'النشأة والتأسيس التاريخي', 'summary' => 'مراحل تشكيل القوات المسلحة الأردنية — الجيش العربي منذ عام 1921.'],
['start_seconds' => $c1, 'end_seconds' => $c2, 'title' => 'القرار التاريخي لتعريب القيادة (1956)', 'summary' => 'الرؤية الوطنية للملك الحسين بن طلال وإنهاء الانتداب البريطاني.'],
['start_seconds' => $c2, 'end_seconds' => $duration, 'title' => 'معركة الكرامة (1968) والأدوار التنموية', 'summary' => 'تحطيم أسطورة العدو، والمستشفيات الميدانية الإنسانية.']
],
'socratic_checkpoints' => [
[
'timestamp_seconds' => 150,
'timestamp_seconds' => $cp1,
'question_text' => 'في أي عام تم تأسيس الجيش العربي الأردني؟',
'options' => ['عام 1921 في عهد الملك المؤسس عبدالله الأول', 'عام 1956', 'عام 1968', 'عام 1946'],
'correct_index' => 0,
'rewind_seconds' => 45,
'rewind_seconds' => $rewind,
'explanation' => 'تأسس الجيش العربي عام 1921 مع تأسيس إمارة شرق الأردن.'
],
[
'timestamp_seconds' => 360,
'timestamp_seconds' => $cp2,
'question_text' => 'متى تم اتخاذ القرار التاريخي بتعريب قيادة الجيش العربي؟',
'options' => ['1 آذار 1956 بقيادة الملك الحسين بن طلال', '21 آذار 1968', '11 نيسان 1921', '25 أيار 1946'],
'correct_index' => 0,
'rewind_seconds' => 45,
'rewind_seconds' => $rewind,
'explanation' => 'صدر قرار تعريب القيادة التاريخي في 1 آذار 1956.'
],
[
'timestamp_seconds' => max(480, (int)($duration * 0.8)),
'question_text' => 'ما هي المعركة التاريخية التي شكلت أول نصر عسكري عربي وحطمت أسطورة الجيش الذي لا يُقهر؟',
'options' => ['معركة الكرامة الخالدة (21 آذار 1968)', 'معركة القدس 1948', 'معركة اللطرون', 'معركة باب الواد'],
'correct_index' => 0,
'rewind_seconds' => 60,
'explanation' => 'معركة الكرامة في 21 آذار 1968 هي أول نصر عسكري للجيش العربي.'
]
]
];
@@ -239,34 +254,26 @@ class AiVideoAnalyzerService
// Default: Mathematics (Tawjihi 2008 Scientific)
return [
'timeline_chapters' => [
['start_seconds' => 0, 'end_seconds' => 180, 'title' => 'مقدمة المفهوم والتمهيد الهندسي', 'summary' => 'توضيح المعنى الفيزيائي والهندسي لمفهوم المشتقة الأولى وميل المماس.'],
['start_seconds' => 180, 'end_seconds' => 380, 'title' => 'عرض القواعد الأساسية والاشتقاق', 'summary' => 'قواعد اشتقاق كثيرات الحدود والاقترانات المثلثية وحاصل الضرب.'],
['start_seconds' => 380, 'end_seconds' => $duration, 'title' => 'حل المسائل النموذجية والأسئلة الوزارية', 'summary' => 'تطبيق القواعد على مسائل امتحانات الثانوية العامة المعتمدة.']
['start_seconds' => 0, 'end_seconds' => $c1, 'title' => 'مقدمة المفهوم والتمهيد الهندسي', 'summary' => 'توضيح المعنى الفيزيائي والهندسي لمفهوم المشتقة الأولى وميل المماس.'],
['start_seconds' => $c1, 'end_seconds' => $c2, 'title' => 'عرض القواعد الأساسية والاشتقاق', 'summary' => 'قواعد اشتقاق كثيرات الحدود والاقترانات المثلثية وحاصل الضرب.'],
['start_seconds' => $c2, 'end_seconds' => $duration, 'title' => 'حل المسائل النموذجية والأسئلة الوزارية', 'summary' => 'تطبيق القواعد على مسائل امتحانات الثانوية العامة المعتمدة.']
],
'socratic_checkpoints' => [
[
'timestamp_seconds' => 180,
'timestamp_seconds' => $cp1,
'question_text' => 'إذا كان الاقتران f(x) = c (اقتران ثابت)، فما هي قيمة مشتقته f\'(x)؟',
'options' => ['f\'(x) = 0 دائماً', 'f\'(x) = c', 'f\'(x) = 1', 'f\'(x) = x'],
'correct_index' => 0,
'rewind_seconds' => 45,
'rewind_seconds' => $rewind,
'explanation' => 'مشتقة أي عدد ثابت تساوي صفراً دائماً حسب نص الكتاب المدرسي.'
],
[
'timestamp_seconds' => 360,
'timestamp_seconds' => $cp2,
'question_text' => 'إذا كان f(x) = sin(3x)، فما هي قيمة المشتقة f\'(x) وفق قاعدة مشتقات الزوايا؟',
'options' => ['3 cos(3x) (مشتقة الزاوية ضرب مشتقة الاقتران)', 'cos(3x)', '-3 cos(3x)', '3 sin(3x)'],
'correct_index' => 0,
'rewind_seconds' => 45,
'rewind_seconds' => $rewind,
'explanation' => 'مشتقة sin(ax) هي a*cos(ax).'
],
[
'timestamp_seconds' => max(480, (int)($duration * 0.8)),
'question_text' => 'ما هي مشتقة حاصل ضرب اقترانين [f(x) * g(x)]\' حسب المنهاج الوزاري؟',
'options' => ['الأول في مشتقة الثاني + الثاني في مشتقة الأول', 'مشتقة الأول في مشتقة الثاني', 'الأول في مشتقة الثاني - الثاني في مشتقة الأول', 'مجموع المشتقات فقط'],
'correct_index' => 0,
'rewind_seconds' => 60,
'explanation' => 'قاعدة مشتقة الضرب: [f*g]\' = f*g\' + g*f\'.'
]
]
];