الأدلةمقترحEnglish

الهندسة

نمذجة البيانات والمعرفات وواجهات API والتخزين والعرض والصوت والبحث والتخزين المؤقت في البرمجيات القرآنية، مع الاختبار الذي يتحقق من كل قاعدة.

القاعدة الأساسية: ابنِ نموذج البيانات على المصحف بدل الشاشة، واستخدم في كل جدول وواجهة API وملف اسم المفهوم الذي يحتويه كما يرد في القاموس، وسجّل هوية النص الذي بُني منه حتى يمكن إعادة بنائه من ذلك النص.

تطبق هذه الصفحة قواعد النص والتسمية على أجزاء النظام التي تحتوي على المصحف، وتوضح متى تكون القاعدة خيارًا مقبولًا ضمن عدة خيارات. والحالات المذكورة مأخوذة من دراسة مسحية لكود مشروع قرآني مستخدم فعليًا، واستندت إليها القواعد لأنها حالات شائعة.

1. نمذجة البيانات

نسجل مفاهيم النموذج في القاموس، ونخصص سجلًا لكل مجموعة محددة القيم.

1.1 خصص جدولًا مستقلًا لكل عنصر في السلسلة الأساسية mushaf_editionsurahayahwordtoken، على أن ينتج token عن طريقة تقسيم معلنة وألا يحل محل الكلمة.

CREATE TABLE mushaf_editions (
  id                        INTEGER PRIMARY KEY,
  code                      VARCHAR(64) NOT NULL UNIQUE,
  riwayah_id                INTEGER NOT NULL REFERENCES riwayahs(id),
  ayah_numbering_system_id  INTEGER NOT NULL REFERENCES ayah_numbering_systems(id),
  version                   VARCHAR(32) NOT NULL,   -- إصدار البيانات
  source_hash               CHAR(64) NOT NULL       -- بصمة ملف المصدر بصيغة sha256
);

CREATE TABLE ayahs (
  id                        INTEGER PRIMARY KEY,
  mushaf_edition_id         INTEGER NOT NULL REFERENCES mushaf_editions(id),
  surah_number              SMALLINT NOT NULL,
  ayah_number               SMALLINT NOT NULL,
  text                      TEXT NOT NULL COLLATE utf8mb4_bin,
  has_basmalah              BOOLEAN NOT NULL,
  UNIQUE (mushaf_edition_id, surah_number, ayah_number)
);

أنواع الأعمدة خيار من عدة خيارات صحيحة، أما الأسماء والمفاتيح الأجنبية فثابتة، وفي examples/schema/ النموذج الكامل.

يفحصه: كل اسم عمود يقابل اسم code معتمدًا، ويفحص ذلك tools/check_example_files.py على examples/.

1.2 خصص جداول مستقلة للكيانات riwayah وayah_numbering_system وpage وline بدل وضعها أعمدة في ayah، لأن رقم الآية يعتمد على نظام عد الآي وصفحتها تعتمد على الطبعة.

يفحصه: فحص للمخطط يتأكد من خلو ayah من أعمدة مستقلة للصفحة والسطر والترقيم.

1.3 اربط المصحف والتسجيل وdataset بالرواية وليس بشخص، فالعمود هو riwayah_id ويوضع rawi_id في صف الرواية.

كان mushafs.rawi_id في الكود المفحوص يشير إلى جدول أشخاص، فأصبح طلب «كل التسجيلات برواية ورش» يعني طلب «كل التسجيلات التي يؤديها ورش نفسه».

يفحصه: فحص للمخطط يتأكد من أن جداول المصحف والتسجيل وdataset لا تحمل rawi_id.

1.4 خزّن القيم العددية بنوع عددي بدل السلاسل النصية.

تضمن ملف التصدير المفحوص surah_number: "2" وwords_count: "6140".

يفحصه: فحص للمخطط يتأكد من أن القيم العددية لا تُخزن في أعمدة نصية.

