#!/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()