belgica 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. belgica-0.5.0/.gitignore +22 -0
  2. belgica-0.5.0/LICENSE +24 -0
  3. belgica-0.5.0/LICENSE-DATA +28 -0
  4. belgica-0.5.0/PKG-INFO +87 -0
  5. belgica-0.5.0/README.md +63 -0
  6. belgica-0.5.0/hatch_build.py +30 -0
  7. belgica-0.5.0/pyproject.toml +84 -0
  8. belgica-0.5.0/src/belgica/__init__.py +145 -0
  9. belgica-0.5.0/src/belgica/_arrondissement_boundaries.py +69 -0
  10. belgica-0.5.0/src/belgica/_canton_boundaries.py +66 -0
  11. belgica-0.5.0/src/belgica/_competence.py +80 -0
  12. belgica-0.5.0/src/belgica/_contacts.py +45 -0
  13. belgica-0.5.0/src/belgica/_court_locations.py +33 -0
  14. belgica-0.5.0/src/belgica/_courts.py +166 -0
  15. belgica-0.5.0/src/belgica/_data.py +13 -0
  16. belgica-0.5.0/src/belgica/_geometry.py +73 -0
  17. belgica-0.5.0/src/belgica/_interest_rates.py +51 -0
  18. belgica-0.5.0/src/belgica/_places.py +51 -0
  19. belgica-0.5.0/src/belgica/_postal_codes.py +72 -0
  20. belgica-0.5.0/src/belgica/_territory.py +132 -0
  21. belgica-0.5.0/src/belgica/_time.py +100 -0
  22. belgica-0.5.0/src/belgica/data/arrondissement-boundaries.json +316 -0
  23. belgica-0.5.0/src/belgica/data/arrondissements.json +110 -0
  24. belgica-0.5.0/src/belgica/data/canton-boundaries.json +3859 -0
  25. belgica-0.5.0/src/belgica/data/cantons.json +5511 -0
  26. belgica-0.5.0/src/belgica/data/court-locations.json +2643 -0
  27. belgica-0.5.0/src/belgica/data/courts.json +21134 -0
  28. belgica-0.5.0/src/belgica/data/houses-of-justice.json +1043 -0
  29. belgica-0.5.0/src/belgica/data/interest-rates.json +449 -0
  30. belgica-0.5.0/src/belgica/data/judicial-areas.json +42 -0
  31. belgica-0.5.0/src/belgica/data/municipalities.json +5087 -0
  32. belgica-0.5.0/src/belgica/data/postal-codes.json +5762 -0
  33. belgica-0.5.0/src/belgica/data/prisons.json +1822 -0
  34. belgica-0.5.0/src/belgica/data/provinces.json +152 -0
  35. belgica-0.5.0/src/belgica/data/regions.json +44 -0
  36. belgica-0.5.0/src/belgica/py.typed +0 -0
  37. belgica-0.5.0/src/belgica/types.py +306 -0
  38. belgica-0.5.0/tests/conftest.py +10 -0
  39. belgica-0.5.0/tests/test_api.py +104 -0
  40. belgica-0.5.0/tests/test_parity.py +149 -0
  41. belgica-0.5.0/tests/test_types.py +82 -0
  42. belgica-0.5.0/uv.lock +417 -0
