Files
saqel/backend/app/Services/CurriculumService.php
T

304 lines
17 KiB
PHP

<?php
namespace App\Services;
use App\Core\Database;
/**
* 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 {
$subjectsCount = (int)(Database::selectOne("SELECT COUNT(*) as total FROM subjects")['total'] ?? 0);
if ($subjectsCount === 0) {
Database::query(
"INSERT INTO subjects (id, name, code, stream, is_active) VALUES
(1, 'الرياضيات العلمي', 'math_scientific', 'scientific', 1),
(2, 'الثقافة العسكرية والتربية الوطنية', 'military_culture', 'common', 1),
(3, 'الفيزياء', 'physics_scientific', 'scientific', 1),
(4, 'الكيمياء', 'chemistry_scientific', 'scientific', 1),
(5, 'العلوم الحياتية (الأحياء)', 'biology_scientific', 'scientific', 1),
(6, 'اللغة الإنجليزية', 'english_common', 'common', 1),
(7, 'اللغة العربية المشتركة', 'arabic_common', 'common', 1)
ON DUPLICATE KEY UPDATE name = VALUES(name)"
);
}
$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");
}
self::$schemaChecked = true;
} catch (\Throwable $e) {
error_log("CurriculumService schema note: " . $e->getMessage());
}
}
/**
* 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
{
self::ensureSchema();
$course = Database::selectOne("SELECT * FROM courses WHERE id = ? LIMIT 1", [$courseId]);
$courseTitle = $course['title'] ?? 'الرياضيات العلمي — توجيهي 2008';
$curriculumMap = [
'الرياضيات' => [
'grade' => 'توجيهي 2008 — الفرع العلمي',
'subject' => 'الرياضيات — الفصل الدراسي الأول',
'unit' => 'الوحدة الأولى: التفاضل وتطبيقاته',
'core_topics' => [
'قواعد الاشتقاق الأساسية: مشتقة الثابت صفر، مشتقة x^n هي n*x^(n-1)، ومشتقة المجموع والفرق.',
'مشتقة حاصل ضرب اقترانين: الأول في مشتقة الثاني + الثاني في مشتقة الأول [f*g]\' = f*g\' + g*f\'.',
'مشتقة حاصل قسمة اقترانين: (المقام في مشتقة البسط - البسط في مشتقة المقام) مقسوماً على مربع المقام.',
'مشتقات الاقترانات المثلثية: مشتقة sin(u) هي cos(u)*u\'، ومشتقة cos(u) هي -sin(u)*u\'، ومشتقة tan(u) هي sec^2(u)*u\'.',
'قاعدة السلسلة: مشتقة الاقتران المركب f(g(x)) هي f\'(g(x)) * g\'(x).'
]
],
'الثقافة العسكرية' => [
'grade' => 'مدارس الثقافة العسكرية — المرحلة الثانوية',
'subject' => 'التربية الوطنية والثقافة العسكرية',
'unit' => 'الوحدة الأولى: القوات المسلحة الأردنية — الجيش العربي النشأة والتطور',
'core_topics' => [
'تأسيس الجيش العربي عام 1921 وتطوره في عهد الملك المؤسس عبدالله الأول.',
'تعريب قيادة الجيش العربي في الأول من آذار عام 1956 بقرار تاريخي من الملك الحسين بن طلال.',
'معركة الكرامة الخالدة في 21 آذار 1968 كأول نصر عربي حديث وتحطيم أسطورة الجيش الذي لا يُقهر.',
'الأدوار التنموية والإنسانية للجيش العربي والمستشفيات الميدانية وقوات حفظ السلام الدولية.'
]
]
];
$matchedSubject = 'الرياضيات';
if (str_contains($courseTitle, 'عسكرية') || str_contains($courseTitle, 'وطنية') || str_contains($lessonTitle, 'كرامة') || str_contains($lessonTitle, 'تعريب')) {
$matchedSubject = 'الثقافة العسكرية';
}
return $curriculumMap[$matchedSubject];
}
}