1.5 حمّل المجموعات المحددة القيم، مثل أنظمة عد الآي والقراءات والسور ومواضع السجدة، من السجلات، ولا تكتبها يدويًا بصيغة enum في ملف ترحيل.

يفحصه: التحقق من أن كل قيمة في عمود التصنيف موجودة في سجله.

2. المعرفات والعنونة

الموضع هو رقم السورة ورقم الآية ونظام عد الآي معًا، والرقم وحده لا يحدد موضعًا.

2.1 استخدم الصيغة المختصرة ayah_key مثل 2:255 لتمثيل الموضع نصيًا، مع ذكر نظام عد الآي الذي يكمل عناصره الـ3، ولا تخزنها أو تتبادلها دونه.

يفحصه: وجود ayah_numbering_system مع ayah_key في جميع الجداول ونقاط API، مباشرةً أو عبر الطبعة.

2.2 حدد موضع الكلمة بحقل word_key مثل 2:255:3، الذي يجمع مفتاح الآية وword_position وفق تقسيم معلن تُرقّم إصداراته مع dataset.

يفحصه: عدد وحدات التقسيم المحفوظ بوصفه قيمة مرجعية لكل إصدار.

2.3 استخدم في id معرفًا داخليًا ثابتًا لا يحمل معنى، ولا تستخدم النص أو بصمته معرفًا أو تعيد استخدام المعرف بعد حذفه.

يفحصه: فحص للمخطط يتأكد من أن المفاتيح الأساسية أعداد صحيحة أو سلاسل لا تحمل معنى.

3. تصميم واجهات API

مسارات الموارد تستخدم الاسم المعتمد والجمع البرمجي، وكل معامل يغير النص يُذكر صراحةً.

3.1 استخدم المسارات /surahs/{surah_number}/ayahs/{ayah_number}، ولا تستخدم /chapters/{n}/verses/{m}.

يفحصه: فحص lint لمواصفات OpenAPI يتأكد من أن كل جزء من المسار يقابل مدخلًا في القاموس (examples/api/).

3.2 حدد قيمًا افتراضية موثقة للمعاملين riwayah وayah_numbering_system، وأعد في الاستجابة القيمة المستخدمة لكل منهما.

GET /surahs/2/ayahs/255?riwayah=hafs_an_asim&ayah_numbering_system=kufi

{
  "ayah_key": "2:255",
  "surah_number": 2,
  "ayah_number": 255,
  "riwayah": "hafs_an_asim",
  "ayah_numbering_system": "kufi",
  "mushaf_edition": "…",
  "version": "1.2.0",
  "source_hash": "sha256:…",
  "text": "…",
  "search_key": "…"
}

استخدام حفص والعد الكوفي افتراضيًا دون إعلان يعطي نتيجة خاطئة لكل قارئ يستخدم غيرهما، ولا يوضح لأي قارئ أن هاتين القيمتين استُخدمتا.

يفحصه: اختبار لعقد API يتأكد من أن كل استجابة نصية تحمل الحقلين.

3.3 أعد حقل text كما خُزن تمامًا ومعه source_hash وversion الخاص بـdataset، وضع الصورة المشتقة مثل search_key في حقل مستقل.

يفحصه: فحص lint على OpenAPI يتأكد من أن كل مخطط يحمل text يحمل معه source_hash وversion.

3.4 قسّم النتائج إلى صفحات تنتهي كل منها عند نهاية آية، دون تقسيم الآية بين صفحتين.

يفحصه: اختبار لعقد API على آخر عنصر في كل صفحة.

3.5 أعد استجابة 404 للموضع غير الموجود، واذكر فيها نظام عد الآي المستخدم للتحقق، دون تخمين أقرب موضع.

يفحصه: اختبار لعقد API برقم آية صحيح في نظام وغير صحيح في آخر.

4. التخزين والترميز

قاعدة البيانات تقارن النص القرآني بايتًا بايتًا، ولا تغير فيه شيئًا عند الإدخال أو الاسترجاع.

