SPB Git forge

spb/resto-ka

Public

Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)

52commits 1branches 0releases
11.6 MBsize
maindefault branch
19 days agolast push
Python 69.3% TypeScript 16.7% CSS 7.9% JavaScript 4.7% HTML 1.4%
15.6 KB · 284 lines python
Raw Blame History
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