Créa·Ka — annuaire public cross-plateforme des créateurs de contenu québécois (crea-ka.com)
Python 73.6%
HTML 13.2%
TypeScript 6%
JavaScript 4.5%
CSS 1.7%
Dockerfile 0.6%
1# ==============================================================================2# Author: Simon-Pierre Boucher <contact@spboucher.ai>3# File: creaka/connectors/region_bio.py4# Desc: Connecteur ENRICHISSEMENT — région/ville depuis la localisation5# DÉCLARÉE dans la bio publique du créateur (« humoriste de6# Sherbrooke », « based in Montreal », « vlogueuse montréalaise »).7# Mode d'accès : AUCUNE requête (relit les bios déjà en base, comme8# bio-liens). Palier 1 (§9). Vague 4.9# ==============================================================================10"""Région/ville depuis la bio publique — prudence maximale (§7/§15).1112On n'infère JAMAIS : on ne retient une ville que si le créateur la déclare13lui-même dans sa bio avec une tournure locative sans ambiguïté (« de X »,14« basé à X », « from X », gentilé comme « montréalais »). Villes au nom15piégeux exclues (Alma, Waterloo, Varennes… : prénoms, homonymes hors Québec,16particules de noms de famille). Si la bio mentionne des villes de RÉGIONS17différentes, on s'abstient (ambigu). Ne remplit que les champs VIDES.18"""19from __future__ import annotations2021import re2223from ..normalize import strip_accents24from ..schema import Creator25from .base import BaseConnector2627# villes SÛRES (clé sans accent) → (ville affichée, région §7).28# ⚠ liste volontairement restrictive : pas d'Alma (prénom), pas de Waterloo29# (Ontario), pas de Varennes (particule de nom), pas d'Anjou (France/quartier).30_SAFE_CITIES: dict[str, tuple[str, str]] = {31 "montreal": ("Montréal", "Montréal"),32 "verdun": ("Verdun", "Montréal"),33 "lachine": ("Lachine", "Montréal"),34 "outremont": ("Outremont", "Montréal"),35 "westmount": ("Westmount", "Montréal"),36 "ville de quebec": ("Québec", "Capitale-Nationale"),37 "quebec city": ("Québec", "Capitale-Nationale"),38 "baie-saint-paul": ("Baie-Saint-Paul", "Capitale-Nationale"),39 "la malbaie": ("La Malbaie", "Capitale-Nationale"),40 "laval": ("Laval", "Laval"),41 "gatineau": ("Gatineau", "Outaouais"),42 "sherbrooke": ("Sherbrooke", "Estrie"),43 "magog": ("Magog", "Estrie"),44 "granby": ("Granby", "Estrie"),45 "trois-rivieres": ("Trois-Rivières", "Mauricie"),46 "shawinigan": ("Shawinigan", "Mauricie"),47 "la tuque": ("La Tuque", "Mauricie"),48 "saguenay": ("Saguenay", "Saguenay–Lac-Saint-Jean"),49 "chicoutimi": ("Chicoutimi", "Saguenay–Lac-Saint-Jean"),50 "jonquiere": ("Jonquière", "Saguenay–Lac-Saint-Jean"),51 "roberval": ("Roberval", "Saguenay–Lac-Saint-Jean"),52 "dolbeau-mistassini": ("Dolbeau-Mistassini", "Saguenay–Lac-Saint-Jean"),53 "rimouski": ("Rimouski", "Bas-Saint-Laurent"),54 "riviere-du-loup": ("Rivière-du-Loup", "Bas-Saint-Laurent"),55 "matane": ("Matane", "Bas-Saint-Laurent"),56 "amqui": ("Amqui", "Bas-Saint-Laurent"),57 "gaspe": ("Gaspé", "Gaspésie–Îles-de-la-Madeleine"),58 "chandler": ("Chandler", "Gaspésie–Îles-de-la-Madeleine"),59 "iles-de-la-madeleine": ("Îles-de-la-Madeleine",60 "Gaspésie–Îles-de-la-Madeleine"),61 "rouyn-noranda": ("Rouyn-Noranda", "Abitibi-Témiscamingue"),62 "val-d'or": ("Val-d'Or", "Abitibi-Témiscamingue"),63 "amos": ("Amos", "Abitibi-Témiscamingue"),64 "la sarre": ("La Sarre", "Abitibi-Témiscamingue"),65 "sept-iles": ("Sept-Îles", "Côte-Nord"),66 "baie-comeau": ("Baie-Comeau", "Côte-Nord"),67 "havre-saint-pierre": ("Havre-Saint-Pierre", "Côte-Nord"),68 "chibougamau": ("Chibougamau", "Nord-du-Québec"),69 "levis": ("Lévis", "Chaudière-Appalaches"),70 "thetford mines": ("Thetford Mines", "Chaudière-Appalaches"),71 "montmagny": ("Montmagny", "Chaudière-Appalaches"),72 "saint-georges": ("Saint-Georges", "Chaudière-Appalaches"),73 "drummondville": ("Drummondville", "Centre-du-Québec"),74 "victoriaville": ("Victoriaville", "Centre-du-Québec"),75 "longueuil": ("Longueuil", "Montérégie"),76 "brossard": ("Brossard", "Montérégie"),77 "boucherville": ("Boucherville", "Montérégie"),78 "chambly": ("Chambly", "Montérégie"),79 "beloeil": ("Belœil", "Montérégie"),80 "sainte-julie": ("Sainte-Julie", "Montérégie"),81 "saint-hyacinthe": ("Saint-Hyacinthe", "Montérégie"),82 "sorel-tracy": ("Sorel-Tracy", "Montérégie"),83 "chateauguay": ("Châteauguay", "Montérégie"),84 "saint-jean-sur-richelieu": ("Saint-Jean-sur-Richelieu", "Montérégie"),85 "salaberry-de-valleyfield": ("Salaberry-de-Valleyfield", "Montérégie"),86 "vaudreuil-dorion": ("Vaudreuil-Dorion", "Montérégie"),87 "cowansville": ("Cowansville", "Montérégie"),88 "terrebonne": ("Terrebonne", "Lanaudière"),89 "repentigny": ("Repentigny", "Lanaudière"),90 "mascouche": ("Mascouche", "Lanaudière"),91 "joliette": ("Joliette", "Lanaudière"),92 "rawdon": ("Rawdon", "Lanaudière"),93 "blainville": ("Blainville", "Laurentides"),94 "boisbriand": ("Boisbriand", "Laurentides"),95 "mirabel": ("Mirabel", "Laurentides"),96 "saint-jerome": ("Saint-Jérôme", "Laurentides"),97 "sainte-therese": ("Sainte-Thérèse", "Laurentides"),98 "sainte-adele": ("Sainte-Adèle", "Laurentides"),99 "sainte-agathe-des-monts": ("Sainte-Agathe-des-Monts", "Laurentides"),100 "mont-laurier": ("Mont-Laurier", "Laurentides"),101 "mont-tremblant": ("Mont-Tremblant", "Laurentides"),102 "lachute": ("Lachute", "Laurentides"),103}104105# gentilés sans ambiguïté → ville (déclaration d'appartenance dans la bio)106_DEMONYMS: dict[str, str] = {107 "montrealais": "montreal", "montrealaise": "montreal",108 "trifluvien": "trois-rivieres", "trifluvienne": "trois-rivieres",109 "sherbrookois": "sherbrooke", "sherbrookoise": "sherbrooke",110 "gatinois": "gatineau", "gatinoise": "gatineau",111 "lavallois": "laval", "lavalloise": "laval",112 "longueuillois": "longueuil", "longueuilloise": "longueuil",113 "rimouskois": "rimouski", "rimouskoise": "rimouski",114 "gaspesien": "gaspe", "gaspesienne": "gaspe",115}116117# tournure locative OBLIGATOIRE devant le nom de ville : c'est elle qui fait118# de la mention une DÉCLARATION (« de Sherbrooke ») et non une anecdote119# (« j'ai visité Sherbrooke »)120_LOCATIVE = (r"(?:de|d'|du|a|au|from|in|based in|base(?:e)? a|situe(?:e)? a|"121 r"originaire de|native de|natif de|vit a|vis a|habite a|"122 r"direct(?:ement)? de|region de|coin de|ici a)")123124_CITY_RE = re.compile(125 r"(?<![\w-])" + _LOCATIVE + r"\s+(?:la\s+|le\s+|l')?(" +126 "|".join(sorted(map(re.escape, _SAFE_CITIES), key=len, reverse=True)) +127 r")(?![\w-])")128_DEMONYM_RE = re.compile(129 r"(?<![\w-])(" + "|".join(sorted(_DEMONYMS, key=len, reverse=True)) +130 r")(?![\w-])")131132133def declared_cities(text: str) -> set[str]:134 """Bio publique → clés de villes DÉCLARÉES (tournure locative ou gentilé)."""135 blob = strip_accents(text or "").lower()136 keys = set(_CITY_RE.findall(blob))137 keys |= {_DEMONYMS[d] for d in _DEMONYM_RE.findall(blob)}138 return keys139140141class RegionBioConnector(BaseConnector):142 source_id = "region-bio"143 kind = "enrichment"144 request_delay = 0.0 # aucune requête réseau145146 def enrich(self, creators: list[Creator]) -> list[Creator]:147 enriched: list[Creator] = []148 for cr in creators:149 if cr.region and cr.city:150 continue151 keys = declared_cities(cr.bio)152 if not keys:153 continue154 regions = {_SAFE_CITIES[k][1] for k in keys}155 if len(regions) != 1:156 continue # villes de régions différentes → ambigu, abstention157 changed = False158 if not cr.region:159 cr.region = next(iter(regions))160 changed = True161 if not cr.city and len(keys) == 1:162 cr.city = _SAFE_CITIES[next(iter(keys))][0]163 changed = True164 if changed:165 enriched.append(cr)166 return enriched167