4.1 استخدم collation ثنائيًا لأعمدة النص، حتى تكون نتيجة المقارنة 'مُحَمَّد' = 'محمد' غير صحيحة.

في MySQL نستخدم utf8mb4_bin، ويجب أن يعيد SELECT 'مُحَمَّد' = 'محمد'; القيمة 0.

يفحصه: تشغيل استعلام collation بوصفه اختبارًا على قاعدة البيانات التي تنشرها.

4.2 أبقِ النص دون تطبيع طوال انتقاله من الملف إلى القارئ، سواء في برنامج التشغيل أو ORM أو خيار «تنظيف المخرجات» في أداة تسلسل البيانات.

يفحصه: قاعدة lint تمنع normalize في مسار النص.

4.3 تحقق عند إدخال البيانات من التزام النص بقائمة المحارف المسموح بها، وارفض الاستيراد إذا وجدت محرفًا خارجها.

يفحصه: اختبار استيراد بمحرف واحد خارج القائمة.

4.4 استخدم search_key للبحث، فإضافة فهرس للبحث النصي الكامل إلى text باستخدام collation يتجاهل الحركات تجعل البحث يجري في الحقل الخطأ.

يفحصه: فحص للمخطط يتأكد من عدم وجود فهرس للبحث النصي الكامل على عمود النص.

5. العرض والخطوط

نفصل بين 3 طبقات: الرسم والضبط والخط، ويتكون النص من الرسم والضبط فقط.

5.1 خزّن تخطيط الصفحات والأسطر في dataset مرتبط بـmushaf_edition يحدد صفحة كل كلمة وسطرها دون تخزين النص.

صف التخطيط:  mushaf_edition = "…"   page_number = 3   line_number = 7
             word_key = "2:6:1" … "2:7:4"

يفحصه: اختبار لقطة لكل صفحة بعد إعادة بنائها من النص وdataset التخطيط.

5.2 تعامل مع النص المرمّز بالخط، أي الآية المكتوبة بقيم codepoint الخاصة بخط معين، بوصفه طبقة مشتقة تسجل اسم الخط وإصداره وبصمة المصدر.

يفحصه: فحص بصمة الطبقة المشتقة.

5.3 أوقف البناء إذا افتقد الخط شكلًا مطلوبًا في النص القرآني، ولا تستخدم خطًا بديلًا لرسمه، لأن الشكل البديل قد يُقرأ على أنه علامة أخرى.

يفحصه: اختبار تغطية cmap لكل إصدار من الخط، وفحص يتأكد من أن مسار عرض النص يستخدم خطًا واحدًا.

6. الصوت والتلاوة

التسجيل تلاوة يؤديها قارئ برواية معينة، ولها نمط أداء ومرتبة قراءة.

6.1 اربط القارئ الذي يؤدي التسجيل بالرواية التي يقرأ بها، ولا تسجله بوصفه راويها، فالقارئ يختلف عن الراوي.

يفحصه: فحص rawi_id على المخطط المذكور أعلاه.

6.2 سجّل نمط الأداء في recitation_style وطريقة تقطيع الصوت ضمن خصائص الملفات، فهي ليست من خصائص التلاوة.

وضع الكود المفحوص «مرتل» و«مجود» و«معلم» في عمود type، و«حسب السور» و«حسب الآيات» في تصنيف.

يفحصه: فحص المصطلحات الذي يبلغ عن type على مفهوم قرآني.

6.3 يمثل ayah_timing وword_timing طبقتين مشتقتين، ويسجل كل ملف بصمة التسجيل الصوتي الذي ضُبطت التوقيتات وفقه وإصدار النص ونظام عد الآي.

# ayah_timing v1
# riwayah:                hafs_an_asim
# audio_sha256:           …
# text_version:           1.2.0
# ayah_numbering_system:  kufi
ayah_key   start_ms   end_ms
1:1        0          5320
1:2        5320       9870

