قرارات التصميم
أُعيد تصميم الحزمة قبل أن يكون لها أول مستخدم، فأمكن اتخاذ كل قرار بحسب وجاهته لا من أجل التوافق مع ما سبق. تسجّل هذه الصفحة كل قرار، وسببه، وما يترتب عليه. القرار الجديد يحصل على مدخل جديد؛ والقرار الذي يُتراجع عنه يُعلَّم ولا يُحذف.
1. البيانات هي المنتج؛ والكود وسيلة تيسير
القرار. المصادر الرسمية هي الحقيقة؛ وملفات JSON في data/ تُبنى منها وهي المنتج. ويمكن استخدامها من دون أي شيء من TypeScript: من Python، أو من جدول بيانات (تتضمن الإصدارات جداول CSV) أو من jq. تُعرَّف بنية كل ملف مرة واحدة، بأنواع TypeScript، وتُنشر بوصفها JSON Schema مولَّدًا منها؛ ويفشل CI حين تختلف الأنواع والمخططات والبيانات. تضيف المكتبة الأنواع والفهارس والدوال المساعدة، ولا شيء فيها ضروري لفهم البيانات.
السبب. البيانات المرجعية تعيش أطول من الكود المحيط بها، ومعظم مستهلكي البيانات القانونية البلجيكية ليسوا مشاريع JavaScript.
النتيجة. لكل ملف JSON Schema ومدخل في datapackage.json بمصادره وترخيصه. وأسماء الحقول وقيمها مشروحة في التوثيق، لا في الكود وحده.
2. الحقائق القانونية بيانات، ولا تُحسب أبدًا
القرار. كل ما له معنى قانوني (اسم محكمة، إقليم كانتون، سعر فائدة) يأتي حرفيًا من مصدر رسمي ويُخزَّن. يجوز للكود أن ينسّق أو يبحث؛ ولا يجوز له أن يركّب حقيقة قانونية.
السبب. الكود الذي يجمّع حقيقة قد يخطئ بطرق لا تكشفها الاختبارات: أسماء المحاكم الفرنسية التي كانت تبنيها formatCourtName في نسخة سابقة كانت خاطئة (Tribunal de paix …، في حين تسمّي المحاكم نفسها Justice de paix …؛ #10). أما القيمة المخزَّنة فيمكن التحقق منها مقابل مصدرها.
النتيجة. تُخزَّن أسماء المحاكم كما تنشرها كل محكمة، بكل لغة تنشر بها، وأُزيلت formatCourtName. وتبقى أقاليم الكانتونات نصًا قانونيًا حرفيًا، مع أساسها القانوني في شكل رابط ELI. وحيث يغيب المصدر يغيب الحقل: لا شيء يُملأ بالتخمين. والأمر نفسه في التحليل (parsing): نص مواعيد عمل فيه ملاحظة، أو رقم هاتف بلا رمز منطقته، أو عنوان مكتوب على شكل جملة، يُترك ولا يُخمَّن.
3. قاعدة واحدة للمعرّفات
القرار. رمز رسمي حيث يغطي رمزٌ كل السجلات، وإلا فـkebab-case بأحرف صغيرة مشتق من الاسم باللغة المحلية؛ والمحاكم type + المكان. والرموز الرسمية التي لا تغطي إلا بعض السجلات (ECLI وOVO) حقول. انظر المعرّفات.
4. الروابط حقول
القرار. لا تشير السجلات بعضها إلى بعض إلا عبر حقول تحمل معرّف السجل الآخر، وتُسمّى باسم مجموعة البيانات التي تشير إليها (arrondissement، canton، municipality). ولا يُبنى معرّف من معرّف آخر.
السبب. العلاقة المخبّأة في صيغة نصية تنكسر بصمت حين يتغير أحد طرفيها. أما الحقل فيتحقق منه المخطط (الذي يسرد المعرّفات الصالحة) والاختبارات.
النتيجة. عنوان المحكمة أو السجن أو الـjustitiehuis / maison de justice يرتبط ببلديته بالطريقة نفسها (address.municipality)، عبر الرمز البريدي في جدول التحويل الخاص بـStatbel، ولا يكون أبدًا عبر اسم المدينة (#28)؛ وسجل الرمز البريدي يرتبط ببلديته بدلًا من نسخ اسمها.
5. قيم معيارية، والعرض في الكود
القرار. تُخزَّن كل قيمة بصيغة معيارية قياسية واحدة، والتنسيق للقراء دالة:
| القيمة | تُخزَّن بصيغة | المعيار |
|---|---|---|
| اليوم | 2024-06-15 | ISO 8601 |
| الهاتف | +3253820701 | E.164 |
| IBAN | BE73679200844560 | الصيغة الإلكترونية لـISO 13616 |
| مواعيد العمل | Mo-Fr 08:30-12:30,13:30-16:00 | opening_hours في OpenStreetMap |
| اللغة | المفاتيح nl وfr وde | ISO 639-1 |
السبب. صيغة واحدة لكل قيمة تجعل القيم قابلة للمقارنة وللتحقق (رقم التحقق في IBAN مختبَر) وقابلة للاستخدام من برامج أخرى (روابط tel:، وأنظمة الدفع، ومحلّلات مواعيد العمل). أعراف العرض تختلف بحسب السياق؛ والبيانات لا ينبغي أن تختلف.
6. المصدر على كل سجل
القرار. تسرد كل مجموعة بيانات مصادرها وترخيصها وتاريخ مراجعتها في datapackage.json؛ ويسرد كل سجل مستخرَج الصفحات التي قُرئ منها واليوم الذي رُئي فيه محتواها أول مرة (sources: [{ url, retrieved }]).
السبب. «من أين أتى هذا، وما عمره؟» هو السؤال الأول في البيانات القانونية.
7. تبني الآلاتُ البيانات، من لقطات محفوظة
القرار. كل أسبوع يجلب workflow المصادر الرسمية، ويحفظ الصفحات بوصفها لقطات (snapshots) على الفرع sources، ويعيد بناء البيانات منها بالمحلّلات الموجودة في scripts/parse، ويختبرها، ويفتح pull request حين تتغير. لا شيء يُكتب يدويًا. ومهمة أسبوعية منفصلة تتحقق من أسعار الفائدة الناقصة، والمراجعات المتأخرة، والمصادر المعطّلة.
السبب. البيانات القانونية تتقادم بصمت؛ الناس ينسون، والجداول الزمنية لا تنسى. البيانات المدخلة يدويًا لا يمكن التحقق منها ولا إعادة إنتاجها؛ أما المحلّل فيمكن اختباره، واللقطات تُظهر بالضبط ما قاله المصدر في ذلك اليوم. ويُبقي الـpull request إنسانًا في الحلقة: البيانات في main لا تتغير أبدًا دون مراجعة.
النتيجة. تعيش اللقطات على فرعها الخاص، كي لا يُثقَل تاريخ الحزمة بميغابايتات من HTML. تُختبر المحلّلات على نسخ مقتطعة من صفحات حقيقية. والصفحة التي لا يستطيع المحلّل تصنيفها توقف البناء بدلًا من إنتاج سجل نصف صحيح. مصدران يرفضان خوادم GitHub (ejustice وpolice.be)؛ فيُجلبان من جهاز عادي (#52).
8. التوزيع: ESM فقط، Node 22.12+، يُبنى عند الإصدار
القرار. الحزمة ESM فقط، وتتطلب Node 22.12 أو أحدث، ومخرجات البناء لا تُضاف إلى المستودع: يبنيها workflow الإصدار وينشرها على npm، مع provenance.
السبب. انتهى عمر Node 18 و20. ومنذ Node 22.12 يحمّل require() وحدات ESM، فبناء CommonJS منفصل لن يضيف إلا نسخة ثانية من البيانات (خطر الحزمة المزدوجة) ومزيدًا مما يجب اختباره. ومخرجات البناء المضافة إلى المستودع تتقادم، وقد عطّلت التحديثات الآلية للتبعيات.
النتيجة. يعمل كل من import وrequire()، مع اختبار على Node 22.12 و22 و24 و26. لكل مجموعة بيانات entry point خاص بها، فالتطبيق الذي لا يحتاج إلا إلى أسعار الفائدة يحمّل بضعة كيلوبايتات، لا المحاكم والنص القانوني لكل كانتون (تقيس صفحة البدء كل واحد منها من البناء).
9. أنواع تحمل ضمانات البيانات
القرار. المعرّفات أنواع union مولَّدة؛ ومجموعات البيانات readonly؛ والبحث بمعرّف معروف يُرجع السجل، لا record | undefined؛ والمفردات المغلقة (أنواع المحاكم، والأصناف، وأنواع جهات الاتصال) أنواع union. أما تسميات جهات الاتصال فنص حر، كما تنشرها المحكمة: القائمة المغلقة كانت ستعني إسقاط مئات أسماء أقلام المحاكم على بضع كلمات، وهذا تخمين.
السبب. تظهر الأخطاء أثناء الكتابة بدلًا من بيئة الإنتاج، والأنواع توثّق البيانات.
10. بوابات الجودة
القرار. يجب أن يجتاز كل تغيير: TypeScript الصارم (بما في ذلك noUncheckedIndexedAccess وexactOptionalPropertyTypes)، وESLint بقواعد صارمة واعية بالأنواع، وPrettier، وتغطية اختبارات 100 % لـsrc/، والتحقق من كل البيانات بـJSON Schema، واختبارات السلامة (المعرّفات، والروابط، والأعداد، والفترات)، وpublint + arethetypeswrong على الحزمة المحزومة.
السبب. لا تستحق حزمة مرجعية الاعتماد عليها إلا إذا كانت أوثق من نسخ البيانات يدويًا.
11. الزمن جزء من النموذج
القرار. تحمل السجلات فترة سريان (validFrom وvalidTo، واليومان مشمولان)، وتذكر كل مجموعة بيانات اليوم الذي تبدأ منه وصف الوضع (territoryCoverage، وtemporalCoverage في datapackage.json)، وتقبل دوال البحث يومًا اختياريًا: getCanton(id, { on })، getCantonsForMunicipality(refnis, { on }). واليوم السابق للتغطية يرمي RangeError.
السبب. الأسئلة القانونية تتعلق بيوم: أي محكمة كانت مختصة يوم تبليغ الاستدعاء، وأي سعر كان ساريًا يوم استحقاق الفاتورة. الكانتونات والبلديات والمحاكم تتغير؛ والنموذج الذي لا يعرف إلا «الآن» يجيب عن أسئلة الماضي بخريطة اليوم، ويبدو صحيحًا وهو يفعل ذلك. وإضافة الزمن لاحقًا كانت ستغيّر كل معرّف وكل دالة بحث، لذا فهو في النموذج قبل أن يكون التاريخ في البيانات (#27).
النتيجة. لأسعار الفائدة فترات منذ الآن. البحث بمعرّف دون يوم يعطي نسخة اليوم، فتحتفظ المعرّفات المعروفة بنوعها غير undefined؛ أما مع يوم فقد لا يجد شيئًا. تحمل كل مجموعة بيانات الفترة، لا الإقليم وحده: السجل المستخرَج الذي يتوقف المصدر عن إدراجه يُنهى بـvalidTo بدلًا من حذفه (scripts/parse/retire.ts)، فيبقى المعرّف بعد نشره، والمحكمة التي تُغلق تبقى المحكمة التي سمّاها استدعاء العام الماضي.
12. الإصدارات تحمل البيانات، لا الحزمة وحدها
القرار. لكل إصدار على GitHub البيانات بصيغة JSON وبصيغة جداول CSV، مع المخططات وdatapackage.json، في ملف zip واحد. npm طريقة للحصول على البيانات، لا الطريقة الوحيدة.
السبب. أكثر من يحتاج إلى البيانات المرجعية القانونية البلجيكية يعملون في جداول البيانات وبلغات أخرى، لا في JavaScript.
13. الهندسة مصدر ثانٍ، والنص هو الغالب
القرار. حدود الكانتونات هي رقمنة NGI (المعهد الجغرافي الوطني) لملحق Gerechtelijk Wetboek / Code judiciaire، مأخوذة كما نُشرت (رأسًا برأس، بلا تبسيط، والإحداثيات بدقة سنتيمتر) ومطابَقة مع الكانتونات بالاسم في اللغتين، مع جدول أسماء بديلة صريح للباقي. يبقى نص territory للكانتون هو القانون: حيث تختلف الهندسة والنص، يغلب النص. وللهندسة entry point خاص بها ولا يعيد جذر الحزمة تصديرها.
السبب. مضلّعات NGI قراءة رسمية للنص نفسه، فيمكن الاستشهاد بها وإعادة بنائها؛ أما حدٌّ يُشتق هنا من بيانات الشوارع فسيكون قراءة ثالثة لا يستطيع أحد التحقق منها. التبسيط سيحرّك الخط، والخطوط أهم ما تكون حيث تلتقي الكانتونات. ومليون موضع سيقزّم كل مجموعة بيانات أخرى، فلا ينبغي لأحد أن يحمّلها مصادفة.
النتيجة. تجيب findCantonsAt(lon, lat) من الهندسة، والتوثيق يقول ذلك؛ والنقطة القريبة من خط حالةٌ يُرجع فيها إلى النص. ويفشل البناء الأسبوعي حين لا يكون لكانتون حد أو حين لا تطابق ميزة (feature) أي كانتون (#29).
لم يُحسم بعد
| السؤال | الـissue |
|---|---|
| التاريخ قبل 1 يناير 2025 | #27 |
| حساب الفائدة على فترة (عدّ الأيام، الفائدة المركّبة) | غير مخطط له إلى أن يمكن استناد الطريقة إلى اجتهاد قضائي أو توجيه رسمي |