SPB Git forge

spb/sorti-ka

Public

Toutes les sorties et tous les événements du Québec, un seul endroit — 7 connecteurs, fiches SSR, design Groupe KA.

58commits 1branches 0releases
13.7 MBsize
maindefault branch
17 days agolast push
HTML 82.9% Python 15.2% TypeScript 0.9% JavaScript 0.7%
3.6 KB · 78 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/laval.py : connecteur Ville de Laval — calendrier des activités5#   Source    : donneesquebec.ca, jeu « Calendrier des activités » (laval.ca)6#   Extraction: fichier JSON officiel (données ouvertes, licence CC 4.0)7#   Couverture: ~300 activités municipales (bibliothèques, culture, famille,8#               plein air) avec dates, catégories, clientèles, lieux, images.9#   https://www.donneesquebec.ca/recherche/dataset/calendrier-des-activites10# -----------------------------------------------------------------------------11from __future__ import annotations1213import re1415from ..normalize import utc_to_local16from ..schema import Event17from .base import BaseConnector1819# la Ville publie l'annulation DANS le titre : « ANNULÉ - Rencontre avec … »20_CANCELLED_RE = re.compile(r"^\s*annul[eé]e?\s*[-–—:]\s*", re.I)2122FEED = ("https://www.donneesquebec.ca/recherche/dataset/"23        "df7add74-a741-4374-b3bd-22da8921c8b3/resource/"24        "b51a25de-bd06-4247-87ba-2b1ea8228005/download/calendrier-activites.json")25SITE = "https://www.laval.ca"262728def _labels(items) -> list[str]:29    return [i.get("Label", "") for i in items or [] if i and i.get("Label")]303132class LavalConnector(BaseConnector):33    source_id = "laval"3435    def fetch(self) -> list[Event]:36        events: list[Event] = []37        for rec in self.get_json(FEED):38            title = (rec.get("Title") or "").strip()39            page = (rec.get("PageUrl") or "").strip()40            if not title or not page:41                continue42            # statut : préfixe « ANNULÉ - » du titre → cancelled (titre nettoyé)43            status = ""44            if _CANCELLED_RE.match(title):45                status = "cancelled"46                title = _CANCELLED_RE.sub("", title).strip()47            image = rec.get("ImageUrl") or ""48            # une activité récurrente = une entrée par occurrence, même PageUrl49            # → l'id externe inclut la date pour ne pas écraser les occurrences50            slug = page.rsplit("/", 1)[-1].removesuffix(".aspx")51            day = (rec.get("EventDate") or "")[:10]52            # EventDate/EndDate sont publiés en UTC (vérifié 2026-08 : les53            # activités d'été tombent à 18h30 locales une fois converties) →54            # date + heure locales Québec ; minuit local = « toute la journée ».55            start_local, time_local = utc_to_local(rec.get("EventDate"))56            end_local, _ = utc_to_local(rec.get("EndDate"))57            events.append(Event(58                source=self.source_id,59                external_id=f"{slug}-{day}" if day else slug,60                url=SITE + page,61                title=title,62                description=rec.get("Excerpt") or "",63                raw_categories=(_labels(rec.get("Categories"))64                                + _labels(rec.get("Subcategories"))65                                + _labels(rec.get("Tags"))),66                audience=", ".join(_labels(rec.get("Clienteles"))),67                venue=", ".join(_labels(rec.get("Locations"))),68                city="Laval",69                region="Laval",70                start_date=start_local or rec.get("EventDate"),71                start_time=time_local,72                end_date=end_local or rec.get("EndDate"),73                status=status,74                organizer="Ville de Laval",75                image=(SITE + image) if image.startswith("/") else image,76            ))77        return events78