Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)
Python 69.3%
TypeScript 16.7%
CSS 7.9%
JavaScript 4.7%
HTML 1.4%
1# ==============================================================================2# Author: Simon-Pierre Boucher <contact@spboucher.ai>3# File: restoka/regions.py4# Desc: Rattachement obligatoire de chaque restaurant à l'une des 17 régions5# administratives du Québec : table ville → région, repli par préfixe6# de code postal, puis par coordonnées (boîtes englobantes).7# ==============================================================================8from __future__ import annotations910import re11import unicodedata1213REGIONS = [14 "Bas-Saint-Laurent", "Saguenay–Lac-Saint-Jean", "Capitale-Nationale",15 "Mauricie", "Estrie", "Montréal", "Outaouais", "Abitibi-Témiscamingue",16 "Côte-Nord", "Nord-du-Québec", "Gaspésie–Îles-de-la-Madeleine",17 "Chaudière-Appalaches", "Laval", "Lanaudière", "Laurentides",18 "Montérégie", "Centre-du-Québec",19]202122def strip_accents(text: str) -> str:23 return "".join(c for c in unicodedata.normalize("NFD", text)24 if unicodedata.category(c) != "Mn")252627def _key(city: str) -> str:28 s = strip_accents((city or "").strip().lower())29 s = re.sub(r"^(ville de|city of)\s+", "", s)30 s = re.sub(r"\s*\(.*\)$", "", s) # « Fiset Sorel-Tracy (arr.) »31 s = re.sub(r"[^a-z0-9]+", "-", s)32 return s.strip("-")333435# Ville (clé normalisée) → région. Municipalités principales + arrondissements36# fréquents dans les adresses des plateformes. Étendre au besoin (une ville37# inconnue n'est jamais inventée : repli code postal, puis coordonnées).38VILLE_REGION: dict[str, str] = {39 # --- Montréal (île) -------------------------------------------------------40 **{v: "Montréal" for v in (41 "montreal", "montreal-est", "montreal-ouest", "montreal-nord",42 "anjou", "lasalle", "lachine", "verdun", "outremont", "westmount",43 "saint-leonard", "saint-laurent", "ahuntsic", "ahuntsic-cartierville",44 "rosemont", "villeray", "hochelaga-maisonneuve", "mercier",45 "riviere-des-prairies", "pointe-aux-trembles", "pierrefonds",46 "roxboro", "dollard-des-ormeaux", "pointe-claire", "kirkland",47 "beaconsfield", "baie-d-urfe", "sainte-anne-de-bellevue", "senneville",48 "dorval", "mont-royal", "ville-mont-royal", "cote-saint-luc",49 "hampstead", "montreal-royal", "le-plateau-mont-royal", "griffintown",50 "ville-marie", "cote-des-neiges", "notre-dame-de-grace",51 "ile-bizard", "sainte-genevieve",52 )},53 # --- Laval ----------------------------------------------------------------54 **{v: "Laval" for v in (55 "laval", "chomedey", "sainte-rose", "vimont", "duvernay", "fabreville",56 "sainte-dorothee", "pont-viau", "laval-des-rapides", "auteuil",57 )},58 # --- Capitale-Nationale ---------------------------------------------------59 **{v: "Capitale-Nationale" for v in (60 "quebec", "l-ancienne-lorette", "ancienne-lorette",61 "saint-augustin-de-desmaures", "sainte-foy", "beauport", "charlesbourg",62 "loretteville", "val-belair", "cap-rouge", "sillery", "limoilou",63 "wendake", "boischatel", "l-ange-gardien", "chateau-richer",64 "sainte-anne-de-beaupre", "beaupre", "saint-ferreol-les-neiges",65 "baie-saint-paul", "la-malbaie", "clermont", "saint-simeon",66 "petite-riviere-saint-francois", "donnacona", "pont-rouge",67 "saint-raymond", "portneuf", "cap-sante", "neuville",68 "lac-beauport", "stoneham", "stoneham-et-tewkesbury", "deschambault",69 "deschambault-grondines",70 "sainte-catherine-de-la-jacques-cartier", "shannon", "sainte-brigitte-de-laval",71 "ile-d-orleans", "saint-pierre-de-l-ile-d-orleans",72 )},73 # --- Chaudière-Appalaches -------------------------------------------------74 **{v: "Chaudière-Appalaches" for v in (75 "levis", "saint-romuald", "charny", "saint-nicolas",76 "saint-jean-chrysostome", "pintendre", "saint-redempteur",77 "breakeyville", "saint-etienne-de-lauzon", "saint-henri",78 "montmagny", "l-islet", "saint-jean-port-joli", "sainte-marie",79 "saint-georges", "saint-joseph-de-beauce", "beauceville",80 "thetford-mines", "lac-etchemin", "saint-anselme", "scott",81 "saint-come-liniere", "st-georges-de-beauce", "saint-georges-de-beauce",82 "vallee-jonction", "saint-lambert-de-lauzon", "laurier-station",83 "saint-apollinaire", "saint-agapit",84 )},85 # --- Montérégie -----------------------------------------------------------86 **{v: "Montérégie" for v in (87 "longueuil", "brossard", "saint-lambert", "greenfield-park",88 "saint-hubert", "boucherville", "saint-bruno-de-montarville",89 "sainte-julie", "varennes", "vercheres", "contrecoeur", "beloeil",90 "mont-saint-hilaire", "otterburn-park", "mcmasterville",91 "saint-basile-le-grand", "chambly", "carignan", "richelieu", "marieville",92 "saint-jean-sur-richelieu", "iberville", "saint-luc",93 "granby", "waterloo", "cowansville", "bromont", "farnham",94 "salaberry-de-valleyfield", "vaudreuil-dorion", "l-ile-perrot",95 "pincourt", "saint-lazare", "hudson", "rigaud", "les-coteaux",96 "coteau-du-lac", "beauharnois", "chateauguay", "mercier-monteregie",97 "sainte-catherine", "saint-constant", "delson", "candiac",98 "la-prairie", "saint-philippe", "saint-remi", "napierville",99 "sorel-tracy", "fiset-sorel-tracy", "sainte-anne-de-sorel",100 "saint-hyacinthe", "sainte-helene-de-bagot", "acton-vale", "lac-brome",101 "sutton-monteregie",102 "huntingdon", "ormstown", "kahnawake", "saint-amable",103 )},104 # --- Estrie ----------------------------------------------------------------105 **{v: "Estrie" for v in (106 "sherbrooke", "magog", "orford", "north-hatley", "ayer-s-cliff",107 "coaticook", "stanstead", "rock-forest", "fleurimont", "lennoxville",108 "windsor", "val-des-sources", "asbestos", "danville", "richmond",109 "east-angus", "cookshire-eaton", "lac-megantic", "sutton",110 "knowlton", "eastman", "valcourt", "waterville", "compton",111 )},112 # --- Mauricie ---------------------------------------------------------------113 **{v: "Mauricie" for v in (114 "trois-rivieres", "cap-de-la-madeleine", "shawinigan", "grand-mere",115 "la-tuque", "louiseville", "yamachiche", "saint-tite",116 "sainte-anne-de-la-perade", "batiscan", "nicolet-mauricie", "maskinonge",117 "saint-boniface", "saint-etienne-des-gres", "notre-dame-du-mont-carmel",118 )},119 # --- Centre-du-Québec -------------------------------------------------------120 **{v: "Centre-du-Québec" for v in (121 "drummondville", "victoriaville", "becancour", "nicolet",122 "plessisville", "princeville", "warwick", "kingsey-falls",123 "saint-cyrille-de-wendover", "gentilly", "daveluyville",124 )},125 # --- Lanaudière -------------------------------------------------------------126 **{v: "Lanaudière" for v in (127 "terrebonne", "mascouche", "repentigny", "l-assomption", "charlemagne",128 "lachenaie", "la-plaine", "joliette", "notre-dame-des-prairies",129 "saint-charles-borromee", "berthierville", "lavaltrie", "lanoraie",130 "rawdon", "saint-donat", "saint-michel-des-saints", "saint-lin-laurentides",131 "saint-roch-de-l-achigan", "l-epiphanie", "saint-sulpice",132 )},133 # --- Laurentides ------------------------------------------------------------134 **{v: "Laurentides" for v in (135 "saint-jerome", "saint-antoine", "bellefeuille", "lafontaine",136 "blainville", "boisbriand", "sainte-therese", "rosemere", "lorraine",137 "bois-des-filion", "deux-montagnes", "saint-eustache", "mirabel",138 "oka", "pointe-calumet", "sainte-marthe-sur-le-lac", "saint-joseph-du-lac",139 "prevost", "saint-sauveur", "saint-sauveur-des-monts", "piedmont",140 "sainte-adele", "morin-heights",141 "sainte-agathe-des-monts", "val-david", "val-morin", "saint-donat-laurentides",142 "mont-tremblant", "labelle", "mont-laurier", "riviere-rouge",143 "sainte-anne-des-plaines", "lachute", "brownsburg-chatham", "grenville",144 )},145 # --- Outaouais --------------------------------------------------------------146 **{v: "Outaouais" for v in (147 "gatineau", "hull", "aylmer", "buckingham", "masson-angers",148 "chelsea", "cantley", "wakefield", "la-peche", "val-des-monts",149 "papineauville", "montebello", "thurso", "maniwaki", "fort-coulonge",150 "shawville", "campbell-s-bay",151 )},152 # --- Abitibi-Témiscamingue ---------------------------------------------------153 **{v: "Abitibi-Témiscamingue" for v in (154 "rouyn-noranda", "val-d-or", "amos", "la-sarre", "malartic",155 "riviere-heva",156 "senneterre", "ville-marie-temiscamingue", "temiscaming", "macamic",157 )},158 # --- Saguenay–Lac-Saint-Jean -------------------------------------------------159 **{v: "Saguenay–Lac-Saint-Jean" for v in (160 "saguenay", "chicoutimi", "jonquiere", "la-baie", "laterriere",161 "alma", "roberval", "saint-felicien", "dolbeau-mistassini",162 "normandin", "saint-prime", "metabetchouan", "hebertville",163 "saint-ambroise", "saint-honore", "l-anse-saint-jean", "petit-saguenay",164 )},165 # --- Bas-Saint-Laurent -------------------------------------------------------166 **{v: "Bas-Saint-Laurent" for v in (167 "rimouski", "riviere-du-loup", "matane", "mont-joli", "amqui",168 "causapscal", "trois-pistoles", "temiscouata-sur-le-lac", "cabano",169 "degelis", "pohenegamook", "la-pocatiere", "saint-pascal",170 "kamouraska", "le-bic", "sainte-luce", "sainte-flavie", "saint-fabien",171 )},172 # --- Gaspésie–Îles-de-la-Madeleine --------------------------------------------173 **{v: "Gaspésie–Îles-de-la-Madeleine" for v in (174 "gaspe", "perce", "chandler", "grande-riviere", "paspebiac",175 "bonaventure", "new-richmond", "carleton-sur-mer", "maria",176 "matapedia", "sainte-anne-des-monts", "cap-chat", "murdochville",177 "les-iles-de-la-madeleine", "cap-aux-meules", "havre-aubert",178 )},179 # --- Côte-Nord ----------------------------------------------------------------180 **{v: "Côte-Nord" for v in (181 "baie-comeau", "sept-iles", "port-cartier", "forestville",182 "les-escoumins", "tadoussac", "bergeronnes", "havre-saint-pierre",183 "fermont", "blanc-sablon", "natashquan", "chute-aux-outardes",184 )},185 # --- Nord-du-Québec -------------------------------------------------------------186 **{v: "Nord-du-Québec" for v in (187 "chibougamau", "chapais", "lebel-sur-quevillon", "matagami",188 "radisson", "kuujjuaq", "mistissini", "waskaganish",189 )},190}191192# Repli nº 1 : préfixe de code postal (3 caractères) → région. Couverture193# partielle mais fiable (les préfixes listés sont sans ambiguïté).194_POSTAL_REGION = {195 "H7": "Laval", # Laval au complet196 "G1": "Capitale-Nationale", "G2": "Capitale-Nationale",197 "G3": "Capitale-Nationale",198 "G6": "Chaudière-Appalaches", "G5V": "Chaudière-Appalaches",199 "G5X": "Chaudière-Appalaches",200 "G7": "Saguenay–Lac-Saint-Jean", "G8": "Saguenay–Lac-Saint-Jean",201 "G5L": "Bas-Saint-Laurent", "G5R": "Bas-Saint-Laurent",202 "G4X": "Gaspésie–Îles-de-la-Madeleine", "G4W": "Bas-Saint-Laurent",203 "G4R": "Côte-Nord", "G4Z": "Côte-Nord", "G5C": "Côte-Nord",204 "G9A": "Mauricie", "G8T": "Mauricie", "G9N": "Mauricie", "G9H": "Centre-du-Québec",205 "J1": "Estrie",206 "J2": "Montérégie", "J3": "Montérégie", "J4": "Montérégie", "J5R": "Montérégie",207 "J6": "Montérégie",208 "J7": "Laurentides", "J8": "Outaouais", "J9": "Abitibi-Témiscamingue",209 "J9B": "Outaouais",210 "J5W": "Lanaudière", "J6E": "Lanaudière",211}212213# Repli nº 2 : ancres géographiques (lat, lng) des principaux pôles de chaque214# région — le point le plus proche gagne. Plus robuste que des boîtes215# englobantes qui se chevauchent ; couvre les 17 régions. Utilisé seulement216# quand ni la ville ni le code postal ne tranchent (POI ruraux d'OSM, etc.).217_REGION_ANCHORS: list[tuple[str, float, float]] = [218 ("Montréal", 45.55, -73.65), ("Montréal", 45.47, -73.82),219 ("Laval", 45.585, -73.75),220 ("Montérégie", 45.50, -73.42), ("Montérégie", 45.30, -73.26),221 ("Montérégie", 45.40, -74.03), ("Montérégie", 45.05, -74.13),222 ("Montérégie", 45.63, -72.95), ("Montérégie", 46.04, -73.11),223 ("Montérégie", 45.40, -72.73),224 ("Lanaudière", 45.75, -73.45), ("Lanaudière", 46.02, -73.44),225 ("Lanaudière", 46.35, -73.87), ("Lanaudière", 46.02, -73.78),226 ("Laurentides", 45.78, -74.00), ("Laurentides", 45.88, -74.17),227 ("Laurentides", 46.12, -74.60), ("Laurentides", 46.55, -75.50),228 ("Laurentides", 45.65, -74.34),229 ("Outaouais", 45.48, -75.70), ("Outaouais", 45.65, -76.24),230 ("Outaouais", 46.38, -75.98), ("Outaouais", 45.88, -77.10),231 ("Abitibi-Témiscamingue", 48.24, -79.03), ("Abitibi-Témiscamingue", 48.10, -77.78),232 ("Abitibi-Témiscamingue", 48.57, -78.12), ("Abitibi-Témiscamingue", 47.34, -79.44),233 ("Estrie", 45.40, -71.90), ("Estrie", 45.27, -72.15),234 ("Estrie", 45.60, -71.00), ("Estrie", 45.03, -72.10), ("Estrie", 45.77, -71.62),235 ("Centre-du-Québec", 45.88, -72.48), ("Centre-du-Québec", 46.06, -71.96),236 ("Centre-du-Québec", 46.22, -72.62),237 ("Mauricie", 46.35, -72.55), ("Mauricie", 46.57, -72.75),238 ("Mauricie", 47.44, -72.78), ("Mauricie", 46.28, -72.94),239 ("Capitale-Nationale", 46.81, -71.22), ("Capitale-Nationale", 46.94, -71.00),240 ("Capitale-Nationale", 46.68, -71.75), ("Capitale-Nationale", 47.44, -70.50),241 ("Capitale-Nationale", 46.86, -71.90),242 ("Chaudière-Appalaches", 46.74, -71.28), ("Chaudière-Appalaches", 46.12, -70.67),243 ("Chaudière-Appalaches", 46.05, -71.30), ("Chaudière-Appalaches", 46.98, -70.55),244 ("Chaudière-Appalaches", 46.55, -70.88),245 ("Saguenay–Lac-Saint-Jean", 48.43, -71.07), ("Saguenay–Lac-Saint-Jean", 48.55, -71.65),246 ("Saguenay–Lac-Saint-Jean", 48.51, -72.23), ("Saguenay–Lac-Saint-Jean", 48.88, -72.23),247 ("Bas-Saint-Laurent", 48.45, -68.53), ("Bas-Saint-Laurent", 47.83, -69.53),248 ("Bas-Saint-Laurent", 48.85, -67.53), ("Bas-Saint-Laurent", 47.37, -70.03),249 ("Gaspésie–Îles-de-la-Madeleine", 48.83, -64.48), ("Gaspésie–Îles-de-la-Madeleine", 48.40, -64.68),250 ("Gaspésie–Îles-de-la-Madeleine", 48.10, -66.10), ("Gaspésie–Îles-de-la-Madeleine", 49.13, -66.50),251 ("Gaspésie–Îles-de-la-Madeleine", 47.38, -61.87),252 ("Côte-Nord", 49.22, -68.15), ("Côte-Nord", 50.22, -66.38),253 ("Côte-Nord", 48.15, -69.72), ("Côte-Nord", 50.28, -63.60),254 ("Nord-du-Québec", 49.92, -74.37), ("Nord-du-Québec", 49.78, -77.63),255 ("Nord-du-Québec", 53.79, -73.67), ("Nord-du-Québec", 58.10, -68.40),256]257258259def region_from_latlng(lat: float, lng: float) -> str:260 """Région la plus proche par ancre (approximatif — dernier recours)."""261 best, best_d2 = "", float("inf")262 for name, alat, alng in _REGION_ANCHORS:263 d2 = (lat - alat) ** 2 + ((lng - alng) * 0.7) ** 2 # ~pondération lng264 if d2 < best_d2:265 best, best_d2 = name, d2266 return best267268269def attach_region(city: str, postal_code: str = "",270 lat: float | None = None, lng: float | None = None) -> str:271 """Région administrative d'un restaurant. Retourne "" si indéterminable272 (jamais de région inventée)."""273 region = VILLE_REGION.get(_key(city))274 if region:275 return region276 pc = (postal_code or "").replace(" ", "").upper()277 if pc:278 for prefix in (pc[:3], pc[:2]):279 if prefix in _POSTAL_REGION:280 return _POSTAL_REGION[prefix]281 if lat is not None and lng is not None:282 return region_from_latlng(lat, lng)283 return ""284