# ----------------------------------------------------------------------------- # Sorti-Ka — Agrégateur de sorties & événements (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # regions.py : les 17 régions administratives du Québec + correspondances # région touristique → région administrative et ville → région # (patron Lou-Ka : rattacher chaque fiche à sa région via l'adresse) # ----------------------------------------------------------------------------- from __future__ import annotations import re import unicodedata # Les 17 régions administratives officielles (ordre des numéros 01-17). REGIONS: list[str] = [ "Bas-Saint-Laurent", "Saguenay–Lac-Saint-Jean", "Capitale-Nationale", "Mauricie", "Estrie", "Montréal", "Outaouais", "Abitibi-Témiscamingue", "Côte-Nord", "Nord-du-Québec", "Gaspésie–Îles-de-la-Madeleine", "Chaudière-Appalaches", "Laval", "Lanaudière", "Laurentides", "Montérégie", "Centre-du-Québec", ] def _k(s: str) -> str: """Clé de comparaison : minuscules, sans accents, tirets/points/apostrophes unifiés.""" s = unicodedata.normalize("NFD", s or "") s = "".join(c for c in s if unicodedata.category(c) != "Mn") s = s.lower().replace("’", "'") return re.sub(r"[\s\-–—_'.]+", " ", s).strip() # Région touristique (SIT Québec / ATR, 22 régions) → région administrative. TOURIST_TO_ADMIN: dict[str, str] = { _k("Abitibi-Témiscamingue"): "Abitibi-Témiscamingue", _k("Baie-James"): "Nord-du-Québec", _k("Eeyou Istchee Baie-James"): "Nord-du-Québec", _k("Nunavik"): "Nord-du-Québec", _k("Bas-Saint-Laurent"): "Bas-Saint-Laurent", _k("Cantons-de-l'Est"): "Estrie", _k("Centre-du-Québec"): "Centre-du-Québec", _k("Charlevoix"): "Capitale-Nationale", _k("Chaudière-Appalaches"): "Chaudière-Appalaches", _k("Côte-Nord"): "Côte-Nord", _k("Duplessis"): "Côte-Nord", _k("Manicouagan"): "Côte-Nord", _k("Gaspésie"): "Gaspésie–Îles-de-la-Madeleine", _k("Îles-de-la-Madeleine"): "Gaspésie–Îles-de-la-Madeleine", _k("Lanaudière"): "Lanaudière", _k("Laurentides"): "Laurentides", _k("Laval"): "Laval", _k("Mauricie"): "Mauricie", _k("Montérégie"): "Montérégie", _k("Montréal"): "Montréal", _k("Outaouais"): "Outaouais", _k("Québec"): "Capitale-Nationale", _k("Région de Québec"): "Capitale-Nationale", _k("Saguenay–Lac-Saint-Jean"): "Saguenay–Lac-Saint-Jean", } # Villes → région administrative. Table PRINCIPALE : data/villes_regions.json, # générée depuis le Répertoire des municipalités du Québec (MAMH, données # ouvertes, 1 193 municipalités sans ambiguïté). La table manuelle ci-dessous # sert de repli et couvre quelques graphies fréquentes des sources. _VILLES_JSON = None def _villes_table() -> dict[str, str]: global _VILLES_JSON if _VILLES_JSON is None: import json from pathlib import Path p = Path(__file__).resolve().parent.parent / "data" / "villes_regions.json" try: _VILLES_JSON = json.loads(p.read_text(encoding="utf-8"))["villes"] except Exception: _VILLES_JSON = {} return _VILLES_JSON CITY_TO_ADMIN: dict[str, str] = { _k(city): region for city, region in { "Montréal": "Montréal", "Québec": "Capitale-Nationale", "Lévis": "Chaudière-Appalaches", "Laval": "Laval", "Gatineau": "Outaouais", "Longueuil": "Montérégie", "Brossard": "Montérégie", "Saint-Jean-sur-Richelieu": "Montérégie", "Granby": "Montérégie", "Salaberry-de-Valleyfield": "Montérégie", "Sorel-Tracy": "Montérégie", "Vaudreuil-Dorion": "Montérégie", "Sherbrooke": "Estrie", "Magog": "Estrie", "Bromont": "Estrie", "Trois-Rivières": "Mauricie", "Shawinigan": "Mauricie", "La Tuque": "Mauricie", "Saguenay": "Saguenay–Lac-Saint-Jean", "Alma": "Saguenay–Lac-Saint-Jean", "Roberval": "Saguenay–Lac-Saint-Jean", "Dolbeau-Mistassini": "Saguenay–Lac-Saint-Jean", "Rimouski": "Bas-Saint-Laurent", "Rivière-du-Loup": "Bas-Saint-Laurent", "Matane": "Bas-Saint-Laurent", "Gaspé": "Gaspésie–Îles-de-la-Madeleine", "Percé": "Gaspésie–Îles-de-la-Madeleine", "Les Îles-de-la-Madeleine": "Gaspésie–Îles-de-la-Madeleine", "Baie-Comeau": "Côte-Nord", "Sept-Îles": "Côte-Nord", "Rouyn-Noranda": "Abitibi-Témiscamingue", "Val-d'Or": "Abitibi-Témiscamingue", "Amos": "Abitibi-Témiscamingue", "Chibougamau": "Nord-du-Québec", "Drummondville": "Centre-du-Québec", "Victoriaville": "Centre-du-Québec", "Bécancour": "Centre-du-Québec", "Joliette": "Lanaudière", "Terrebonne": "Lanaudière", "Repentigny": "Lanaudière", "Mascouche": "Lanaudière", "Saint-Jérôme": "Laurentides", "Mont-Tremblant": "Laurentides", "Blainville": "Laurentides", "Saint-Sauveur": "Laurentides", "Mirabel": "Laurentides", "Saint-Georges": "Chaudière-Appalaches", "Thetford Mines": "Chaudière-Appalaches", "Montmagny": "Chaudière-Appalaches", "Baie-Saint-Paul": "Capitale-Nationale", "La Malbaie": "Capitale-Nationale", "Wendake": "Capitale-Nationale", "L'Ancienne-Lorette": "Capitale-Nationale", }.items() } def region_from_tourist(label: str) -> str: """Région administrative à partir d'une région touristique (SITQ/ATR).""" return TOURIST_TO_ADMIN.get(_k(label), "") def region_from_city(city: str) -> str: """Région administrative à partir d'une ville (MAMH d'abord, repli manuel).""" key = _k(city) for candidate in ( key, # « St-Damase » → « saint damase », « Ste-Anne » → « sainte anne » re.sub(r"\bste\b", "sainte", re.sub(r"\bst\b", "saint", key)), # « Sainte-Adèle (secteur X) » / « Québec, arr. de … » key.split("(")[0].split(",")[0].strip(), ): hit = _villes_table().get(candidate) or CITY_TO_ADMIN.get(candidate, "") if hit: return hit return "" def is_valid_region(region: str) -> bool: return region in REGIONS