يفحصه: خطوة في البناء تتحقق من مطابقة بصمة الصوت وإصدار النص في ترويسة كل ملف للبيانات الحالية.

6.4 يسمي التطبيق مفاهيم البث والإذاعات وقوائم التشغيل، لأنها تخصه ولا يحدد المعيار أسماءها.

يفحصه: قاعدة نطاق المعيار في القسم 30.

6.5 اجعل تشغيل التلاوة بطلب من القارئ فقط، مع تعطيل التشغيل التلقائي افتراضيًا وإبقاء الإشعارات والإعلانات خالية من التلاوة.

يفحصه: قاعدة لمراجعة الواجهة.

7. البحث

يُشتق مفتاح البحث من النص بدالة حتمية لها رقم إصدار، ولا يراه القارئ أبدًا.

7.1 استخدم دالة تبسيط النص المرفقة مع dataset للاستعلام والفهرس معًا دون كتابة دالة خاصة بالمشروع، وإن لم يُرفق dataset دالة فعرّف واحدة وأعلن عنها ورقّم إصداراتها معه، واقتصر فيها على حذف علامات الضبط والتطويل وتوحيد صور الحروف التي ينبغي ألا يميز البحث بينها.

search_key(text)، دالة التبسيط التي يعلنها dataset معيّن:
  1. احذف علامات الضبط والتطويل (U+0640)
  2. أ إ آ ٱ → ا     ؤ → و     ئ → ي
  3. ى → ي           ة → ه
  4. اختصر المحارف البيضاء المتتابعة إلى مسافة واحدة

يرفق كل من Quran Text وQuran SVG Elements وQuran Engine دالة لتبسيط النص، وتختلف الدوال الثلاث؛ وللبحث في فهرس نستخدم الدالة التي بُني بها، فالاستعلام الذي تُطبّق عليه دالة أخرى لا يجد نتائج.

يفحصه: حالات مرجعية لتحويل المدخلات إلى مفاتيح على غرار tools/test_translit.py، واختبار يتأكد من أن الاستعلام والفهرس يستخدمان الإصدار نفسه من دالة التبسيط.

7.2 خصص رقم إصدار للدالة، وعُدّ أي تغيير فيها تغييرًا في dataset يستلزم إعادة بناء الفهرس.

يفحصه: اختبار يتأكد من أن إصدار الفهرس يساوي إصدار الدالة.

7.3 اربط النتيجة بالموضع وبالنص المخزن text، ولا تربطها بمفتاح البحث.

يفحصه: اختبار لعقد API على استجابة البحث.

8. التخزين المؤقت

يتضمن مفتاح التخزين المؤقت كل ما يغير النص، ولا تنتهي صلاحية النص القرآني المخزن مؤقتًا بمرور الوقت وحده.

8.1 ضمّن مفتاح التخزين المؤقت إصدار dataset وبصمة المصدر والطبعة والرواية ونظام عد الآي، حتى لو كانت القيمة الافتراضية لاثنين منها واحدة حاليًا.

مفتاح التخزين المؤقت:  ayah:{version}:{source_hash:12}:{mushaf_edition}:{ayah_numbering_system}:{ayah_key}
مسار CDN:              /text/1.2.0/{mushaf_edition}/2/255.json

تبطل بصمة المصدر صلاحية النسخة المخزنة مؤقتًا عند تصحيح dataset قبل تغيير رقم إصداره.

يفحصه: اختبار لصيغة مفتاح التخزين المؤقت.

8.2 أبطل صلاحية كل نص مخزن مؤقتًا من الإصدار السابق عند نشر إصدار بيانات MAJOR، وأبقِ ملفات النص في CDN ثابتة مع تضمين رقم الإصدار في المسار بدل سلسلة الاستعلام.

يفحصه: اختبار يتأكد من أن رفع رقم الإصدار يغير جميع روابط النص.

المصدر: quran-ws/docs، عند d1d6be33be9d. ما لم يُوسم «معتمد» فهو مقترح للنقاش، ولا يُبنى عليه بعد.