Skip to content

Проєктні рішення ​

Пакет було перепроєктовано ще до появи перших користувачів, тож кожен вибір можна було робити по суті, а не заради сумісності. Ця сторінка фіксує кожне рішення, чому його ухвалено і що з нього випливає. Нове рішення отримує новий запис; скасоване позначається, а не видаляється.

1. Продукт — це дані; код — лише зручність ​

Рішення. Істина — це офіційні джерела; JSON-файли в data/ будуються з них і є продуктом. Ними можна користуватися без жодного TypeScript: з Python, електронної таблиці (релізи містять CSV-таблиці) або jq. Структура кожного файлу визначається один раз, типами TypeScript, і публікується як згенерована з них JSON Schema; CI падає, коли типи, схеми й дані не узгоджуються. Бібліотека додає типи, індекси й допоміжні функції, і жодна з них не потрібна, щоб зрозуміти дані.

Чому. Довідкові дані живуть довше за код навколо них, а більшість споживачів бельгійських правових даних — не JavaScript-проєкти.

Отже. Кожен файл має JSON Schema і запис у datapackage.json з джерелами та ліцензією. Назви полів і значення пояснено в документації, а не лише в коді.

Рішення. Усе, що має правове значення (назва суду, територія кантону, ставка), береться дослівно з офіційного джерела і зберігається. Код може форматувати чи шукати; конструювати правовий факт він не може.

