diff --git a/TODO b/TODO
new file mode 100644
index 0000000000000000000000000000000000000000..57aa6fa87ca044d40c13d31df85426df8d09be54
--- /dev/null
+++ b/TODO
@@ -0,0 +1,15 @@
+### Dořešit v importu škol ###
+
+WARNING: Obec Černovice je podle rejstříku v okrese CZ0632, podle RUIAN je na výběr ['CZ0641', 'CZ0321', 'CZ0422', 'CZ0633'] => dořešit ručně!
+WARNING: Obec Zlín je podle rejstříku v okrese CZ0721, ale pod RUIAN v CZ0724 => preferuji RUIAN
+WARNING: Obec Ostrava je podle rejstříku v okrese CZ0108, ale pod RUIAN v CZ0806 => preferuji RUIAN
+WARNING: Obec Brno je podle rejstříku v okrese CZ0643, ale pod RUIAN v CZ0642 => preferuji RUIAN
+WARNING: Obec Brno je podle rejstříku v okrese CZ0643, ale pod RUIAN v CZ0642 => preferuji RUIAN
+WARNING: Obec Řepice je podle rejstříku v okrese CZ0313, ale pod RUIAN v CZ0316 => preferuji RUIAN
+WARNING: Obec Chrudim je podle rejstříku v okrese CZ0532, ale pod RUIAN v CZ0531 => preferuji RUIAN
+WARNING: Obec Chlumec je podle rejstříku v okrese CZ0426, podle RUIAN je na výběr ['CZ0427', 'CZ0312'] => dořešit ručně!
+WARNING: Obec Hranice je podle rejstříku v okrese CZ0712, podle RUIAN je na výběr ['CZ0311', 'CZ0411', 'CZ0714'] => dořešit ručně!
+WARNING: Obec Úvaly je podle rejstříku v okrese CZ0203, ale pod RUIAN v CZ0209 => preferuji RUIAN
+WARNING: Obec Dobřichovice je podle rejstříku v okrese CZ0202, ale pod RUIAN v CZ020A => preferuji RUIAN
+WARNING: Obec Pchery je podle rejstříku v okrese CZ0204, ale pod RUIAN v CZ0203 => preferuji RUIAN
+WARNING: Obec Poděbrady je podle rejstříku v okrese CZ0204, ale pod RUIAN v CZ0208 => preferuji RUIAN
diff --git a/bin/dump-regions b/bin/dump-regions
new file mode 100755
index 0000000000000000000000000000000000000000..51ffa604d6680d6bcd81aad472e7068f836c00ea
--- /dev/null
+++ b/bin/dump-regions
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# Show all regions in the DB
+
+import mo.db as db
+from collections import defaultdict
+
+session = db.get_session()
+
+root = None
+children = defaultdict(list)
+
+
+def walk(place, expected_level=0):
+ indent = '\t' * place.level
+ print(f"{indent}{place.name} [{place.type.name}] #{place.place_id} NUTS={place.nuts}")
+ assert place.level == expected_level
+ for c in sorted(children[place.place_id], key=lambda p: p.name):
+ walk(c, expected_level + 1)
+
+
+for place in session.query(db.Place).all():
+ if place.parent:
+ children[place.parent].append(place)
+ else:
+ assert not root
+ root = place
+
+walk(root)
diff --git a/bin/init-schools b/bin/init-schools
index 9e94fadbb2785e675cf83e6c7810835ff08c8975..343fa843f1470aed41ff97de961084683e27d356 100755
--- a/bin/init-schools
+++ b/bin/init-schools
@@ -1,12 +1,19 @@
#!/usr/bin/env python3
-# Initialize schools from parsed school register
-# Uses db/skoly/parsed/*.tsv
-
-from typing import List, Dict
+# Naplní databázi školami a obcemi, v nichž školy sídlí
+# Používá db/skoly/parsed/*.tsv
+#
+# Pozor, zrada: rejstřík škol je sice rozdělený do okresů dle NUTS/LAU,
+# ale školy tam řadí podle úřadu, u nějž je škole registrovaná, což vůbec
+# nemusí odpovídat skutečnému sídlu školy. Proto si poněkud magicky pomáháme
+# číselníkem obcí z RUIANu.
+
+from typing import List, Dict, DefaultDict
import sys
from pathlib import Path
import mo.db as db
import re
+import csv
+from collections import defaultdict
session = db.get_session()
new_town_cnt = 0
@@ -14,9 +21,11 @@ new_school_cnt = 0
def import_schools(path: Path, nuts: str):
- # XXX: The school register uses several invalid NUTS codes :( Fix them!
+ # XXX: Rejstřík škol používá několik chybných/obsoletních NUTS kódů :(
nuts = re.sub('^CZ011', 'CZ010', nuts)
nuts = re.sub('^CZ021', 'CZ020', nuts)
+ nuts = re.sub('^CZ061', 'CZ063', nuts)
+ nuts = re.sub('^CZ062', 'CZ064', nuts)
nuts = re.sub('^CZ081', 'CZ080', nuts)
with path.open('r') as file:
@@ -43,8 +52,8 @@ def import_schools(path: Path, nuts: str):
addr = make_address(misto, ulice, cp, co)
addr2 = make_address(misto2, ulice2, cp2, co2)
- if addr != addr2:
- print(f"WARNING: Address mismatch, check regions: <{addr}> != <{addr2}>", file=sys.stderr)
+ # if addr != addr2:
+ # print(f"WARNING: Škola má dvě různé adresy: <{addr}> != <{addr2}>", file=sys.stderr)
town = lookup_town(misto2, nuts)
print(town)
@@ -82,10 +91,32 @@ def make_address(misto: str, ulice: str, cp: str, co: str) -> str:
def lookup_town(misto: str, region_nuts: str) -> db.Place:
- town = session.query(db.Place).filter_by(level=3, name=misto).first()
+ ruian_nuts = ruian_obec_to_okres_nuts[misto]
+ region = None
+
+ if region_nuts in ruian_nuts:
+ nuts = region_nuts
+ elif not ruian_nuts:
+ if misto.startswith('Praha '):
+ # XXX: Pražské obvody nejsou v RUIANu
+ region = session.query(db.Place).filter_by(level=2, name=misto).first()
+ assert region
+ else:
+ nuts = region_nuts
+ print(f"WARNING: Obec {misto} není v RUIAN", file=sys.stderr)
+ elif len(ruian_nuts) == 1:
+ nuts = ruian_nuts[0]
+ print(f"WARNING: Obec {misto} je podle rejstříku v okrese {region_nuts}, ale pod RUIAN v {nuts} => preferuji RUIAN", file=sys.stderr)
+ else:
+ nuts = region_nuts
+ print(f"WARNING: Obec {misto} je podle rejstříku v okrese {region_nuts}, podle RUIAN je na výběr {ruian_nuts} => dořešit ručně!", file=sys.stderr)
+
+ if not region:
+ region = session.query(db.Place).filter_by(level=2, nuts=nuts).first()
+ assert region
+
+ town = session.query(db.Place).filter_by(level=3, parent=region.place_id, name=misto).first()
if town is None:
- region = session.query(db.Place).filter_by(level=2, nuts=region_nuts).first()
- assert region is not None, f"Failed to find region with NUTS code {region_nuts}"
town = db.Place(level=3, parent=region.place_id, name=misto, type=db.PlaceType.region)
session.add(town)
session.flush()
@@ -94,6 +125,40 @@ def lookup_town(misto: str, region_nuts: str) -> db.Place:
return town
+def load_ruian_csv(name):
+ with open(name) as file:
+ reader = csv.reader(file, delimiter=';')
+ rows = list(reader)
+ columns = {}
+ i = 0
+ for h in rows[0]:
+ columns[h] = i
+ i += 1
+ return columns, rows[1:]
+
+
+ruian_obec_to_okres_nuts: DefaultDict[str, List[str]] = defaultdict(list)
+
+
+def load_ruian():
+ ocols, okresy = load_ruian_csv('db/ruian/UI_OKRES.csv')
+ okres_by_id: Dict[int, List[str]] = {}
+ for o in okresy:
+ id = int(o[ocols['KOD']])
+ assert id not in okres_by_id
+ okres_by_id[id] = o
+
+ mcols, mesta = load_ruian_csv('db/ruian/UI_OBEC.csv')
+ for m in mesta:
+ jmeno = m[mcols['NAZEV']]
+ oid = int(m[mcols['OKRES_KOD']])
+ okres = okres_by_id[oid]
+ # print(f"{jmeno} -> {okres}")
+ ruian_obec_to_okres_nuts[jmeno].append(okres[ocols['NUTS_LAU']])
+
+
+load_ruian()
+
for path in Path('db/skoly/parsed').glob('*.tsv'):
m = re.fullmatch(r'^[A-Z]-(CZ\w+)\.tsv', path.name)
assert m is not None
@@ -101,4 +166,4 @@ for path in Path('db/skoly/parsed').glob('*.tsv'):
import_schools(path, nuts)
session.commit()
-print(f"Imported {new_school_cnt} schools, created {new_town_cnt} new towns.")
+print(f"Importováno {new_school_cnt} škol, založeno {new_town_cnt} nových obcí.")