SPB Git forge

spb/crea-ka

Public

Créa·Ka — annuaire public cross-plateforme des créateurs de contenu québécois (crea-ka.com)

52commits 1branches 0releases
11.3 MBsize
maindefault branch
19 days agolast push
Python 73.6% HTML 13.2% TypeScript 6% JavaScript 4.5% CSS 1.7% Dockerfile 0.6%
7.8 KB · 167 lines python
Raw Blame History
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