| 5 | # Fichier : jobka/normalize.py |
5 | # Fichier : jobka/normalize.py |
| 6 | # Rôle : Normalisation commune des offres (salaires, dates, mode/type |
6 | # Rôle : Normalisation commune des offres (salaires, dates, mode/type |
| 7 | # d'emploi, lieu, catégorie) — jamais de valeur inventée |
7 | # d'emploi, lieu, catégorie) — jamais de valeur inventée |
| 8 | −# Créé : 2026-08-17 Modifié : 2026-08-17 |
8 | +# Créé : 2026-08-17 Modifié : 2026-08-18 |
| 9 | # ============================================================================= |
9 | # ============================================================================= |
| 10 | """Couche de normalisation commune appelée par JobPosting.finalize(). |
10 | """Couche de normalisation commune appelée par JobPosting.finalize(). |
| 11 | |
11 | |
| 358 | r"\bqc\b|\bquebec\b|\bqu[e]bec, canada\b|province de quebec", re.I) |
358 | r"\bqc\b|\bquebec\b|\bqu[e]bec, canada\b|province de quebec", re.I) |
| 359 | _POSTAL_RE = re.compile(r"\b([GHJ]\d[A-Z])\s*(\d[A-Z]\d)\b", re.I) # G/H/J = Québec |
359 | _POSTAL_RE = re.compile(r"\b([GHJ]\d[A-Z])\s*(\d[A-Z]\d)\b", re.I) # G/H/J = Québec |
| 360 | |
360 | |
| |
361 | +# Villes canadiennes hors Québec (et grandes villes étrangères) fréquentes |
| |
362 | +# dans les flux pancanadiens/mondiaux — jamais des villes d'offres |
| |
363 | +# québécoises : à rejeter lors du parsing de lieu (clés sans accents). |
| |
364 | +_NON_QC_CITIES = frozenset({ |
| |
365 | + "toronto", "ottawa", "vancouver", "calgary", "edmonton", "winnipeg", |
| |
366 | + "mississauga", "brampton", "hamilton", "kitchener", "waterloo", "halifax", |
| |
367 | + "dartmouth", "victoria", "saskatoon", "regina", "st. john's", "st johns", |
| |
368 | + "saint john", "fredericton", "moncton", "charlottetown", "london", |
| |
369 | + "markham", "vaughan", "richmond hill", "oakville", "burlington", "oshawa", |
| |
370 | + "whitby", "ajax", "pickering", "guelph", "kingston", "barrie", |
| |
371 | + "kelowna", "kamloops", "nanaimo", "abbotsford", "surrey", "burnaby", |
| |
372 | + "coquitlam", "langley", "north york", "scarborough", "etobicoke", |
| |
373 | + "north vancouver", "milton", "cambridge", "st. catharines", |
| |
374 | + "niagara falls", "thunder bay", "sudbury", "greater sudbury", |
| |
375 | + "sault ste. marie", "red deer", "lethbridge", "medicine hat", |
| |
376 | + "grande prairie", "fort mcmurray", "yellowknife", "whitehorse", "iqaluit", |
| |
377 | + "can", "canada", "ontario", "alberta", "british columbia", "manitoba", |
| |
378 | + "saskatchewan", "nova scotia", "new brunswick", |
| |
379 | + "new york", "new york city", "san francisco", "boston", "chicago", |
| |
380 | + "seattle", "austin", "denver", "los angeles", "miami", "atlanta", |
| |
381 | + "dallas", "houston", "philadelphia", "washington", "phoenix", "portland", |
| |
382 | + "paris", "londres", "amsterdam", "berlin", "munich", "dublin", "madrid", |
| |
383 | + "barcelona", "barcelone", "lisbon", "lisbonne", "sydney", "melbourne", |
| |
384 | + "singapore", "singapour", "tokyo", "bangalore", "bengaluru", "mumbai", |
| |
385 | + "mexico city", "sao paulo", "bogota", "buenos aires", |
| |
386 | +}) |
| |
387 | + |
| 361 | |
388 | |
| 362 | def canonical_city(raw: str) -> str: |
389 | def canonical_city(raw: str) -> str: |
| 363 | """Canonicalise un nom de ville (« Montreal » -> « Montréal », |
390 | """Canonicalise un nom de ville (« Montreal » -> « Montréal », |
| 403 | seg_key = _key(seg) |
430 | seg_key = _key(seg) |
| 404 | if (seg and len(seg) <= 40 and re.search(r"[a-z]", seg_key) |
431 | if (seg and len(seg) <= 40 and re.search(r"[a-z]", seg_key) |
| 405 | and len(seg.split()) <= 3 |
432 | and len(seg.split()) <= 3 |
| |
433 | + and seg_key not in _NON_QC_CITIES |
| 406 | and not re.search( |
434 | and not re.search( |
| 407 | r"remote|teletravail|canada|multiple|various|partout|" |
435 | r"remote|teletravail|canada|multiple|various|partout|" |
| 408 | r"\d+\s+locations?|\d+\s+lieux|caisse|succursale|" |
436 | r"\d+\s+locations?|\d+\s+lieux|caisse|succursale|" |
| 416 | key = _key(clean_text(raw)) |
444 | key = _key(clean_text(raw)) |
| 417 | if not key: |
445 | if not key: |
| 418 | return False |
446 | return False |
| |
447 | + if key in _NON_QC_CITIES: # libellé = ville canadienne/monde hors QC |
| |
448 | + return False |
| 419 | if _QC_MARKERS.search(key): |
449 | if _QC_MARKERS.search(key): |
| 420 | return True |
450 | return True |
| 421 | m = _POSTAL_RE.search(raw) |
451 | m = _POSTAL_RE.search(raw) |
| 422 | |
452 | |