Чому. Код, що складає факт, може помилятися так, що тести цього не помітять: французькі назви судів, які будувала колишня formatCourtName, були неправильні (Tribunal de paix …, тоді як суди називають себе Justice de paix …; #10). Збережене значення можна звірити з джерелом.

Отже. Назви судів зберігаються так, як їх публікує кожен суд, усіма мовами, якими він публікує, а formatCourtName більше немає. Території кантонів залишаються дослівним текстом закону, з правовою підставою у вигляді посилання ELI. Де джерела немає, немає й поля: нічого не заповнюється навмання. Те саме стосується розбору: текст годин роботи з приміткою всередині, номер телефону без коду зони чи адреса, записана реченням, пропускаються, а не вгадуються.

3. Одне правило для ідентифікаторів ​

Рішення. Офіційний код, якщо такий охоплює всі записи, інакше — kebab-case у нижньому регістрі з назви місцевою мовою; для судів — type + місце. Офіційні коди, що охоплюють лише частину записів (ECLI, OVO), є полями. Див. Ідентифікатори.

Рішення. Записи посилаються один на одного лише через поля з id іншого запису, названі за набором даних, на який вони вказують (arrondissement, canton, municipality). Жоден id не будується з іншого.

Чому. Зв'язок, схований у форматі рядка, тихо ламається, коли змінюється будь-яка зі сторін. Поле перевіряється схемою (яка перелічує допустимі id) і тестами.

Отже. Адреса суду, в'язниці чи justitiehuis / maison de justice так само пов'язана зі своєю громадою (address.municipality) — через поштовий індекс у таблиці відповідності Statbel, ніколи через назву міста (#28); запис поштового індексу посилається на свою громаду, а не копіює її назву.

5. Канонічні значення, подання в коді ​

Рішення. Кожне значення зберігається в одній канонічній, стандартній формі, а форматування для людей — це функція:

ЗначенняЗберігається якСтандарт
День2024-06-15ISO 8601
Телефон+3253820701E.164
IBANBE73679200844560електронний формат ISO 13616
Години роботиMo-Fr 08:30-12:30,13:30-16:00OpenStreetMap opening_hours
Моваключі nl, fr, deISO 639-1

Чому. Одна форма для кожного значення робить значення порівнюваними, придатними до перевірки (контрольну суму IBAN протестовано) і зручними для іншого програмного забезпечення (посилання tel:, платіжні системи, парсери годин роботи). Конвенції відображення залежать від контексту; дані — не повинні.

6. Походження в кожному записі ​

Рішення. Кожен набір даних перелічує свої джерела, ліцензію і дату перегляду в datapackage.json; кожен зібраний з вебсторінок запис перелічує сторінки, з яких його прочитано, і день, коли їхній вміст було вперше побачено (sources: [{ url, retrieved }]).

Чому. «Звідки це і наскільки воно старе?» — перше питання щодо правових даних.

7. Дані будують машини, зі збережених знімків ​

Рішення. Щотижня workflow завантажує офіційні джерела, зберігає сторінки як знімки в гілці sources, перебудовує з них дані парсерами в scripts/parse, тестує їх і відкриває pull request, якщо щось змінилося. Нічого не вводиться вручну. Окреме щотижневе завдання перевіряє, чи не бракує процентних ставок, чи не прострочено перегляди і чи не зникли джерела.

Чому. Правові дані старіють непомітно; люди забувають, розклади — ні. Введені вручну дані неможливо перевірити чи відтворити; парсер можна протестувати, а знімки точно показують, що казало джерело того дня. Pull request залишає людину в процесі: дані в main ніколи не змінюються без перегляду.

Отже. Знімки живуть в окремій гілці, щоб історію пакета не обтяжували мегабайти HTML. Парсери тестуються на скорочених копіях справжніх сторінок. Сторінка, яку парсер не може розпізнати, зупиняє збірку, а не породжує напівправильний запис. Два джерела відмовляють серверам GitHub (ejustice, police.be); їх завантажують зі звичайної машини (#52).

8. Розповсюдження: лише ESM, Node 22.12+, збірка під час релізу ​

Рішення. Пакет — лише ESM, потребує Node 22.12 або новішої версії, а результат збірки не комітиться: його збирає і публікує в npm release workflow, з provenance.

Чому. Node 18 і 20 досягли кінця підтримки. Починаючи з Node 22.12, require() завантажує ESM, тож окрема збірка CommonJS лише додала б другу копію даних (dual-package hazard) і більше роботи з тестування. Закомічений результат збірки застаріває і ламав автоматичні оновлення залежностей.

Отже. Працюють і import, і require(), протестовано на Node 22.12, 22, 24 і 26. Кожен набір даних має власний entry point, тож застосунок, якому потрібні лише процентні ставки, завантажує кілька кілобайтів, а не суди й текст закону для кожного кантону (сторінка Початок роботи вимірює кожен з них зі збірки).

9. Типи, що несуть гарантії даних ​

Рішення. Id — це згенеровані union-типи; набори даних — readonly; пошук за відомим id повертає запис, а не record | undefined; закриті словники (типи судів, види, типи контактів) — це union-типи. Мітки контактів — вільний текст, як їх публікує суд: закритий список означав би зведення сотень назв канцелярій до кількох слів, тобто вгадування.

Чому. Помилки виявляються ще під час набору коду, а не в production, і типи документують дані.

10. Контроль якості ​

Рішення. Кожна зміна проходить: строгий TypeScript (включно з noUncheckedIndexedAccess і exactOptionalPropertyTypes), ESLint зі строгими правилами з урахуванням типів, Prettier, 100 % покриття тестами src/, валідацію всіх даних за JSON Schema, тести цілісності (id, зв'язки, кількості, періоди), а також publint + arethetypeswrong на запакованому пакеті.

Чому. Від довідкового пакета варто залежати лише тоді, коли він надійніший, ніж копіювати дані вручну.

11. Час — частина моделі ​

Рішення. Записи мають період чинності (validFrom, validTo, обидва дні включно), кожен набір даних указує, з якого дня він описує ситуацію (territoryCoverage і temporalCoverage в datapackage.json), а функції пошуку приймають необов'язковий день: getCanton(id, { on }), getCantonsForMunicipality(refnis, { on }). День до початку охоплення викидає RangeError.

Чому. Правові питання стосуються певного дня: який суд був компетентним, коли вручали повістку, яка ставка діяла, коли настав строк оплати рахунку. Кантони, громади й суди змінюються; модель, яка знає лише «зараз», відповідає на питання про минуле сьогоднішньою картою і при цьому виглядає правильною. Додати час пізніше означало б змінити кожен id і кожен пошук, тому він є в моделі ще до того, як історія з'явиться в даних (#27).

Отже. Процентні ставки вже мають періоди. Пошук за id без дня дає сьогоднішню версію, тож відомі id зберігають свій тип без undefined; з днем пошук може нічого не знайти. Період мають усі набори даних, а не лише територія: зібраний запис, який джерело перестає наводити, виводиться з ужитку з validTo, а не видаляється (scripts/parse/retire.ts), тож id, щойно опублікований, залишається, а суд, що закривається, і далі є тим судом, який назвала торішня повістка.

12. Релізи містять дані, а не лише пакет ​

Рішення. Кожен реліз на GitHub містить дані у форматі JSON і у вигляді CSV-таблиць, разом зі схемами й datapackage.json, одним zip-архівом. npm — один зі способів отримати дані, але не єдиний.

Чому. Люди, яким бельгійські правові довідкові дані потрібні найбільше, працюють в електронних таблицях та інших мовах, а не в JavaScript.

13. Геометрія — друге джерело, і текст має перевагу ​

Рішення. Межі кантонів — це оцифрування додатка до Gerechtelijk Wetboek / Code judiciaire (Судового кодексу), виконане Національним географічним інститутом (NGI), взяте як опубліковано (вершина за вершиною, без спрощення, координати з точністю до сантиметра) і зіставлене з кантонами за назвою обома мовами, з явною таблицею псевдонімів для решти. Текст territory кантону залишається законом: де геометрія і текст розходяться, перевагу має текст. Геометрія має власний entry point, і корінь пакета її не реекспортує.

Чому. Полігони NGI — це офіційне прочитання того самого тексту, тож на них можна посилатися і їх можна відтворити; межа, виведена тут із даних про вулиці, була б третім прочитанням, яке ніхто не може перевірити. Спрощення зсунуло б лінію, а лінії найважливіші саме там, де кантони сходяться. Мільйон позицій затьмарив би всі інші набори даних, тож ніхто не повинен завантажити їх випадково.

Отже. findCantonsAt(lon, lat) відповідає на основі геометрії, і документація про це каже; точка поблизу лінії — випадок для тексту. Щотижнева збірка падає, коли кантон не має межі або об'єкт не відповідає жодному кантону (#29).

Ще не вирішено ​

ПитанняIssue
Історія до 1 січня 2025 року#27
Нарахування процентів за період (підрахунок днів, капіталізація)Не заплановано, доки метод не можна буде обґрунтувати судовою практикою чи офіційними настановами

Код за ліцензією MIT, дані за CC BY 4.0. Це довідкові дані, а не юридична консультація: перевіряйте офіційні джерела.