# ============================================================================== # Author: Simon-Pierre Boucher # File: creaka/connectors/region_bio.py # Desc: Connecteur ENRICHISSEMENT — région/ville depuis la localisation # DÉCLARÉE dans la bio publique du créateur (« humoriste de # Sherbrooke », « based in Montreal », « vlogueuse montréalaise »). # Mode d'accès : AUCUNE requête (relit les bios déjà en base, comme # bio-liens). Palier 1 (§9). Vague 4. # ============================================================================== """Région/ville depuis la bio publique — prudence maximale (§7/§15). On n'infère JAMAIS : on ne retient une ville que si le créateur la déclare lui-même dans sa bio avec une tournure locative sans ambiguïté (« de X », « basé à X », « from X », gentilé comme « montréalais »). Villes au nom piégeux exclues (Alma, Waterloo, Varennes… : prénoms, homonymes hors Québec, particules de noms de famille). Si la bio mentionne des villes de RÉGIONS différentes, on s'abstient (ambigu). Ne remplit que les champs VIDES. """ from __future__ import annotations import re from ..normalize import strip_accents from ..schema import Creator from .base import BaseConnector # villes SÛRES (clé sans accent) → (ville affichée, région §7). # ⚠ liste volontairement restrictive : pas d'Alma (prénom), pas de Waterloo # (Ontario), pas de Varennes (particule de nom), pas d'Anjou (France/quartier). _SAFE_CITIES: dict[str, tuple[str, str]] = { "montreal": ("Montréal", "Montréal"), "verdun": ("Verdun", "Montréal"), "lachine": ("Lachine", "Montréal"), "outremont": ("Outremont", "Montréal"), "westmount": ("Westmount", "Montréal"), "ville de quebec": ("Québec", "Capitale-Nationale"), "quebec city": ("Québec", "Capitale-Nationale"), "baie-saint-paul": ("Baie-Saint-Paul", "Capitale-Nationale"), "la malbaie": ("La Malbaie", "Capitale-Nationale"), "laval": ("Laval", "Laval"), "gatineau": ("Gatineau", "Outaouais"), "sherbrooke": ("Sherbrooke", "Estrie"), "magog": ("Magog", "Estrie"), "granby": ("Granby", "Estrie"), "trois-rivieres": ("Trois-Rivières", "Mauricie"), "shawinigan": ("Shawinigan", "Mauricie"), "la tuque": ("La Tuque", "Mauricie"), "saguenay": ("Saguenay", "Saguenay–Lac-Saint-Jean"), "chicoutimi": ("Chicoutimi", "Saguenay–Lac-Saint-Jean"), "jonquiere": ("Jonquière", "Saguenay–Lac-Saint-Jean"), "roberval": ("Roberval", "Saguenay–Lac-Saint-Jean"), "dolbeau-mistassini": ("Dolbeau-Mistassini", "Saguenay–Lac-Saint-Jean"), "rimouski": ("Rimouski", "Bas-Saint-Laurent"), "riviere-du-loup": ("Rivière-du-Loup", "Bas-Saint-Laurent"), "matane": ("Matane", "Bas-Saint-Laurent"), "amqui": ("Amqui", "Bas-Saint-Laurent"), "gaspe": ("Gaspé", "Gaspésie–Îles-de-la-Madeleine"), "chandler": ("Chandler", "Gaspésie–Îles-de-la-Madeleine"), "iles-de-la-madeleine": ("Îles-de-la-Madeleine", "Gaspésie–Îles-de-la-Madeleine"), "rouyn-noranda": ("Rouyn-Noranda", "Abitibi-Témiscamingue"), "val-d'or": ("Val-d'Or", "Abitibi-Témiscamingue"), "amos": ("Amos", "Abitibi-Témiscamingue"), "la sarre": ("La Sarre", "Abitibi-Témiscamingue"), "sept-iles": ("Sept-Îles", "Côte-Nord"), "baie-comeau": ("Baie-Comeau", "Côte-Nord"), "havre-saint-pierre": ("Havre-Saint-Pierre", "Côte-Nord"), "chibougamau": ("Chibougamau", "Nord-du-Québec"), "levis": ("Lévis", "Chaudière-Appalaches"), "thetford mines": ("Thetford Mines", "Chaudière-Appalaches"), "montmagny": ("Montmagny", "Chaudière-Appalaches"), "saint-georges": ("Saint-Georges", "Chaudière-Appalaches"), "drummondville": ("Drummondville", "Centre-du-Québec"), "victoriaville": ("Victoriaville", "Centre-du-Québec"), "longueuil": ("Longueuil", "Montérégie"), "brossard": ("Brossard", "Montérégie"), "boucherville": ("Boucherville", "Montérégie"), "chambly": ("Chambly", "Montérégie"), "beloeil": ("Belœil", "Montérégie"), "sainte-julie": ("Sainte-Julie", "Montérégie"), "saint-hyacinthe": ("Saint-Hyacinthe", "Montérégie"), "sorel-tracy": ("Sorel-Tracy", "Montérégie"), "chateauguay": ("Châteauguay", "Montérégie"), "saint-jean-sur-richelieu": ("Saint-Jean-sur-Richelieu", "Montérégie"), "salaberry-de-valleyfield": ("Salaberry-de-Valleyfield", "Montérégie"), "vaudreuil-dorion": ("Vaudreuil-Dorion", "Montérégie"), "cowansville": ("Cowansville", "Montérégie"), "terrebonne": ("Terrebonne", "Lanaudière"), "repentigny": ("Repentigny", "Lanaudière"), "mascouche": ("Mascouche", "Lanaudière"), "joliette": ("Joliette", "Lanaudière"), "rawdon": ("Rawdon", "Lanaudière"), "blainville": ("Blainville", "Laurentides"), "boisbriand": ("Boisbriand", "Laurentides"), "mirabel": ("Mirabel", "Laurentides"), "saint-jerome": ("Saint-Jérôme", "Laurentides"), "sainte-therese": ("Sainte-Thérèse", "Laurentides"), "sainte-adele": ("Sainte-Adèle", "Laurentides"), "sainte-agathe-des-monts": ("Sainte-Agathe-des-Monts", "Laurentides"), "mont-laurier": ("Mont-Laurier", "Laurentides"), "mont-tremblant": ("Mont-Tremblant", "Laurentides"), "lachute": ("Lachute", "Laurentides"), } # gentilés sans ambiguïté → ville (déclaration d'appartenance dans la bio) _DEMONYMS: dict[str, str] = { "montrealais": "montreal", "montrealaise": "montreal", "trifluvien": "trois-rivieres", "trifluvienne": "trois-rivieres", "sherbrookois": "sherbrooke", "sherbrookoise": "sherbrooke", "gatinois": "gatineau", "gatinoise": "gatineau", "lavallois": "laval", "lavalloise": "laval", "longueuillois": "longueuil", "longueuilloise": "longueuil", "rimouskois": "rimouski", "rimouskoise": "rimouski", "gaspesien": "gaspe", "gaspesienne": "gaspe", } # tournure locative OBLIGATOIRE devant le nom de ville : c'est elle qui fait # de la mention une DÉCLARATION (« de Sherbrooke ») et non une anecdote # (« j'ai visité Sherbrooke ») _LOCATIVE = (r"(?:de|d'|du|a|au|from|in|based in|base(?:e)? a|situe(?:e)? a|" r"originaire de|native de|natif de|vit a|vis a|habite a|" r"direct(?:ement)? de|region de|coin de|ici a)") _CITY_RE = re.compile( r"(? set[str]: """Bio publique → clés de villes DÉCLARÉES (tournure locative ou gentilé).""" blob = strip_accents(text or "").lower() keys = set(_CITY_RE.findall(blob)) keys |= {_DEMONYMS[d] for d in _DEMONYM_RE.findall(blob)} return keys class RegionBioConnector(BaseConnector): source_id = "region-bio" kind = "enrichment" request_delay = 0.0 # aucune requête réseau def enrich(self, creators: list[Creator]) -> list[Creator]: enriched: list[Creator] = [] for cr in creators: if cr.region and cr.city: continue keys = declared_cities(cr.bio) if not keys: continue regions = {_SAFE_CITIES[k][1] for k in keys} if len(regions) != 1: continue # villes de régions différentes → ambigu, abstention changed = False if not cr.region: cr.region = next(iter(regions)) changed = True if not cr.city and len(keys) == 1: cr.city = _SAFE_CITIES[next(iter(keys))][0] changed = True if changed: enriched.append(cr) return enriched