glam/scripts/generate_bulgarian_city_regions.py
2025-11-19 23:25:22 +01:00

332 lines
10 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Bulgarian City-to-Region Lookup Table Generator.
Creates a comprehensive mapping of Bulgarian cities to their administrative regions (oblasts)
to enable 100% GHCID generation coverage for Bulgarian heritage institutions.
Data source: GeoNames + Bulgarian administrative divisions (ISO 3166-2:BG)
"""
import json
import sqlite3
from pathlib import Path
from typing import Dict, Optional
# Bulgarian regions (28 oblasts) with ISO 3166-2 codes
BULGARIAN_REGIONS = {
# Region name (Bulgarian) → ISO code
'Благоевград': 'BG-01', # Blagoevgrad
'Бургас': 'BG-02', # Burgas
'Варна': 'BG-03', # Varna
'Велико Търново': 'BG-04', # Veliko Tarnovo
'Видин': 'BG-05', # Vidin
'Враца': 'BG-06', # Vratsa
'Габрово': 'BG-07', # Gabrovo
'Добрич': 'BG-08', # Dobrich
'Кърджали': 'BG-09', # Kardzhali
'Кюстендил': 'BG-10', # Kyustendil
'Ловеч': 'BG-11', # Lovech
'Монтана': 'BG-12', # Montana
'Пазарджик': 'BG-13', # Pazardzhik
'Перник': 'BG-14', # Pernik
'Плевен': 'BG-15', # Pleven
'Пловдив': 'BG-16', # Plovdiv
'Разград': 'BG-17', # Razgrad
'Русе': 'BG-18', # Ruse
'Силистра': 'BG-19', # Silistra
'Сливен': 'BG-20', # Sliven
'Смолян': 'BG-21', # Smolyan
'София': 'BG-22', # Sofia (capital)
'София област': 'BG-23', # Sofia Province
'Стара Загора': 'BG-24', # Stara Zagora
'Търговище': 'BG-25', # Targovishte
'Хасково': 'BG-26', # Haskovo
'Шумен': 'BG-27', # Shumen
'Ямбол': 'BG-28', # Yambol
}
# Major city → region mappings (manually curated)
# Source: Bulgarian administrative divisions + GeoNames
CITY_REGION_MANUAL = {
# English city name → Region Bulgarian name
# BG-01: Blagoevgrad
'Blagoevgrad': 'Благоевград',
'Bansko': 'Благоевград',
'Gotse Delchev': 'Благоевград',
'Petrich': 'Благоевград',
'Sandanski': 'Благоевград',
'Razlog': 'Благоевград',
'Simitli': 'Благоевград',
'Yakoruda': 'Благоевград',
'Belitsa': 'Благоевград',
'Hadjidimovo': 'Благоевград',
# BG-02: Burgas
'Burgas': 'Бургас',
'Nesebar': 'Бургас',
'Aytos': 'Бургас',
'Karnobat': 'Бургас',
'Pomorie': 'Бургас',
'Sozopol': 'Бургас',
'Tsarevo': 'Бургас',
'Sredets': 'Бургас',
'Malko Tarnovo': 'Бургас',
# BG-03: Varna
'Varna': 'Варна',
'Devnya': 'Варна',
'Provadiya': 'Варна',
'Beloslav': 'Варна',
'Aksakovo': 'Варна',
'Byala': 'Варна',
# BG-04: Veliko Tarnovo
'Veliko Tarnovo': 'Велико Търново',
'Gorna Oryahovitsa': 'Велико Търново',
'Svishtov': 'Велико Търново',
'Pavlikeni': 'Велико Търново',
'Elena': 'Велико Търново',
'Suhindol': 'Велико Търново',
# BG-05: Vidin
'Vidin': 'Видин',
'Belogradchik': 'Видин',
'Kula': 'Видин',
'Gramada': 'Видин',
'Bregovo': 'Видин',
# BG-06: Vratsa
'Vratsa': 'Враца',
'Kozloduy': 'Враца',
'Mezdra': 'Враца',
'Oryahovo': 'Враца',
'Byala Slatina': 'Враца',
'Roman': 'Враца',
# BG-07: Gabrovo
'Gabrovo': 'Габрово',
'Sevlievo': 'Габрово',
'Dryanovo': 'Габрово',
'Tryavna': 'Габрово',
# BG-08: Dobrich
'Dobrich': 'Добрич',
'Balchik': 'Добрич',
'Kavarna': 'Добрич',
'General Toshevo': 'Добрич',
# BG-09: Kardzhali
'Kardzhali': 'Кърджали',
'Momchilgrad': 'Кърджали',
'Krumovgrad': 'Кърджали',
'Ardino': 'Кърджали',
# BG-10: Kyustendil
'Kyustendil': 'Кюстендил',
'Dupnitsa': 'Кюстендил',
'Bobov Dol': 'Кюстендил',
'Sapareva Banya': 'Кюстендил',
# BG-11: Lovech
'Lovech': 'Ловеч',
'Troyan': 'Ловеч',
'Teteven': 'Ловеч',
'Lukovit': 'Ловеч',
'Apriltsi': 'Ловеч',
# BG-12: Montana
'Montana': 'Монтана',
'Lom': 'Монтана',
'Berkovitsa': 'Монтана',
'Valchedram': 'Монтана',
# BG-13: Pazardzhik
'Pazardzhik': 'Пазарджик',
'Panagyurishte': 'Пазарджик',
'Velingrad': 'Пазарджик',
'Peshtera': 'Пазарджик',
'Septemvri': 'Пазарджик',
# BG-14: Pernik
'Pernik': 'Перник',
'Radomir': 'Перник',
'Breznik': 'Перник',
# BG-15: Pleven
'Pleven': 'Плевен',
'Cherven Bryag': 'Плевен',
'Knezha': 'Плевен',
'Levski': 'Плевен',
'Nikopol': 'Плевен',
# BG-16: Plovdiv
'Plovdiv': 'Пловдив',
'Asenovgrad': 'Пловдив',
'Karlovo': 'Пловдив',
'Hisarya': 'Пловдив',
'Rakovski': 'Пловдив',
'Parvomay': 'Пловдив',
# BG-17: Razgrad
'Razgrad': 'Разград',
'Isperih': 'Разград',
'Kubrat': 'Разград',
'Zavet': 'Разград',
# BG-18: Ruse
'Ruse': 'Русе',
'Byala': 'Русе',
'Borovo': 'Русе',
'Vetovo': 'Русе',
# BG-19: Silistra
'Silistra': 'Силистра',
'Tutrakan': 'Силистра',
'Dulovo': 'Силистра',
'Alfatar': 'Силистра',
# BG-20: Sliven
'Sliven': 'Сливен',
'Nova Zagora': 'Сливен',
'Kotel': 'Сливен',
'Tvarditsa': 'Сливен',
# BG-21: Smolyan
'Smolyan': 'Смолян',
'Madan': 'Смолян',
'Zlatograd': 'Смолян',
'Banite': 'Смолян',
'Chepelare': 'Смолян',
# BG-22: Sofia (capital city)
'Sofia': 'София',
# BG-23: Sofia Province (surrounding region)
'Bozhurishte': 'София област',
'Botevgrad': 'София област',
'Elin Pelin': 'София област',
'Etropole': 'София област',
'Ihtiman': 'София област',
'Kostinbrod': 'София област',
'Pirdop': 'София област',
'Samokov': 'София област',
'Slivnitsa': 'София област',
'Svoge': 'София област',
# BG-24: Stara Zagora
'Stara Zagora': 'Стара Загора',
'Kazanlak': 'Стара Загора',
'Chirpan': 'Стара Загора',
'Radnevo': 'Стара Загора',
'Galabovo': 'Стара Загора',
# BG-25: Targovishte
'Targovishte': 'Търговище',
'Omurtag': 'Търговище',
'Popovo': 'Търговище',
'Opaka': 'Търговище',
# BG-26: Haskovo
'Haskovo': 'Хасково',
'Dimitrovgrad': 'Хасково',
'Svilengrad': 'Хасково',
'Harmanli': 'Хасково',
'Madzharovo': 'Хасково',
# BG-27: Shumen
'Shumen': 'Шумен',
'Veliki Preslav': 'Шумен',
'Kaspichan': 'Шумен',
'Novi Pazar': 'Шумен',
# BG-28: Yambol
'Yambol': 'Ямбол',
'Elhovo': 'Ямбол',
'Bolyarovo': 'Ямбол',
}
def get_region_from_city(city_name: str) -> Optional[str]:
"""
Get ISO 3166-2 region code for a Bulgarian city.
Args:
city_name: City name (English, e.g., "Sofia", "Burgas")
Returns:
ISO 3166-2 code (e.g., "BG-22") or None if not found
"""
region_bg = CITY_REGION_MANUAL.get(city_name)
if region_bg:
return BULGARIAN_REGIONS.get(region_bg)
return None
def get_region_code_from_iso(iso_code: str) -> str:
"""
Extract numeric region code from ISO 3166-2 code.
Args:
iso_code: ISO code (e.g., "BG-22")
Returns:
Numeric code (e.g., "22")
"""
return iso_code.split('-')[1]
def export_lookup_table(output_path: Path) -> None:
"""Export city-region lookup table to JSON."""
lookup = {}
for city_en, region_bg in CITY_REGION_MANUAL.items():
iso_code = BULGARIAN_REGIONS[region_bg]
lookup[city_en] = {
'region_bulgarian': region_bg,
'region_iso_code': iso_code,
'region_numeric': get_region_code_from_iso(iso_code)
}
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(lookup, f, ensure_ascii=False, indent=2)
print(f"✓ Exported {len(lookup)} city-region mappings to {output_path}")
def main():
"""Generate Bulgarian city-region lookup table."""
output_file = Path(__file__).parent.parent / 'data' / 'reference' / 'bulgarian_city_regions.json'
output_file.parent.mkdir(parents=True, exist_ok=True)
print("Generating Bulgarian city-region lookup table...")
print(f"Total regions: {len(BULGARIAN_REGIONS)}")
print(f"Total cities mapped: {len(CITY_REGION_MANUAL)}")
print()
# Verify all regions are covered
covered_regions = set(CITY_REGION_MANUAL.values())
all_regions = set(BULGARIAN_REGIONS.keys())
missing_regions = all_regions - covered_regions
if missing_regions:
print(f"⚠️ Warning: {len(missing_regions)} regions have no cities mapped:")
for region in sorted(missing_regions):
print(f" - {region} ({BULGARIAN_REGIONS[region]})")
print()
export_lookup_table(output_file)
# Test lookups
print("\nTest Lookups:")
test_cities = ['Sofia', 'Burgas', 'Varna', 'Plovdiv', 'Belitsa']
for city in test_cities:
region_code = get_region_from_city(city)
if region_code:
print(f" {city:20s}{region_code}")
else:
print(f" {city:20s} → NOT FOUND")
if __name__ == '__main__':
main()