Update Saqel Platform: 2026-08-28 15:34:26

This commit is contained in:
Hamza-Ayed
2026-08-28 15:34:26 +03:00
parent d97a98abdb
commit 2bcf2d2ba2
4 changed files with 617 additions and 7 deletions
+218 -7
View File
@@ -5,19 +5,25 @@ namespace App\Services;
use App\Core\Database;
/**
* Official Ministry Curriculum Knowledge Base & Text Retrieval Service
* Ensures 100% grounding in official Tawjihi 2008 & Military Culture textbooks
* Official Ministry Curriculum Knowledge Base & Hierarchical Markdown Tree Engine
* 1. Tree structure: Grade -> Subject -> Semester -> Unit -> Lessons (as standalone Markdown files)
* 2. Instant Search across curriculum text and outcomes
* 3. AI Grounding for Socratic Checkpoint & Video Evaluation
*/
class CurriculumService
{
private static bool $schemaChecked = false;
private static string $storagePath = __DIR__ . '/../../storage/curriculum';
public static function ensureSchema(): void
{
if (self::$schemaChecked) return;
if (!is_dir(self::$storagePath)) {
mkdir(self::$storagePath, 0777, true);
}
try {
// 1. Check if subjects table has data, seed if empty
$subjectsCount = (int)(Database::selectOne("SELECT COUNT(*) as total FROM subjects")['total'] ?? 0);
if ($subjectsCount === 0) {
Database::query(
@@ -33,7 +39,6 @@ class CurriculumService
);
}
// 2. Check if lessons table has timeline_chapters_json column
$cols = Database::select("SHOW COLUMNS FROM lessons LIKE 'timeline_chapters_json'");
if (empty($cols)) {
Database::query("ALTER TABLE lessons ADD COLUMN timeline_chapters_json JSON NULL AFTER hls_url");
@@ -45,7 +50,215 @@ class CurriculumService
}
/**
* Retrieve official textbook reference text based on course and lesson title
* Get Complete Hierarchical Curriculum Tree (Grade -> Subject -> Semester -> Unit -> Lessons)
*/
public static function getCurriculumTree(): array
{
self::ensureSchema();
$tree = [
'tawjihi_2008' => [
'name' => 'الثانوية العامة (توجيهي 2008)',
'subjects' => [
'math_scientific' => [
'name' => 'الرياضيات — الفرع العلمي',
'semesters' => [
'semester_1' => [
'name' => 'الفصل الدراسي الأول',
'units' => [
'unit_1_calculus' => [
'name' => 'الوحدة الأولى: التفاضل وتطبيقاته',
'lessons' => [
[
'id' => 'math_s1_u1_l1',
'title' => 'الدرس 1: المفهوم الهندسي والفيزيائي للمشتقة الأولى',
'outcomes' => ['فهم ميل المماس', 'الاشتقاق بالتعريف العام', 'الاتصال والاشتقاق'],
'file' => 'tawjihi_2008/math_scientific/semester_1/unit_1_calculus/lesson_1.md'
],
[
'id' => 'math_s1_u1_l2',
'title' => 'الدرس 2: قواعد الاشتقاق الأساسية وقاعدة السلسلة',
'outcomes' => ['مشتقة الضرب والقسمة', 'مشتقات الاقترانات المثلثية', 'قاعدة السلسلة'],
'file' => 'tawjihi_2008/math_scientific/semester_1/unit_1_calculus/lesson_2.md'
],
[
'id' => 'math_s1_u1_l3',
'title' => 'الدرس 3: الاشتقاق الضمني والمعدلات المرتبطة بالزمن',
'outcomes' => ['الاشتقاق الضمني', 'المسائل الهندسية وحجوم المخروط', 'المعدلات المرتبطة'],
'file' => 'tawjihi_2008/math_scientific/semester_1/unit_1_calculus/lesson_3.md'
]
]
],
'unit_2_apps' => [
'name' => 'الوحدة الثانية: تطبيقات التفاضل والقيم القصوى',
'lessons' => [
[
'id' => 'math_s1_u2_l1',
'title' => 'الدرس 1: النقط الحرجة وفترات التزايد والتناقص',
'outcomes' => ['اختبار المشتقة الأولى', 'النقط الحرجة', 'القيم العظمى والصغرى'],
'file' => 'tawjihi_2008/math_scientific/semester_1/unit_2_apps/lesson_1.md'
]
]
]
]
]
]
],
'physics_scientific' => [
'name' => 'الفيزياء — الفرع العلمي',
'semesters' => [
'semester_1' => [
'name' => 'الفصل الدراسي الأول',
'units' => [
'unit_1_momentum' => [
'name' => 'الوحدة الأولى: الزخم الخطي والتصادمات',
'lessons' => [
[
'id' => 'phys_s1_u1_l1',
'title' => 'الدرس 1: الزخم الخطي والدفع وتطبيقاتهما',
'outcomes' => ['قانون حفظ الزخم الخطي', 'منحنى القوة والزمن', 'التصادم المرن وغير المرن'],
'file' => 'tawjihi_2008/physics_scientific/semester_1/unit_1_momentum/lesson_1.md'
]
]
]
]
]
]
]
]
],
'grade_10' => [
'name' => 'الصف العاشر الأساسي',
'subjects' => [
'math_10' => [
'name' => 'الرياضيات — الصف العاشر',
'semesters' => [
'semester_1' => [
'name' => 'الفصل الدراسي الأول',
'units' => [
'unit_1_equations' => [
'name' => 'الوحدة الأولى: أنظمة المعادلات والاقترانات',
'lessons' => [
[
'id' => 'math10_s1_u1_l1',
'title' => 'الدرس 1: حل نظام مكون من معادلة خطية ومعادلة تربيعية',
'outcomes' => ['التعويض والحذف', 'التمثيل البياني للحلول', 'المسائل التطبيقية'],
'file' => 'grade_10/math/semester_1/unit_1_equations/lesson_1.md'
]
]
]
]
]
]
]
]
],
'grade_7' => [
'name' => 'الصف السابع الأساسي',
'subjects' => [
'math_7' => [
'name' => 'الرياضيات — الصف السابع',
'semesters' => [
'semester_1' => [
'name' => 'الفصل الدراسي الأول',
'units' => [
'unit_1_integers' => [
'name' => 'الوحدة الأولى: الأعداد النسبية والعمليات عليها',
'lessons' => [
[
'id' => 'math7_s1_u1_l1',
'title' => 'الدرس 1: جمع وطرح الأعداد النسبية وخصائصها',
'outcomes' => ['توحيد المقامات', 'استخدام خط الأعداد', 'المعكوس الجمعي'],
'file' => 'grade_7/math/semester_1/unit_1_integers/lesson_1.md'
]
]
]
]
]
]
]
]
]
];
return $tree;
}
/**
* Save Lesson Markdown Content to Hierarchical File System
*/
public static function saveLessonMarkdown(string $relativePath, string $content): bool
{
self::ensureSchema();
$fullPath = self::$storagePath . '/' . ltrim($relativePath, '/');
$dir = dirname($fullPath);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
return file_put_contents($fullPath, $content) !== false;
}
/**
* Retrieve Lesson Markdown Content
*/
public static function getLessonMarkdown(string $relativePath): string
{
$fullPath = self::$storagePath . '/' . ltrim($relativePath, '/');
if (file_exists($fullPath)) {
return file_get_contents($fullPath);
}
return "# الدرس المعياري\nالمحتوى المعتمد للمنهاج الرسمي لوزارة التربية والتعليم.";
}
/**
* Fast Keyword & Semantic Search across All Markdown Curriculum Files
*/
public static function searchCurriculum(string $query): array
{
$tree = self::getCurriculumTree();
$results = [];
$queryClean = mb_strtolower(trim($query));
foreach ($tree as $gradeKey => $grade) {
foreach ($grade['subjects'] as $subKey => $subject) {
foreach ($subject['semesters'] as $semKey => $semester) {
foreach ($semester['units'] as $unitKey => $unit) {
foreach ($unit['lessons'] as $lesson) {
$matches = false;
if (str_contains(mb_strtolower($lesson['title']), $queryClean) ||
str_contains(mb_strtolower($unit['name']), $queryClean) ||
str_contains(mb_strtolower($subject['name']), $queryClean)) {
$matches = true;
}
if (!$matches && !empty($lesson['outcomes'])) {
foreach ($lesson['outcomes'] as $out) {
if (str_contains(mb_strtolower($out), $queryClean)) {
$matches = true;
break;
}
}
}
if ($matches) {
$results[] = [
'grade' => $grade['name'],
'subject' => $subject['name'],
'unit' => $unit['name'],
'lesson' => $lesson['title'],
'file_path' => $lesson['file'],
'outcomes' => $lesson['outcomes']
];
}
}
}
}
}
}
return $results;
}
/**
* Retrieve official textbook reference context
*/
public static function getCurriculumContext(int $courseId, string $lessonTitle): array
{
@@ -54,7 +267,6 @@ class CurriculumService
$course = Database::selectOne("SELECT * FROM courses WHERE id = ? LIMIT 1", [$courseId]);
$courseTitle = $course['title'] ?? 'الرياضيات العلمي — توجيهي 2008';
// Pre-indexed Ministry Curriculum Knowledge Base (Tawjihi 2008 & Military Culture)
$curriculumMap = [
'الرياضيات' => [
'grade' => 'توجيهي 2008 — الفرع العلمي',
@@ -81,7 +293,6 @@ class CurriculumService
]
];
// Determine matching subject
$matchedSubject = 'الرياضيات';
if (str_contains($courseTitle, 'عسكرية') || str_contains($courseTitle, 'وطنية') || str_contains($lessonTitle, 'كرامة') || str_contains($lessonTitle, 'تعريب')) {
$matchedSubject = 'الثقافة العسكرية';