SPB Git forge

spb/job-ka

Public
229commits 1branches 0releases
38.1 MBsize
maindefault branch
4 h agolast push
HTML 82.1% Python 14.6% TypeScript 1.9% CSS 1% JavaScript 0.5%
3.5 KB · 83 lines python
Raw Blame History
1# =============================================================================2# Job·Ka — Groupe KA3# Auteur  : Simon-Pierre Boucher4# Contact : contact@spboucher.ai5# Fichier : jobka/quality.py6# Rôle    : Contrôle qualité avant publication — règles anti-aberrations,7#           quarantaine (offres douteuses non publiées, jamais supprimées)8# Créé    : 2026-08-19   Modifié : 2026-08-199# =============================================================================10"""Quarantaine qualité (patron hérité de Lou·Ka).1112Une offre qui viole une règle de plausibilité n'est PAS publiée « à peu13près » : elle reste en base (active, resynchronisée normalement) avec la14colonne ``quarantine`` = JSON des motifs, et les points d'accès publics15l'excluent. Dès que la source corrige la donnée (ou qu'un correctif de16parseur passe), le contrôle se ré-évalue au sync suivant et l'offre est17réintégrée automatiquement.1819Règles (déterministes, bornes en CAD) :20- salaire hors bornes : < 15 $/h ou > 200 $/h équivalent ; annuel < 25 k$ ou21  > 500 k$ ;22- ville manifestement hors Québec ;23- dates incohérentes : date limite antérieure à la publication ;24- description absente ou < 50 caractères ;25- titre vide.26(Les titres tout-majuscules ne sont PAS quarantainés : la colonne27``title_clean`` corrige la casse à l'affichage.)28"""29from __future__ import annotations3031from .normalize import _NON_QC_CITIES, strip_accents  # type: ignore3233MIN_HOURLY, MAX_HOURLY = 15.0, 200.034MIN_YEARLY, MAX_YEARLY = 25000.0, 500000.035MIN_DESCRIPTION = 50363738def issues(*, title: str, description: str, city: str,39           date_posted: str | None, date_deadline: str | None,40           salary_hour_min: float | None, salary_hour_max: float | None,41           salary_year_min: float | None, salary_year_max: float | None,42           salary_unit: str | None = None) -> list[str]:43    """Motifs de quarantaine d'une offre (liste vide = publiable)."""44    out: list[str] = []4546    if not (title or "").strip():47        out.append("titre_vide")4849    if len((description or "").strip()) < MIN_DESCRIPTION:50        out.append("description_courte")5152    key = " ".join(strip_accents((city or "").lower()).split())53    if key and key in _NON_QC_CITIES:54        out.append("ville_hors_quebec")5556    if date_posted and date_deadline and date_deadline < date_posted:57        out.append("dates_incoherentes")5859    # bornes salariales : les bornes HORAIRES ne s'appliquent qu'aux salaires60    # affichés à l'heure (un annuel de 460 k$ n'est pas « > 200 $/h ») ; les61    # autres unités sont jugées sur l'équivalent annuel62    if salary_unit == "hour":63        for v in (salary_hour_min, salary_hour_max):64            if v is not None and not (MIN_HOURLY <= v <= MAX_HOURLY):65                out.append("salaire_hors_bornes")66                break67    else:68        for v in (salary_year_min, salary_year_max):69            if v is not None and not (MIN_YEARLY <= v <= MAX_YEARLY):70                out.append("salaire_hors_bornes")71                break72    return out737475def issues_for_posting(job) -> list[str]:76    """Motifs de quarantaine d'un JobPosting finalisé (utilisé au sync)."""77    return issues(78        title=job.title, description=job.description, city=job.city,79        date_posted=job.date_posted, date_deadline=job.date_deadline,80        salary_hour_min=job.salary_hour_min(), salary_hour_max=job.salary_hour_max(),81        salary_year_min=job.salary_year_min(), salary_year_max=job.salary_year_max(),82        salary_unit=job.salary_unit)83