@@ -0,0 +1,22 @@
1
+ node_modules/
2
+ .DS_Store
3
+ docs/.vitepress/dist/
4
+ docs/.vitepress/cache/
5
+ dist/
6
+ coverage/
7
+ .sources/
8
+ csv/
9
+ # Generated from data/courts.json and data/cantons.json by pnpm build:data-files; published, not committed
10
+ data/courts/
11
+ data/cantons/
12
+ # Python package: the data and licences are copied in at build time
13
+ python/src/belgica/data/
14
+ python/LICENSE
15
+ python/LICENSE-DATA
16
+ python/tests/.cases/
17
+ python/.venv/
18
+ python/dist/
19
+ __pycache__/
20
+ .pytest_cache/
21
+ .mypy_cache/
22
+ .ruff_cache/
belgica-0.5.0/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pieter-Jan Scheir
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ This licence covers the code (src/, scripts/, test/ and the built files in
24
+ dist/). The data in data/ is licensed under CC BY 4.0; see LICENSE-DATA.
@@ -0,0 +1,28 @@
1
+ The data in data/ is licensed under the Creative Commons Attribution 4.0
2
+ International licence (CC BY 4.0):
3
+ https://creativecommons.org/licenses/by/4.0/legalcode
4
+
5
+ Copyright (c) 2026 Pieter-Jan Scheir
6
+
7
+ You may share and adapt the data for any purpose, including commercially, as
8
+ long as you give appropriate credit, link to the licence, and indicate if
9
+ changes were made. Credit "belgica (https://github.com/pieterjanscheir/belgica)"
10
+ and pass on the source attributions below.
11
+
12
+ Sources:
13
+
14
+ - Municipalities (data/municipalities.json) and the REFNIS codes in
15
+ data/cantons.json: © Statbel (Directorate-general Statistics - Statistics
16
+ Belgium), CC BY 4.0, https://statbel.fgov.be/
17
+ - Postal codes (data/postal-codes.json) and the municipality of each address
18
+ (address.municipality in data/courts.json, data/prisons.json and
19
+ data/houses-of-justice.json): Statbel's conversion table from postal codes
20
+ to REFNIS codes, © Statbel, CC BY 4.0, https://statbel.fgov.be/en/cc-40
21
+ - Cantons, arrondissements and judicial areas: the annex to the Belgian
22
+ Judicial Code, as consolidated on Justel (https://www.ejustice.just.fgov.be/).
23
+ Official acts are not protected by copyright in Belgium (art. XI.172 §2 WER).
24
+ - Interest rates: FOD Financiën and the Belgisch Staatsblad.
25
+ - Court addresses and contacts: the courts' own websites and just.fgov.be.
26
+
27
+ The data is provided as is, without warranty. It is reference data, not legal
28
+ advice: check the official sources before relying on it.
belgica-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.5
2
+ Name: belgica
3
+ Version: 0.5.0
4
+ Summary: Belgian legal reference data: courts, judicial cantons and arrondissements, municipalities and postal codes, and legal and commercial interest rates.
5
+ Project-URL: Homepage, https://pieterjanscheir.github.io/belgica/
6
+ Project-URL: Documentation, https://pieterjanscheir.github.io/belgica/guide/python
7
+ Project-URL: Source, https://github.com/pieterjanscheir/belgica
8
+ Project-URL: Issues, https://github.com/pieterjanscheir/belgica/issues
9
+ Project-URL: Changelog, https://github.com/pieterjanscheir/belgica/blob/main/CHANGELOG.md
10
+ Author: Pieter-Jan Scheir
11
+ License-Expression: MIT AND CC-BY-4.0
12
+ Keywords: arrondissements,belgian,belgium,courts,geojson,interest-rates,judicial-cantons,justice-of-the-peace,legal,municipalities,postal-codes,refnis,vredegerecht
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Legal Industry
16
+ Classifier: Natural Language :: Dutch
17
+ Classifier: Natural Language :: French
18
+ Classifier: Natural Language :: German
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Topic :: Scientific/Engineering :: GIS
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+
25
+ # belgica
26
+
27
+ Belgian legal reference data for Python: every court with its registries' contacts, prisons,
28
+ houses of justice, judicial cantons and arrondissements, municipalities and their postal codes,
29
+ and the legal and commercial interest rates. Every record says where it comes from, and the
30
+ scraped data is rebuilt from the official sources every week.
31
+
32
+ The same data and helpers as the [`belgica` npm package](https://www.npmjs.com/package/belgica),
33
+ with the same version number. The tests check that both give the same answers.
34
+
35
+ ```bash
36
+ pip install belgica
37
+ ```
38
+
39
+ Python 3.11 or later, no dependencies. Documentation:
40
+ **https://pieterjanscheir.github.io/belgica/guide/python**
41
+
42
+ It's reference data, not legal advice: check the official sources before relying on it.
43
+
44
+ ```python
45
+ from belgica import (
46
+ find_arrondissements_at,
47
+ find_cantons_at,
48
+ find_competent_courts,
49
+ find_courts,
50
+ find_courts_for_postal_code,
51
+ get_cantons_for_municipality,
52
+ get_contact,
53
+ get_court,
54
+ get_court_location,
55
+ get_interest_rate,
56
+ get_justice_of_the_peace,
57
+ )
58
+
59
+ get_court('court-of-appeal-brussel')['name'] # {'nl': 'Hof van Beroep Brussel', 'fr': "Cour d'appel de Bruxelles"}
60
+ get_contact(get_court('court-of-appeal-liege'), 'email', 'Greffe pénal') # 'liege.ca.greffepenal@just.fgov.be'
61
+ find_courts(type='enterprise-court', arrondissement='west-vlaanderen')
62
+
63
+ [canton] = get_cantons_for_municipality('44088') # merelbeke-melle
64
+ get_justice_of_the_peace(canton['id'])['address'] # where to file
65
+ get_cantons_for_municipality('44021', on='2025-09-15') # Gent 1–5 on that day: a split city
66
+ find_cantons_at(3.7268, 51.053) # [gent-1]: the canton of a point
67
+ find_arrondissements_at(3.7268, 51.053) # [oost-vlaanderen]: and its arrondissement
68
+ get_court_location('justice-of-the-peace-gent-1')['geometry'] # the court on the map, a GeoJSON Point
69
+ find_competent_courts('23088', 'police-court') # [police-court-vilvoorde]: which court, by REFNIS code
70
+ find_courts_for_postal_code('1040', 'police-court') # per municipality: Brussel and Etterbeek share the code
71
+
72
+ get_interest_rate('2024-06-15', 'legal') # InterestRateResult(rate=5.75, period_from='2024-01-01', period_to='2024-12-31')
73
+ ```
74
+
75
+ The records are the dicts from the JSON files, with their keys as in the JSON (`validFrom`,
76
+ `postalCode`), typed with the `TypedDict`s in `belgica.types`. Each data file is read on first
77
+ use; the canton boundaries (22 MB) only by `find_cantons_at` and `get_canton_boundary`, the
78
+ arrondissement boundaries only by `find_arrondissements_at` and `get_arrondissement_boundary`.
79
+
80
+ Lookups take `on`, a day as a `datetime.date` or `'YYYY-MM-DD'`, and answer for today without it.
81
+ A day before a dataset's coverage raises a `ValueError` rather than answer with today's data.
82
+
83
+ ## Licence
84
+
85
+ Code MIT. Data CC BY 4.0: municipality, province, region and postal-code data © Statbel, canton and
86
+ arrondissement boundaries and court locations © National Geographic Institute (NGI). See the
87
+ [repository](https://github.com/pieterjanscheir/belgica) for the sources of every dataset.
@@ -0,0 +1,63 @@
1
+ # belgica
2
+
3
+ Belgian legal reference data for Python: every court with its registries' contacts, prisons,
4
+ houses of justice, judicial cantons and arrondissements, municipalities and their postal codes,
5
+ and the legal and commercial interest rates. Every record says where it comes from, and the
6
+ scraped data is rebuilt from the official sources every week.
7
+
8
+ The same data and helpers as the [`belgica` npm package](https://www.npmjs.com/package/belgica),
9
+ with the same version number. The tests check that both give the same answers.
10
+
11
+ ```bash
12
+ pip install belgica
13
+ ```
14
+
15
+ Python 3.11 or later, no dependencies. Documentation:
16
+ **https://pieterjanscheir.github.io/belgica/guide/python**
17
+
18
+ It's reference data, not legal advice: check the official sources before relying on it.
19
+
20
+ ```python
21
+ from belgica import (
22
+ find_arrondissements_at,
23
+ find_cantons_at,
24
+ find_competent_courts,
25
+ find_courts,
26
+ find_courts_for_postal_code,
27
+ get_cantons_for_municipality,
28
+ get_contact,
29
+ get_court,
30
+ get_court_location,
31
+ get_interest_rate,
32
+ get_justice_of_the_peace,
33
+ )
34
+
35
+ get_court('court-of-appeal-brussel')['name'] # {'nl': 'Hof van Beroep Brussel', 'fr': "Cour d'appel de Bruxelles"}
36
+ get_contact(get_court('court-of-appeal-liege'), 'email', 'Greffe pénal') # 'liege.ca.greffepenal@just.fgov.be'
37
+ find_courts(type='enterprise-court', arrondissement='west-vlaanderen')
38
+
39
+ [canton] = get_cantons_for_municipality('44088') # merelbeke-melle
40
+ get_justice_of_the_peace(canton['id'])['address'] # where to file
41
+ get_cantons_for_municipality('44021', on='2025-09-15') # Gent 1–5 on that day: a split city
42
+ find_cantons_at(3.7268, 51.053) # [gent-1]: the canton of a point
43
+ find_arrondissements_at(3.7268, 51.053) # [oost-vlaanderen]: and its arrondissement
44
+ get_court_location('justice-of-the-peace-gent-1')['geometry'] # the court on the map, a GeoJSON Point
45
+ find_competent_courts('23088', 'police-court') # [police-court-vilvoorde]: which court, by REFNIS code
46
+ find_courts_for_postal_code('1040', 'police-court') # per municipality: Brussel and Etterbeek share the code
47
+
48
+ get_interest_rate('2024-06-15', 'legal') # InterestRateResult(rate=5.75, period_from='2024-01-01', period_to='2024-12-31')
49
+ ```
50
+
51
+ The records are the dicts from the JSON files, with their keys as in the JSON (`validFrom`,
52
+ `postalCode`), typed with the `TypedDict`s in `belgica.types`. Each data file is read on first
53
+ use; the canton boundaries (22 MB) only by `find_cantons_at` and `get_canton_boundary`, the
54
+ arrondissement boundaries only by `find_arrondissements_at` and `get_arrondissement_boundary`.
55
+
56
+ Lookups take `on`, a day as a `datetime.date` or `'YYYY-MM-DD'`, and answer for today without it.
57
+ A day before a dataset's coverage raises a `ValueError` rather than answer with today's data.
58
+
59
+ ## Licence
60
+
61
+ Code MIT. Data CC BY 4.0: municipality, province, region and postal-code data © Statbel, canton and
62
+ arrondissement boundaries and court locations © National Geographic Institute (NGI). See the
63
+ [repository](https://github.com/pieterjanscheir/belgica) for the sources of every dataset.
@@ -0,0 +1,30 @@
1
+ """Copies the data and the licences from the repository into the package before a build.
2
+
3
+ Building from the repository (python/ in a checkout) takes the files from ../; building a wheel
4
+ from the sdist finds them already in it.
5
+ """
6
+
7
+ import shutil
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
12
+
13
+
14
+ def copy_from_repository(root: Path) -> None:
15
+ repository = root.parent
16
+ if not (repository / 'package.json').exists():
17
+ return
18
+ target = root / 'src' / 'belgica' / 'data'
19
+ target.mkdir(exist_ok=True)
20
+ for stale in target.glob('*.json'):
21
+ stale.unlink()
22
+ for file in (repository / 'data').glob('*.json'):
23
+ shutil.copyfile(file, target / file.name)
24
+ for name in ('LICENSE', 'LICENSE-DATA'):
25
+ shutil.copyfile(repository / name, root / name)
26
+
27
+
28
+ class CustomBuildHook(BuildHookInterface): # type: ignore[type-arg]
29
+ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
30
+ copy_from_repository(Path(self.root))
@@ -0,0 +1,84 @@
1
+ [project]
2
+ name = "belgica"
3
+ # The same version as the npm package: test_package.py checks it against ../package.json
4
+ version = "0.5.0"
5
+ description = "Belgian legal reference data: courts, judicial cantons and arrondissements, municipalities and postal codes, and legal and commercial interest rates."
6
+ readme = "README.md"
7
+ license = "MIT AND CC-BY-4.0"
8
+ license-files = ["LICENSE", "LICENSE-DATA"]
9
+ authors = [{ name = "Pieter-Jan Scheir" }]
10
+ requires-python = ">=3.11"
11
+ dependencies = []
12
+ keywords = [
13
+ "belgium",
14
+ "belgian",
15
+ "legal",
16
+ "courts",
17
+ "judicial-cantons",
18
+ "arrondissements",
19
+ "municipalities",
20
+ "refnis",
21
+ "postal-codes",
22
+ "geojson",
23
+ "interest-rates",
24
+ "vredegerecht",
25
+ "justice-of-the-peace",
26
+ ]
27
+ classifiers = [
28
+ "Development Status :: 4 - Beta",
29
+ "Intended Audience :: Developers",
30
+ "Intended Audience :: Legal Industry",
31
+ "Natural Language :: Dutch",
32
+ "Natural Language :: French",
33
+ "Natural Language :: German",
34
+ "Programming Language :: Python :: 3",
35
+ "Topic :: Scientific/Engineering :: GIS",
36
+ "Typing :: Typed",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://pieterjanscheir.github.io/belgica/"
41
+ Documentation = "https://pieterjanscheir.github.io/belgica/guide/python"
42
+ Source = "https://github.com/pieterjanscheir/belgica"
43
+ Issues = "https://github.com/pieterjanscheir/belgica/issues"
44
+ Changelog = "https://github.com/pieterjanscheir/belgica/blob/main/CHANGELOG.md"
45
+
46
+ [dependency-groups]
47
+ # hatchling: the tests copy the data in with hatch_build.py
48
+ dev = ["hatchling>=1.27", "pytest>=8", "ruff>=0.14", "mypy>=1.18"]
49
+
50
+ [build-system]
51
+ requires = ["hatchling>=1.27"]
52
+ build-backend = "hatchling.build"
53
+
54
+ [tool.hatch.build.hooks.custom]
55
+ # hatch_build.py copies ../data, ../LICENSE and ../LICENSE-DATA in
56
+
57
+ [tool.hatch.build.targets.sdist]
58
+ # Copied in by the build hook, so not in git
59
+ artifacts = ["src/belgica/data/*.json", "LICENSE", "LICENSE-DATA"]
60
+ # Made by pnpm build:cases for the day they were made
61
+ exclude = ["tests/.cases"]
62
+
63
+ [tool.hatch.build.targets.wheel]
64
+ packages = ["src/belgica"]
65
+ artifacts = ["src/belgica/data/*.json"]
66
+
67
+ [tool.pytest.ini_options]
68
+ testpaths = ["tests"]
69
+
70
+ [tool.ruff]
71
+ line-length = 100
72
+ target-version = "py311"
73
+ # The examples there are laid out for reading, one call a line
74
+ extend-exclude = ["README.md"]
75
+
76
+ [tool.ruff.lint]
77
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
78
+
79
+ [tool.ruff.format]
80
+ quote-style = "single"
81
+
82
+ [tool.mypy]
83
+ strict = true
84
+ files = ["src", "tests"]
@@ -0,0 +1,145 @@
1
+ """Belgian legal reference data: courts, judicial cantons and arrondissements, municipalities and
2
+ postal codes, prisons, houses of justice, and legal and commercial interest rates.
3
+
4
+ The records are the dicts from the JSON files, typed in `belgica.types`. Each data file is read
5
+ on first use. Lookups take `on`, a day as a `date` or 'YYYY-MM-DD', and give today's answer
6
+ without it. Documentation: https://pieterjanscheir.github.io/belgica/guide/python
7
+ """
8
+
9
+ from ._arrondissement_boundaries import (
10
+ ARRONDISSEMENT_BOUNDARIES_COVERAGE_FROM,
11
+ arrondissement_boundaries,
12
+ find_arrondissements_at,
13
+ get_arrondissement_boundary,
14
+ )
15
+ from ._canton_boundaries import (
16
+ CANTON_BOUNDARIES_COVERAGE_FROM,
17
+ canton_boundaries,
18
+ find_cantons_at,
19
+ get_canton_boundary,
20
+ )
21
+ from ._competence import (
22
+ CantonCourt,
23
+ PostalCodeCourts,
24
+ find_competent_courts,
25
+ find_courts_for_postal_code,
26
+ find_justices_of_the_peace,
27
+ )
28
+ from ._contacts import format_iban, format_phone_number, get_contact
29
+ from ._court_locations import COURT_LOCATIONS_COVERAGE_FROM, court_locations, get_court_location
30
+ from ._courts import (
31
+ COURTS_COVERAGE_FROM,
32
+ courts,
33
+ find_courts,
34
+ format_court_name,
35
+ get_court,
36
+ get_courts_for_canton,
37
+ get_divisions,
38
+ get_justice_of_the_peace,
39
+ )
40
+ from ._interest_rates import (
41
+ InterestRateResult,
42
+ get_interest_rate,
43
+ get_latest_known_rate_date,
44
+ interest_rates,
45
+ )
46
+ from ._places import (
47
+ HOUSES_OF_JUSTICE_COVERAGE_FROM,
48
+ PRISONS_COVERAGE_FROM,
49
+ get_house_of_justice,
50
+ get_prison,
51
+ houses_of_justice,
52
+ prisons,
53
+ )
54
+ from ._postal_codes import (
55
+ POSTAL_CODES_COVERAGE_FROM,
56
+ get_municipalities_for_postal_code,
57
+ get_postal_code,
58
+ get_postal_codes,
59
+ postal_codes,
60
+ )
61
+ from ._territory import (
62
+ TERRITORY_COVERAGE_FROM,
63
+ arrondissements,
64
+ cantons,
65
+ get_arrondissement,
66
+ get_arrondissement_for_municipality,
67
+ get_canton,
68
+ get_cantons_for_municipality,
69
+ get_judicial_area,
70
+ get_municipality,
71
+ get_province,
72
+ get_province_for_municipality,
73
+ get_region,
74
+ get_region_for_municipality,
75
+ judicial_areas,
76
+ municipalities,
77
+ provinces,
78
+ regions,
79
+ )
80
+ from ._time import Day, applies_on, to_day
81
+
82
+ __all__ = [
83
+ 'ARRONDISSEMENT_BOUNDARIES_COVERAGE_FROM',
84
+ 'CANTON_BOUNDARIES_COVERAGE_FROM',
85
+ 'COURTS_COVERAGE_FROM',
86
+ 'COURT_LOCATIONS_COVERAGE_FROM',
87
+ 'HOUSES_OF_JUSTICE_COVERAGE_FROM',
88
+ 'POSTAL_CODES_COVERAGE_FROM',
89
+ 'PRISONS_COVERAGE_FROM',
90
+ 'TERRITORY_COVERAGE_FROM',
91
+ 'CantonCourt',
92
+ 'Day',
93
+ 'InterestRateResult',
94
+ 'PostalCodeCourts',
95
+ 'applies_on',
96
+ 'arrondissement_boundaries',
97
+ 'arrondissements',
98
+ 'canton_boundaries',
99
+ 'cantons',
100
+ 'court_locations',
101
+ 'courts',
102
+ 'find_arrondissements_at',
103
+ 'find_cantons_at',
104
+ 'find_competent_courts',
105
+ 'find_courts',
106
+ 'find_courts_for_postal_code',
107
+ 'find_justices_of_the_peace',
108
+ 'format_court_name',
109
+ 'format_iban',
110
+ 'format_phone_number',
111
+ 'get_arrondissement',
112
+ 'get_arrondissement_boundary',
113
+ 'get_arrondissement_for_municipality',
114
+ 'get_canton',
115
+ 'get_canton_boundary',
116
+ 'get_cantons_for_municipality',
117
+ 'get_contact',
118
+ 'get_court',
119
+ 'get_court_location',
120
+ 'get_courts_for_canton',
121
+ 'get_divisions',
122
+ 'get_house_of_justice',
123
+ 'get_interest_rate',
124
+ 'get_judicial_area',
125
+ 'get_justice_of_the_peace',
126
+ 'get_latest_known_rate_date',
127
+ 'get_municipalities_for_postal_code',
128
+ 'get_municipality',
129
+ 'get_postal_code',
130
+ 'get_postal_codes',
131
+ 'get_prison',
132
+ 'get_province',
133
+ 'get_province_for_municipality',
134
+ 'get_region',
135
+ 'get_region_for_municipality',
136
+ 'houses_of_justice',
137
+ 'interest_rates',
138
+ 'judicial_areas',
139
+ 'municipalities',
140
+ 'postal_codes',
141
+ 'prisons',
142
+ 'provinces',
143
+ 'regions',
144
+ 'to_day',
145
+ ]
@@ -0,0 +1,69 @@
1
+ """The boundaries of the judicial arrondissements, and the arrondissement of a point"""
2
+
3
+ from functools import cache
4
+
5
+ from ._data import load
6
+ from ._geometry import BoundingBox, bounding_box, contains, in_box, require_coordinates
7
+ from ._territory import get_arrondissement
8
+ from ._time import Day, applying_on, pick_version, versions_by_id
9
+ from .types import Arrondissement, ArrondissementBoundary
10
+
11
+ ARRONDISSEMENT_BOUNDARIES_COVERAGE_FROM = '2026-10-21'
12
+ """The boundaries describe the situation from the day the NGI's file was first read for them. A
13
+ boundary the NGI changes replaces the previous one; an arrondissement the NGI drops keeps its
14
+ boundary with a `validTo`. Lookups for an earlier day raise a `ValueError`."""
15
+
16
+ _DATASET = 'arrondissement boundaries'
17
+
18
+
19
+ @cache
20
+ def arrondissement_boundaries() -> tuple[ArrondissementBoundary, ...]:
21
+ """Every boundary, every version, read on first use"""
22
+ return tuple(load('arrondissement-boundaries'))
23
+
24
+
25
+ @cache
26
+ def _by_id() -> dict[str, list[ArrondissementBoundary]]:
27
+ return versions_by_id(arrondissement_boundaries())
28
+
29
+
30
+ def get_arrondissement_boundary(id: str, on: Day | None = None) -> ArrondissementBoundary | None:
31
+ """The boundary of an arrondissement, by arrondissement id: today's version, or the one of a
32
+ given day"""
33
+ return pick_version(_by_id().get(id), on, ARRONDISSEMENT_BOUNDARIES_COVERAGE_FROM, _DATASET)
34
+
35
+
36
+ @cache
37
+ def _index() -> list[tuple[ArrondissementBoundary, BoundingBox]]:
38
+ return [
39
+ (boundary, bounding_box(boundary['geometry'])) for boundary in arrondissement_boundaries()
40
+ ]
41
+
42
+
43
+ def find_arrondissements_at(
44
+ longitude: float, latitude: float, on: Day | None = None
45
+ ) -> list[Arrondissement]:
46
+ """The arrondissements whose boundary holds a point, on a day (today by default). Longitude
47
+ first, then latitude, in decimal degrees (WGS 84), as GeoJSON orders them. One arrondissement
48
+ for a point in Belgium; none outside it; two for a point on the line between two. Raises
49
+ `ValueError` for coordinates that aren't finite numbers, or a day before the coverage."""
50
+ require_coordinates(longitude, latitude)
51
+ applying = {
52
+ id(boundary)
53
+ for boundary in applying_on(
54
+ arrondissement_boundaries(), on, ARRONDISSEMENT_BOUNDARIES_COVERAGE_FROM, _DATASET
55
+ )
56
+ }
57
+ found = []
58
+ for boundary, box in _index():
59
+ if (
60
+ id(boundary) in applying
61
+ and in_box(box, longitude, latitude)
62
+ and contains(boundary['geometry'], longitude, latitude)
63
+ ):
64
+ # Every boundary belongs to an arrondissement that applies on the same days
65
+ # (test/arrondissement-boundaries.test.ts)
66
+ arrondissement = get_arrondissement(boundary['arrondissement'], on)
67
+ if arrondissement:
68
+ found.append(arrondissement)
69
+ return found
@@ -0,0 +1,66 @@
1
+ """The boundaries of the judicial cantons, and the canton of a point"""
2
+
3
+ from functools import cache
4
+
5
+ from ._data import load
6
+ from ._geometry import BoundingBox, bounding_box, contains, in_box, require_coordinates
7
+ from ._territory import get_canton
8
+ from ._time import Day, applying_on, pick_version, versions_by_id
9
+ from .types import Canton, CantonBoundary
10
+
11
+ CANTON_BOUNDARIES_COVERAGE_FROM = '2026-10-21'
12
+ """The boundaries describe the situation from the day the NGI's file was first read. A boundary
13
+ the NGI changes replaces the previous one; a canton the NGI drops keeps its boundary with a
14
+ `validTo`. Lookups for an earlier day raise a `ValueError`."""
15
+
16
+ _DATASET = 'canton boundaries'
17
+
18
+
19
+ @cache
20
+ def canton_boundaries() -> tuple[CantonBoundary, ...]:
21
+ """Every boundary, every version. About 22 MB of JSON, read on first use."""
22
+ return tuple(load('canton-boundaries'))
23
+
24
+
25
+ @cache
26
+ def _by_id() -> dict[str, list[CantonBoundary]]:
27
+ return versions_by_id(canton_boundaries())
28
+
29
+
30
+ def get_canton_boundary(id: str, on: Day | None = None) -> CantonBoundary | None:
31
+ """The boundary of a canton, by canton id: today's version, or the one of a given day"""
32
+ return pick_version(_by_id().get(id), on, CANTON_BOUNDARIES_COVERAGE_FROM, _DATASET)
33
+
34
+
35
+ @cache
36
+ def _index() -> list[tuple[CantonBoundary, BoundingBox]]:
37
+ return [(boundary, bounding_box(boundary['geometry'])) for boundary in canton_boundaries()]
38
+
39
+
40
+ def find_cantons_at(longitude: float, latitude: float, on: Day | None = None) -> list[Canton]:
41
+ """The cantons whose boundary holds a point, on a day (today by default). Longitude first,
42
+ then latitude, in decimal degrees (WGS 84), as GeoJSON orders them. One canton for a point in
43
+ Belgium; none outside it; two for a point on the line between two cantons. The boundary is the
44
+ NGI's digitisation of the annex to the Judicial Code: where a point lies close to a boundary,
45
+ the canton's `territory` text decides. Raises `ValueError` for coordinates that aren't finite
46
+ numbers, or a day before the coverage."""
47
+ require_coordinates(longitude, latitude)
48
+ applying = {
49
+ id(boundary)
50
+ for boundary in applying_on(
51
+ canton_boundaries(), on, CANTON_BOUNDARIES_COVERAGE_FROM, _DATASET
52
+ )
53
+ }
54
+ found = []
55
+ for boundary, box in _index():
56
+ if (
57
+ id(boundary) in applying
58
+ and in_box(box, longitude, latitude)
59
+ and contains(boundary['geometry'], longitude, latitude)
60
+ ):
61
+ # Every boundary belongs to a canton that applies on the same days
62
+ # (tests/test_data.py)
63
+ canton = get_canton(boundary['canton'], on)
64
+ if canton:
65
+ found.append(canton)
66
+ return found