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%

Vague connecteurs 2026-08-25 : +26 sources actives (10 ATR, 15 municipales, 6 billetteries/agrégateurs), 20 connecteurs enrichis (prix/heures/adresses/images), +13 salles JSON-LD, +8 tenants — 61 sources actives, 22 310 événements actifs

- ATR : cotenord, abitibi, ilesdelamadeleine, charlevoix, eeyouistchee, tourismelaval, quebeccite, mtlorg + réhabilitation mauricie (XHR acolyte) et laurentides (Scrapfly)
- Municipal : gatineau, levis, terrebonne, troisrivieres, rimouski, victoriaville, drummondville, granby, saintjerome, sainthyacinthe, rouynnoranda, shawinigan, joliette, magog, soreltracy
- Billetteries/agrégateurs : showpass (API publique), zeffy (pages orgs __NEXT_DATA__), signelaval, quoifaire, evenementsattractions, banq ; weezevent/accesculture écartés documentés (accesculture retiré : domaine squatté, contenu 2015)
- Enrichis : ticketpro prix 0→93 %, ticketacces adresses+prix, tuxedobillet fiches Scrapfly (+71 evts janv-fév), lavitrine v3 (GPS/prix JSON-LD des représentations), lepointdevente adresses, quebecanimee heures/adresses, sitq og:image/description, montreal images 100 %, bandsintown images, atuvu heures/prix, cantonsdelest 25→596 (pagination AJAX CSRF), 7 autres ATR fiches détail
- sallesjsonld : 17 salles (+13 : Denise-Pelletier, MBAM, MNBAQ, MCQ, Pointe-à-Callière, Cinémathèque, TOHU, Spect'Art, Hector-Charland, Cogeco, Premier Acte, Maison littérature, Chasse-galerie), Trident réparé
- Registre sources.json : 68 entrées, docs/connecteurs régénérés, 131 tests verts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 25, 2026) parent 84363e1

128 changed files +16,147 −1,300

modified data/ovation_orgs.json +1 −1
@@ -1,5 +1,5 @@
1 1 {
2 − "_comment": "Sorti-Ka — Organisations du Réseau Ovation (m.ovation.ca) à moissonner. Auteur : Simon-Pierre Boucher — contact@spboucher.ai. Codes base36 validés par balayage 000000-00002Z le 2026-08-21 (un code valide répond 200 sur /fr/Search/AllEvents, un invalide 302). Réseau 100 % québécois — ajouter une organisation = un bloc ici, 0 code. Les clubs sportifs à 0 événement hors saison restent listés (la saison les réactive).",
2 + "_comment": "Sorti-Ka — Organisations du Réseau Ovation (m.ovation.ca) à moissonner. Auteur : Simon-Pierre Boucher — contact@spboucher.ai. Codes base36 validés par balayage 000000-00002Z le 2026-08-21 (un code valide répond 200 sur /fr/Search/AllEvents, un invalide 302). Re-balayage 000020-000041 le 2026-08-25 (crawl-delay 30 s respecté) : AUCUN nouveau code valide — le réseau n'a pas ajouté d'organisation depuis. Réseau 100 % québécois — ajouter une organisation = un bloc ici, 0 code. Les clubs sportifs à 0 événement hors saison restent listés (la saison les réactive).",
3 3 "organisations": [
4 4 {"code": "00000O", "nom": "Centre culturel de l'Université de Sherbrooke", "ville": "Sherbrooke"},
5 5 {"code": "00000P", "nom": "Maison des Arts Desjardins Drummondville", "ville": "Drummondville"},
modified data/salles_jsonld.json +124 −3
@@ -1,5 +1,5 @@
1 1 {
2 − "_comment": "Sorti-Ka — Salles à JSON-LD Event (connecteur générique sallesjsonld). Ajouter une salle = 1 bloc : listes (pages programmation), motif (regex des liens de fiche), ville/salle par défaut. robots.txt vérifié salle par salle AVANT ajout (2026-08-19) — voir docs/connecteurs/sallesjsonld.md. Salles sondées SANS JSON-LD Event (ne pas ajouter sans re-sonder) : palaismontcalm.ca, lediamant.ca, imperialbell.com, mtelus.com, clubsoda.ca, theatrecorona.ca (evenko, déjà couvert). Auteur : Simon-Pierre Boucher — contact@spboucher.ai",
2 + "_comment": "Sorti-Ka — Salles à JSON-LD Event (connecteur générique sallesjsonld). Ajouter une salle = 1 bloc : listes (pages programmation), motif (regex des liens de fiche), ville/salle par défaut. robots.txt vérifié salle par salle AVANT ajout (2026-08-19 pour l'amorce, 2026-08-25 pour la vague 2) — voir docs/connecteurs/sallesjsonld.md. Salles sondées SANS JSON-LD Event exploitable (ne pas ajouter sans re-sonder) : palaismontcalm.ca (re-sondé 2026-08-25), lediamant.ca (re-sondé 2026-08-25), imperialbell.com, mtelus.com, clubsoda.ca, theatrecorona.ca (evenko, déjà couvert), duceppe.com (Event sans startDate), rideauvert.qc.ca (startDate epoch 1970), theatrelalicorne.com, theatreprospero.com, quatsous.com, espacego.com, usine-c.com, maisontheatre.com, centaurtheatre.com, segalcentre.org, phi.ca, bordee.qc.ca, theatreperiscope.qc.ca, lesgrosbecs.qc.ca (liste JS), lerift.ca, tangentedanse.ca, agoradanse.com (Event sans startDate), auxecuries.com, casadelpopolo.com, cabaretliondor.com, theatreparadoxe.com, theatrerialto.ca, musee-mccord-stewart.ca, pauline-julien.com, valspec.com, theatregillesvigneault.com (tribe sans LD ; billetterie Tuxedo /Web/), theatredumarais.com, theatredesjardins.com, co-motion.ca, spec.qc.ca, almaspectacles.com, vieuxbureaudeposte.com, theatrebelcourt.com, kameleart.com, maximum90.ca, sallejmd.com, comitedesspectacles.com, cdspectacles.com, festivalenchanson.com, cotecour.ca. Écartées pour HEURES FAUSSÉES (offset menteur : décalage explicite ≠ heure locale affichée) : petittheatre.org (-05:00 l'été), museepop.ca (+00:00 = heure locale). Écartées pour LISTE NON FIABLE : lachapelle.org (SSR intermittent, liens absents du HTML serveur), tnm.qc.ca (Nuxt/Cloudflare : la page d'accueil sert 0 à 31 liens selon le cache ; fiches <saison>.tnm.qc.ca/<saison>/<id> avec Event valide si le rendu revient), theatredaujourdhui.qc.ca (fiches /spectacles/<slug> avec Event valide mais accueil/saisons rendus client ; l'index /spectacles est une ARCHIVE avec dates 1979-2001), cinemabeaubien.com / cinemaduparc.com / cinemadumusee.com (HTTP 523 récurrents). Auteur : Simon-Pierre Boucher — contact@spboucher.ai",
3 3 "salles": [
4 4 {
5 5 "id": "grandtheatre",
@@ -13,11 +13,11 @@
13 13 {
14 14 "id": "trident",
15 15 "nom": "Théâtre du Trident",
16 − "listes": ["https://www.letrident.com/spectacles/"],
16 + "listes": ["https://www.letrident.com/"],
17 17 "motif": "letrident\\.com/piece/[a-z0-9-]{3,}",
18 18 "salle": "Théâtre du Trident",
19 19 "ville": "Québec",
20 − "robots": "Disallow: (vide — tout permis, vérifié 2026-08-19)"
20 + "robots": "Disallow: (vide — tout permis, vérifié 2026-08-19) — /spectacles/ renvoie 404 depuis ≤2026-08-25, liste = page d'accueil (réparé 2026-08-25)"
21 21 },
22 22 {
23 23 "id": "capitole",
@@ -37,6 +37,127 @@
37 37 "salle": "Théâtre Lionel-Groulx",
38 38 "ville": "Sainte-Thérèse",
39 39 "robots": "Disallow: (vide — tout permis, Yoast, vérifié 2026-08-22)"
40 + },
41 + {
42 + "id": "denisepelletier",
43 + "nom": "Théâtre Denise-Pelletier (Montréal)",
44 + "listes": ["https://www.denise-pelletier.qc.ca/pieces/",
45 + "https://www.denise-pelletier.qc.ca/pieces/page/2/"],
46 + "motif": "denise-pelletier\\.qc\\.ca/pieces/(?!page/|feed)[a-z0-9-]{3,}",
47 + "salle": "Théâtre Denise-Pelletier",
48 + "ville": "Montréal",
49 + "robots": "Disallow: /wp-admin/ et *.pdf seulement (vérifié 2026-08-25) — Event/date par représentation (-04:00 exact)"
50 + },
51 + {
52 + "id": "tohu",
53 + "nom": "TOHU — Cité des arts du cirque (Montréal)",
54 + "listes": ["https://tohu.ca/fr/programmation/spectacles?type=evenements",
55 + "https://tohu.ca/fr/programmation/activites-gratuites?type=evenements"],
56 + "motif": "tohu\\.ca/fr/programmation/(?!spectacles|billetterie|festival|scolaire)[a-z0-9-]{3,}",
57 + "salle": "TOHU",
58 + "ville": "Montréal",
59 + "robots": "Disallow: /cpresources/ /vendor/ *type=date* seulement (vérifié 2026-08-25) — Event à plage de dates, minuit = placeholder"
60 + },
61 + {
62 + "id": "mbam",
63 + "nom": "Musée des beaux-arts de Montréal",
64 + "listes": ["https://www.mbam.qc.ca/fr/expositions/"],
65 + "motif": "mbam\\.qc\\.ca/fr/expositions/[a-z0-9-]{3,}",
66 + "salle": "Musée des beaux-arts de Montréal",
67 + "ville": "Montréal",
68 + "robots": "Disallow: /fr/recherche/ /en/search/ /wp-content/* seulement (vérifié 2026-08-25) — expositions = Event à plage de dates"
69 + },
70 + {
71 + "id": "pacmusee",
72 + "nom": "Pointe-à-Callière — cité d'archéologie et d'histoire (Montréal)",
73 + "listes": ["https://pacmusee.qc.ca/fr/expositions/",
74 + "https://pacmusee.qc.ca/fr/calendrier/"],
75 + "motif": "pacmusee\\.qc\\.ca/fr/(expositions/detail|calendrier/evenement)/[a-z0-9-]{3,}",
76 + "salle": "Pointe-à-Callière",
77 + "ville": "Montréal",
78 + "robots": "Aucun Disallow (vérifié 2026-08-25) — expositions et événements du calendrier"
79 + },
80 + {
81 + "id": "cinematheque",
82 + "nom": "Cinémathèque québécoise (Montréal)",
83 + "listes": ["https://www.cinematheque.qc.ca/fr/"],
84 + "motif": "cinematheque\\.qc\\.ca/fr/cinema/[a-z0-9-]{3,}",
85 + "salle": "Cinémathèque québécoise",
86 + "ville": "Montréal",
87 + "robots": "Aucun Disallow (vérifié 2026-08-25) — 1 Event daté par projection affichée (date sans heure)"
88 + },
89 + {
90 + "id": "mnbaq",
91 + "nom": "Musée national des beaux-arts du Québec",
92 + "listes": ["https://www.mnbaq.org/programmation/expositions",
93 + "https://www.mnbaq.org/programmation/visites-guidees"],
94 + "motif": "mnbaq\\.org/programmation/(expositions|visites-guidees|ateliers-et-cours|activites-et-evenements|spectacles-et-concerts)/[a-z0-9-]{3,}",
95 + "salle": "Musée national des beaux-arts du Québec",
96 + "ville": "Québec",
97 + "robots": "Disallow: /cpresources/ /vendor/ etc. seulement (vérifié 2026-08-25) — expositions, visites, ateliers avec Event horodaté (-04:00 exact)"
98 + },
99 + {
100 + "id": "mcq",
101 + "nom": "Musée de la civilisation (Québec)",
102 + "listes": ["https://mcq.org/decouvrir/expositions/"],
103 + "motif": "mcq\\.org/decouvrir/expositions/[a-z0-9-]{3,}",
104 + "salle": "Musée de la civilisation",
105 + "ville": "Québec",
106 + "robots": "Pas de robots.txt (404, vérifié 2026-08-25) — expositions = Event à plage de dates ; hôte SANS www (www.mcq.org/decouvrir 404)"
107 + },
108 + {
109 + "id": "maisonlitterature",
110 + "nom": "Maison de la littérature (Québec)",
111 + "listes": ["https://www.maisondelalitterature.qc.ca/evenements/"],
112 + "motif": "maisondelalitterature\\.qc\\.ca/evenements/(?!jsf/)[a-z0-9-]{3,}",
113 + "salle": "Maison de la littérature",
114 + "ville": "Québec",
115 + "robots": "Aucun Disallow (vérifié 2026-08-25) — Event horodaté sans offset (heure locale littérale, vérifiée = affichage)"
116 + },
117 + {
118 + "id": "premieracte",
119 + "nom": "Théâtre Premier Acte (Québec)",
120 + "listes": ["https://premieracte.ca/"],
121 + "motif": "premieracte\\.ca/spectacles/[a-z0-9-]{3,}",
122 + "salle": "Premier Acte",
123 + "ville": "Québec",
124 + "robots": "Disallow: /wp/wp-admin/ seulement (vérifié 2026-08-25) — Event à plage de dates, suffixe fuseau littéral « America/Toronto » (heure prise littérale, correcte)"
125 + },
126 + {
127 + "id": "spectart",
128 + "nom": "Spect'Art Rimouski (Salle Desjardins-Telus)",
129 + "listes": ["https://spectart.com/programmation/"],
130 + "motif": "spectart\\.com/programmation/(?!genre/)[a-z0-9-]{3,}",
131 + "salle": "Salle Desjardins-Telus",
132 + "ville": "Rimouski",
133 + "robots": "Aucun Disallow (vérifié 2026-08-25) — Event horodaté sans offset (heure locale littérale)"
134 + },
135 + {
136 + "id": "amphitheatrecogeco",
137 + "nom": "Amphithéâtre Cogeco (Trois-Rivières)",
138 + "listes": ["https://www.amphitheatrecogeco.com/"],
139 + "motif": "amphitheatrecogeco\\.com/(spectacles|evenements)/[a-z0-9-]{3,}",
140 + "salle": "Amphithéâtre Cogeco",
141 + "ville": "Trois-Rivières",
142 + "robots": "Pas de robots.txt (404, vérifié 2026-08-25) — Event daté (date sans heure)"
143 + },
144 + {
145 + "id": "hectorcharland",
146 + "nom": "Théâtre Hector-Charland (L'Assomption)",
147 + "listes": ["https://hector-charland.com/programmation-spectacles"],
148 + "motif": "hector-charland\\.com/programmation-spectacles/[a-z0-9-]{3,}",
149 + "salle": "Théâtre Hector-Charland",
150 + "ville": "L'Assomption",
151 + "robots": "Disallow: /cpresources/ /vendor/ /.env /cache/ seulement (Craft, vérifié 2026-08-25) — Event horodaté sans offset, location.name par fiche"
152 + },
153 + {
154 + "id": "chassegalerie",
155 + "nom": "Café culturel de la Chasse-galerie (Lavaltrie)",
156 + "listes": ["https://www.chasse-galerie.ca/fr"],
157 + "motif": "chasse-galerie\\.ca/fr/calendrier/[a-z0-9-]{3,}",
158 + "salle": "Café culturel de la Chasse-galerie",
159 + "ville": "Lavaltrie",
160 + "robots": "Disallow: /rubberduck/ /rss/ seulement (vérifié 2026-08-25) — Event horodaté -04:00 exact"
40 161 }
41 162 ]
42 163 }
modified data/sources.json +343 −86
@@ -6,7 +6,7 @@
6 6 "name": "SIT Québec — Ministère du Tourisme",
7 7 "url": "https://www.donneesquebec.ca/recherche/dataset/sit-quebec-evenements",
8 8 "type": "donnees-ouvertes",
9 − "extraction": "API JSON (Tourinsoft, syndication publique) — dates, géoloc, thèmes, contacts (TelephonePrincipals/Courriels → description « Renseignements : … ») ; le flux ne publie ni Descriptions* ni tarifs (vérifié 2026-08)",
9 + "extraction": "API JSON (Tourinsoft, syndication publique) — dates, géoloc, thèmes, contacts ; le flux ne publie ni heure (T00:00:00 partout) ni tarif ni photo (re-vérifié 2026-08-25) → enrichissement incrémental des SITES OFFICIELS des événements : og:image + og:description (filtre anti-slogan <30 car.), cache versionné v2, plafond SITQ_IMG_MAX=200/sync (90 % des sites livrent une vraie description)",
10 10 "acces_legal": "Licence Creative Commons 4.0 (Données Québec) — API officielle publiée par le Ministère du Tourisme",
11 11 "couverture": "Événements touristiques, toutes les régions du Québec",
12 12 "cadence": "horaire (watcher PM2)",
@@ -17,7 +17,7 @@
17 17 "name": "Ville de Montréal — Événements publics",
18 18 "url": "https://donnees.montreal.ca/dataset/evenements-publics",
19 19 "type": "donnees-ouvertes",
20 − "extraction": "API CKAN datastore_search (JSON, paginée) — venue = emplacement (si vrai nom de lieu) ou titre_adresse ; address = adresse_principale + adresse_secondaire ; le jeu de données ne publie pas l'heure → heure locale captée dans le JSON-LD des fiches montreal.ca (« startDate »:\"…T19:30:00-04:00\") lors de l'enrichissement og:image incrémental (cache, plafond MTL_IMG_MAX/sync)",
20 + "extraction": "API CKAN datastore_search (JSON, paginée) — venue = emplacement (si vrai nom de lieu) ou titre_adresse ; address = adresse_principale + adresse_secondaire ; le jeu de données ne publie pas l'heure → heure locale captée dans le JSON-LD des fiches montreal.ca (« startDate »:\"…T19:30:00-04:00\") lors de l'enrichissement og:image incrémental (cache, plafond MTL_IMG_MAX/sync). MAJ 2026-08-25 : plafond MTL_IMG_MAX doublé (600/sync), collecte complète puis priorisation jamais-visités/date proche — convergence atteinte : image 100 % et heure 99 % des événements à venir",
21 21 "acces_legal": "Licence CC BY 4.0 (donnees.montreal.ca) — attribution : Ville de Montréal",
22 22 "couverture": "Événements municipaux, île de Montréal",
23 23 "cadence": "horaire (watcher PM2)",
@@ -28,7 +28,7 @@
28 28 "name": "Le point de vente — billetterie québécoise",
29 29 "url": "https://lepointdevente.com/billets",
30 30 "type": "billetterie",
31 − "extraction": "pages de liste HTML rendues serveur (paginées), parse des cartes (titre, date FR + heure « 17 août 2026, 20h00 » → start_time, salle, ville, image) + fiches détail /billets/<id> incrémentales (cache data/lepointdevente_cache.json, 150 fiches/sync, re-visite 7 j) : description og:description, heure précise, tarifs JSON réels via /plugins/rates/<id> → price_min/price_label/is_free ; artistes extraits des titres par patrons sûrs seulement (« Artiste - Salle », « X en concert »)",
31 + "extraction": "pages de liste HTML rendues serveur (paginées), parse des cartes (titre, date FR + heure « 17 août 2026, 20h00 » → start_time, salle, ville, image) + fiches détail /billets/<id> incrémentales (cache data/lepointdevente_cache.json, 150 fiches/sync, re-visite 7 j) : description og:description, heure précise, tarifs JSON réels via /plugins/rates/<id> → price_min/price_label/is_free ; artistes extraits des titres par patrons sûrs seulement (« Artiste - Salle », « X en concert »). MAJ 2026-08-25 (fiches v2, LPDV_DETAIL_MAX=150/sync) : adresse civique en microdonnées schema.org (streetAddress + addressLocality, 84 % des fiches) → géocodage aval",
32 32 "acces_legal": "robots.txt : « User-Agent: * / Allow: * » — GET throttlés, identification honnête, lien direct vers chaque billetterie",
33 33 "couverture": "Concerts, humour, théâtre, festivals — partout au Québec (~2 500 événements à venir)",
34 34 "cadence": "horaire (watcher PM2)",
@@ -86,7 +86,7 @@
86 86 "name": "Tourisme Cantons-de-l'Est — calendrier officiel (ATR Estrie)",
87 87 "url": "https://www.cantonsdelest.com/evenements",
88 88 "type": "tourisme-regional",
89 − "extraction": "page liste rendue serveur (~25 fiches à venir) puis fiches détail incrémentales (cache data/cantonsdelest_cache.json, 40 fiches/sync, re-visite 7 j) : JSON-LD Event complet (nom, dates, description riche, adresse civique, images Tourinsoft mto.media.tourinsoft.eu) + GPS exact (data-lat/data-lng de la carte) ; même plateforme Tourinsoft que le SIT Québec → recoupement géré par dedup_key",
89 + "extraction": "page 1 SSR + pagination AJAX POST /event/more {_csrf, ids, offset} jusqu'à épuisement (~596 occurrences, CDE_PAGE_MAX=120) — carte : lien /evenements/<id>/<slug>/<idDate>, catégorie, titre, image, « Ville | date FR (abrégés janv./févr. gérés) » ; fiches incrémentales (CDE_FICHE_MAX=40/sync, re-visite 7 j) : JSON-LD Event (dates précises, description, adresse, CP) + GPS data-lat/lng ; recoupement SITQ par dedup_key",
90 90 "acces_legal": "pages publiques de l'ATR, rendu serveur — GET throttlés 0,8 s, identification honnête, lien vers la fiche originale",
91 91 "couverture": "Festivals, spectacles, événements — Cantons-de-l'Est (Estrie)",
92 92 "cadence": "horaire (watcher PM2, incrémental)",
@@ -121,7 +121,7 @@
121 121 "name": "evenko — grands concerts et festivals",
122 122 "url": "https://evenko.ca/fr/evenements",
123 123 "type": "promoteur",
124 − "extraction": "API /api/search (proxy Algolia) — format percé : GET ?body=base64({params:[query,options],lang:'fr-CA'}), filters entity_type:evenko_show, ~5 requêtes/sync ; heure locale du spectacle via show_time (epoch → America/Montreal, respect de hide_show_time) ; artistes = champ headliners explicite ; gratuité : champ free (sondé null sur 100 % des hits, 2026-08) + étiquettes « gratuit » (event_tag/promotional_tag) ; aucun montant exposé par l'index",
124 + "extraction": "API /api/search (proxy Algolia) — format percé : GET ?body=base64({params:[query,options],lang:'fr-CA'}), filters entity_type:evenko_show, ~5 requêtes/sync ; heure locale du spectacle via show_time (epoch → America/Montreal, respect de hide_show_time) ; artistes = champ headliners explicite ; gratuité : champ free (sondé null sur 100 % des hits, 2026-08) + étiquettes « gratuit » (event_tag/promotional_tag) ; aucun montant exposé par l'index. MAJ 2026-08-25 : aucun montant exposé (index Algolia complet 660 hits + JSON-LD des pages événement sans price) — prix uniquement chez Ticketmaster (connecteur ticketmaster, priceRanges)",
125 125 "acces_legal": "robots.txt : Allow /api/search* (explicite) — lien billets = URL Ticketmaster officielle",
126 126 "couverture": "Grands concerts (Centre Bell, Place Bell, MTELUS…), festivals (Osheaga, ÎleSoniq…)",
127 127 "cadence": "horaire (watcher PM2)",
@@ -132,7 +132,7 @@
132 132 "name": "Atuvu.ca — calendrier culturel",
133 133 "url": "https://atuvu.ca/",
134 134 "type": "agregateur",
135 − "extraction": "Scrapfly (Cloudflare) — sitemaps lastmod + fiches .event-play (date FR + heure « 20h00 » → start_time), cache incrémental data/atuvu_cache.json, plafond 120 fiches/sync",
135 + "extraction": "sitemaps XML publics + fiches HTML en GET direct (repli Scrapfly seulement sur défi Cloudflare) — cache versionné v2 : heure précise (92 % des fiches v2), tarifs après mise en vente (46 %), séances multiples le même jour suffixées -HHMM ; re-visite roulante 14 j des fiches à venir, plafond ATUVU_MAX_FETCH/sync",
136 136 "acces_legal": "robots.txt : Allow / + sitemap public",
137 137 "couverture": "Sorties culturelles, Grand Montréal",
138 138 "cadence": "horaire (watcher PM2, incrémental)",
@@ -143,7 +143,7 @@
143 143 "name": "La Vitrine culturelle",
144 144 "url": "https://www.lavitrine.com/",
145 145 "type": "billetterie",
146 − "extraction": "GET directs — sitemap.xml (2 000 fiches evenement + 185 exposition) + fiches HTML (bloc event_calendar), cache incrémental, 150 fiches/sync, re-visite 7 j",
146 + "extraction": "sitemap.xml + fiches rendues serveur (carrousel dates/heures/salles, description, og:image) + pages REPRÉSENTATION (…/<ficheId>/<dateId>) : JSON-LD Event complet → adresse, code postal, GPS bruts de la source (100 % constaté), prix billetterie (73 %) ; date+heure locales prises du fil d'Ariane (le startDate JSON-LD est en UTC et change de jour — piège évité) ; incrémental versionné v3, plafond LV_MAX_FETCH/sync",
147 147 "acces_legal": "robots.txt : Allow / + Content-Signal search=yes, use=reference — usage index + lien vers la fiche originale",
148 148 "couverture": "Spectacles et sorties culturelles, Grand Montréal",
149 149 "cadence": "horaire (watcher PM2, incrémental)",
@@ -177,7 +177,7 @@
177 177 "name": "Bandsintown — dates de concerts par artiste",
178 178 "url": "https://rest.bandsintown.com/",
179 179 "type": "agregateur",
180 − "extraction": "API publique /artists/{artiste}/events (app_id format widget « js_www.sorti-ka.com », testé accepté 2026-08 ; un app_id arbitraire simple est refusé) — pas de recherche géographique : on interroge les artistes déjà extraits de la base (evenko/lepointdevente/ticketmaster, événements à venir), plafond 100 artistes/sync avec rotation et cache data/bandsintown_cache.json (re-visite 3 j) ; on ne garde que les dates au Québec (venue.region=QC) : datetime local (date+heure), salle géolocalisée, lineup → artistes, free → gratuité, lien billets",
180 + "extraction": "API publique /artists/{artiste}/events (app_id format widget « js_www.sorti-ka.com », testé accepté 2026-08 ; un app_id arbitraire simple est refusé) — pas de recherche géographique : on interroge les artistes déjà extraits de la base (evenko/lepointdevente/ticketmaster, événements à venir), plafond 100 artistes/sync avec rotation et cache data/bandsintown_cache.json (re-visite 3 j) ; on ne garde que les dates au Québec (venue.region=QC) : datetime local (date+heure), salle géolocalisée, lineup → artistes, free → gratuité, lien billets. MAJ 2026-08-25 : image d'artiste reprise de n'importe quel événement de la réponse, sinon 1 GET de repli sur le profil /artists/<nom> (100 % constaté sur l'échantillon) ; rotation BIT_ARTIST_MAX/sync",
181 181 "acces_legal": "API publique Bandsintown (app_id d'identification) — usage index + lien vers la fiche originale",
182 182 "couverture": "Concerts au Québec des artistes en tournée connus de la base",
183 183 "cadence": "horaire (watcher PM2, rotation d'artistes)",
@@ -185,51 +185,36 @@
185 185 },
186 186 {
187 187 "id": "gatineau",
188 − "name": "Ville de Gatineau — Événements",
189 − "url": "https://www.gatineau.ca/",
188 + "name": "Ville de Gatineau — Calendrier officiel",
189 + "url": "https://calendrier.gatineau.cloud",
190 190 "type": "municipal",
191 − "extraction": "n/d",
192 − "acces_legal": "n/d",
191 + "extraction": "API JSON interne POST /calendrier/json/evenements.aspx (1 requête) — dates UTC→locale, lieux et images ~100 %. RÉACTIVÉE 2026-08-25 (l'abandon visait l'open data, mort ; le calendrier applicatif est vivant)",
192 + "acces_legal": "application municipale publique, robots: all",
193 193 "couverture": "Gatineau (Outaouais)",
194 − "cadence": "n/d",
195 − "statut": "abandonnee",
196 − "note": "Aucun jeu de données « événements » sur Données Québec (portail Gatineau : géomatique seulement) et site 100 % JavaScript sans API découverte (vérifié 2026-08-18)"
197 − },
198 − {
199 − "id": "trois-rivieres",
200 − "name": "Ville de Trois-Rivières — Événements",
201 − "url": "https://www.v3r.net/",
202 − "type": "municipal",
203 − "extraction": "n/d",
204 − "acces_legal": "n/d",
205 − "couverture": "Trois-Rivières (Mauricie)",
206 − "cadence": "n/d",
207 − "statut": "abandonnee",
208 − "note": "API REST WordPress verrouillée (401 rest_not_logged_in) et aucun jeu « événements » sur Données Québec (vérifié 2026-08-18)"
194 + "cadence": "quotidien",
195 + "statut": "actif"
209 196 },
210 197 {
211 198 "id": "levis",
212 − "name": "Ville de Lévis — Calendrier",
213 − "url": "https://www.ville.levis.qc.ca/",
199 + "name": "Ville de Lévis — Calendrier des événements",
200 + "url": "https://levis.ca/fr/calendrier-des-evenements",
214 201 "type": "municipal",
215 − "extraction": "n/d",
216 − "acces_legal": "n/d",
202 + "extraction": "GraphQL public du site (queryToken de lecture extrait de la page à chaque run). RÉACTIVÉE 2026-08-25 (l'abandon visait le WAF sur GET — l'API GraphQL publique passe)",
203 + "acces_legal": "API du site municipal public, jeton public, robots.txt permissif",
217 204 "couverture": "Lévis (Chaudière-Appalaches)",
218 − "cadence": "n/d",
219 − "statut": "abandonnee",
220 − "note": "Site derrière un WAF (403 sur GET identifiés) et aucun jeu « événements » sur Données Québec (vérifié 2026-08-18)"
205 + "cadence": "quotidien",
206 + "statut": "actif"
221 207 },
222 208 {
223 209 "id": "terrebonne",
224 210 "name": "Ville de Terrebonne — Calendrier",
225 − "url": "https://terrebonne.ca/calendrier/",
211 + "url": "https://terrebonne.ca/calendrier-des-evenements/",
226 212 "type": "municipal",
227 − "extraction": "n/d",
228 − "acces_legal": "n/d",
213 + "extraction": "API REST wp-json/wp/v2/event via Scrapfly (Cloudflare), 3-5 appels/sync. RÉACTIVÉE 2026-08-25",
214 + "acces_legal": "API publique du site municipal ; anti-bot contourné avec volumes minimes",
229 215 "couverture": "Terrebonne (Lanaudière)",
230 − "cadence": "n/d",
231 − "statut": "abandonnee",
232 − "note": "Cloudflare bloque tout GET non navigateur (403) et aucun jeu « événements » sur Données Québec (vérifié 2026-08-18)"
216 + "cadence": "quotidien",
217 + "statut": "actif"
233 218 },
234 219 {
235 220 "id": "lavalweb",
@@ -249,7 +234,7 @@
249 234 "url": "https://tourismeoutaouais.com/calendrier-des-evenements",
250 235 "type": "tourisme-regional",
251 236 "evaluation": "ÉVALUATION 2026-08-19 (avant connexion) : accès constaté = site Astro, la page calendrier embarque TOUT le calendrier dans les props sérialisées de l'îlot CalendrierDesEvenements (162 événements dont 25 futurs mesurés) — 1 GET/sync ; l'API GraphQL admin.tourismeoutaouais.com exige un jeton (403) ; robots.txt lu : Allow / ; effort ●.",
252 − "extraction": "props JSON de l'îlot Astro (déballage [type, valeur]) : id, titre, url (interne ou site officiel), municipalité, territoire, photo CDN, startDate/endDate ISO ; ni heure ni tarif publiés → jamais inventés",
237 + "extraction": "props JSON de l'îlot Astro (1 GET, tout le calendrier) + fiches internes /attraits/<slug> des événements À VENIR, incrémentales (OUT_FICHE_MAX/sync, re-visite 7 j) : description, adresse+ville (div.address), site web officiel ; ni heure ni tarif → jamais inventés",
253 238 "acces_legal": "robots.txt : User-agent: * / Allow: / — GET direct throttlé 0,8 s, identification honnête, lien vers la fiche/site officiel",
254 239 "couverture": "Festivals et événements, Outaouais (337 fiches seulement au baseline régional)",
255 240 "cadence": "horaire (watcher PM2)",
@@ -261,7 +246,7 @@
261 246 "url": "https://lanaudiere.ca/fr/evenements-lanaudiere",
262 247 "type": "tourisme-regional",
263 248 "evaluation": "ÉVALUATION 2026-08-19 (avant connexion) : accès constaté = CMS Umbraco, liste SSR limitée à ~10 cartes (44 résultats annoncés, reste en JS) → passage par le SITEMAP officiel (83 URLs de fiches fr mesurées) ; fiches structurées (member-title-1-1 : titre, plage de dates FR, municipalité) + og:image Tourinsoft + og:description ; robots.txt lu : Disallow /*? (aucune URL à paramètres requêtée) + Crawl-delay 2 s (respecté : GET séquentiels 2,1 s) ; effort ●●.",
264 − "extraction": "sitemap.xml (1 GET) → fiches /fr/evenements-lanaudiere/<slug>/ incrémentales (cache data/lanaudiere_cache.json, LAN_FICHE_MAX/sync, re-visite 7 j) : titre, plage de dates FR, municipalité, og:image/og:description ; médias Tourinsoft → recoupement SITQ géré par dedup_key",
249 + "extraction": "sitemap.xml (1 GET, ~85 fiches FR) → fiches incrémentales (LAN_FICHE_MAX/sync, re-visite 7 j, cache v2, Crawl-delay 2 s honoré) : en-tête member-title (titre, plage FR, municipalité), description expandable-intro, adresse+CP member-coords, organisateur, site web member-website, audience « Clientèle admise », og:image",
265 250 "acces_legal": "robots.txt : Disallow /*? + Crawl-delay: 2 — respectés à la lettre (aucun paramètre d'URL, 2,1 s entre GET), identification honnête",
266 251 "couverture": "Festivals et événements, Lanaudière",
267 252 "cadence": "horaire (watcher PM2, incrémental)",
@@ -285,7 +270,7 @@
285 270 "url": "https://www.bassaintlaurent.ca/fr/quoi-faire/evenements/",
286 271 "type": "tourisme-regional",
287 272 "evaluation": "ÉVALUATION 2026-08-19 (avant connexion) : accès constaté = WordPress (Bedrock) rendu serveur, 27 cartes complètes sur la liste (titre, plage de dates FR, municipalité, image) ; fiches avec GPS exact embarqué + og:description ; robots.txt lu : rien d'interdit sur /fr/ ; volume mesuré 27 ; couverture = BSL (168 fiches seulement au baseline) ; effort ●.",
288 − "extraction": "liste SSR (1 GET, cartes .card--type-event) + fiches incrémentales (cache data/bassaintlaurent_cache.json, BSL_FICHE_MAX/sync, re-visite 7 j) : og:description + GPS exact de la carte embarquée",
273 + "extraction": "liste SSR (1 GET, cartes .card--type-event, garde-fou ajax-load-more__end-page) + fiches incrémentales (BSL_FICHE_MAX/sync, re-visite 7 j, cache v2) : window.locations JSON → adresse civique, ville, CP, GPS exact, site web ; description longue « À propos » (fallback og:description)",
289 274 "acces_legal": "pages publiques de l'ATR — GET throttlés 0,8 s, identification honnête, lien fiche originale",
290 275 "couverture": "Festivals et événements, Bas-Saint-Laurent",
291 276 "cadence": "horaire (watcher PM2, incrémental)",
@@ -297,7 +282,7 @@
297 282 "url": "https://animee.ville.quebec.qc.ca/activites/",
298 283 "type": "municipal",
299 284 "evaluation": "ÉVALUATION 2026-08-19 (avant connexion) : accès constaté = portail municipal SSR ; la liste /activites/ porte TOUT en attributs data- (date-debut/date-fin ISO, arrondissement, catégories, prix « Gratuit ») : 126 séances / 73 activités mesurées, 1 GET ; fiches pour description/lieu ; le calendrier général ville.quebec.qc.ca n'expose pas de flux structuré (vérifié) ; effort ●. Comble l'absence de source municipale/gratuite de la 2ᵉ ville (inventaire Capitale-Nationale ~100 % billetteries).",
300 − "extraction": "liste /activites/ (1 GET, attributs data-* : dates ISO, arrondissement, catégorie, prix) + fiches incrémentales (cache data/quebecanimee_cache.json, QA_FICHE_MAX/sync, re-visite 7 j) : og:description, lieu ; une carte par occurrence datée → une fiche par séance",
285 + "extraction": "liste HTML rendue serveur + fiches détail incrémentales (QA_FICHE_MAX/sync, cache v2) : heure via boutons data-date/data-heure (62 %), salle réelle .lieu-nom (74 %, zéro bruit), adresse civique .lieu-adresse (72 %) pour le géocodage aval ; venue/heures des entrées v1 muselés (bruit « Lieux » du menu) en attendant le backfill",
301 286 "acces_legal": "site municipal public rendu serveur — GET throttlés 0,8 s, identification honnête, lien fiche officielle",
302 287 "couverture": "Activités gratuites/à faible coût, Ville de Québec (Capitale-Nationale)",
303 288 "cadence": "horaire (watcher PM2, incrémental)",
@@ -333,7 +318,7 @@
333 318 "url": "https://grandtheatre.qc.ca/programmation",
334 319 "type": "salle-de-spectacle",
335 320 "evaluation": "ÉVALUATION 2026-08-19 (avant connexion) : moteur configurable (data/salles_jsonld.json, patron stores.json de fabri-ka) pour la longue traîne des salles qui publient schema.org/Event sur leurs fiches. Sondage 2026-08-19 : AVEC Event JSON-LD = Grand Théâtre de Québec (108 fiches listées), Théâtre du Trident, Théâtre Capitole (via gestev.com) ; SANS (écartées de la config, re-sonder plus tard) = Palais Montcalm, Le Diamant, Impérial Bell, MTelus/Corona/Club Soda (evenko, déjà couverts), Petit Champlain / Albert-Rousseau (plateforme JS « pages/collections ») ; robots.txt lu salle par salle avant ajout ; effort ●● (moteur) puis ● par salle.",
336 − "extraction": "par salle configurée : page(s) de programmation → liens de fiche (motif regex) → fiches incrémentales (cache data/sallesjsonld_cache.json, SALLES_FICHE_MAX/sync partagé, re-visite 7 j) → blocs JSON-LD @type Event (nom, dates horodatées, salle, adresse, offres → prix, statut, image) ; heure « T00:00:00 » traitée comme placeholder",
321 + "extraction": "par salle configurée : page(s) de programmation → liens de fiche (motif regex) → fiches incrémentales (cache data/sallesjsonld_cache.json, SALLES_FICHE_MAX/sync partagé, re-visite 7 j) → blocs JSON-LD @type Event (nom, dates horodatées, salle, adresse, offres → prix, statut, image) ; heure « T00:00:00 » traitée comme placeholder. Vague 2 2026-08-25 : 17 salles configurées (+13 validées live : Théâtre Denise-Pelletier, MBAM, MNBAQ, Musée de la civilisation, Pointe-à-Callière, Cinémathèque québécoise, TOHU, Spect'Art Rimouski, Hector-Charland, Amphithéâtre Cogeco, Premier Acte, Maison de la littérature, Chasse-galerie Lavaltrie) ; liste du Trident réparée (page d'accueil) ; ~40 salles sondées et écartées (sans JSON-LD, heures faussées ou SSR intermittent — consignées dans le _comment de la config)",
337 322 "acces_legal": "robots.txt vérifié PAR SALLE avant ajout à la config (consigné dans data/salles_jsonld.json) — GET throttlés 0,8 s, identification honnête, lien fiche originale",
338 323 "couverture": "Longue traîne des salles de spectacle QC (amorce : Grand Théâtre de Québec, Trident, Capitole — Capitale-Nationale)",
339 324 "cadence": "horaire (watcher PM2, incrémental)",
@@ -341,34 +326,32 @@
341 326 },
342 327 {
343 328 "id": "mauricie",
344 − "name": "Tourisme Mauricie — événements (ATR)",
329 + "name": "Tourisme Mauricie — calendrier des événements (ATR)",
345 330 "url": "https://tourismemauricie.com/evenements",
346 331 "type": "tourisme-regional",
347 − "extraction": "n/d",
348 − "acces_legal": "n/d",
349 − "couverture": "Mauricie",
350 − "cadence": "n/d",
351 − "statut": "abandonnee",
352 − "note": "ÉCARTÉE 2026-08-19 (Phase 3) : liste /evenements rendue JS — 1 seule carte SSR par page (vérifié pages 1 et 2), cartes chargées par admin-ajax non documenté, API REST /wp-json verrouillée (401 rest_not_logged_in). À re-sonder (percer la facette admin-ajax) dans une vague ultérieure."
332 + "extraction": "API JSON interne du thème (GET /wp-json/acolyte/v1/get-events-data?lang=fr&page=N, ~174 événements : titre, url, ville, salle, catégories, date FR, GPS, images) + fiches détail incrémentales (JSON-LD Event : date ISO, adresse, description ; cache, MAU_FICHE_MAX=12, re-visite 7 j) ; région par la VILLE (l'ATR couvre des villes du Centre-du-Québec). RÉHABILITÉE 2026-08-25 (écartée Phase 3 pour rendu JS — l'endpoint XHR interne est percé)",
333 + "acces_legal": "API REST WordPress publique (aucune auth), robots.txt sans interdiction — GET throttlés, UA honnête",
334 + "couverture": "Mauricie (+ villes limitrophes du Centre-du-Québec)",
335 + "cadence": "quotidienne",
336 + "statut": "actif"
353 337 },
354 338 {
355 339 "id": "laurentides",
356 − "name": "Tourisme Laurentides — événements (ATR)",
357 − "url": "https://www.laurentides.com/fr/quoifaire/evenements",
340 + "name": "Tourisme Laurentides — calendrier des événements (ATR)",
341 + "url": "https://www.laurentides.com/evenements/",
358 342 "type": "tourisme-regional",
359 − "extraction": "n/d",
360 − "acces_legal": "n/d",
343 + "extraction": "API REST /wp-json/wp/v2/mec-events (~228 fiches : titre, description, image) via auto-escalade anti-bot (WAF 403 → Scrapfly ASP) ; dates+heures réelles, tarif, salle et adresse via fiches détail MEC incrémentales pilotées par « modified » (cache, LAU_FICHE_MAX=10/sync). RÉHABILITÉE 2026-08-25 (écartée Phase 3 pour WAF)",
344 + "acces_legal": "API REST publique ; WAF contourné par proxy/Scrapfly avec requêtes throttlées et plafonnées — UA honnête, lien vers la fiche originale",
361 345 "couverture": "Laurentides",
362 − "cadence": "n/d",
363 − "statut": "abandonnee",
364 − "note": "ÉCARTÉE 2026-08-19 (Phase 3) : WAF — 403 sur GET identifié (UA honnête). Scrapfly possible mais non justifié (la région est déjà la 4ᵉ mieux couverte : 620 fiches au baseline). À re-sonder."
346 + "cadence": "quotidienne (backfill des fiches ~3 semaines, puis incrémental)",
347 + "statut": "actif"
365 348 },
366 349 {
367 350 "id": "gaspesie",
368 351 "name": "Tourisme Gaspésie — événements (ATR)",
369 352 "url": "https://www.tourisme-gaspesie.com/fr/festival-et-evenements/",
370 353 "type": "tourisme-regional",
371 − "extraction": "Scrapfly (asp, country=ca, sans render_js — HTML rendu serveur) : liste (1 crédit) + fiches incrémentales (cache, GASP_FICHE_MAX=8/sync, re-visite 14 j) — titre h1, plage de dates FR, adresse/ville/CP (bloc coordonnées), og:description/og:image. Fiches sans date écartées (festivals annuels non datés).",
354 + "extraction": "Scrapfly (asp, country=ca, sans render_js) : liste (1 crédit) + fiches incrémentales (GASP_FICHE_MAX/sync, re-visite 14 j, cache v2) — titre h1, plage FR, og:description/og:image + JSON-LD Organization → GPS exact, adresse/ville/CP structurés, site web officiel",
372 355 "acces_legal": "Contenu public de l'ATR ; WAF contourné avec modération (budget ≤ 9 crédits Scrapfly/sync, consigné Phase 4), attribution + lien vers la fiche officielle",
373 356 "couverture": "Gaspésie–Îles-de-la-Madeleine",
374 357 "cadence": "horaire (watcher PM2) — cache 14 j, ~2 fiches re-visitées/j",
@@ -380,7 +363,7 @@
380 363 "name": "Tuxedo Billet — billetterie québécoise multi-tenants",
381 364 "url": "https://www.tuxedobillet.com/",
382 365 "type": "billetterie",
383 − "extraction": "Acteur Apify maison gorgeous_thistle/ka-tuxedo (Playwright, proxy résidentiel CA, code versionné dans apify/ka-tuxedo/) : les tenants <org>.tuxedobillet.com sont des SPA Angular sur Firebase (RTDB verrouillée, 401 même avec jeton anonyme) → rendu navigateur obligatoire. 1 run/TUX_TTL_HOURS (20 h), 29 tenants QC configurés dans data/tuxedo_tenants.json (ville vérifiée). Liste sans heure ni prix → jamais inventés.",
366 + "extraction": "Acteur Apify maison gorgeous_thistle/ka-tuxedo (Playwright, proxy résidentiel CA, code versionné dans apify/ka-tuxedo/) : les tenants <org>.tuxedobillet.com sont des SPA Angular sur Firebase (RTDB verrouillée, 401 même avec jeton anonyme) → rendu navigateur obligatoire. 1 run/TUX_TTL_HOURS (20 h), 29 tenants QC configurés dans data/tuxedo_tenants.json (ville vérifiée). Liste sans heure ni prix → jamais inventés. MAJ 2026-08-25 : fiches /main/<slug> rendues Scrapfly render_js (TUX_FICHE_MAX=20/sync, re-visite 7 j, version v2 = mois janv./févr. compris, +71 événements) : 1 Event par séance datée avec HEURE, statut COMPLET→soldout, catégorie, description. Prix et adresse non publiés (choix des sièges seulement) → jamais inventés. 35 tenants QC (+6 : chateauscenes, azimutdiffusion, culture-umontreal, mcgillathletics, osjm, lesaintjean) ; 10 tenants à liens /Web/ incompatibles consignés dans le _comment du fichier tenants",
384 367 "acces_legal": "Billetteries publiques ; rendu navigateur modéré (1 page/tenant/run, ~1 run par jour), lien d'achat vers la fiche officielle du tenant",
385 368 "couverture": "29 salles/diffuseurs QC : Théâtre Outremont, Le Gesù, Centre Pierre-Péladeau, FTA, FIMAV, Espace Théâtre (Mont-Laurier), L'Anglicane (Lévis), Chandler, Lac-Mégantic, Îles-de-la-Madeleine…",
386 369 "cadence": "quotidienne (TTL 20 h dans le watcher horaire)",
@@ -404,7 +387,7 @@
404 387 "name": "Tourisme Montérégie — événements (ATR)",
405 388 "url": "https://www.tourisme-monteregie.qc.ca/membre/categorie/quoi-faire/festivals-et-evenements/",
406 389 "type": "tourisme-regional",
407 − "extraction": "API REST WordPress publique /wp-json/wp/v2/cpt_event (paginée per_page=100) — titre, description, plage de dates FR, lat/long, site web, image (gisement Tourinsoft, acf.syndic_object_id EVENMTOV… ; recoupement SITQ géré par dedup_key). Fiches détail incrémentales (MTG_FICHE_MAX=15/sync, re-visite 7 j) : JSON-LD Event → adresse civique, ville, code postal.",
390 + "extraction": "API REST WP /wp-json/wp/v2/cpt_event (per_page=100) + taxonomie /event-categorie (1 GET → catégories brutes) — titre, description (content, fallback excerpt), plage FR, GPS ACF, site web, image ; fiches incrémentales (MTG_FICHE_MAX/sync, re-visite 7 j) : JSON-LD Event → adresse, ville, CP",
408 391 "acces_legal": "API REST WordPress publique (aucune auth), robots.txt sans interdiction sur /wp-json/ — GET throttlés 0,8 s, UA honnête",
409 392 "couverture": "Montérégie (~78 fiches à venir, vérifié 2026-08-21)",
410 393 "cadence": "horaire (watcher PM2)",
@@ -415,7 +398,7 @@
415 398 "name": "Tourisme Centre-du-Québec — événements (ATR)",
416 399 "url": "https://tourismecentreduquebec.com/evenements/",
417 400 "type": "tourisme-regional",
418 − "extraction": "Liste rendue serveur (1 GET) : blocs events_listing (plage de dates FR + titre + lien) ; fiches incrémentales (CDQ_FICHE_MAX=10/sync, re-visite 7 j) : bloc « Adresse », og:description, og:image. Ni ville structurée, ni heure, ni tarif → jamais inventés (région dérivée de l'ATR).",
401 + "extraction": "liste rendue serveur (1 GET, ~9 fiches) ; fiches incrémentales (CDQ_FICHE_MAX=10/sync, re-visite 7 j, cache v2) : champs <small> Adresse/Ville/Entreprise (→ organisateur), description event_details_row, « Programme » → catégories brutes, og:image. Href « site web » vide à la source ; ni heure, ni GPS, ni tarif → jamais inventés",
419 402 "acces_legal": "Site public de l'ATR, robots.txt sans interdiction — GET throttlés 0,8 s, UA honnête",
420 403 "couverture": "Centre-du-Québec (première source de la région — 0 fiche avant connexion ; ~10 fiches)",
421 404 "cadence": "horaire (watcher PM2)",
@@ -426,7 +409,7 @@
426 409 "name": "Tourisme Chaudière-Appalaches — festivals et événements (ATR)",
427 410 "url": "https://chaudiereappalaches.com/planifier-votre-sejour/ete-automne/festivals-et-evenements/",
428 411 "type": "tourisme-regional",
429 − "extraction": "Liste rendue serveur paginée (?listpage=1..3, ~12 fiches/page) → fiches incrémentales (CHA_FICHE_MAX=12/sync, re-visite 7 j) : JSON-LD Event COMPLET (dates ISO, description, adresse, ville, CP, GPS, images) via le parseur générique ld_events (sallesjsonld).",
412 + "extraction": "liste SSR paginée (?listpage=1..3) → fiches incrémentales (CHA_FICHE_MAX=12/sync, re-visite 7 j, cache v2) : JSON-LD Event complet (dates ISO, description, adresse, ville, CP, GPS, images) + payload « const HwSheet » → site web officiel et tarif textuel (price_label brut) ; heure non publiée (openingPeriods.hasHours=false)",
430 413 "acces_legal": "Site public de l'ATR, robots.txt sans interdiction sur /planifier-votre-sejour/ — GET throttlés 0,8 s, UA honnête",
431 414 "couverture": "Chaudière-Appalaches (~36 fiches à venir, vérifié 2026-08-21)",
432 415 "cadence": "horaire (watcher PM2)",
@@ -437,7 +420,7 @@
437 420 "name": "TicketAcces.net — billetterie régionale québécoise",
438 421 "url": "https://www.ticketacces.net/fr/portail/index.cfm",
439 422 "type": "billetterie",
440 − "extraction": "HTML ColdFusion (cp1252) : annuaire complet des organisations dans le portail (cartes data-nom/ville/région, filtre Québec — 12 orgs NB + 1 ON exclues) + calendrier AGRÉGÉ multi-organisations en 4 GET (catégories Spectacles/Cinéma/Sport/Autre) : 1 Event par représentation datée (titre, date+heure FR, salle, ville, image). Fiches incrémentales (TA_DETAIL_MAX=25/sync) : og:description, adresse, prix plancher.",
423 + "extraction": "HTML ColdFusion (cp1252) : annuaire complet des organisations du portail (filtre Québec — 12 orgs NB + 1 ON exclues) + calendrier AGRÉGÉ en 4 GET : 1 Event par représentation datée (titre, date+heure FR, salle, ville, image). Fiches incrémentales (TA_DETAIL_MAX=47/sync = 1 par hôte, re-visite 7 j, version de parse « v ») : og:description, ADRESSE CIVIQUE de la salle (ville suffixée retirée → géocodage aval), prix plancher (les « 0,00 $ » écartés). Crawl-delay 120 s/hôte honoré",
441 424 "acces_legal": "Billetterie publique, robots.txt sans interdiction sur le portail (vérifié 2026-08-21) ; GET throttlés 1 s, UA honnête, cookie de détection ColdFusion géré par session, lien d'achat vers la fiche officielle",
442 425 "couverture": "47 organisations QC — dont 14 en Abitibi-Témiscamingue (Agora des Arts, Théâtre du cuivre, culture Val-d'Or/Amos/La Sarre/Malartic, Théâtre du Rift…), Bas-Saint-Laurent (Océanic, 3L), Côte-Nord (Drakkar), Charlevoix (Domaine Forget), Nord-du-Québec (Lebel-sur-Quévillon)… ~1 300 séances (vérifié 2026-08-21)",
443 426 "cadence": "horaire (watcher PM2)",
@@ -448,7 +431,7 @@
448 431 "name": "Réseau Ovation — billetterie québécoise multi-salles",
449 432 "url": "https://reseau.ovation.ca/",
450 433 "type": "billetterie",
451 − "extraction": "Sous-site mobile m.ovation.ca (HTML rendu serveur, AUCUN anti-bot) : par organisation (codes base36 validés par balayage, config data/ovation_orgs.json), /fr/Search/AllEvents paginé (?pageNumber=N) → 1 eventId = 1 représentation ; fiches fragment EventDescription (≈5 Ko, OVA_FICHE_MAX=40/sync) : date pleine FR + heure, catégories, ARTISTES (liens EventsByPerformer), description, salle + adresse + ville + code postal. Prix non exposé → jamais inventé.",
434 + "extraction": "Sous-site mobile m.ovation.ca (HTML rendu serveur, AUCUN anti-bot) : par organisation (codes base36 validés par balayage, config data/ovation_orgs.json), /fr/Search/AllEvents paginé (?pageNumber=N) → 1 eventId = 1 représentation ; fiches fragment EventDescription (≈5 Ko, OVA_FICHE_MAX=40/sync) : date pleine FR + heure, catégories, ARTISTES (liens EventsByPerformer), description, salle + adresse + ville + code postal. Prix non exposé → jamais inventé. MAJ 2026-08-25 : prix NON PUBLIÉS (pages Event mobile+desktop sans aucun montant ni JSON-LD offers ; tarifs uniquement sous /event/tickets, Disallow robots.txt avec /api/) → jamais requêté, jamais inventé ; re-balayage des organisations sans nouveauté",
452 435 "acces_legal": "Billetterie publique sans auth ; GET throttlés 0,7 s, UA honnête, lien d'achat vers la fiche officielle",
453 436 "couverture": "26 organisations 100 % QC : Salle Albert-Rousseau, Petit Champlain, Diffusion Saguenay, Gatineau (Odyssée, Jean-Despréz), Vieux Terrebonne, Palace de Granby, Vieux Clocher Magog/Waterloo, La Tuque, Festival Western de St-Tite, clubs LHJMQ/LNAH…",
454 437 "cadence": "horaire (watcher PM2)",
@@ -459,7 +442,7 @@
459 442 "name": "Ticketpro — billetterie canadienne (tenants Québec)",
460 443 "url": "https://www.ticketpro.ca/",
461 444 "type": "billetterie",
462 − "extraction": "API JSON publique de la nouvelle plateforme SPA par tenant (<org>.ticketpro.ca/api/v1/pages, aucun anti-bot ni rendu JS) : 1 fiche par spectacle avec titre, artiste, description, image, lieu complet (adresse, ville, code postal, lat/lng), toutes les dates horodatées, tags et drapeaux cancelled/soldout → 1 Event par (page, date à venir). 6 tenants QC dans data/ticketpro_tenants.json (tproqc agrège la province mais pas tprotsd), dédup inter-tenants par id de page. Lieux de restauration (soupers-spectacles St-Denis) et musée à créneaux exclus par config. startingPrice toujours 0 → aucun prix émis.",
445 + "extraction": "API JSON publique de la nouvelle plateforme SPA par tenant (<org>.ticketpro.ca/api/v1/pages, aucun anti-bot ni rendu JS) : 1 fiche par spectacle avec titre, artiste, description, image, lieu complet (adresse, ville, code postal, lat/lng), toutes les dates horodatées, tags et drapeaux cancelled/soldout → 1 Event par (page, date à venir). 6 tenants QC dans data/ticketpro_tenants.json (tproqc agrège la province mais pas tprotsd), dédup inter-tenants par id de page. Lieux de restauration (soupers-spectacles St-Denis) et musée à créneaux exclus par config. startingPrice toujours 0 → aucun prix émis. MAJ 2026-08-25 : startingPrice vide dans la liste MAIS tarifs réels publics en 2 GET/spectacle (détail /api/v1/pages/<id> → offre ?options=prices) : priceLevels.price_min en cents (taxes+frais inclus) → prix plancher (92,8 % des séances) + statut PAR séance (saleState, séances datées sans heure → repli au jour) ; cache data/ticketpro_cache.json, TP_PRICE_MAX=40/sync, re-visite 3 j ; 8 tenants QC (+arsenalcontemporarysale, +lefifa)",
463 446 "acces_legal": "Billetterie publique ; robots.txt n'interdit que /admin/ /gui/ /report/ (vérifié 2026-08-22) ; API du site officiel consommée telle quelle, GET throttlés 1 s, UA honnête, lien d'achat vers la fiche officielle /fr/pages/<id> du tenant",
464 447 "couverture": "Théâtre St-Denis + Studio St-Denis (Montréal), Fun Nation (Salle Pierre-Mercure, Monument-National, Club Soda), ExpoCité (Québec), Salle André-Gagnon (La Pocatière), Palais des congrès de Montréal, La Belle Église de Chertsey, Théâtre Rialto, Huntingdon… ~200 spectacles / ~1 100 séances datées (vérifié 2026-08-22)",
465 448 "cadence": "horaire (watcher PM2)",
@@ -467,27 +450,25 @@
467 450 },
468 451 {
469 452 "id": "zeffy",
470 − "name": "Zeffy — billetterie OSBL (Montréal)",
453 + "name": "Zeffy — billetterie OSBL",
471 454 "url": "https://www.zeffy.com/",
472 455 "type": "billetterie",
473 − "extraction": "—",
474 − "acces_legal": "robots.txt permissif, sitemaps publics",
475 − "couverture": "Événements communautaires/OSBL partout au Canada et aux É.-U.",
476 − "cadence": "—",
477 − "statut": "abandonnee",
478 − "raison": "ÉCARTÉE 2026-08-22 (Phase 5) : découverte impraticable — sitemap api.zeffy.com = 547 834 URLs /ticketing/ (dont 54 905 fr) SANS lastmod, date ni géolocalisation ; il faudrait rendre chaque fiche Next.js pour savoir si elle est QC et à venir. À revisiter seulement si une API de recherche publique apparaît."
456 + "extraction": "sitemap des organisations (api.zeffy.com/sitemap/organizations/sitemap-0.xml, ~4 400 pages fr-CA/en-CA) → pages org rendues serveur (__NEXT_DATA__ organizationPage.upcomingEvents : dates UTC, adresse, image, URL) filtrées « , QC » et formCategory=Event, puis fiches billetterie JSON-LD offers[].price pour les tarifs réels ; incrémental cache 7 j, plafonds ZEFFY_ORG_MAX=400 / ZEFFY_DETAIL_MAX=80. RÉACTIVÉE 2026-08-25 (le sitemap billets était infaisable ; les pages d'organisations sont la bonne surface)",
457 + "acces_legal": "robots.txt Disallow: /*=$ uniquement (aucune URL visée) + sitemaps publiés ; GET throttlés, identification honnête, pas d'anti-bot constaté",
458 + "couverture": "~100-200 événements OSBL QC actifs (galas, spectacles-bénéfice, ateliers communautaires), toutes régions",
459 + "cadence": "quotidienne",
460 + "statut": "actif"
479 461 },
480 462 {
481 463 "id": "showpass",
482 464 "name": "Showpass — billetterie canadienne",
483 465 "url": "https://www.showpass.com/",
484 466 "type": "billetterie",
485 − "extraction": "—",
486 − "acces_legal": "API publique explicitement autorisée par robots.txt (Allow: /api/)",
487 − "couverture": "13 663 événements canadiens, très majoritairement Alberta/C.-B.",
488 − "cadence": "—",
489 − "statut": "abandonnee",
490 − "raison": "ÉCARTÉE 2026-08-22 (Phase 5) : /api/public/events/ n'offre AUCUN filtre géo fonctionnel (venue__city, location, coordinates ignorés — vérifié) ET l'inventaire est presque entièrement hors Québec : échantillon de 1 000 événements sur 10 pages réparties → 0,3 % QC (3/1000, vérifié 2026-08-22). ~137 pages à paginer par sync pour ~40 événements QC : ROI nul. À revisiter seulement si un filtre serveur apparaît."
467 + "extraction": "API JSON publique /api/public/events/ paginée, couverture du Québec par 10 points géo (location__point_location=lat,lng,rayon + starts_on__gte), filtre province=QC côté connecteur ; heure locale via local_starts_on ; tarifs ticket_types[].price. RÉACTIVÉE 2026-08-25 (l'écartement « aucun filtre géo » est invalidé — le paramètre fonctionne)",
468 + "acces_legal": "robots.txt Allow: /api/ (API publique explicitement autorisée), GET throttlés 0,8 s, identification honnête",
469 + "couverture": "~30-40 événements QC actifs (soirées, croisières, festivals, Candlelight) — toutes régions balayées",
470 + "cadence": "quotidienne",
471 + "statut": "actif"
491 472 },
492 473 {
493 474 "id": "weezevent",
@@ -495,11 +476,11 @@
495 476 "url": "https://www.weezevent.com/",
496 477 "type": "billetterie",
497 478 "extraction": "—",
498 − "acces_legal": "robots.txt permissif",
499 − "couverture": "Festivals/événements FR + QC",
479 + "acces_legal": "robots.txt permissif mais aucune surface de découverte publique",
480 + "couverture": "minisites my.weezevent.com avec JSON-LD Event complet, mais non énumérables (sitemap = 28 URLs tournantes France, pas de recherche publique, API par organisateur)",
500 481 "cadence": "—",
501 482 "statut": "abandonnee",
502 − "raison": "ÉCARTÉE 2026-08-22 (Phase 5) : sitemap = slugs relatifs sans domaine/date/lieu, très majoritairement France ; aucun index public des événements QC. Les gros festivals QC clients de Weezevent sont déjà captés par les ATR/agrégateurs."
483 + "note": "ÉCART CONFIRMÉ 2026-08-25 : aucune surface d'énumération publique — tout connecteur dépendrait de l'index Google. Les gros clients QC sont déjà captés par les ATR/agrégateurs."
503 484 },
504 485 {
505 486 "id": "accesculture",
@@ -507,11 +488,287 @@
507 488 "url": "https://www.accesculture.com/",
508 489 "type": "agregateur",
509 490 "extraction": "—",
510 − "acces_legal": "robots.txt permissif, sitemap public",
511 − "couverture": "Diffuseurs des arrondissements de Montréal",
491 + "acces_legal": "—",
492 + "couverture": "AUCUNE : domaine recyclé (ferme SEO hôtels) hébergeant une copie fossilisée du site 2015 ; toute date « déduite » serait fabriquée",
512 493 "cadence": "—",
513 494 "statut": "abandonnee",
514 − "raison": "ÉCARTÉE 2026-08-22 (Phase 5) : SPA prerender (apiUrl json.backend.accesculture.com non documentée) et billetterie déléguée à La Vitrine (cartUrl secure.lavitrine.com) → catalogue déjà largement couvert par le connecteur lavitrine (5 000+ actifs). Risque de doublons > gain."
495 + "note": "RE-SONDÉE 2026-08-25 : domaine squatté, contenu figé 2015 (dates réelles : mars-mai 2015) — connecteur retiré pour éviter la fabrication de dates. L'offre des maisons de la culture reste couverte par montreal + lavitrine."
496 + },
497 + {
498 + "id": "cotenord",
499 + "name": "Tourisme Côte-Nord — événements (ATR)",
500 + "url": "https://www.tourismecote-nord.com/fr/activites/culture-et-decouverte/evenements-et-spectacles/",
501 + "type": "tourisme-regional",
502 + "extraction": "JSON embarqué « var fiches » de la page liste (1 GET/sync : titre, description, dates ISO, ville, adresse, GPS, tarif, site web) + fiches détail incrémentales pour l'image (cache, CN_FICHE_MAX=10, re-visite 7 j)",
503 + "acces_legal": "Pages publiques de l'ATR, robots.txt « Allow: / » — GET throttlés, UA honnête, lien vers la fiche originale",
504 + "couverture": "Côte-Nord (Manicouagan + Duplessis)",
505 + "cadence": "hebdomadaire",
506 + "statut": "actif"
507 + },
508 + {
509 + "id": "abitibi",
510 + "name": "Tourisme Abitibi-Témiscamingue — festivals et événements (ATR)",
511 + "url": "https://abitibi-temiscamingue.org/festivals-et-evenements/",
512 + "type": "tourisme-regional",
513 + "extraction": "Pages liste SSR (3-4 pages × 12 cartes : titre, plage de dates FR, ville, image Tourinsoft) via auto-escalade anti-bot (Cloudflare challenge → Scrapfly ASP), budget ABT_PAGE_MAX=4 requêtes/sync, aucune fiche détail",
514 + "acces_legal": "Pages publiques de l'ATR — requêtes throttlées et plafonnées (coût Scrapfly), UA honnête, lien vers la fiche originale",
515 + "couverture": "Abitibi-Témiscamingue",
516 + "cadence": "hebdomadaire",
517 + "statut": "actif"
518 + },
519 + {
520 + "id": "ilesdelamadeleine",
521 + "name": "Tourisme Îles-de-la-Madeleine — festivals et événements (ATR)",
522 + "url": "https://www.tourismeilesdelamadeleine.com/fr/decouvrir-les-iles/experiences/culture/festivals-et-evenements/",
523 + "type": "tourisme-regional",
524 + "extraction": "Cartes SSR de la liste (titre, dates FR, catégorie, image) + fiches détail incrémentales (microdonnées schema.org : adresse/ville/CP, description, dates ; cache, IDM_FICHE_MAX=10, re-visite 7 j)",
525 + "acces_legal": "Pages publiques de l'ATR, robots.txt permissif — GET throttlés, UA honnête, lien vers la fiche originale",
526 + "couverture": "Îles-de-la-Madeleine",
527 + "cadence": "hebdomadaire",
528 + "statut": "actif"
529 + },
530 + {
531 + "id": "charlevoix",
532 + "name": "Tourisme Charlevoix — calendrier des événements (ATR)",
533 + "url": "https://www.tourisme-charlevoix.com/fr/evenements",
534 + "type": "tourisme-regional",
535 + "extraction": "Index Algolia public du site (prod_event_fr, clé search-only embarquée dans le JS) : 1 requête/sync (end>=hier) → titre, description, adresse, GPS, catégories, dates+heures réelles ; enrichissement /api/get-event incrémental (image, site web, prix, gratuité ; cache, CHX_FICHE_MAX=20, re-visite 7 j)",
536 + "acces_legal": "Clé Algolia « search-only » et API publiées par le site lui-même — requêtes throttlées, UA honnête, lien vers la fiche originale",
537 + "couverture": "Charlevoix (→ Capitale-Nationale)",
538 + "cadence": "quotidienne",
539 + "statut": "actif"
540 + },
541 + {
542 + "id": "eeyouistchee",
543 + "name": "Eeyou Istchee Baie-James — événements (ATR)",
544 + "url": "https://www.eeyouistcheebaiejames.com/fr/quoi-faire/evenements/",
545 + "type": "tourisme-regional",
546 + "extraction": "Cartes SSR de la liste (titre, ville, plage de dates FR, image) + fiches détail incrémentales (description, code postal, site web ; cache, EIB_FICHE_MAX=10, re-visite 7 j)",
547 + "acces_legal": "Pages publiques de l'ATR (domaine decrochezcommejamais.com → redirection) — GET throttlés, UA honnête, lien vers la fiche originale",
548 + "couverture": "Eeyou Istchee Baie-James (→ Nord-du-Québec)",
549 + "cadence": "hebdomadaire",
550 + "statut": "actif"
551 + },
552 + {
553 + "id": "tourismelaval",
554 + "name": "Tourisme Laval — événements (ATR)",
555 + "url": "https://www.tourismelaval.com/explorer-possibilites/evenements/",
556 + "type": "tourisme-regional",
557 + "extraction": "Cartes SSR FacetWP (CPT event, 2 pages : titre, plage de dates FR, catégorie, extrait, image) — REST wp/v2/event verrouillé (401 Kadence) et calendrier tribe mort à la source (déc. 2025)",
558 + "acces_legal": "Pages publiques de l'ATR — GET throttlés, UA honnête, lien vers la fiche originale",
559 + "couverture": "Laval",
560 + "cadence": "hebdomadaire",
561 + "statut": "actif"
562 + },
563 + {
564 + "id": "quebeccite",
565 + "name": "Québec cité — calendrier des événements (ATR)",
566 + "url": "https://www.quebec-cite.com/fr/quoi-faire-quebec/evenements",
567 + "type": "tourisme-regional",
568 + "extraction": "Index Algolia public du site (default_fr_event_date_start_asc, clé search-only du thème Drupal) : 1 requête/sync (event_date_end>=hier) → titre, résumé, salle, GPS, prix, gratuité, catégories, image, lien officiel ; dates à 12:00 UTC sur tout l'index = jour entier → aucune heure émise",
569 + "acces_legal": "Clé Algolia « search-only » publiée par le site lui-même — requêtes throttlées, UA honnête, lien vers la fiche originale",
570 + "couverture": "Région de Québec (→ Capitale-Nationale)",
571 + "cadence": "quotidienne",
572 + "statut": "actif"
573 + },
574 + {
575 + "id": "mtlorg",
576 + "name": "Tourisme Montréal (mtl.org) — festivals et événements (ATR)",
577 + "url": "https://www.mtl.org/fr/quoi-faire/festivals-et-evenements",
578 + "type": "tourisme-regional",
579 + "extraction": "Index Algolia public du site (prod_fr, facettes _sections:Festivals et événements + content_type:product) : 1 requête/sync → titre, description, plages de dates structurées _open_dates, adresse, GPS, sous-catégories, image CDN ; les fiches sans date (salles/attraits) sont écartées",
580 + "acces_legal": "Clé Algolia « search-only » publiée par le site lui-même — requêtes throttlées, UA honnête, lien vers la fiche originale",
581 + "couverture": "Montréal",
582 + "cadence": "quotidienne",
583 + "statut": "actif"
584 + },
585 + {
586 + "id": "saguenaylacsaintjean",
587 + "name": "Tourisme Saguenay–Lac-Saint-Jean (ATR)",
588 + "url": "https://saguenaylacsaintjean.ca/quoi-faire",
589 + "type": "tourisme-regional",
590 + "extraction": "n/d",
591 + "acces_legal": "n/d",
592 + "couverture": "Saguenay–Lac-Saint-Jean",
593 + "cadence": "n/d",
594 + "statut": "abandonnee",
595 + "note": "ÉCARTÉE 2026-08-25 (vague ATR) : site TYPO3 = annuaire d'attraits SANS calendrier — aucune date d'événement publiée (fiches festivals = horaires d'ouverture seulement, vérifié live + sitemap). La ville de Saguenay reste couverte par saguenaysorties. À re-sonder si l'ATR publie un agenda."
596 + },
597 + {
598 + "id": "troisrivieres",
599 + "name": "Ville de Trois-Rivières — Calendrier des activités",
600 + "url": "https://www.v3r.net/activites-et-loisirs/calendrier-des-activites",
601 + "type": "municipal",
602 + "extraction": "endpoint JSON public /ajax/wp-queries/archive-activites.php (POST, 1 requête — celui du composant AngularJS ; wp-json 401 contourné proprement). Remplace la source « trois-rivieres » abandonnée (API WP verrouillée)",
603 + "acces_legal": "site municipal public, robots.txt permissif, identification honnête",
604 + "couverture": "Trois-Rivières (Mauricie)",
605 + "cadence": "quotidien",
606 + "statut": "actif"
607 + },
608 + {
609 + "id": "rimouski",
610 + "name": "Ville de Rimouski — Événements",
611 + "url": "https://rimouski.ca/loisirs-culture/activites/evenements",
612 + "type": "municipal",
613 + "extraction": "flux RSS étendu du site (1 requête, balises maison startDate/eventTime/location — salle et adresse séparées)",
614 + "acces_legal": "flux RSS public du site municipal",
615 + "couverture": "Rimouski (Bas-Saint-Laurent)",
616 + "cadence": "quotidien",
617 + "statut": "actif"
618 + },
619 + {
620 + "id": "victoriaville",
621 + "name": "Ville de Victoriaville — Quoi faire à Victo",
622 + "url": "https://victoriaville.ca/calendriers-cours-et-ateliers/quoi-faire-a-victo",
623 + "type": "municipal",
624 + "extraction": "HTML serveur Craft, navigation ?dateDebut par semaine (VIC_WEEKS=4) ; « 00h00 » = placeholder toute-la-journée → heure None",
625 + "acces_legal": "page municipale publique, robots.txt permissif",
626 + "couverture": "Victoriaville (Centre-du-Québec)",
627 + "cadence": "quotidien",
628 + "statut": "actif"
629 + },
630 + {
631 + "id": "drummondville",
632 + "name": "Ville de Drummondville — Calendrier des événements",
633 + "url": "https://www.drummondville.ca/culture-loisirs-et-sports/calendrier-des-evenements/",
634 + "type": "municipal",
635 + "extraction": "HTML serveur Toolset (?wpv_view_count extrait du formulaire + wpv_paged) + fiches (lieu, coût) en cache incrémental (DRU_FICHES=40)",
636 + "acces_legal": "site municipal public, robots.txt permissif (wp-admin seulement)",
637 + "couverture": "Drummondville (Centre-du-Québec)",
638 + "cadence": "quotidien",
639 + "statut": "actif"
640 + },
641 + {
642 + "id": "granby",
643 + "name": "Ville de Granby — Calendrier des événements (liste éditoriale)",
644 + "url": "https://www.granby.ca/fr/calendrier-evenements",
645 + "type": "municipal",
646 + "extraction": "HTML serveur (blocs éditoriaux h5 + dates FR + lien externe), parsing tolérant, 1 requête — aucun calendrier structuré à la source (jsonapi désactivé, listing Drupal vide côté serveur)",
647 + "acces_legal": "site municipal public, robots.txt permissif",
648 + "couverture": "Granby (Estrie)",
649 + "cadence": "hebdomadaire",
650 + "statut": "actif"
651 + },
652 + {
653 + "id": "saintjerome",
654 + "name": "Ville de Saint-Jérôme — Calendrier",
655 + "url": "https://www.vsj.ca/calendrier/",
656 + "type": "municipal",
657 + "extraction": "HTML serveur FacetWP (?fwp_calendar_dates=<aujourd'hui>&fwp_paged=N, 10 cartes/page) — date, heure, lieu+adresse, description, image",
658 + "acces_legal": "site municipal public, robots.txt permissif",
659 + "couverture": "Saint-Jérôme (Laurentides)",
660 + "cadence": "quotidien",
661 + "statut": "actif"
662 + },
663 + {
664 + "id": "sainthyacinthe",
665 + "name": "Ville de Saint-Hyacinthe — Calendrier des événements",
666 + "url": "https://www.st-hyacinthe.ca/loisirs-et-culture/evenement/calendrier-des-evenements",
667 + "type": "municipal",
668 + "extraction": "HTML serveur (?ms=2 « tous les mois », 1 requête) + fiches modales /php/load-modal-info.php en cache incrémental (STH_FICHES=60) : lieu, clientèle, organisateur, gratuité",
669 + "acces_legal": "site municipal public, GET throttlés, identification honnête",
670 + "couverture": "Saint-Hyacinthe (Montérégie)",
671 + "cadence": "quotidien",
672 + "statut": "actif"
673 + },
674 + {
675 + "id": "rouynnoranda",
676 + "name": "Ville de Rouyn-Noranda — Événements",
677 + "url": "https://www.rouyn-noranda.ca/evenements",
678 + "type": "municipal",
679 + "extraction": "HTML serveur OctoberCMS + fiches (heures/lieu) en cache ; dates publiées SANS année → résolution « prochaine occurrence » documentée",
680 + "acces_legal": "site municipal public, robots.txt permissif",
681 + "couverture": "Rouyn-Noranda (Abitibi-Témiscamingue)",
682 + "cadence": "quotidien",
683 + "statut": "actif"
684 + },
685 + {
686 + "id": "shawinigan",
687 + "name": "Ville de Shawinigan — Calendrier des événements",
688 + "url": "https://www.shawinigan.ca/loisirs-et-culture/evenements/calendrier/",
689 + "type": "municipal",
690 + "extraction": "HTML serveur (plugin tri-evenements, cartes -single et -list, date fiable dans l'URL) + fiches (date de fin, lieu) en cache",
691 + "acces_legal": "site municipal public, robots.txt permissif",
692 + "couverture": "Shawinigan (Mauricie)",
693 + "cadence": "quotidien",
694 + "statut": "actif"
695 + },
696 + {
697 + "id": "joliette",
698 + "name": "Ville de Joliette — Événements",
699 + "url": "https://www.joliette.ca/evenements",
700 + "type": "municipal",
701 + "extraction": "HTML serveur OctoberCMS/Blanko /evenements/N + fiches (heures, « Gratuit ») en cache",
702 + "acces_legal": "site municipal public, robots.txt permissif",
703 + "couverture": "Joliette (Lanaudière)",
704 + "cadence": "quotidien",
705 + "statut": "actif"
706 + },
707 + {
708 + "id": "magog",
709 + "name": "Ville de Magog — Événements et activités (agenda Google public)",
710 + "url": "https://www.ville.magog.qc.ca/calendrier/evenements-et-activites/",
711 + "type": "municipal",
712 + "extraction": "flux iCal Google public embarqué par la page municipale (1 232 VEVENT, 1 requête) : conversion UTC/TZID/VALUE=DATE + expansion RRULE DAILY/WEEKLY (COUNT/UNTIL/BYDAY) plafonnée à 365 j",
713 + "acces_legal": "calendrier Google publié publiquement par la Ville sur sa page",
714 + "couverture": "Magog (Estrie)",
715 + "cadence": "quotidien",
716 + "statut": "actif"
717 + },
718 + {
719 + "id": "soreltracy",
720 + "name": "Ville de Sorel-Tracy — Événements",
721 + "url": "https://ville.sorel-tracy.qc.ca/evenements",
722 + "type": "municipal",
723 + "extraction": "HTML serveur OctoberCMS /evenements/N + fiches (heures, lieu, coût) en cache (SOR_FICHES=40)",
724 + "acces_legal": "site municipal public, robots.txt permissif",
725 + "couverture": "Sorel-Tracy (Montérégie)",
726 + "cadence": "quotidien",
727 + "statut": "actif"
728 + },
729 + {
730 + "id": "signelaval",
731 + "name": "Signé Laval — calendrier culturel officiel de Laval",
732 + "url": "https://signelaval.com/",
733 + "type": "agregateur",
734 + "extraction": "sitemap public /sitemap/events/N.xml puis fiches rendues serveur : JSON-LD Event Footlight/artsdata (dates ISO -04:00, lieu, adresse, image IIIF, organisateur ; champs multilingues en liste → variante fr) ; incrémental cache 7 j, plafond SIGNELAVAL_MAX_FETCH",
735 + "acces_legal": "robots.txt sans Disallow + sitemap public, GET directs throttlés 0,8 s, identification honnête",
736 + "couverture": "~290 fiches (bibliothèques, spectacles, expositions, plein air — Laval)",
737 + "cadence": "hebdomadaire",
738 + "statut": "actif"
739 + },
740 + {
741 + "id": "quoifaire",
742 + "name": "Quoi faire — agrégateur de sorties québécois",
743 + "url": "https://quoifaire.com/",
744 + "type": "agregateur",
745 + "extraction": "listes HTML rendues serveur /<ville>/evenements (+?page=N, 5 sections villes) puis fiches serveur : dates FR, heure (À confirmer→None), prix publié brut, région touristique, lieu/adresse, lat/lng Google Maps, auditoire, og:image ; incrémental cache 7 j, plafond QUOIFAIRE_DETAIL_MAX=150",
746 + "acces_legal": "robots.txt Allow: / (seuls /search et admin exclus, non visités), GET throttlés 0,8 s, identification honnête",
747 + "couverture": "centaines de fiches à venir (Québec, Montréal, Saguenay, Outaouais, Mauricie) — site lent, sections parfois 502 (tolérées)",
748 + "cadence": "hebdomadaire",
749 + "statut": "actif"
750 + },
751 + {
752 + "id": "evenementsattractions",
753 + "name": "Événements Attractions Québec (quebecvacances.com)",
754 + "url": "https://www.quebecvacances.com/",
755 + "type": "agregateur",
756 + "extraction": "sitemap paginé /sitemap.xml?page=1..4 → fiches /fr/evenements/<slug> JSON-LD Event (dates de la prochaine édition, lieu, ville, image Cloudinary) ; anti-bot 403 → chaîne de secours Oxylabs/Scrapfly/Bright Data avec réparation d'encodage UTF-8 ; incrémental cache 14 j, plafond EAQ_DETAIL_MAX=80",
757 + "acces_legal": "robots.txt User-agent: * / Allow: / ; rate-limiting poli, identification honnête ; coût escalade contenu par cache",
758 + "couverture": "696 fiches sitemap (festivals/événements membres EAQ, toute la province) — ~1/3 avec dates publiées",
759 + "cadence": "hebdomadaire",
760 + "statut": "actif"
761 + },
762 + {
763 + "id": "banq",
764 + "name": "BAnQ — calendrier des activités",
765 + "url": "https://www.banq.qc.ca/calendrier/a-venir",
766 + "type": "agregateur",
767 + "extraction": "Scrapfly obligatoire (anti-bot Radware) : liste rendue JS → fiches HTML serveur via Scrapfly sans JS, JSON-LD Event complet (dates ISO -04:00, lieu, adresse, image, organisateur, offers.price 0.00 = gratuité publiée) ; incrémental cache 14 j, plafond BANQ_DETAIL_MAX=50",
768 + "acces_legal": "robots.txt Drupal standard (calendrier non exclu) ; 1 rendu JS + ≤50 fiches par sync, cadence contenue",
769 + "couverture": "~140 activités à venir (Grande Bibliothèque + centres régionaux — ateliers, conférences, heure du conte)",
770 + "cadence": "hebdomadaire",
771 + "statut": "actif"
515 772 }
516 773 ]
517 774 }
\ No newline at end of file
modified data/ticketpro_tenants.json +4 −2
@@ -1,12 +1,14 @@
1 1 {
2 − "_comment": "Sorti-Ka — tenants québécois de la billetterie Ticketpro (nouvelle plateforme SPA, API /api/v1). Auteur : Simon-Pierre Boucher <contact@spboucher.ai>. Chaque tenant = <domaine>.ticketpro.ca ; l'API /api/v1/pages y est publique (vérifié 2026-08-22). tproqc agrège la majorité des salles QC mais PAS tprotsd (vérifié) → les deux sont requis, dédup par id de page. Tenants hors Québec (tproatlantic, tprobarriecolts, goredsgo…) exclus par périmètre. tpromuseerodrigue exclu : créneaux d'admission de musée (176 plages quasi identiques), pas des événements. exclure_lieux : réservations de restaurants (soupers-spectacles du groupe St-Denis) filtrées par nom de lieu normalisé (sous-chaîne, sans accents ni casse).",
2 + "_comment": "Sorti-Ka — tenants québécois de la billetterie Ticketpro (nouvelle plateforme SPA, API /api/v1). Auteur : Simon-Pierre Boucher <contact@spboucher.ai>. Chaque tenant = <domaine>.ticketpro.ca ; l'API /api/v1/pages y est publique (vérifié 2026-08-22). tproqc agrège la majorité des salles QC mais PAS tprotsd (vérifié) → les deux sont requis, dédup par id de page. Tenants hors Québec (tproatlantic, tprobarriecolts, goredsgo, tpromaclab/tprocob/tproastor/ticketsnw/tprowest/tproshelltheatre — vérifiés AB/ON/NS/BC 2026-08-25…) exclus par périmètre. Tenants QC REDONDANTS avec tproqc (0 page nouvelle, vérifié live 2026-08-25) — ne pas ajouter : pierremercure, espacestdenis, tproestival. tpromuseerodrigue exclu : créneaux d'admission de musée (176 plages quasi identiques), pas des événements. exclure_lieux : réservations de restaurants (soupers-spectacles du groupe St-Denis) filtrées par nom de lieu normalisé (sous-chaîne, sans accents ni casse).",
3 3 "tenants": [
4 4 { "domaine": "tproqc", "nom": "Ticketpro Québec (agrégateur provincial)" },
5 5 { "domaine": "tprotsd", "nom": "Théâtre St-Denis / Studio St-Denis (Montréal)" },
6 6 { "domaine": "tprofunnation", "nom": "Fun Nation (Montréal)" },
7 7 { "domaine": "expocitetpro", "nom": "ExpoCité — Centre de foires & Pavillon Guy-Lafleur (Québec)" },
8 8 { "domaine": "sag", "nom": "Salle André-Gagnon (La Pocatière)" },
9 − { "domaine": "tpropdc", "nom": "Palais des congrès de Montréal" }
9 + { "domaine": "tpropdc", "nom": "Palais des congrès de Montréal" },
10 + { "domaine": "arsenalcontemporarysale", "nom": "Arsenal art contemporain (Montréal)" },
11 + { "domaine": "lefifa", "nom": "Le FIFA — Festival international du film sur l'art (Montréal)" }
10 12 ],
11 13 "exclure_lieux": [
12 14 "restaurant le moliere",
modified data/tuxedo_tenants.json +8 −2
@@ -1,5 +1,5 @@
1 1 {
2 − "_comment": "Sorti-Ka — Tenants Tuxedo Billet (*.tuxedobillet.com) moissonnés par l'acteur Apify gorgeous_thistle/ka-tuxedo. Auteur : Simon-Pierre Boucher — contact@spboucher.ai. QUÉBEC SEULEMENT (sagouine = Nouveau-Brunswick, exclu). Découverts via Serper le 2026-08-21 (énumération non exhaustive — ajouter un tenant = un bloc ici, 0 code). Ville vérifiée avant ajout (espacetheatre = Mont-Laurier, PAS Saint-Hyacinthe). Les tenants à ville incertaine (lacaserne, legrandespace, salledesilets, arcf, billetterieacces) sont EXCLUS en attendant vérification.",
2 + "_comment": "Sorti-Ka — Tenants Tuxedo Billet (*.tuxedobillet.com) moissonnés par l'acteur Apify gorgeous_thistle/ka-tuxedo. Auteur : Simon-Pierre Boucher — contact@spboucher.ai. QUÉBEC SEULEMENT (sagouine, arcf = Saint-Jean NB et billetterieacces/evencia = Acadie NB, exclus). Découverts via Serper 2026-08-21, 2e vague 2026-08-25 (validation par rendu Scrapfly : tuiles .tuxCmsEdit__webShowTile + liens /main/<slug>, le format que l'acteur extrait). Ville vérifiée avant ajout (espacetheatre = Mont-Laurier ; lesaintjean = 774 rue Saint-Jean à Québec ; chateauscenes = Ville de Châteauguay). ⚠️ TENANTS INCOMPATIBLES avec l'acteur actuel (tuiles rendues mais liens NON /main/ : format /Web/<slug> ou /<nom-tenant>/<slug>) — à ajouter quand ka-tuxedo saura les lire (volume constaté 2026-08-25) : theatregillesvigneault (Saint-Jérôme, ~172 tuiles), rdlenspectacles (Rivière-du-Loup, ~50), salledesilets (Montréal, ~41), centredesartsbc (Baie-Comeau, ~35), legrandespace (Sherbrooke, ~27), lacaserne (Québec, ~21), larotonde (Québec, ~13), felixleclerc (Saint-Pierre-de-l'Île-d'Orléans, ~7), fesp (Fermont, ~1), culture3r (Trois-Rivières, doublon Ovation 00000W). Sondés VIDES 2026-08-25 (re-sonder plus tard) : misqa, valmorin, maisonpourladanse, smamontreal, theatredubic. theatredaujourdhui-abonnement non ajouté (abonnements seulement).",
3 3 "tenants": [
4 4 {"domaine": "espacetheatre", "nom": "Espace Théâtre", "ville": "Mont-Laurier"},
5 5 {"domaine": "theatreoutremont", "nom": "Théâtre Outremont", "ville": "Montréal"},
@@ -28,6 +28,12 @@
28 28 {"domaine": "centrepierrepeladeau", "nom": "Centre Pierre-Péladeau", "ville": "Montréal"},
29 29 {"domaine": "latribu-lenational", "nom": "Le National (La Tribu)", "ville": "Montréal"},
30 30 {"domaine": "cdn-ndg", "nom": "Maisons de la culture CDN–NDG", "ville": "Montréal"},
31 − {"domaine": "mcgillmusic", "nom": "École de musique Schulich — McGill", "ville": "Montréal"}
31 + {"domaine": "mcgillmusic", "nom": "École de musique Schulich — McGill", "ville": "Montréal"},
32 + {"domaine": "chateauscenes", "nom": "Château Scènes (Ville de Châteauguay)", "ville": "Châteauguay"},
33 + {"domaine": "azimutdiffusion", "nom": "Azimut Diffusion", "ville": "Sorel-Tracy"},
34 + {"domaine": "culture-umontreal", "nom": "Activités culturelles de l'Université de Montréal", "ville": "Montréal"},
35 + {"domaine": "lesaintjean", "nom": "Le Saint-Jean Comédie-Club", "ville": "Québec"},
36 + {"domaine": "osjm", "nom": "Orchestre symphonique des jeunes de Montréal", "ville": "Montréal"},
37 + {"domaine": "mcgillathletics", "nom": "McGill Athletics & Recreation", "ville": "Montréal"}
32 38 ]
33 39 }
modified docs/connecteurs/INDEX.md +67 −43
@@ -1,8 +1,8 @@
1 1 # Sorti-Ka — Connecteurs de sources d'événements
2 2
3 −> Index généré le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` (rejouable : `python3 scripts/gen_connector_docs.py`). Croise `data/sources.json`, l'introspection de `sortika/connectors/*.py` et la base live `data/sortika.db`.
3 +> Index généré le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` (rejouable : `python3 scripts/gen_connector_docs.py`). Croise `data/sources.json`, l'introspection de `sortika/connectors/*.py` et la base live `data/sortika.db`.
4 4
5 −**44 sources au registre** — 31 actives/prêtes, 13 abandonnées · **25969 événements en base** (dont 18244 actifs).
5 +**68 sources au registre** — 62 actives/prêtes, 6 abandonnées · **31938 événements en base** (dont 22310 actifs).
6 6
7 7 ⚠️ **Attributions requises** : Ville de Montréal (CC BY 4.0) ; SIT Québec / Ministère du Tourisme et Ville de Laval (CC 4.0 — Données Québec). Détails dans chaque fiche, section « Licence / attribution ».
8 8
@@ -10,37 +10,68 @@
10 10
11 11 | Source | Type | Statut | Actifs | GPS | Prix | Heure | Artistes | Image | Salle | Dernier sync |
12 12 |---|---|---|---|---|---|---|---|---|---|---|
13 −| [SIT Québec — Ministère du Tourisme](sitq.md) | donnees-ouvertes | actif | 1014 | 97 % | 0 % | 0 % | 0 % | 0 % | 0 % | 63 % | 74 % | 2026-08-22 22:28 |
14 −| [Ville de Montréal — Événements publics](montreal.md) | donnees-ouvertes | actif | 3522 | 99 % | 100 % | 99 % | 99 % | 0 % | 0 % | 100 % | 4 % | 2026-08-22 22:27 |
15 −| [Le point de vente — billetterie québécoise](lepointdevente.md) | billetterie | actif | 1790 | 30 % | 92 % | 93 % | 0 % | 0 % | 1 % | 100 % | 99 % | 2026-08-22 22:27 |
16 −| [Ville de Laval — Calendrier des activités](laval.md) | donnees-ouvertes | morte-a-la-source | 17 | 0 % | 0 % | 100 % | 0 % | 0 % | 0 % | 100 % | 94 % | 2026-08-22 23:03 |
17 −| [Ville de Sherbrooke — Calendrier des événements](sherbrooke.md) | municipal | actif | 109 | 0 % | 100 % | 85 % | 92 % | 0 % | 0 % | 99 % | 0 % | 2026-08-22 22:28 |
18 −| [Ville de Longueuil — Événements (nouveau portail)](longueuil.md) | municipal | actif | 4 | 75 % | 0 % | 100 % | 50 % | 0 % | 0 % | 100 % | 0 % | 2026-08-22 22:27 |
19 −| [Eventbrite — billetterie libre-service (Québec)](eventbrite.md) | billetterie | actif | 435 | 100 % | 88 % | 100 % | 100 % | 0 % | 0 % | 98 % | 100 % | 2026-08-22 23:03 |
20 −| [Tourisme Cantons-de-l'Est — calendrier officiel (ATR Estrie)](cantonsdelest.md) | tourisme-regional | actif | 11 | 82 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-22 23:02 |
21 −| [evenko — grands concerts et festivals](evenko.md) | promoteur | actif | 413 | 97 % | 0 % | 99 % | 0 % | 1 % | 99 % | 100 % | 100 % | 2026-08-22 23:02 |
22 −| [Atuvu.ca — calendrier culturel](atuvu.md) | agregateur | actif | 217 | 82 % | 24 % | 46 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-22 23:01 |
23 −| [La Vitrine culturelle](lavitrine.md) | billetterie | actif | 5039 | 69 % | 0 % | 100 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-22 22:26 |
24 −| [Ville de Brossard — Calendrier des événements](brossard.md) | municipal | actif | 146 | 95 % | 0 % | 96 % | 0 % | 0 % | 0 % | 100 % | 95 % | 2026-08-22 23:02 |
25 −| [Ticketmaster — Discovery API (Québec)](ticketmaster.md) | billetterie | actif | 359 | 100 % | 0 % | 96 % | 0 % | 4 % | 0 % | 89 % | 100 % | 2026-08-22 22:29 |
26 −| [Bandsintown — dates de concerts par artiste](bandsintown.md) | agregateur | actif | 519 | 100 % | 1 % | 100 % | 0 % | 0 % | 100 % | 8 % | 100 % | 2026-08-22 23:01 |
27 −| [Ville de Laval — calendrier vivant laval.ca](lavalweb.md) | municipal | actif | 443 | 75 % | 0 % | 98 % | 0 % | 0 % | 0 % | 0 % | 100 % | 2026-08-22 22:26 |
28 −| [Tourisme Outaouais — calendrier des événements (ATR)](outaouais.md) | tourisme-regional | actif | 24 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-22 22:27 |
29 −| [Tourisme Lanaudière — événements (ATR)](lanaudiere.md) | tourisme-regional | actif | 44 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 93 % | 0 % | 2026-08-22 23:03 |
30 −| [Sorties Saguenay — portail événementiel de la Ville](saguenaysorties.md) | municipal | actif | 438 | 80 % | 0 % | 100 % | 0 % | 0 % | 0 % | 100 % | 86 % | 2026-08-22 22:28 |
31 −| [Tourisme Bas-Saint-Laurent — événements (ATR)](bassaintlaurent.md) | tourisme-regional | actif | 20 | 100 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-22 23:01 |
32 −| [Québec animée — Ville de Québec](quebecanimee.md) | municipal | actif | 102 | 100 % | 100 % | 0 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-22 22:27 |
33 −| [Place des Arts — calendrier multi-salles](placedesarts.md) | salle-de-spectacle | actif | 346 | 94 % | 73 % | 100 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-22 22:27 |
34 −| [LHJMQ — matchs des équipes québécoises](lhjmq.md) | sport | actif | 417 | 99 % | 0 % | 100 % | 0 % | 0 % | 0 % | 0 % | 100 % | 2026-08-22 22:27 |
35 −| [Salles de spectacle — moteur générique JSON-LD Event](sallesjsonld.md) | salle-de-spectacle | actif | 308 | 100 % | 97 % | 100 % | 53 % | 1 % | 0 % | 13 % | 100 % | 2026-08-22 22:59 |
36 −| [Tourisme Gaspésie — événements (ATR)](gaspesie.md) | tourisme-regional | actif | 10 | 90 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-22 23:03 |
37 −| [Tuxedo Billet — billetterie québécoise multi-tenants](tuxedobillet.md) | billetterie | actif | 442 | 44 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-22 22:29 |
38 −| [Tourisme Montérégie — événements (ATR)](monteregie.md) | tourisme-regional | actif | 28 | 100 % | 0 % | 0 % | 0 % | 0 % | 0 % | 96 % | 0 % | 2026-08-22 22:27 |
39 −| [Tourisme Centre-du-Québec — événements (ATR)](centreduquebec.md) | tourisme-regional | actif | 9 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-22 23:02 |
40 −| [Tourisme Chaudière-Appalaches — festivals et événements (ATR)](chaudiereappalaches.md) | tourisme-regional | actif | 28 | 100 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-22 23:02 |
41 −| [TicketAcces.net — billetterie régionale québécoise](ticketacces.md) | billetterie | actif | 1318 | 74 % | 21 % | 100 % | 0 % | 0 % | 0 % | 99 % | 100 % | 2026-08-22 22:28 |
42 −| [Réseau Ovation — billetterie québécoise multi-salles](ovation.md) | billetterie | actif | 311 | 100 % | 0 % | 100 % | 0 % | 0 % | 90 % | 100 % | 100 % | 2026-08-22 22:27 |
43 −| [Ticketpro — billetterie canadienne (tenants Québec)](ticketpro.md) | billetterie | actif | 361 | 99 % | 0 % | 100 % | 0 % | 0 % | 78 % | 100 % | 100 % | 2026-08-22 22:57 |
13 +| [SIT Québec — Ministère du Tourisme](sitq.md) | donnees-ouvertes | actif | 963 | 97 % | 0 % | 0 % | 0 % | 0 % | 0 % | 63 % | 75 % | 2026-08-25 18:06 |
14 +| [Ville de Montréal — Événements publics](montreal.md) | donnees-ouvertes | actif | 3691 | 99 % | 100 % | 99 % | 99 % | 0 % | 0 % | 100 % | 4 % | 2026-08-25 18:05 |
15 +| [Le point de vente — billetterie québécoise](lepointdevente.md) | billetterie | actif | 1734 | 34 % | 93 % | 94 % | 0 % | 0 % | 1 % | 100 % | 99 % | 2026-08-25 18:05 |
16 +| [Ville de Laval — Calendrier des activités](laval.md) | donnees-ouvertes | morte-a-la-source | 1 | 0 % | 0 % | 100 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 18:01 |
17 +| [Ville de Sherbrooke — Calendrier des événements](sherbrooke.md) | municipal | actif | 141 | 0 % | 100 % | 87 % | 94 % | 0 % | 0 % | 99 % | 0 % | 2026-08-25 18:06 |
18 +| [Ville de Longueuil — Événements (nouveau portail)](longueuil.md) | municipal | actif | 4 | 75 % | 0 % | 100 % | 50 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 18:05 |
19 +| [Eventbrite — billetterie libre-service (Québec)](eventbrite.md) | billetterie | actif | 435 | 100 % | 88 % | 100 % | 100 % | 0 % | 0 % | 98 % | 100 % | 2026-08-25 18:01 |
20 +| [Tourisme Cantons-de-l'Est — calendrier officiel (ATR Estrie)](cantonsdelest.md) | tourisme-regional | actif | 12 | 100 % | 0 % | 0 % | 0 % | 0 % | 0 % | 92 % | 0 % | 2026-08-25 18:00 |
21 +| [evenko — grands concerts et festivals](evenko.md) | promoteur | actif | 621 | 84 % | 0 % | 100 % | 0 % | 1 % | 100 % | 100 % | 100 % | 2026-08-25 18:00 |
22 +| [Atuvu.ca — calendrier culturel](atuvu.md) | agregateur | actif | 227 | 82 % | 22 % | 56 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 17:59 |
23 +| [La Vitrine culturelle](lavitrine.md) | billetterie | actif | 5008 | 72 % | 0 % | 100 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 18:04 |
24 +| [Ville de Brossard — Calendrier des événements](brossard.md) | municipal | actif | 147 | 94 % | 0 % | 95 % | 95 % | 0 % | 0 % | 100 % | 94 % | 2026-08-25 18:00 |
25 +| [Ticketmaster — Discovery API (Québec)](ticketmaster.md) | billetterie | actif | 360 | 100 % | 0 % | 97 % | 0 % | 4 % | 0 % | 88 % | 100 % | 2026-08-25 18:07 |
26 +| [Bandsintown — dates de concerts par artiste](bandsintown.md) | agregateur | actif | 515 | 100 % | 1 % | 100 % | 0 % | 0 % | 100 % | 8 % | 100 % | 2026-08-25 17:59 |
27 +| [Ville de Gatineau — Calendrier officiel](gatineau.md) | municipal | actif | 254 | 87 % | 0 % | 99 % | 99 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 18:28 |
28 +| [Ville de Lévis — Calendrier des événements](levis.md) | municipal | actif | 153 | 9 % | 0 % | 74 % | 73 % | 0 % | 0 % | 97 % | 12 % | 2026-08-25 18:28 |
29 +| [Ville de Terrebonne — Calendrier](terrebonne.md) | municipal | actif | 31 | 39 % | 0 % | 100 % | 100 % | 0 % | 0 % | 87 % | 100 % | 2026-08-25 18:34 |
30 +| [Ville de Laval — calendrier vivant laval.ca](lavalweb.md) | municipal | actif | 427 | 89 % | 0 % | 98 % | 0 % | 0 % | 0 % | 0 % | 100 % | 2026-08-25 18:04 |
31 +| [Tourisme Outaouais — calendrier des événements (ATR)](outaouais.md) | tourisme-regional | actif | 23 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 18:05 |
32 +| [Tourisme Lanaudière — événements (ATR)](lanaudiere.md) | tourisme-regional | actif | 35 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 91 % | 0 % | 2026-08-25 18:01 |
33 +| [Sorties Saguenay — portail événementiel de la Ville](saguenaysorties.md) | municipal | actif | 438 | 80 % | 0 % | 100 % | 0 % | 0 % | 0 % | 100 % | 86 % | 2026-08-25 18:06 |
34 +| [Tourisme Bas-Saint-Laurent — événements (ATR)](bassaintlaurent.md) | tourisme-regional | actif | 17 | 100 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 17:59 |
35 +| [Québec animée — Ville de Québec](quebecanimee.md) | municipal | actif | 95 | 40 % | 100 % | 0 % | 0 % | 0 % | 0 % | 100 % | 74 % | 2026-08-25 18:05 |
36 +| [Place des Arts — calendrier multi-salles](placedesarts.md) | salle-de-spectacle | actif | 344 | 94 % | 74 % | 100 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 18:05 |
37 +| [LHJMQ — matchs des équipes québécoises](lhjmq.md) | sport | actif | 410 | 99 % | 0 % | 100 % | 0 % | 0 % | 0 % | 0 % | 100 % | 2026-08-25 18:05 |
38 +| [Salles de spectacle — moteur générique JSON-LD Event](sallesjsonld.md) | salle-de-spectacle | actif | 685 | 98 % | 87 % | 90 % | 57 % | 0 % | 0 % | 53 % | 100 % | 2026-08-25 18:53 |
39 +| [Tourisme Mauricie — calendrier des événements (ATR)](mauricie.md) | tourisme-regional | actif | 157 | 94 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 98 % | 2026-08-25 18:24 |
40 +| [Tourisme Laurentides — calendrier des événements (ATR)](laurentides.md) | tourisme-regional | actif | 228 | 2 % | 4 % | 5 % | 5 % | 0 % | 0 % | 92 % | 5 % | 2026-08-25 18:46 |
41 +| [Tourisme Gaspésie — événements (ATR)](gaspesie.md) | tourisme-regional | actif | 7 | 86 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 18:01 |
42 +| [Tuxedo Billet — billetterie québécoise multi-tenants](tuxedobillet.md) | billetterie | actif | 382 | 80 % | 0 % | 9 % | 0 % | 2 % | 0 % | 100 % | 100 % | 2026-08-25 19:00 |
43 +| [Tourisme Montérégie — événements (ATR)](monteregie.md) | tourisme-regional | actif | 26 | 100 % | 0 % | 0 % | 0 % | 0 % | 0 % | 96 % | 0 % | 2026-08-25 18:05 |
44 +| [Tourisme Centre-du-Québec — événements (ATR)](centreduquebec.md) | tourisme-regional | actif | 9 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 18:00 |
45 +| [Tourisme Chaudière-Appalaches — festivals et événements (ATR)](chaudiereappalaches.md) | tourisme-regional | actif | 28 | 100 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 18:00 |
46 +| [TicketAcces.net — billetterie régionale québécoise](ticketacces.md) | billetterie | actif | 1371 | 94 % | 56 % | 100 % | 0 % | 0 % | 0 % | 99 % | 100 % | 2026-08-25 18:06 |
47 +| [Réseau Ovation — billetterie québécoise multi-salles](ovation.md) | billetterie | actif | 459 | 100 % | 0 % | 100 % | 0 % | 0 % | 90 % | 99 % | 100 % | 2026-08-25 18:05 |
48 +| [Ticketpro — billetterie canadienne (tenants Québec)](ticketpro.md) | billetterie | actif | 395 | 99 % | 85 % | 100 % | 0 % | 2 % | 70 % | 100 % | 100 % | 2026-08-25 18:54 |
49 +| [Zeffy — billetterie OSBL](zeffy.md) | billetterie | actif | 15 | 80 % | 100 % | 100 % | 100 % | 0 % | 0 % | 87 % | 0 % | 2026-08-25 18:49 |
50 +| [Showpass — billetterie canadienne](showpass.md) | billetterie | actif | 45 | 100 % | 98 % | 100 % | 89 % | 2 % | 0 % | 89 % | 100 % | 2026-08-25 18:35 |
51 +| [Tourisme Côte-Nord — événements (ATR)](cotenord.md) | tourisme-regional | actif | 10 | 100 % | 20 % | 0 % | 0 % | 0 % | 0 % | 40 % | 0 % | 2026-08-25 18:18 |
52 +| [Tourisme Abitibi-Témiscamingue — festivals et événements (ATR)](abitibi.md) | tourisme-regional | actif | 32 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 18:44 |
53 +| [Tourisme Îles-de-la-Madeleine — festivals et événements (ATR)](ilesdelamadeleine.md) | tourisme-regional | actif | 0 | — | — | — | — | — | — | 2026-08-25 18:18 |
54 +| [Tourisme Charlevoix — calendrier des événements (ATR)](charlevoix.md) | tourisme-regional | actif | 154 | 100 % | 49 % | 97 % | 97 % | 0 % | 0 % | 66 % | 0 % | 2026-08-25 18:25 |
55 +| [Eeyou Istchee Baie-James — événements (ATR)](eeyouistchee.md) | tourisme-regional | actif | 22 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 18:19 |
56 +| [Tourisme Laval — événements (ATR)](tourismelaval.md) | tourisme-regional | actif | 21 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 0 % | 2026-08-25 18:19 |
57 +| [Québec cité — calendrier des événements (ATR)](quebeccite.md) | tourisme-regional | actif | 469 | 97 % | 90 % | 0 % | 0 % | 0 % | 0 % | 100 % | 97 % | 2026-08-25 18:24 |
58 +| [Tourisme Montréal (mtl.org) — festivals et événements (ATR)](mtlorg.md) | tourisme-regional | actif | 546 | 95 % | 0 % | 0 % | 0 % | 0 % | 0 % | 79 % | 92 % | 2026-08-25 18:25 |
59 +| [Ville de Trois-Rivières — Calendrier des activités](troisrivieres.md) | municipal | actif | 17 | 59 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 18:29 |
60 +| [Ville de Rimouski — Événements](rimouski.md) | municipal | actif | 56 | 96 % | 0 % | 89 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 18:28 |
61 +| [Ville de Victoriaville — Quoi faire à Victo](victoriaville.md) | municipal | actif | 290 | 58 % | 0 % | 78 % | 0 % | 0 % | 0 % | 100 % | 99 % | 2026-08-25 18:29 |
62 +| [Ville de Drummondville — Calendrier des événements](drummondville.md) | municipal | actif | 117 | 3 % | 50 % | 91 % | 0 % | 0 % | 0 % | 100 % | 50 % | 2026-08-25 18:34 |
63 +| [Ville de Granby — Calendrier des événements (liste éditoriale)](granby.md) | municipal | actif | 8 | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 0 % | 2026-08-25 18:19 |
64 +| [Ville de Saint-Jérôme — Calendrier](saintjerome.md) | municipal | actif | 28 | 71 % | 0 % | 86 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 18:29 |
65 +| [Ville de Saint-Hyacinthe — Calendrier des événements](sainthyacinthe.md) | municipal | actif | 17 | 41 % | 100 % | 0 % | 0 % | 0 % | 0 % | 100 % | 94 % | 2026-08-25 18:29 |
66 +| [Ville de Rouyn-Noranda — Événements](rouynnoranda.md) | municipal | actif | 4 | 25 % | 0 % | 100 % | 75 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 18:19 |
67 +| [Ville de Shawinigan — Calendrier des événements](shawinigan.md) | municipal | actif | 7 | 14 % | 0 % | 0 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 18:19 |
68 +| [Ville de Joliette — Événements](joliette.md) | municipal | actif | 10 | 70 % | 60 % | 100 % | 60 % | 0 % | 0 % | 100 % | 100 % | 2026-08-25 18:19 |
69 +| [Ville de Magog — Événements et activités (agenda Google public)](magog.md) | municipal | actif | 190 | 99 % | 0 % | 100 % | 100 % | 0 % | 0 % | 0 % | 100 % | 2026-08-25 18:25 |
70 +| [Ville de Sorel-Tracy — Événements](soreltracy.md) | municipal | actif | 64 | 66 % | 69 % | 67 % | 56 % | 0 % | 0 % | 100 % | 72 % | 2026-08-25 18:27 |
71 +| [Signé Laval — calendrier culturel officiel de Laval](signelaval.md) | agregateur | actif | 172 | 99 % | 0 % | 90 % | 54 % | 1 % | 0 % | 100 % | 100 % | 2026-08-25 18:37 |
72 +| [Quoi faire — agrégateur de sorties québécois](quoifaire.md) | agregateur | actif | 113 | 100 % | 100 % | 56 % | 0 % | 0 % | 0 % | 100 % | 42 % | 2026-08-25 18:52 |
73 +| [Événements Attractions Québec (quebecvacances.com)](evenementsattractions.md) | agregateur | actif | 20 | 40 % | 0 % | 0 % | 0 % | 0 % | 0 % | 55 % | 0 % | 2026-08-25 18:48 |
74 +| [BAnQ — calendrier des activités](banq.md) | agregateur | actif | 50 | 96 % | 100 % | 100 % | 100 % | 4 % | 0 % | 100 % | 96 % | 2026-08-25 18:42 |
44 75
45 76 ## Sources abandonnées
46 77
@@ -48,17 +79,10 @@
48 79 |---|---|---|
49 80 | [Ville de Saguenay — Événements culturels](saguenay.md) | donnees-ouvertes | Jeu de données périmé — dernière date d'événement : 2022-06 (vérifié 2026-08-17). REMPLACÉE 2026… |
50 81 | [Ville de Repentigny — Événements](repentigny.md) | donnees-ouvertes | Jeu de données périmé — dernière date d'événement : 2021-09 (vérifié 2026-08-17) |
51 −| [Ville de Gatineau — Événements](gatineau.md) | municipal | Aucun jeu de données « événements » sur Données Québec (portail Gatineau : géomatique seulement)… |
52 −| [Ville de Trois-Rivières — Événements](trois-rivieres.md) | municipal | API REST WordPress verrouillée (401 rest_not_logged_in) et aucun jeu « événements » sur Données … |
53 −| [Ville de Lévis — Calendrier](levis.md) | municipal | Site derrière un WAF (403 sur GET identifiés) et aucun jeu « événements » sur Données Québec (vé… |
54 −| [Ville de Terrebonne — Calendrier](terrebonne.md) | municipal | Cloudflare bloque tout GET non navigateur (403) et aucun jeu « événements » sur Données Québec (… |
55 −| [Tourisme Mauricie — événements (ATR)](mauricie.md) | tourisme-regional | ÉCARTÉE 2026-08-19 (Phase 3) : liste /evenements rendue JS — 1 seule carte SSR par page (vérifié… |
56 −| [Tourisme Laurentides — événements (ATR)](laurentides.md) | tourisme-regional | ÉCARTÉE 2026-08-19 (Phase 3) : WAF — 403 sur GET identifié (UA honnête). Scrapfly possible mais … |
57 82 | [Clubs professionnels (CF Montréal, Alouettes, Rocket, Victoire…)](clubs-pros.md) | sport | ÉCARTÉE 2026-08-19 (Phase 3) : les matchs à domicile passent déjà par les billetteries connectée… |
58 −| [Zeffy — billetterie OSBL (Montréal)](zeffy.md) | billetterie | n/d |
59 −| [Showpass — billetterie canadienne](showpass.md) | billetterie | n/d |
60 −| [Weezevent — billetterie libre-service](weezevent.md) | billetterie | n/d |
61 −| [Accès culture — réseau culturel de Montréal](accesculture.md) | agregateur | n/d |
83 +| [Weezevent — billetterie libre-service](weezevent.md) | billetterie | ÉCART CONFIRMÉ 2026-08-25 : aucune surface d'énumération publique — tout connecteur dépendrait d… |
84 +| [Accès culture — réseau culturel de Montréal](accesculture.md) | agregateur | RE-SONDÉE 2026-08-25 : domaine squatté, contenu figé 2015 (dates réelles : mars-mai 2015) — conn… |
85 +| [Tourisme Saguenay–Lac-Saint-Jean (ATR)](saguenaylacsaintjean.md) | tourisme-regional | ÉCARTÉE 2026-08-25 (vague ATR) : site TYPO3 = annuaire d'attraits SANS calendrier — aucune date … |
62 86
63 87 ## Régénérer cette documentation
64 88
added docs/connecteurs/abitibi.md +115 −0
@@ -0,0 +1,115 @@
1 +# Tourisme Abitibi-Témiscamingue — festivals et événements (ATR) (`abitibi`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/abitibi.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Tourisme Abitibi-Témiscamingue — festivals et événements (ATR)](https://abitibi-temiscamingue.org/festivals-et-evenements/)
10 +- **Couverture** : Abitibi-Témiscamingue
11 +- **Extraction** : Pages liste SSR (3-4 pages × 12 cartes : titre, plage de dates FR, ville, image Tourinsoft) via auto-escalade anti-bot (Cloudflare challenge → Scrapfly ASP), budget ABT_PAGE_MAX=4 requêtes/sync, aucune fiche détail
12 +
13 +## Accès
14 +
15 +- **Type** : tourisme-regional
16 +- **Endpoint** (`BASE`) : `https://abitibi-temiscamingue.org`
17 +- **Auth** : Aucune côté source ; `SCRAPFLY_API_KEY` requis pour le backend Scrapfly
18 +- **Pagination** : par numéro de page
19 +- **Backend** : Scrapfly (anti-bot / rendu JS) via `base.get_scrapfly()`
20 +- **Throttle** : 1.0 s entre requêtes
21 +- **Accès légal** : Pages publiques de l'ATR — requêtes throttlées et plafonnées (coût Scrapfly), UA honnête, lien vers la fiche originale
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/abitibi.py : Tourisme Abitibi-Témiscamingue (ATR, vague ATR 2026-08)
26 +> Source : abitibi-temiscamingue.org/festivals-et-evenements/ (domaine
27 +> tourisme-abitibi-temiscamingue.org → redirection) — répertoire
28 +> officiel des festivals et événements de l'ATR (WordPress,
29 +> gisement Tourinsoft : images mto.media.tourinsoft.eu).
30 +> Extraction: pages liste RENDUES SERVEUR /festivals-et-evenements/[page/N/]
31 +> (~3 pages × 12 cartes vérifié 2026-08-25) : lien
32 +> /evenements/<slug>/, image, ville, titre, plage de dates FR
33 +> (« Du 26 au 30 août 2026 »). Le site est derrière un challenge
34 +> Cloudflare (403 + « Just a moment ») → chaque GET passe par
35 +> l'AUTO-ESCALADE anti-bot de BaseConnector.get() (Oxylabs →
36 +> Scrapfly ASP → Bright Data) ; budget borné : ABT_PAGE_MAX
37 +> pages/sync, AUCUNE fiche détail (la carte porte tout ce que la
38 +> source publie de structuré ; wp-json est aussi derrière le
39 +> challenge et n'expose pas de CPT événement).
40 +> Accès : pages publiques de l'ATR — requêtes throttlées et plafonnées
41 +> (coût par requête), identification honnête, lien vers la fiche
42 +> originale. Ni prix, ni heure, ni description structurés sur la
43 +> carte → jamais inventés.
44 +
45 +</details>
46 +
47 +## Champs récupérés → schéma
48 +
49 +Complétude mesurée sur les **32 événements actifs** de la source ; exemples tirés d'un événement réel (`abitibi:16522`).
50 +
51 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
52 +|---|---|---|---|
53 +| `title` — Titre | titre de l'événement | 100 % | Beltane – La Feste Païenne par le Cirque Collini |
54 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
55 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://abitibi-temiscamingue.org/evenements/beltane-la-feste-paienne-par-le-cirque-collini-2/ |
56 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["festival", "arts-scene"] |
57 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
58 +| `address` — Adresse | adresse civique | 0 % | ∅ |
59 +| `city` — Ville | municipalité | 100 % | Rouyn-Noranda |
60 +| `region` — Région | région administrative | 100 % | Abitibi-Témiscamingue |
61 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 0 % | ∅ |
62 +| `start_date` — Date début | date locale AAAA-MM-JJ | 25 % | 2026-08-26 |
63 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 25 % | 2026-08-30 |
64 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
65 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
66 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
67 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
68 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
69 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
70 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
71 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
72 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://mto.media.tourinsoft.eu/upload/Beltane-La-Feste-Paienne.png?width=500 |
73 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
74 +
75 +## Fréquence & budgets
76 +
77 +- **Cadence** : hebdomadaire
78 +- **Budgets / plafonds (variables d'environnement)** :
79 +
80 +| Variable | Défaut | Rôle |
81 +|---|---|---|
82 +| `ABT_PAGE_MAX` | `4` | — |
83 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
84 +
85 +## Volumétrie & complétude live
86 +
87 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
88 +
89 +- **Événements en base** : 32 (dont **32 actifs**)
90 +- **GPS** : 0/32 actifs (0 %)
91 +- **Prix** : 0/32 actifs (0 %)
92 +- **Heure (start_time)** : 0/32 actifs (0 %)
93 +- **Heure de fin** : 0/32 actifs (0 %)
94 +- **Statut (annulé/reporté/complet)** : 0/32 actifs (0 %)
95 +- **Artistes** : 0/32 actifs (0 %)
96 +- **Image** : 32/32 actifs (100 %)
97 +- **Salle (venue)** : 0/32 actifs (0 %)
98 +- **Dernier sync** : 2026-08-25 18:44 — fetched 32, +32 ajoutés, ~0 mis à jour, -0 retirés
99 +- **Syncs enregistrés** (sync_log) : 1
100 +
101 +## Erreurs & dépannage
102 +
103 +Aucune erreur dans `sync_log` pour cette source. ✅
104 +
105 +- Rejouer un sync isolé : `python3 run.py sync --source abitibi` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='abitibi' ORDER BY ts DESC LIMIT 5;"`
106 +
107 +## Licence / attribution
108 +
109 +- Pages publiques de l'ATR — requêtes throttlées et plafonnées (coût Scrapfly), UA honnête, lien vers la fiche originale
110 +
111 +## Historique
112 +
113 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
114 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
115 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/accesculture.md +6 −5
@@ -1,21 +1,22 @@
1 1 # Accès culture — réseau culturel de Montréal (`accesculture`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : abandonnee** · Type : `agregateur` · Connecteur : *aucun (source sans connecteur runtime)*
6 6
7 7 ## Description
8 8
9 9 - **Source** : [Accès culture — réseau culturel de Montréal](https://www.accesculture.com/)
10 −- **Couverture** : Diffuseurs des arrondissements de Montréal
10 +- **Couverture** : AUCUNE : domaine recyclé (ferme SEO hôtels) hébergeant une copie fossilisée du site 2015 ; toute date « déduite » serait fabriquée
11 11 - **Extraction** : —
12 +- **Note** : RE-SONDÉE 2026-08-25 : domaine squatté, contenu figé 2015 (dates réelles : mars-mai 2015) — connecteur retiré pour éviter la fabrication de dates. L'offre des maisons de la culture reste couverte par montreal + lavitrine.
12 13
13 14 ## Accès
14 15
15 16 - **Type** : agregateur
16 17 - **Endpoint** : voir URL source (https://www.accesculture.com/)
17 18 - **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 −- **Accès légal** : robots.txt permissif, sitemap public
19 +- **Accès légal** : —
19 20
20 21 ## Champs récupérés → schéma
21 22
@@ -35,11 +36,11 @@
35 36
36 37 ## Licence / attribution
37 38
38 −- robots.txt permissif, sitemap public
39 +- —
39 40
40 41 ## Historique
41 42
42 −- **Source abandonnée** — sans note
43 +- **Source abandonnée** — RE-SONDÉE 2026-08-25 : domaine squatté, contenu figé 2015 (dates réelles : mars-mai 2015) — connecteur retiré pour éviter la fabrication de dates. L'offre des maisons de la culture reste couverte par montreal + lavitrine.
43 44 - **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
44 45 - **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
45 46 - **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/atuvu.md +43 −35
@@ -1,6 +1,6 @@
1 1 # Atuvu.ca — calendrier culturel (`atuvu`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `agregateur` · Connecteur : `sortika/connectors/atuvu.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Atuvu.ca — calendrier culturel](https://atuvu.ca/)
10 10 - **Couverture** : Sorties culturelles, Grand Montréal
11 −- **Extraction** : Scrapfly (Cloudflare) — sitemaps lastmod + fiches .event-play (date FR + heure « 20h00 » → start_time), cache incrémental data/atuvu_cache.json, plafond 120 fiches/sync
11 +- **Extraction** : sitemaps XML publics + fiches HTML en GET direct (repli Scrapfly seulement sur défi Cloudflare) — cache versionné v2 : heure précise (92 % des fiches v2), tarifs après mise en vente (46 %), séances multiples le même jour suffixées -HHMM ; re-visite roulante 14 j des fiches à venir, plafond ATUVU_MAX_FETCH/sync
12 12
13 13 ## Accès
14 14
@@ -24,44 +24,52 @@
24 24
25 25 > connectors/atuvu.py : connecteur Atuvu.ca — calendrier culturel (Grand Montréal +)
26 26 > Source : atuvu.ca (théâtre, musique, humour, danse, expositions…)
27 −> Extraction: SCRAPFLY obligatoire (Cloudflare) — sitemaps XML publics
27 +> Extraction: GET direct d'abord (vérifié 200 depuis le nœud de prod,
28 +> 2026-08-25 ; escalade anti-bot auto du BaseConnector), repli
29 +> SCRAPFLY si défi Cloudflare — sitemaps XML publics
28 30 > (evenements_N_2000.xml, lastmod par fiche) puis pages fiche
29 −> (h1, blocs .event-play : date FR, salle, ville, prix).
30 −> INCRÉMENTAL avec cache disque (data/atuvu_cache.json) : seules
31 −> les fiches nouvelles ou modifiées sont re-scrapées, plafonné à
32 −> ATUVU_MAX_FETCH pages/sync pour contenir le coût en crédits.
31 +> (h1, blocs .event-play : date FR + HEURE, salle, ville, prix).
32 +> INCRÉMENTAL avec cache disque (data/atuvu_cache.json), version
33 +> de parse « v » (patron lavitrine) : fiches nouvelles/modifiées
34 +> d'abord, backfill v<2 (rows figés sans heure/prix par un
35 +> parseur antérieur — constaté 2026-08-25), puis re-visite
36 +> roulante 14 j des fiches à venir (les tarifs apparaissent
37 +> souvent APRÈS la mise en vente, sans bump de lastmod),
38 +> plafonné à ATUVU_MAX_FETCH pages/sync.
33 39 > Accès : robots.txt « User-Agent: * / Allow: / » + sitemap public.
34 40 > Prix : bloc .price-reg de la fiche (prix régulier) → price_min ;
35 41 > « Invitation gratuite » = offre membre, PAS gratuité → ignorée.
42 +> Séances : 2 représentations le même jour = 2 événements (suffixe -HHMM
43 +> dès la 2ᵉ séance du jour ; la 1ʳᵉ garde l'id historique).
36 44
37 45 </details>
38 46
39 47 ## Champs récupérés → schéma
40 48
41 −Complétude mesurée sur les **217 événements actifs** de la source ; exemples tirés d'un événement réel (`atuvu:on-daaaaance-2026-08-22`).
49 +Complétude mesurée sur les **227 événements actifs** de la source ; exemples tirés d'un événement réel (`atuvu:requiem-de-mozart-2026-11-07`).
42 50
43 51 | Champ (`events.*`) | Description | Complétude | Exemple réel |
44 52 |---|---|---|---|
45 −| `title` — Titre | titre de l'événement | 100 % | On Daaaaance |
53 +| `title` — Titre | titre de l'événement | 100 % | Requiem de Mozart |
46 54 | `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
47 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://atuvu.ca/billets-spectacle/on-daaaaance |
48 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique", "arts-scene"] |
49 −| `venue` — Salle (venue) | nom du lieu | 100 % | Maison Mère Baie-Saint-Paul |
55 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://atuvu.ca/billets-spectacle/requiem-de-mozart |
56 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique", "cinema"] |
57 +| `venue` — Salle (venue) | nom du lieu | 100 % | Basilique Notre-Dame de Montréal |
50 58 | `address` — Adresse | adresse civique | 0 % | ∅ |
51 −| `city` — Ville | municipalité | 98 % | Baie-Saint-Paul |
52 −| `region` — Région | région administrative | 96 % | Capitale-Nationale |
53 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 82 % | 47.4384969, -70.5066321 |
54 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
55 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
56 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 46 % | 11:30 |
59 +| `city` — Ville | municipalité | 98 % | Montréal |
60 +| `region` — Région | région administrative | 96 % | Montréal |
61 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 82 % | 45.5044365, -73.5559901 |
62 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-11-07 |
63 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-11-07 |
64 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 56 % | 14:00 |
57 65 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
58 66 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
59 −| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 18 % | ∅ |
60 −| `price_min` — Prix min | tarif minimal réel ($) | 24 % | 0.0 |
61 −| `price_label` — Libellé prix | étiquette tarifaire affichable | 18 % | ∅ |
67 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 19 % | 0 |
68 +| `price_min` — Prix min | tarif minimal réel ($) | 22 % | 54.58 |
69 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 19 % | 54.58 $ (prix régulier atuvu.ca) |
62 70 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
63 71 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
64 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://atuvu.ca/uploads/images/2026/07/on-daaaaance.jpg |
72 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://atuvu.ca/uploads/images/2026/08/requiem-mozart.jpg |
65 73 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
66 74
67 75 ## Fréquence & budgets
@@ -76,19 +84,19 @@ Complétude mesurée sur les **217 événements actifs** de la source ; exemples
76 84
77 85 ## Volumétrie & complétude live
78 86
79 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
80 −
81 −- **Événements en base** : 249 (dont **217 actifs**)
82 −- **GPS** : 177/217 actifs (82 %)
83 −- **Prix** : 51/217 actifs (24 %)
84 −- **Heure (start_time)** : 100/217 actifs (46 %)
85 −- **Heure de fin** : 0/217 actifs (0 %)
86 −- **Statut (annulé/reporté/complet)** : 0/217 actifs (0 %)
87 −- **Artistes** : 0/217 actifs (0 %)
88 −- **Image** : 217/217 actifs (100 %)
89 −- **Salle (venue)** : 217/217 actifs (100 %)
90 −- **Dernier sync** : 2026-08-22 23:01 — fetched 253, +0 ajoutés, ~2 mis à jour, -0 retirés
91 −- **Syncs enregistrés** (sync_log) : 120
87 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
88 +
89 +- **Événements en base** : 273 (dont **227 actifs**)
90 +- **GPS** : 187/227 actifs (82 %)
91 +- **Prix** : 49/227 actifs (22 %)
92 +- **Heure (start_time)** : 127/227 actifs (56 %)
93 +- **Heure de fin** : 0/227 actifs (0 %)
94 +- **Statut (annulé/reporté/complet)** : 0/227 actifs (0 %)
95 +- **Artistes** : 0/227 actifs (0 %)
96 +- **Image** : 227/227 actifs (100 %)
97 +- **Salle (venue)** : 227/227 actifs (100 %)
98 +- **Dernier sync** : 2026-08-25 17:59 — fetched 274, +3 ajoutés, ~12 mis à jour, -0 retirés
99 +- **Syncs enregistrés** (sync_log) : 175
92 100
93 101 ## Erreurs & dépannage
94 102
modified docs/connecteurs/bandsintown.md +22 −17
@@ -1,6 +1,6 @@
1 1 # Bandsintown — dates de concerts par artiste (`bandsintown`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `agregateur` · Connecteur : `sortika/connectors/bandsintown.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Bandsintown — dates de concerts par artiste](https://rest.bandsintown.com/)
10 10 - **Couverture** : Concerts au Québec des artistes en tournée connus de la base
11 −- **Extraction** : API publique /artists/{artiste}/events (app_id format widget « js_www.sorti-ka.com », testé accepté 2026-08 ; un app_id arbitraire simple est refusé) — pas de recherche géographique : on interroge les artistes déjà extraits de la base (evenko/lepointdevente/ticketmaster, événements à venir), plafond 100 artistes/sync avec rotation et cache data/bandsintown_cache.json (re-visite 3 j) ; on ne garde que les dates au Québec (venue.region=QC) : datetime local (date+heure), salle géolocalisée, lineup → artistes, free → gratuité, lien billets
11 +- **Extraction** : API publique /artists/{artiste}/events (app_id format widget « js_www.sorti-ka.com », testé accepté 2026-08 ; un app_id arbitraire simple est refusé) — pas de recherche géographique : on interroge les artistes déjà extraits de la base (evenko/lepointdevente/ticketmaster, événements à venir), plafond 100 artistes/sync avec rotation et cache data/bandsintown_cache.json (re-visite 3 j) ; on ne garde que les dates au Québec (venue.region=QC) : datetime local (date+heure), salle géolocalisée, lineup → artistes, free → gratuité, lien billets. MAJ 2026-08-25 : image d'artiste reprise de n'importe quel événement de la réponse, sinon 1 GET de repli sur le profil /artists/<nom> (100 % constaté sur l'échantillon) ; rotation BIT_ARTIST_MAX/sync
12 12
13 13 ## Accès
14 14
@@ -37,13 +37,18 @@
37 37 > cache entre deux visites (re-visite après 3 jours).
38 38 > Champs : datetime local (date + heure), salle géolocalisée (lat/lng),
39 39 > lineup → artistes, `free` → is_free, offres/billets → url,
40 −> image de l'artiste.
40 +> image de l'artiste. L'objet `artist` n'est renseigné que sur
41 +> CERTAINS événements de la réponse (dict vide sur d'autres —
42 +> constaté 2026-08-25, cause de l'image à 8 %) : l'image est
43 +> reprise de n'importe quel événement de la réponse, sinon du
44 +> profil /artists/{artiste} (1 GET de plus, seulement si
45 +> l'artiste a des dates QC sans image).
41 46
42 47 </details>
43 48
44 49 ## Champs récupérés → schéma
45 50
46 −Complétude mesurée sur les **519 événements actifs** de la source ; exemples tirés d'un événement réel (`bandsintown:108801594`).
51 +Complétude mesurée sur les **515 événements actifs** de la source ; exemples tirés d'un événement réel (`bandsintown:108801594`).
47 52
48 53 | Champ (`events.*`) | Description | Complétude | Exemple réel |
49 54 |---|---|---|---|
@@ -82,19 +87,19 @@ Complétude mesurée sur les **519 événements actifs** de la source ; exemples
82 87
83 88 ## Volumétrie & complétude live
84 89
85 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
86 −
87 −- **Événements en base** : 532 (dont **519 actifs**)
88 −- **GPS** : 519/519 actifs (100 %)
89 −- **Prix** : 5/519 actifs (1 %)
90 −- **Heure (start_time)** : 518/519 actifs (100 %)
91 −- **Heure de fin** : 0/519 actifs (0 %)
92 −- **Statut (annulé/reporté/complet)** : 0/519 actifs (0 %)
93 −- **Artistes** : 519/519 actifs (100 %)
94 −- **Image** : 44/519 actifs (8 %)
95 −- **Salle (venue)** : 519/519 actifs (100 %)
96 −- **Dernier sync** : 2026-08-22 23:01 — fetched 522, +0 ajoutés, ~0 mis à jour, -0 retirés
97 −- **Syncs enregistrés** (sync_log) : 105
90 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
91 +
92 +- **Événements en base** : 546 (dont **515 actifs**)
93 +- **GPS** : 515/515 actifs (100 %)
94 +- **Prix** : 3/515 actifs (1 %)
95 +- **Heure (start_time)** : 514/515 actifs (100 %)
96 +- **Heure de fin** : 0/515 actifs (0 %)
97 +- **Statut (annulé/reporté/complet)** : 0/515 actifs (0 %)
98 +- **Artistes** : 515/515 actifs (100 %)
99 +- **Image** : 40/515 actifs (8 %)
100 +- **Salle (venue)** : 515/515 actifs (100 %)
101 +- **Dernier sync** : 2026-08-25 17:59 — fetched 518, +0 ajoutés, ~0 mis à jour, -0 retirés
102 +- **Syncs enregistrés** (sync_log) : 160
98 103
99 104 ## Erreurs & dépannage
100 105
added docs/connecteurs/banq.md +114 −0
@@ -0,0 +1,114 @@
1 +# BAnQ — calendrier des activités (`banq`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `agregateur` · Connecteur : `sortika/connectors/banq.py`
6 +
7 +## Description
8 +
9 +- **Source** : [BAnQ — calendrier des activités](https://www.banq.qc.ca/calendrier/a-venir)
10 +- **Couverture** : ~140 activités à venir (Grande Bibliothèque + centres régionaux — ateliers, conférences, heure du conte)
11 +- **Extraction** : Scrapfly obligatoire (anti-bot Radware) : liste rendue JS → fiches HTML serveur via Scrapfly sans JS, JSON-LD Event complet (dates ISO -04:00, lieu, adresse, image, organisateur, offers.price 0.00 = gratuité publiée) ; incrémental cache 14 j, plafond BANQ_DETAIL_MAX=50
12 +
13 +## Accès
14 +
15 +- **Type** : agregateur
16 +- **Endpoint** (`BASE`) : `https://www.banq.qc.ca`
17 +- **Auth** : Aucune côté source ; `SCRAPFLY_API_KEY` requis pour le backend Scrapfly
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : Scrapfly (anti-bot / rendu JS) via `base.get_scrapfly()`
20 +- **Throttle** : 1.0 s entre requêtes
21 +- **Accès légal** : robots.txt Drupal standard (calendrier non exclu) ; 1 rendu JS + ≤50 fiches par sync, cadence contenue
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/banq.py : connecteur BAnQ (banq.qc.ca) — activités & conférences
26 +> Source : calendrier des activités de Bibliothèque et Archives nationales
27 +> du Québec (Grande Bibliothèque + centres régionaux) — ateliers,
28 +> conférences, heure du conte, expositions (~140 fiches à venir).
29 +> Extraction: SCRAPFLY obligatoire (anti-bot Radware : GET direct → 302
30 +> challenge, /jsonapi bloqué — sondé 2026-08-25).
31 +> Liste /calendrier/a-venir rendue AVEC JavaScript (les cartes
32 +> sont injectées côté client) → liens /calendrier/a-venir/<id>.
33 +> Fiches : Scrapfly SANS rendu JS (HTML serveur), JSON-LD Event
34 +> complet (nom, dates ISO avec fuseau -04:00, lieu, adresse,
35 +> ville, image, organisateur, offre 0,00 $ → gratuité PUBLIÉE).
36 +> INCRÉMENTAL avec cache disque (data/banq_cache.json) :
37 +> nouvelles fiches d'abord, re-visite après 14 jours, plafond
38 +> BANQ_DETAIL_MAX fiches/sync pour contenir le coût en crédits.
39 +> Accès : robots.txt Drupal standard (le calendrier n'est pas exclu) ;
40 +> 1 rendu JS + quelques fiches par sync, cadence hebdomadaire.
41 +> Prix : offers.price du JSON-LD (« 0.00 » = gratuit affiché par la
42 +> source) → is_free/price_label. Jamais inventé.
43 +
44 +</details>
45 +
46 +## Champs récupérés → schéma
47 +
48 +Complétude mesurée sur les **50 événements actifs** de la source ; exemples tirés d'un événement réel (`banq:135922`).
49 +
50 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
51 +|---|---|---|---|
52 +| `title` — Titre | titre de l'événement | 100 % | Camelot d'un jour |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Venez appuyer la mission de L’Itinéraire ! La Grande Bibliothèque accueille pour une cinquième a… |
54 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.banq.qc.ca/calendrier/a-venir/135922 |
55 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
56 +| `venue` — Salle (venue) | nom du lieu | 96 % | Grande Bibliothèque |
57 +| `address` — Adresse | adresse civique | 96 % | 475, boulevard De Maisonneuve Est |
58 +| `city` — Ville | municipalité | 96 % | Montréal |
59 +| `region` — Région | région administrative | 96 % | Montréal |
60 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 96 % | 45.5153771, -73.5622117 |
61 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-15 |
62 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-15 |
63 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 10:00 |
64 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 100 % | 15:00 |
65 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 4 % | ∅ |
66 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 100 % | 1 |
67 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
68 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 100 % | Gratuit (0,00 $ affiché) |
69 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
70 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Bibliothèque et Archives nationales du Québec |
71 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www2.banq.qc.ca/cal/attachments/24047/135922/Entete-visuel_Camelot.png |
72 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
73 +
74 +## Fréquence & budgets
75 +
76 +- **Cadence** : hebdomadaire
77 +- **Budgets / plafonds (variables d'environnement)** :
78 +
79 +| Variable | Défaut | Rôle |
80 +|---|---|---|
81 +| `BANQ_DETAIL_MAX` | `50` | — |
82 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
83 +
84 +## Volumétrie & complétude live
85 +
86 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
87 +
88 +- **Événements en base** : 50 (dont **50 actifs**)
89 +- **GPS** : 48/50 actifs (96 %)
90 +- **Prix** : 50/50 actifs (100 %)
91 +- **Heure (start_time)** : 50/50 actifs (100 %)
92 +- **Heure de fin** : 50/50 actifs (100 %)
93 +- **Statut (annulé/reporté/complet)** : 2/50 actifs (4 %)
94 +- **Artistes** : 0/50 actifs (0 %)
95 +- **Image** : 50/50 actifs (100 %)
96 +- **Salle (venue)** : 48/50 actifs (96 %)
97 +- **Dernier sync** : 2026-08-25 18:42 — fetched 50, +50 ajoutés, ~0 mis à jour, -0 retirés
98 +- **Syncs enregistrés** (sync_log) : 1
99 +
100 +## Erreurs & dépannage
101 +
102 +Aucune erreur dans `sync_log` pour cette source. ✅
103 +
104 +- Rejouer un sync isolé : `python3 run.py sync --source banq` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='banq' ORDER BY ts DESC LIMIT 5;"`
105 +
106 +## Licence / attribution
107 +
108 +- robots.txt Drupal standard (calendrier non exclu) ; 1 rendu JS + ≤50 fiches par sync, cadence contenue
109 +
110 +## Historique
111 +
112 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
113 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
114 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/bassaintlaurent.md +25 −20
@@ -1,6 +1,6 @@
1 1 # Tourisme Bas-Saint-Laurent — événements (ATR) (`bassaintlaurent`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/bassaintlaurent.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Tourisme Bas-Saint-Laurent — événements (ATR)](https://www.bassaintlaurent.ca/fr/quoi-faire/evenements/)
10 10 - **Couverture** : Festivals et événements, Bas-Saint-Laurent
11 −- **Extraction** : liste SSR (1 GET, cartes .card--type-event) + fiches incrémentales (cache data/bassaintlaurent_cache.json, BSL_FICHE_MAX/sync, re-visite 7 j) : og:description + GPS exact de la carte embarquée
11 +- **Extraction** : liste SSR (1 GET, cartes .card--type-event, garde-fou ajax-load-more__end-page) + fiches incrémentales (BSL_FICHE_MAX/sync, re-visite 7 j, cache v2) : window.locations JSON → adresse civique, ville, CP, GPS exact, site web ; description longue « À propos » (fallback og:description)
12 12
13 13 ## Accès
14 14
@@ -25,12 +25,17 @@
25 25 > connectors/bassaintlaurent.py : Tourisme Bas-Saint-Laurent — événements (ATR)
26 26 > Source : https://www.bassaintlaurent.ca/fr/quoi-faire/evenements/ —
27 27 > WordPress (Bedrock), liste rendue serveur (~27 fiches à venir,
28 −> vérifié 2026-08-19).
28 +> revérifié 2026-08-25 ; le marqueur ajax-load-more__end-page
29 +> confirme que TOUTE la liste tient sur la page 1 — aucun
30 +> événement caché derrière l'AJAX get_grid).
29 31 > Extraction: cartes .card--type-event (lien, plage de dates FR, titre,
30 32 > municipalité, image) + fiches détail incrémentales (cache
31 33 > data/bassaintlaurent_cache.json, BSL_FICHE_MAX/sync, re-visite
32 −> 7 j) : og:description + GPS exact (coordonnées de la carte
33 −> embarquées dans la fiche).
34 +> 7 j, version v=2). ENRICHI (2026-08-25) : bloc window.locations
35 +> de la fiche (JSON structuré) → adresse civique (street), ville,
36 +> code postal, GPS exact, site web officiel ; description longue
37 +> = paragraphes « À propos » (fallback og:description). Pas
38 +> d'heure ni de tarif publiés → jamais inventés.
34 39 > Accès : pages publiques de l'ATR, rendu serveur — GET throttlés 0,8 s,
35 40 > identification honnête, lien vers la fiche originale.
36 41 > Décision : le Bas-Saint-Laurent n'avait que 168 fiches au baseline —
@@ -40,12 +45,12 @@
40 45
41 46 ## Champs récupérés → schéma
42 47
43 −Complétude mesurée sur les **20 événements actifs** de la source ; exemples tirés d'un événement réel (`bassaintlaurent:degelis-ta-riviere-tappelle`).
48 +Complétude mesurée sur les **17 événements actifs** de la source ; exemples tirés d'un événement réel (`bassaintlaurent:degelis-ta-riviere-tappelle`).
44 49
45 50 | Champ (`events.*`) | Description | Complétude | Exemple réel |
46 51 |---|---|---|---|
47 52 | `title` — Titre | titre de l'événement | 100 % | Dégelis, ta rivière t’appelle |
48 −| `description` — Description | texte descriptif (source ou fiche détail) | 90 % | La Clé des chants présente DÉGELIS, TA RIVIÈRE T'APPELLE, au Parc du Centenaire. Vivez une expér… |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | La Clé des chants présente DÉGELIS, TA RIVIÈRE T’APPELLE, au Parc du Centenaire. Vivez une expér… |
49 54 | `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.bassaintlaurent.ca/fr/quoi-faire/evenements/degelis-ta-riviere-tappelle/ |
50 55 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
51 56 | `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
@@ -78,19 +83,19 @@ Complétude mesurée sur les **20 événements actifs** de la source ; exemples
78 83
79 84 ## Volumétrie & complétude live
80 85
81 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
82 −
83 −- **Événements en base** : 27 (dont **20 actifs**)
84 −- **GPS** : 20/20 actifs (100 %)
85 −- **Prix** : 0/20 actifs (0 %)
86 −- **Heure (start_time)** : 0/20 actifs (0 %)
87 −- **Heure de fin** : 0/20 actifs (0 %)
88 −- **Statut (annulé/reporté/complet)** : 0/20 actifs (0 %)
89 −- **Artistes** : 0/20 actifs (0 %)
90 −- **Image** : 20/20 actifs (100 %)
91 −- **Salle (venue)** : 0/20 actifs (0 %)
92 −- **Dernier sync** : 2026-08-22 23:01 — fetched 27, +0 ajoutés, ~0 mis à jour, -0 retirés
93 −- **Syncs enregistrés** (sync_log) : 82
86 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
87 +
88 +- **Événements en base** : 28 (dont **17 actifs**)
89 +- **GPS** : 17/17 actifs (100 %)
90 +- **Prix** : 0/17 actifs (0 %)
91 +- **Heure (start_time)** : 0/17 actifs (0 %)
92 +- **Heure de fin** : 0/17 actifs (0 %)
93 +- **Statut (annulé/reporté/complet)** : 0/17 actifs (0 %)
94 +- **Artistes** : 0/17 actifs (0 %)
95 +- **Image** : 17/17 actifs (100 %)
96 +- **Salle (venue)** : 0/17 actifs (0 %)
97 +- **Dernier sync** : 2026-08-25 17:59 — fetched 27, +0 ajoutés, ~22 mis à jour, -0 retirés
98 +- **Syncs enregistrés** (sync_log) : 137
94 99
95 100 ## Erreurs & dépannage
96 101
modified docs/connecteurs/brossard.md +26 −26
@@ -1,6 +1,6 @@
1 1 # Ville de Brossard — Calendrier des événements (`brossard`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/brossard.py`
6 6
@@ -43,30 +43,30 @@
43 43
44 44 ## Champs récupérés → schéma
45 45
46 −Complétude mesurée sur les **146 événements actifs** de la source ; exemples tirés d'un événement réel (`brossard:37838`).
46 +Complétude mesurée sur les **147 événements actifs** de la source ; exemples tirés d'un événement réel (`brossard:38247`).
47 47
48 48 | Champ (`events.*`) | Description | Complétude | Exemple réel |
49 49 |---|---|---|---|
50 −| `title` — Titre | titre de l'événement | 100 % | Atelier jeux de société (16 septembre 2026) |
51 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Entrée libre. Les enfants âgés de moins de 8 ans doivent être accompagnés d’un adulte. Les enfan… |
52 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://brossard.ca/calendrier-evenements/atelier-jeux-de-societe-16-septembre-2026/ |
50 +| `title` — Titre | titre de l'événement | 100 % | Heure du conte en mandarin (20 décembre 2026) |
51 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Retrouvez tous les plaisirs de l’heure du conte en mandarin. Histoires, comptines et chansons pl… |
52 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://brossard.ca/calendrier-evenements/heure-du-conte-en-mandarin-20-decembre-2026/ |
53 53 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["famille"] |
54 −| `venue` — Salle (venue) | nom du lieu | 95 % | Salle d'animation |
55 −| `address` — Adresse | adresse civique | 95 % | 7855, av San Francisco |
54 +| `venue` — Salle (venue) | nom du lieu | 94 % | Salle d'animation |
55 +| `address` — Adresse | adresse civique | 94 % | 7855, av San Francisco |
56 56 | `city` — Ville | municipalité | 100 % | Brossard |
57 57 | `region` — Région | région administrative | 100 % | Montérégie |
58 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 95 % | 45.4551469, -73.4682277 |
59 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-16 |
60 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-16 |
61 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 96 % | 10:00 |
62 −| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
58 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 94 % | 45.4551469, -73.4682277 |
59 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-12-20 |
60 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-12-20 |
61 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 95 % | 10:30 |
62 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 95 % | 12:00 |
63 63 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
64 64 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
65 65 | `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
66 66 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
67 67 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
68 68 | `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Brossard |
69 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://biblio.brossard.ca/wp-content/uploads/2025/07/jeux_societe_enfant.jpg |
69 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://biblio.brossard.ca/wp-content/uploads/2024/09/heure_conte_mandarin.jpg |
70 70 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
71 71
72 72 ## Fréquence & budgets
@@ -81,19 +81,19 @@ Complétude mesurée sur les **146 événements actifs** de la source ; exemples
81 81
82 82 ## Volumétrie & complétude live
83 83
84 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
85 −
86 −- **Événements en base** : 264 (dont **146 actifs**)
87 −- **GPS** : 138/146 actifs (95 %)
88 −- **Prix** : 0/146 actifs (0 %)
89 −- **Heure (start_time)** : 140/146 actifs (96 %)
90 −- **Heure de fin** : 0/146 actifs (0 %)
91 −- **Statut (annulé/reporté/complet)** : 0/146 actifs (0 %)
92 −- **Artistes** : 0/146 actifs (0 %)
93 −- **Image** : 146/146 actifs (100 %)
94 −- **Salle (venue)** : 138/146 actifs (95 %)
95 −- **Dernier sync** : 2026-08-22 23:02 — fetched 264, +0 ajoutés, ~0 mis à jour, -0 retirés
96 −- **Syncs enregistrés** (sync_log) : 105
84 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
85 +
86 +- **Événements en base** : 266 (dont **147 actifs**)
87 +- **GPS** : 138/147 actifs (94 %)
88 +- **Prix** : 0/147 actifs (0 %)
89 +- **Heure (start_time)** : 140/147 actifs (95 %)
90 +- **Heure de fin** : 140/147 actifs (95 %)
91 +- **Statut (annulé/reporté/complet)** : 0/147 actifs (0 %)
92 +- **Artistes** : 0/147 actifs (0 %)
93 +- **Image** : 147/147 actifs (100 %)
94 +- **Salle (venue)** : 138/147 actifs (94 %)
95 +- **Dernier sync** : 2026-08-25 18:00 — fetched 264, +0 ajoutés, ~0 mis à jour, -0 retirés
96 +- **Syncs enregistrés** (sync_log) : 160
97 97
98 98 ## Erreurs & dépannage
99 99
modified docs/connecteurs/cantonsdelest.md +37 −32
@@ -1,6 +1,6 @@
1 1 # Tourisme Cantons-de-l'Est — calendrier officiel (ATR Estrie) (`cantonsdelest`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/cantonsdelest.py`
6 6
@@ -8,14 +8,14 @@
8 8
9 9 - **Source** : [Tourisme Cantons-de-l'Est — calendrier officiel (ATR Estrie)](https://www.cantonsdelest.com/evenements)
10 10 - **Couverture** : Festivals, spectacles, événements — Cantons-de-l'Est (Estrie)
11 −- **Extraction** : page liste rendue serveur (~25 fiches à venir) puis fiches détail incrémentales (cache data/cantonsdelest_cache.json, 40 fiches/sync, re-visite 7 j) : JSON-LD Event complet (nom, dates, description riche, adresse civique, images Tourinsoft mto.media.tourinsoft.eu) + GPS exact (data-lat/data-lng de la carte) ; même plateforme Tourinsoft que le SIT Québec → recoupement géré par dedup_key
11 +- **Extraction** : page 1 SSR + pagination AJAX POST /event/more {_csrf, ids, offset} jusqu'à épuisement (~596 occurrences, CDE_PAGE_MAX=120) — carte : lien /evenements/<id>/<slug>/<idDate>, catégorie, titre, image, « Ville | date FR (abrégés janv./févr. gérés) » ; fiches incrémentales (CDE_FICHE_MAX=40/sync, re-visite 7 j) : JSON-LD Event (dates précises, description, adresse, CP) + GPS data-lat/lng ; recoupement SITQ par dedup_key
12 12
13 13 ## Accès
14 14
15 15 - **Type** : tourisme-regional
16 16 - **Endpoint** (`BASE`) : `https://www.cantonsdelest.com`
17 17 - **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 −- **Pagination** : aucune (flux unique)
18 +- **Pagination** : offset/limit
19 19 - **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 20 - **Throttle** : 0.8 s entre requêtes
21 21 - **Accès légal** : pages publiques de l'ATR, rendu serveur — GET throttlés 0,8 s, identification honnête, lien vers la fiche originale
@@ -26,13 +26,17 @@
26 26 > Source : cantonsdelest.com/evenements — calendrier officiel de
27 27 > l'association touristique régionale (données Tourinsoft, la
28 28 > même plateforme que le SIT Québec : médias mto.tourinsoft.eu).
29 −> Extraction: page liste rendue serveur (~25 fiches à venir, liens
30 −> /evenements/<id>/<slug>[/<idDate>]) puis fiches détail
29 +> Extraction: la page liste ne rend que ~13 cartes mais publie ~596
30 +> occurrences (data-found) derrière un bouton « Voir plus » →
31 +> PAGINATION AJAX : POST /event/more {ids, offset} avec le
32 +> jeton CSRF Yii de la page (vérifié 2026-08-25). Chaque carte
33 +> donne lien /evenements/<id>/<slug>[/<idDate>], catégorie,
34 +> titre, image et « Ville | date FR ». Fiches détail
31 35 > incrémentales (cache disque data/cantonsdelest_cache.json,
32 36 > plafond CDE_FICHE_MAX/sync, re-visite 7 j) : JSON-LD Event
33 −> complet (nom, dates, description riche, adresse civique,
34 −> images) + GPS exact (attributs data-lat/data-lng de la carte).
35 −> Accès : pages publiques de l'ATR, rendu serveur — GET directs
37 +> complet (description riche, adresse civique, images) + GPS
38 +> exact (attributs data-lat/data-lng de la carte).
39 +> Accès : pages publiques de l'ATR, rendu serveur — GET/POST directs
36 40 > throttlés, identification honnête, lien vers la fiche
37 41 > originale. Recoupement avec le flux SITQ géré par dedup_key.
38 42 > Prix : non publié de façon structurée → jamais inventé.
@@ -41,21 +45,21 @@
41 45
42 46 ## Champs récupérés → schéma
43 47
44 −Complétude mesurée sur les **11 événements actifs** de la source ; exemples tirés d'un événement réel (`cantonsdelest:10138-4642`).
48 +Complétude mesurée sur les **12 événements actifs** de la source ; exemples tirés d'un événement réel (`cantonsdelest:9470-3010`).
45 49
46 50 | Champ (`events.*`) | Description | Complétude | Exemple réel |
47 51 |---|---|---|---|
48 −| `title` — Titre | titre de l'événement | 100 % | Festival Malvina |
49 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Pour sa première édition, le Festival Malvina réunit artistes, chercheurs et spécialistes des te… |
50 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.cantonsdelest.com/evenements/10138/festival-malvina/4642 |
51 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["festival"] |
52 +| `title` — Titre | titre de l'événement | 100 % | Simon Gouache - Pour l'instant |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 92 % | « Je vais faire ça, pour l’instant… » C’est ce que Simon Gouache s'est dit quand il s'est lancé … |
54 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.cantonsdelest.com/evenements/9470/simon-gouache-pour-l-instant/3010 |
55 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
52 56 | `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
53 −| `address` — Adresse | adresse civique | 82 % | 309 Chemin de Malvina |
54 −| `city` — Ville | municipalité | 100 % | Saint-Malo |
57 +| `address` — Adresse | adresse civique | 100 % | 64 Rue Merry N |
58 +| `city` — Ville | municipalité | 100 % | Magog |
55 59 | `region` — Région | région administrative | 100 % | Estrie |
56 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 82 % | 45.186432, -71.447036 |
57 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
58 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-23 |
60 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 45.267565, -72.155245 |
61 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-28 |
62 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-27 |
59 63 | `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
60 64 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
61 65 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
@@ -64,7 +68,7 @@ Complétude mesurée sur les **11 événements actifs** de la source ; exemples
64 68 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
65 69 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 70 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
67 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://mto.media.tourinsoft.eu/upload/Site-du-ccdMalvina-en-visite.jpg?width=1191&height=670 |
71 +| `image` — Image | visuel (og:image ou média source) | 92 % | https://lvc-medias.lavitrine.com/d36c1073-339f-53f5-a6a2-840040831475.jpg |
68 72 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
69 73
70 74 ## Fréquence & budgets
@@ -75,23 +79,24 @@ Complétude mesurée sur les **11 événements actifs** de la source ; exemples
75 79 | Variable | Défaut | Rôle |
76 80 |---|---|---|
77 81 | `CDE_FICHE_MAX` | `40` | fiches/sync |
82 +| `CDE_PAGE_MAX` | `120` | pages AJAX/sync |
78 83 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
79 84
80 85 ## Volumétrie & complétude live
81 86
82 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
83 −
84 −- **Événements en base** : 59 (dont **11 actifs**)
85 −- **GPS** : 9/11 actifs (82 %)
86 −- **Prix** : 0/11 actifs (0 %)
87 −- **Heure (start_time)** : 0/11 actifs (0 %)
88 −- **Heure de fin** : 0/11 actifs (0 %)
89 −- **Statut (annulé/reporté/complet)** : 0/11 actifs (0 %)
90 −- **Artistes** : 0/11 actifs (0 %)
91 −- **Image** : 11/11 actifs (100 %)
92 −- **Salle (venue)** : 0/11 actifs (0 %)
93 −- **Dernier sync** : 2026-08-22 23:02 — fetched 13, +0 ajoutés, ~0 mis à jour, -0 retirés
94 −- **Syncs enregistrés** (sync_log) : 102
87 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
88 +
89 +- **Événements en base** : 83 (dont **12 actifs**)
90 +- **GPS** : 12/12 actifs (100 %)
91 +- **Prix** : 0/12 actifs (0 %)
92 +- **Heure (start_time)** : 0/12 actifs (0 %)
93 +- **Heure de fin** : 0/12 actifs (0 %)
94 +- **Statut (annulé/reporté/complet)** : 0/12 actifs (0 %)
95 +- **Artistes** : 0/12 actifs (0 %)
96 +- **Image** : 11/12 actifs (92 %)
97 +- **Salle (venue)** : 0/12 actifs (0 %)
98 +- **Dernier sync** : 2026-08-25 18:00 — fetched 13, +0 ajoutés, ~0 mis à jour, -0 retirés
99 +- **Syncs enregistrés** (sync_log) : 157
95 100
96 101 ## Erreurs & dépannage
97 102
modified docs/connecteurs/centreduquebec.md +16 −11
@@ -1,6 +1,6 @@
1 1 # Tourisme Centre-du-Québec — événements (ATR) (`centreduquebec`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/centreduquebec.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Tourisme Centre-du-Québec — événements (ATR)](https://tourismecentreduquebec.com/evenements/)
10 10 - **Couverture** : Centre-du-Québec (première source de la région — 0 fiche avant connexion ; ~10 fiches)
11 −- **Extraction** : Liste rendue serveur (1 GET) : blocs events_listing (plage de dates FR + titre + lien) ; fiches incrémentales (CDQ_FICHE_MAX=10/sync, re-visite 7 j) : bloc « Adresse », og:description, og:image. Ni ville structurée, ni heure, ni tarif → jamais inventés (région dérivée de l'ATR).
11 +- **Extraction** : liste rendue serveur (1 GET, ~9 fiches) ; fiches incrémentales (CDQ_FICHE_MAX=10/sync, re-visite 7 j, cache v2) : champs <small> Adresse/Ville/Entreprise (→ organisateur), description event_details_row, « Programme » → catégories brutes, og:image. Href « site web » vide à la source ; ni heure, ni GPS, ni tarif → jamais inventés
12 12
13 13 ## Accès
14 14
@@ -26,14 +26,19 @@
26 26 > (ATR, Phase 4). Première source de la région Centre-du-Québec (0 fiche
27 27 > avant connexion).
28 28 > Source : https://tourismecentreduquebec.com/evenements/ — WordPress,
29 −> liste rendue serveur (~10 fiches à venir, vérifié 2026-08-21) :
30 −> chaque bloc events_listing_results_event = plage de dates FR
29 +> liste rendue serveur (~9 fiches à venir, revérifié 2026-08-25,
30 +> aucune pagination — la liste est complète) : chaque bloc
31 +> events_listing_results_event = plage de dates FR
31 32 > (« 1 septembre 2026 au 6 septembre 2026 ») + titre + lien.
32 33 > Extraction: GET liste (1 requête) → fiches détail incrémentales (cache
33 34 > data/centreduquebec_cache.json, CDQ_FICHE_MAX/sync, re-visite
34 −> 7 j) : bloc « Adresse » (adresse civique), og:image,
35 −> og:description. La fiche ne publie ni ville structurée, ni
36 −> heure, ni tarif → jamais inventés (région dérivée de l'ATR).
35 +> 7 j, version v=2) : champs étiquetés <small> (Adresse, VILLE,
36 +> Entreprise → organisateur), description du bloc
37 +> event_details_row_description, « Programme » (liste d'activités
38 +> → catégories brutes), lien « Visiter le site web » (href vide
39 +> sur toutes les fiches en 2026-08 — bogue du gabarit à la
40 +> source, champ émis dès qu'il sera rempli), og:image.
41 +> Ni heure ni tarif ni GPS publiés → jamais inventés.
37 42 > Accès : site public, robots.txt sans interdiction sur /evenements/ —
38 43 > GET throttlés, UA honnête.
39 44
@@ -46,7 +51,7 @@ Complétude mesurée sur les **9 événements actifs** de la source ; exemples t
46 51 | Champ (`events.*`) | Description | Complétude | Exemple réel |
47 52 |---|---|---|---|
48 53 | `title` — Titre | titre de l'événement | 100 % | Festival du Boeuf d'Inverness |
49 −| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
54 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Soirée de bingo, défilé, spectacles country et folklorique, gala amateurs, soirées dansantes, ho… |
50 55 | `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://tourismecentreduquebec.com/evenements/festival-du-boeuf-dinverness/ |
51 56 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
52 57 | `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
@@ -79,7 +84,7 @@ Complétude mesurée sur les **9 événements actifs** de la source ; exemples t
79 84
80 85 ## Volumétrie & complétude live
81 86
82 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
87 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
83 88
84 89 - **Événements en base** : 9 (dont **9 actifs**)
85 90 - **GPS** : 0/9 actifs (0 %)
@@ -90,8 +95,8 @@ Complétude mesurée sur les **9 événements actifs** de la source ; exemples t
90 95 - **Artistes** : 0/9 actifs (0 %)
91 96 - **Image** : 9/9 actifs (100 %)
92 97 - **Salle (venue)** : 0/9 actifs (0 %)
93 −- **Dernier sync** : 2026-08-22 23:02 — fetched 9, +0 ajoutés, ~0 mis à jour, -0 retirés
94 −- **Syncs enregistrés** (sync_log) : 25
98 +- **Dernier sync** : 2026-08-25 18:00 — fetched 9, +0 ajoutés, ~9 mis à jour, -0 retirés
99 +- **Syncs enregistrés** (sync_log) : 80
95 100
96 101 ## Erreurs & dépannage
97 102
added docs/connecteurs/charlevoix.md +115 −0
@@ -0,0 +1,115 @@
1 +# Tourisme Charlevoix — calendrier des événements (ATR) (`charlevoix`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/charlevoix.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Tourisme Charlevoix — calendrier des événements (ATR)](https://www.tourisme-charlevoix.com/fr/evenements)
10 +- **Couverture** : Charlevoix (→ Capitale-Nationale)
11 +- **Extraction** : Index Algolia public du site (prod_event_fr, clé search-only embarquée dans le JS) : 1 requête/sync (end>=hier) → titre, description, adresse, GPS, catégories, dates+heures réelles ; enrichissement /api/get-event incrémental (image, site web, prix, gratuité ; cache, CHX_FICHE_MAX=20, re-visite 7 j)
12 +
13 +## Accès
14 +
15 +- **Type** : tourisme-regional
16 +- **Endpoint** (`BASE`) : `https://www.tourisme-charlevoix.com`
17 +- **Endpoint** (`ALGOLIA_URL`) : `https://2GIBX867UB-dsn.algolia.net/1/indexes/prod_event_fr/query`
18 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 +- **Pagination** : par numéro de page
20 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
21 +- **Throttle** : 0.8 s entre requêtes
22 +- **Accès légal** : Clé Algolia « search-only » et API publiées par le site lui-même — requêtes throttlées, UA honnête, lien vers la fiche originale
23 +
24 +<details><summary>En-tête documentaire du connecteur</summary>
25 +
26 +> connectors/charlevoix.py : Tourisme Charlevoix (ATR, vague ATR 2026-08)
27 +> Source : tourisme-charlevoix.com/fr/evenements — calendrier officiel de
28 +> l'ATR (frontend SvelteKit + Craft CMS, listing servi par le
29 +> même index Algolia public que le site).
30 +> Extraction: index Algolia « prod_event_fr » (appId/clé de recherche
31 +> publiques, embarquées dans le JS du site — vérifié 2026-08-25 :
32 +> ~700 occurrences avec titre, description, adresse complète,
33 +> GPS, catégories, secteur (ville), start/end en timestamps
34 +> Unix, allDay). 1 requête/sync (hitsPerPage=1000, filtre
35 +> end >= hier). Enrichissement incrémental par l'API du site
36 +> POST /api/get-event {slug} (cache data/charlevoix_cache.json,
37 +> plafond CHX_FICHE_MAX/sync, re-visite 7 j) : image CDN,
38 +> site web, tarif (price), gratuité (free).
39 +> Accès : clé Algolia « search-only » publiée par le site lui-même et
40 +> API publique du frontend — requêtes throttlées, identification
41 +> honnête, lien vers la fiche originale. Prix : texte source
42 +> (price_label) ; is_free seulement si free=true (jamais inventé).
43 +
44 +</details>
45 +
46 +## Champs récupérés → schéma
47 +
48 +Complétude mesurée sur les **154 événements actifs** de la source ; exemples tirés d'un événement réel (`charlevoix:560003_1787788800`).
49 +
50 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
51 +|---|---|---|---|
52 +| `title` — Titre | titre de l'événement | 100 % | Mathieu Grégoire : Hommage à Fiori-Séguin & Cie - Auberge La Fascine Bistro Culturel |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | La Grange de l'Auberge La Fascine Bistro Culturel présente de nombreux spectacles tout au long d… |
54 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.tourisme-charlevoix.com/fr/evenements/mathieu-gregoire-hommage-a-fiori-seguin-cie-au… |
55 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
56 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
57 +| `address` — Adresse | adresse civique | 100 % | 1064, chemin des Coudriers |
58 +| `city` — Ville | municipalité | 100 % | Isle-aux-Coudres |
59 +| `region` — Région | région administrative | 100 % | Capitale-Nationale |
60 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 47.418603, -70.387147 |
61 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-26 |
62 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-26 |
63 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 97 % | 20:00 |
64 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 97 % | 23:00 |
65 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
66 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 49 % | 0 |
67 +| `price_min` — Prix min | tarif minimal réel ($) | 8 % | 30.0 |
68 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 8 % | 30 $ |
69 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
70 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
71 +| `image` — Image | visuel (og:image ou média source) | 66 % | https://cms.tourisme-charlevoix.com/files/images/_1200x655_crop_center-center_none/478461/La-Fas… |
72 +| `website` — Site web | site officiel de l'événement | 66 % | https://www.lafascine.com/programmation |
73 +
74 +## Fréquence & budgets
75 +
76 +- **Cadence** : quotidienne
77 +- **Budgets / plafonds (variables d'environnement)** :
78 +
79 +| Variable | Défaut | Rôle |
80 +|---|---|---|
81 +| `CHX_FICHE_MAX` | `20` | — |
82 +| `CHX_PAGE_MAX` | `5` | — |
83 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
84 +
85 +## Volumétrie & complétude live
86 +
87 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
88 +
89 +- **Événements en base** : 155 (dont **154 actifs**)
90 +- **GPS** : 154/154 actifs (100 %)
91 +- **Prix** : 75/154 actifs (49 %)
92 +- **Heure (start_time)** : 150/154 actifs (97 %)
93 +- **Heure de fin** : 150/154 actifs (97 %)
94 +- **Statut (annulé/reporté/complet)** : 0/154 actifs (0 %)
95 +- **Artistes** : 0/154 actifs (0 %)
96 +- **Image** : 102/154 actifs (66 %)
97 +- **Salle (venue)** : 0/154 actifs (0 %)
98 +- **Dernier sync** : 2026-08-25 18:25 — fetched 155, +155 ajoutés, ~0 mis à jour, -0 retirés
99 +- **Syncs enregistrés** (sync_log) : 1
100 +
101 +## Erreurs & dépannage
102 +
103 +Aucune erreur dans `sync_log` pour cette source. ✅
104 +
105 +- Rejouer un sync isolé : `python3 run.py sync --source charlevoix` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='charlevoix' ORDER BY ts DESC LIMIT 5;"`
106 +
107 +## Licence / attribution
108 +
109 +- Clé Algolia « search-only » et API publiées par le site lui-même — requêtes throttlées, UA honnête, lien vers la fiche originale
110 +
111 +## Historique
112 +
113 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
114 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
115 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/chaudiereappalaches.md +28 −22
@@ -1,6 +1,6 @@
1 1 # Tourisme Chaudière-Appalaches — festivals et événements (ATR) (`chaudiereappalaches`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/chaudiereappalaches.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Tourisme Chaudière-Appalaches — festivals et événements (ATR)](https://chaudiereappalaches.com/planifier-votre-sejour/ete-automne/festivals-et-evenements/)
10 10 - **Couverture** : Chaudière-Appalaches (~36 fiches à venir, vérifié 2026-08-21)
11 −- **Extraction** : Liste rendue serveur paginée (?listpage=1..3, ~12 fiches/page) → fiches incrémentales (CHA_FICHE_MAX=12/sync, re-visite 7 j) : JSON-LD Event COMPLET (dates ISO, description, adresse, ville, CP, GPS, images) via le parseur générique ld_events (sallesjsonld).
11 +- **Extraction** : liste SSR paginée (?listpage=1..3) → fiches incrémentales (CHA_FICHE_MAX=12/sync, re-visite 7 j, cache v2) : JSON-LD Event complet (dates ISO, description, adresse, ville, CP, GPS, images) + payload « const HwSheet » → site web officiel et tarif textuel (price_label brut) ; heure non publiée (openingPeriods.hasHours=false)
12 12
13 13 ## Accès
14 14
@@ -27,15 +27,21 @@
27 27 > avant connexion (essentiellement SITQ).
28 28 > Source : https://chaudiereappalaches.com/planifier-votre-sejour/
29 29 > ete-automne/festivals-et-evenements/ — CMS Woody, liste rendue
30 −> serveur paginée (?listpage=1..3, ~12 fiches/page, vérifié
31 −> 2026-08-21). Chaque fiche embarque un bloc JSON-LD Event
32 −> COMPLET : dates ISO, description, adresse civique, ville,
33 −> code postal, GPS, images.
30 +> serveur paginée (?listpage=1..3, ~12 fiches/page, revérifié
31 +> 2026-08-25 — liste saisonnière unique, pas d'équivalent hiver).
32 +> Chaque fiche embarque un bloc JSON-LD Event COMPLET (dates ISO,
33 +> description, adresse civique, ville, code postal, GPS, images)
34 +> ET un payload JS « const HwSheet = {...} » (fiche touristique
35 +> Raccourci/Hubo).
34 36 > Extraction: pages liste (1 GET/page) → fiches détail incrémentales (cache
35 37 > data/chaudiereappalaches_cache.json, CHA_FICHE_MAX/sync,
36 −> re-visite 7 j) → JSON-LD Event (parseur générique ld_events
37 −> de sallesjsonld). Pas d'heure ni de tarif structurés fiables
38 −> → jamais inventés.
38 +> re-visite 7 j, version v=2) → JSON-LD Event (parseur générique
39 +> ld_events de sallesjsonld). ENRICHI (2026-08-25) : payload
40 +> HwSheet → site web officiel (websites/bookingUrl) et tarif
41 +> textuel publié (tariffFree/tariffComplement → price_label
42 +> brut, ex. « Accès gratuit au site… »). Pas d'heure structurée
43 +> (openingPeriods.hasHours=false partout, schedules vides) →
44 +> jamais inventée.
39 45 > Accès : site public de l'ATR, robots.txt sans interdiction sur
40 46 > /planifier-votre-sejour/ — GET throttlés, UA honnête.
41 47
@@ -43,21 +49,21 @@
43 49
44 50 ## Champs récupérés → schéma
45 51
46 −Complétude mesurée sur les **28 événements actifs** de la source ; exemples tirés d'un événement réel (`chaudiereappalaches:5779956`).
52 +Complétude mesurée sur les **28 événements actifs** de la source ; exemples tirés d'un événement réel (`chaudiereappalaches:5377531`).
47 53
48 54 | Champ (`events.*`) | Description | Complétude | Exemple réel |
49 55 |---|---|---|---|
50 −| `title` — Titre | titre de l'événement | 100 % | Festival Relief |
51 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Amateurs de vélo de montagne et de plein air, rendez-vous du 6 au 8 août prochain pour festoyer!… |
52 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://chaudiereappalaches.com/planifier-votre-sejour/ete-automne/festivals-et-evenements/festi… |
56 +| `title` — Titre | titre de l'événement | 100 % | Les Grands Feux Loto-Québec |
57 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Cet été, préparez-vous à vivre des soirées inoubliables! Tout au long du mois d’août, chaque mar… |
58 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://chaudiereappalaches.com/planifier-votre-sejour/ete-automne/festivals-et-evenements/les-g… |
53 59 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["festival"] |
54 60 | `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
55 −| `address` — Adresse | adresse civique | 100 % | 120, route du Mont Adstock |
56 −| `city` — Ville | municipalité | 100 % | Adstock |
61 +| `address` — Adresse | adresse civique | 100 % | 5995 rue Saint-Laurent |
62 +| `city` — Ville | municipalité | 100 % | Lévis |
57 63 | `region` — Région | région administrative | 100 % | Chaudière-Appalaches |
58 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 46.029143, -71.187773 |
59 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2027-08-06 |
60 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2027-08-08 |
64 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 46.811429, -71.186804 |
65 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2027-08-03 |
66 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2027-08-21 |
61 67 | `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
62 68 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
63 69 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
@@ -66,7 +72,7 @@ Complétude mesurée sur les **28 événements actifs** de la source ; exemples
66 72 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
67 73 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
68 74 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
69 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://hubo-cmaj-prod.s3.eu-west-1.amazonaws.com/medias/6722011b1ce7a9089bd3.jpg |
75 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://hubo-cmaj-prod.s3.eu-west-1.amazonaws.com/medias/78f9f902fa9c93dce103.jpg |
70 76 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
71 77
72 78 ## Fréquence & budgets
@@ -81,7 +87,7 @@ Complétude mesurée sur les **28 événements actifs** de la source ; exemples
81 87
82 88 ## Volumétrie & complétude live
83 89
84 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
90 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
85 91
86 92 - **Événements en base** : 28 (dont **28 actifs**)
87 93 - **GPS** : 28/28 actifs (100 %)
@@ -92,8 +98,8 @@ Complétude mesurée sur les **28 événements actifs** de la source ; exemples
92 98 - **Artistes** : 0/28 actifs (0 %)
93 99 - **Image** : 28/28 actifs (100 %)
94 100 - **Salle (venue)** : 0/28 actifs (0 %)
95 −- **Dernier sync** : 2026-08-22 23:02 — fetched 28, +0 ajoutés, ~0 mis à jour, -0 retirés
96 −- **Syncs enregistrés** (sync_log) : 25
101 +- **Dernier sync** : 2026-08-25 18:00 — fetched 28, +0 ajoutés, ~0 mis à jour, -0 retirés
102 +- **Syncs enregistrés** (sync_log) : 80
97 103
98 104 ## Erreurs & dépannage
99 105
modified docs/connecteurs/clubs-pros.md +1 −1
@@ -1,6 +1,6 @@
1 1 # Clubs professionnels (CF Montréal, Alouettes, Rocket, Victoire…) (`clubs-pros`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : abandonnee** · Type : `sport` · Connecteur : *aucun (source sans connecteur runtime)*
6 6
added docs/connecteurs/cotenord.md +113 −0
@@ -0,0 +1,113 @@
1 +# Tourisme Côte-Nord — événements (ATR) (`cotenord`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/cotenord.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Tourisme Côte-Nord — événements (ATR)](https://www.tourismecote-nord.com/fr/activites/culture-et-decouverte/evenements-et-spectacles/)
10 +- **Couverture** : Côte-Nord (Manicouagan + Duplessis)
11 +- **Extraction** : JSON embarqué « var fiches » de la page liste (1 GET/sync : titre, description, dates ISO, ville, adresse, GPS, tarif, site web) + fiches détail incrémentales pour l'image (cache, CN_FICHE_MAX=10, re-visite 7 j)
12 +
13 +## Accès
14 +
15 +- **Type** : tourisme-regional
16 +- **Endpoint** (`BASE`) : `https://www.tourismecote-nord.com`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : Pages publiques de l'ATR, robots.txt « Allow: / » — GET throttlés, UA honnête, lien vers la fiche originale
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/cotenord.py : connecteur Tourisme Côte-Nord (ATR, vague ATR 2026-08)
26 +> Source : tourismecote-nord.com/fr/activites/culture-et-decouverte/
27 +> evenements-et-spectacles/ — calendrier officiel de l'ATR.
28 +> Extraction: la page liste (rendue serveur) embarque TOUTES les fiches dans
29 +> un bloc JavaScript « var fiches = [...] » (JSON complet vérifié
30 +> 2026-08-25 : ~37 fiches avec titre, description, dates ISO
31 +> dateDebutFiche/dateFinFiche, ville, adresse civique, GPS,
32 +> tarif entreeFicheLangue, site web). 1 GET/sync pour tout le
33 +> gisement. Fiches détail incrémentales (cache disque
34 +> data/cotenord_cache.json, plafond CN_FICHE_MAX/sync,
35 +> re-visite 7 j) UNIQUEMENT pour résoudre l'URL d'image
36 +> optimisée (fichiersUploadOpt/<hash>/<fichier>, hash non
37 +> dérivable du JSON).
38 +> Accès : pages publiques de l'ATR, robots.txt « Allow: / » — GET directs
39 +> throttlés, identification honnête, lien vers la fiche
40 +> originale. Prix : texte brut de la source (price_label),
41 +> jamais inventé.
42 +
43 +</details>
44 +
45 +## Champs récupérés → schéma
46 +
47 +Complétude mesurée sur les **10 événements actifs** de la source ; exemples tirés d'un événement réel (`cotenord:6069`).
48 +
49 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
50 +|---|---|---|---|
51 +| `title` — Titre | titre de l'événement | 100 % | Grande Corvée des monts Groulx |
52 +| `description` — Description | texte descriptif (source ou fiche détail) | 90 % | La 26e édition du bivouac annuel rassemble des bénévoles ayant à coeur l'entretien et la mise en… |
53 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.tourismecote-nord.com/fr/grande-corvee-des-monts-groulx/evenements-et-spectacles/ |
54 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["arts-scene"] |
55 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
56 +| `address` — Adresse | adresse civique | 90 % | 10 ch. du lac Daigle |
57 +| `city` — Ville | municipalité | 100 % | Fermont |
58 +| `region` — Région | région administrative | 100 % | Côte-Nord |
59 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 52.814643, -67.209252 |
60 +| `start_date` — Date début | date locale AAAA-MM-JJ | 50 % | 2026-09-04 |
61 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 50 % | 2026-09-07 |
62 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
63 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
65 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 20 % | 1 |
66 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
67 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 20 % | Gratuit |
68 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
69 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
70 +| `image` — Image | visuel (og:image ou média source) | 40 % | https://www.tourismecote-nord.com/fichiersUploadOpt/1920-764-0-292-5616-3159-0-0-0763366b1c35063… |
71 +| `website` — Site web | site officiel de l'événement | 50 % | https://www.amisdesmontsgroulx.com |
72 +
73 +## Fréquence & budgets
74 +
75 +- **Cadence** : hebdomadaire
76 +- **Budgets / plafonds (variables d'environnement)** :
77 +
78 +| Variable | Défaut | Rôle |
79 +|---|---|---|
80 +| `CN_FICHE_MAX` | `10` | fiches image/sync |
81 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
82 +
83 +## Volumétrie & complétude live
84 +
85 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
86 +
87 +- **Événements en base** : 37 (dont **10 actifs**)
88 +- **GPS** : 10/10 actifs (100 %)
89 +- **Prix** : 2/10 actifs (20 %)
90 +- **Heure (start_time)** : 0/10 actifs (0 %)
91 +- **Heure de fin** : 0/10 actifs (0 %)
92 +- **Statut (annulé/reporté/complet)** : 0/10 actifs (0 %)
93 +- **Artistes** : 0/10 actifs (0 %)
94 +- **Image** : 4/10 actifs (40 %)
95 +- **Salle (venue)** : 0/10 actifs (0 %)
96 +- **Dernier sync** : 2026-08-25 18:18 — fetched 37, +37 ajoutés, ~0 mis à jour, -0 retirés
97 +- **Syncs enregistrés** (sync_log) : 1
98 +
99 +## Erreurs & dépannage
100 +
101 +Aucune erreur dans `sync_log` pour cette source. ✅
102 +
103 +- Rejouer un sync isolé : `python3 run.py sync --source cotenord` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='cotenord' ORDER BY ts DESC LIMIT 5;"`
104 +
105 +## Licence / attribution
106 +
107 +- Pages publiques de l'ATR, robots.txt « Allow: / » — GET throttlés, UA honnête, lien vers la fiche originale
108 +
109 +## Historique
110 +
111 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
112 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
113 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/drummondville.md +110 −0
@@ -0,0 +1,110 @@
1 +# Ville de Drummondville — Calendrier des événements (`drummondville`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/drummondville.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Drummondville — Calendrier des événements](https://www.drummondville.ca/culture-loisirs-et-sports/calendrier-des-evenements/)
10 +- **Couverture** : Drummondville (Centre-du-Québec)
11 +- **Extraction** : HTML serveur Toolset (?wpv_view_count extrait du formulaire + wpv_paged) + fiches (lieu, coût) en cache incrémental (DRU_FICHES=40)
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`LIST_URL`) : `https://www.drummondville.ca/culture-loisirs-et-sports/calendrier-des-evenements/`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : par numéro de page
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : site municipal public, robots.txt permissif (wp-admin seulement)
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/drummondville.py : connecteur Ville de Drummondville — calendrier
26 +> Source : https://www.drummondville.ca/culture-loisirs-et-sports/
27 +> calendrier-des-evenements/ (WordPress + Toolset Views).
28 +> Extraction: la vue Toolset se pagine CÔTÉ SERVEUR par ?wpv_paged=N
29 +> (vérifié 2026-08-25) → cartes HTML (10/page) : plage de dates
30 +> FR, heure texte (« 19 h » / « Horaire variable »), catégorie,
31 +> titre, url fiche, image. Enrichissement par fiche
32 +> /evenement/<slug>/ (lieu « event-place », coût « Gratuit »)
33 +> — CACHE INCRÉMENTAL disque, plafond DRU_FICHES par sync.
34 +> Accès : site municipal public, robots.txt permissif (wp-admin
35 +> seulement). GET directs throttlés, identification honnête.
36 +> Prix : la fiche publie le coût (souvent « Gratuit ») → price_label
37 +> fidèle, jamais inventé.
38 +
39 +</details>
40 +
41 +## Champs récupérés → schéma
42 +
43 +Complétude mesurée sur les **117 événements actifs** de la source ; exemples tirés d'un événement réel (`drummondville:open-mic-parlez-moi-de-vous-parc-mgr-adolphe-demers-2026-08-28`).
44 +
45 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
46 +|---|---|---|---|
47 +| `title` — Titre | titre de l'événement | 100 % | Open Mic : Parlez-moi de vous! \| Parc Mgr-Adolphe-Demers |
48 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
49 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.drummondville.ca/evenement/open-mic-parlez-moi-de-vous-parc-mgr-adolphe-demers/ |
50 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["communautaire"] |
51 +| `venue` — Salle (venue) | nom du lieu | 50 % | Parc Mgr-Adolphe-Demers |
52 +| `address` — Adresse | adresse civique | 0 % | ∅ |
53 +| `city` — Ville | municipalité | 100 % | Drummondville |
54 +| `region` — Région | région administrative | 100 % | Centre-du-Québec |
55 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 3 % | 45.8754175, -72.4883908 |
56 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-28 |
57 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-28 |
58 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 91 % | 18:30 |
59 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
60 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
61 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 50 % | 1 |
62 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
63 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 50 % | Gratuit |
64 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
65 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Drummondville |
66 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.drummondville.ca/wp-content/uploads/2026/05/cheerful-woman-speaking-microphone-works… |
67 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
68 +
69 +## Fréquence & budgets
70 +
71 +- **Cadence** : quotidien
72 +- **Budgets / plafonds (variables d'environnement)** :
73 +
74 +| Variable | Défaut | Rôle |
75 +|---|---|---|
76 +| `DRU_PAGES` | `12` | — |
77 +| `DRU_FICHES` | `40` | — |
78 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
79 +
80 +## Volumétrie & complétude live
81 +
82 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
83 +
84 +- **Événements en base** : 117 (dont **117 actifs**)
85 +- **GPS** : 3/117 actifs (3 %)
86 +- **Prix** : 58/117 actifs (50 %)
87 +- **Heure (start_time)** : 107/117 actifs (91 %)
88 +- **Heure de fin** : 0/117 actifs (0 %)
89 +- **Statut (annulé/reporté/complet)** : 0/117 actifs (0 %)
90 +- **Artistes** : 0/117 actifs (0 %)
91 +- **Image** : 117/117 actifs (100 %)
92 +- **Salle (venue)** : 58/117 actifs (50 %)
93 +- **Dernier sync** : 2026-08-25 18:34 — fetched 117, +117 ajoutés, ~0 mis à jour, -0 retirés
94 +- **Syncs enregistrés** (sync_log) : 1
95 +
96 +## Erreurs & dépannage
97 +
98 +Aucune erreur dans `sync_log` pour cette source. ✅
99 +
100 +- Rejouer un sync isolé : `python3 run.py sync --source drummondville` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='drummondville' ORDER BY ts DESC LIMIT 5;"`
101 +
102 +## Licence / attribution
103 +
104 +- site municipal public, robots.txt permissif (wp-admin seulement)
105 +
106 +## Historique
107 +
108 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
109 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
110 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/eeyouistchee.md +113 −0
@@ -0,0 +1,113 @@
1 +# Eeyou Istchee Baie-James — événements (ATR) (`eeyouistchee`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/eeyouistchee.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Eeyou Istchee Baie-James — événements (ATR)](https://www.eeyouistcheebaiejames.com/fr/quoi-faire/evenements/)
10 +- **Couverture** : Eeyou Istchee Baie-James (→ Nord-du-Québec)
11 +- **Extraction** : Cartes SSR de la liste (titre, ville, plage de dates FR, image) + fiches détail incrémentales (description, code postal, site web ; cache, EIB_FICHE_MAX=10, re-visite 7 j)
12 +
13 +## Accès
14 +
15 +- **Type** : tourisme-regional
16 +- **Endpoint** (`BASE`) : `https://www.eeyouistcheebaiejames.com`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : Pages publiques de l'ATR (domaine decrochezcommejamais.com → redirection) — GET throttlés, UA honnête, lien vers la fiche originale
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/eeyouistchee.py : Eeyou Istchee Baie-James (ATR, vague ATR 2026-08)
26 +> Source : eeyouistcheebaiejames.com/fr/quoi-faire/evenements/ (domaine
27 +> decrochezcommejamais.com → redirection) — calendrier officiel
28 +> de l'ATR (plateforme membres rendue serveur, vérifié
29 +> 2026-08-25 : ~28 cartes événements avec titre, ville, date FR,
30 +> catégorie, image).
31 +> Extraction: 1 GET liste → cartes « update-secondary » (lien fiche membre
32 +> /fr/membres/<ville>/<noFiche>/evenements-et-festivals/, image
33 +> fichiersUploadOpt, titre, « Région : <ville> », date
34 +> « update-meta » : « 30 juillet au 2 août 2026 » ou « Date à
35 +> venir »). Fiches détail incrémentales (cache disque
36 +> data/eeyouistchee_cache.json, plafond EIB_FICHE_MAX/sync,
37 +> re-visite 7 j) : description (paragraphe principal), code
38 +> postal et site web de l'organisme.
39 +> Accès : pages publiques de l'ATR — GET throttlés, identification
40 +> honnête, lien vers la fiche originale. Ni prix ni heure
41 +> structurés publiés → jamais inventés.
42 +
43 +</details>
44 +
45 +## Champs récupérés → schéma
46 +
47 +Complétude mesurée sur les **22 événements actifs** de la source ; exemples tirés d'un événement réel (`eeyouistchee:536-tire-lsq`).
48 +
49 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
50 +|---|---|---|---|
51 +| `title` — Titre | titre de l'événement | 100 % | Tire LSQ |
52 +| `description` — Description | texte descriptif (source ou fiche détail) | 45 % | Le Tir LSQ réunit la communauté de Lebel‑sur‑Quévillon et des visiteurs d'ailleurs pour une jour… |
53 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.eeyouistcheebaiejames.com/fr/membres/brigade-d-incendie-lebel-sur-quevillon/536/even… |
54 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["festival"] |
55 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
56 +| `address` — Adresse | adresse civique | 0 % | ∅ |
57 +| `city` — Ville | municipalité | 100 % | Lebel-sur-Quévillon |
58 +| `region` — Région | région administrative | 100 % | Nord-du-Québec |
59 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 0 % | ∅ |
60 +| `start_date` — Date début | date locale AAAA-MM-JJ | 5 % | ∅ |
61 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 5 % | ∅ |
62 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
63 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
65 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
66 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
67 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
68 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
69 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
70 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.eeyouistcheebaiejames.com/fichiersUploadOpt/494-320-528-0-862-559-0-0-937774455db0cd… |
71 +| `website` — Site web | site officiel de l'événement | 45 % | https://www.numerique.ca |
72 +
73 +## Fréquence & budgets
74 +
75 +- **Cadence** : hebdomadaire
76 +- **Budgets / plafonds (variables d'environnement)** :
77 +
78 +| Variable | Défaut | Rôle |
79 +|---|---|---|
80 +| `EIB_FICHE_MAX` | `10` | — |
81 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
82 +
83 +## Volumétrie & complétude live
84 +
85 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
86 +
87 +- **Événements en base** : 28 (dont **22 actifs**)
88 +- **GPS** : 0/22 actifs (0 %)
89 +- **Prix** : 0/22 actifs (0 %)
90 +- **Heure (start_time)** : 0/22 actifs (0 %)
91 +- **Heure de fin** : 0/22 actifs (0 %)
92 +- **Statut (annulé/reporté/complet)** : 0/22 actifs (0 %)
93 +- **Artistes** : 0/22 actifs (0 %)
94 +- **Image** : 22/22 actifs (100 %)
95 +- **Salle (venue)** : 0/22 actifs (0 %)
96 +- **Dernier sync** : 2026-08-25 18:19 — fetched 28, +28 ajoutés, ~0 mis à jour, -0 retirés
97 +- **Syncs enregistrés** (sync_log) : 1
98 +
99 +## Erreurs & dépannage
100 +
101 +Aucune erreur dans `sync_log` pour cette source. ✅
102 +
103 +- Rejouer un sync isolé : `python3 run.py sync --source eeyouistchee` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='eeyouistchee' ORDER BY ts DESC LIMIT 5;"`
104 +
105 +## Licence / attribution
106 +
107 +- Pages publiques de l'ATR (domaine decrochezcommejamais.com → redirection) — GET throttlés, UA honnête, lien vers la fiche originale
108 +
109 +## Historique
110 +
111 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
112 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
113 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/evenementsattractions.md +114 −0
@@ -0,0 +1,114 @@
1 +# Événements Attractions Québec (quebecvacances.com) (`evenementsattractions`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `agregateur` · Connecteur : `sortika/connectors/evenementsattractions.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Événements Attractions Québec (quebecvacances.com)](https://www.quebecvacances.com/)
10 +- **Couverture** : 696 fiches sitemap (festivals/événements membres EAQ, toute la province) — ~1/3 avec dates publiées
11 +- **Extraction** : sitemap paginé /sitemap.xml?page=1..4 → fiches /fr/evenements/<slug> JSON-LD Event (dates de la prochaine édition, lieu, ville, image Cloudinary) ; anti-bot 403 → chaîne de secours Oxylabs/Scrapfly/Bright Data avec réparation d'encodage UTF-8 ; incrémental cache 14 j, plafond EAQ_DETAIL_MAX=80
12 +
13 +## Accès
14 +
15 +- **Type** : agregateur
16 +- **Endpoint** (`BASE`) : `https://www.quebecvacances.com`
17 +- **Auth** : Aucune côté source ; `SCRAPFLY_API_KEY` requis pour le backend Scrapfly
18 +- **Pagination** : par numéro de page, parcours de sitemap XML
19 +- **Backend** : Scrapfly (anti-bot / rendu JS) via `base.get_scrapfly()`
20 +- **Throttle** : 1.0 s entre requêtes
21 +- **Accès légal** : robots.txt User-agent: * / Allow: / ; rate-limiting poli, identification honnête ; coût escalade contenu par cache
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/evenementsattractions.py : connecteur Événements Attractions Québec
26 +> Source : répertoire consommateur des festivals/événements membres d'EAQ,
27 +> publié sur quebecvacances.com (~350 fiches fr, toute la province).
28 +> (Le site corporatif evenementsattractions.quebec ne liste que
29 +> des formations aux membres — sondé 2026-08-25, sans intérêt.)
30 +> Extraction: sitemap paginé (/sitemap.xml?page=1..N) → fiches
31 +> /fr/evenements/<slug>, chacune avec JSON-LD Event complet
32 +> (nom, description, dates ISO de la prochaine édition, lieu,
33 +> adresse, image Cloudinary). Le site est derrière un anti-bot
34 +> (403 en GET direct) → chaque GET passe par la chaîne de secours
35 +> du BaseConnector (Oxylabs → Scrapfly ASP → Bright Data),
36 +> d'où un cache INCRÉMENTAL agressif (data/
37 +> evenementsattractions_cache.json) : nouvelles fiches d'abord,
38 +> re-visite après 14 jours (festivals annuels stables), plafond
39 +> EAQ_DETAIL_MAX fiches/sync.
40 +> Accès : quebecvacances.com/robots.txt « User-agent: * / Allow: / » →
41 +> crawl permis ; rate-limiting poli, identification honnête.
42 +> Prix : non publié dans le JSON-LD → is_free inconnu (jamais inventé).
43 +
44 +</details>
45 +
46 +## Champs récupérés → schéma
47 +
48 +Complétude mesurée sur les **20 événements actifs** de la source ; exemples tirés d'un événement réel (`evenementsattractions:artistes-en-fete`).
49 +
50 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
51 +|---|---|---|---|
52 +| `title` — Titre | titre de l'événement | 100 % | Artistes en Fête |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Artistes en Fête est un événement unique qui célèbre les arts visuels. Plus de 40 artistes expos… |
54 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.quebecvacances.com/fr/evenements/artistes-en-fete |
55 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["festival"] |
56 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
57 +| `address` — Adresse | adresse civique | 100 % | Centre culturel du Vieux Presbytère : 505, route de la Mer |
58 +| `city` — Ville | municipalité | 100 % | Sainte-Flavie |
59 +| `region` — Région | région administrative | 95 % | Bas-Saint-Laurent |
60 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 40 % | 48.6108549, -68.229553 |
61 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2027-07-22 |
62 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2027-07-25 |
63 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
65 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
66 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
67 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
68 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
69 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
70 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
71 +| `image` — Image | visuel (og:image ou média source) | 55 % | https://res.cloudinary.com/dqmiqekxl/image/upload/c_auto,f_auto/artistes-en-fete-20340-2026-mem-… |
72 +| `website` — Site web | site officiel de l'événement | 100 % | https://www.quebecvacances.com/fr/evenements/artistes-en-fete |
73 +
74 +## Fréquence & budgets
75 +
76 +- **Cadence** : hebdomadaire
77 +- **Budgets / plafonds (variables d'environnement)** :
78 +
79 +| Variable | Défaut | Rôle |
80 +|---|---|---|
81 +| `EAQ_DETAIL_MAX` | `80` | — |
82 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
83 +
84 +## Volumétrie & complétude live
85 +
86 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
87 +
88 +- **Événements en base** : 21 (dont **20 actifs**)
89 +- **GPS** : 8/20 actifs (40 %)
90 +- **Prix** : 0/20 actifs (0 %)
91 +- **Heure (start_time)** : 0/20 actifs (0 %)
92 +- **Heure de fin** : 0/20 actifs (0 %)
93 +- **Statut (annulé/reporté/complet)** : 0/20 actifs (0 %)
94 +- **Artistes** : 0/20 actifs (0 %)
95 +- **Image** : 11/20 actifs (55 %)
96 +- **Salle (venue)** : 0/20 actifs (0 %)
97 +- **Dernier sync** : 2026-08-25 18:48 — fetched 21, +21 ajoutés, ~0 mis à jour, -0 retirés
98 +- **Syncs enregistrés** (sync_log) : 1
99 +
100 +## Erreurs & dépannage
101 +
102 +Aucune erreur dans `sync_log` pour cette source. ✅
103 +
104 +- Rejouer un sync isolé : `python3 run.py sync --source evenementsattractions` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='evenementsattractions' ORDER BY ts DESC LIMIT 5;"`
105 +
106 +## Licence / attribution
107 +
108 +- robots.txt User-agent: * / Allow: / ; rate-limiting poli, identification honnête ; coût escalade contenu par cache
109 +
110 +## Historique
111 +
112 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
113 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
114 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/evenko.md +42 −33
@@ -1,6 +1,6 @@
1 1 # evenko — grands concerts et festivals (`evenko`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `promoteur` · Connecteur : `sortika/connectors/evenko.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [evenko — grands concerts et festivals](https://evenko.ca/fr/evenements)
10 10 - **Couverture** : Grands concerts (Centre Bell, Place Bell, MTELUS…), festivals (Osheaga, ÎleSoniq…)
11 −- **Extraction** : API /api/search (proxy Algolia) — format percé : GET ?body=base64({params:[query,options],lang:'fr-CA'}), filters entity_type:evenko_show, ~5 requêtes/sync ; heure locale du spectacle via show_time (epoch → America/Montreal, respect de hide_show_time) ; artistes = champ headliners explicite ; gratuité : champ free (sondé null sur 100 % des hits, 2026-08) + étiquettes « gratuit » (event_tag/promotional_tag) ; aucun montant exposé par l'index
11 +- **Extraction** : API /api/search (proxy Algolia) — format percé : GET ?body=base64({params:[query,options],lang:'fr-CA'}), filters entity_type:evenko_show, ~5 requêtes/sync ; heure locale du spectacle via show_time (epoch → America/Montreal, respect de hide_show_time) ; artistes = champ headliners explicite ; gratuité : champ free (sondé null sur 100 % des hits, 2026-08) + étiquettes « gratuit » (event_tag/promotional_tag) ; aucun montant exposé par l'index. MAJ 2026-08-25 : aucun montant exposé (index Algolia complet 660 hits + JSON-LD des pages événement sans price) — prix uniquement chez Ticketmaster (connecteur ticketmaster, priceRanges)
12 12
13 13 ## Accès
14 14
@@ -29,41 +29,50 @@
29 29 > options], lang:"fr-CA"}). ~14 requêtes/sync (100 hits/page).
30 30 > Accès : robots.txt Allow /api/search* ; GET throttlés, identification
31 31 > honnête ; lien billets = URL Ticketmaster officielle du hit.
32 −> Prix : champ `free` de la source → is_free, MAIS sondé null sur
33 −> 100 % des hits (462/462, vérifié 2026-08) et l'index ne porte
34 −> aucun montant (additional_information.regular_ticket = libellés
35 −> + URLs Ticketmaster seulement, filtre free:true → 0 hit).
36 −> Complément honnête : étiquettes explicites « gratuit » dans
37 −> event_tag / promotional_tag → is_free=True. On n'invente rien.
32 +> Prix : NON PUBLIÉS par la source. Champ `free` → is_free, MAIS sondé
33 +> null sur 100 % des hits (462/462 vérifié 2026-08 ; re-vérifié
34 +> 660/660 le 2026-08-25) et l'index
35 +> ne porte aucun montant (additional_information.regular_ticket
36 +> = libellés + URLs Ticketmaster seulement, filtre free:true →
37 +> 0 hit ; event_discount = {percentage, amount, minimum} d'un
38 +> rabais, pas un tarif). Re-sondé live 2026-08-25 sur l'index
39 +> COMPLET (660 hits, tous attributs) ET sur les pages événement
40 +> /fr/evenements/<salle>/<slug> de 6 salles : le JSON-LD Event
41 +> y porte offers SANS price/priceCurrency (URL Ticketmaster +
42 +> availability seulement) — les montants ne vivent que chez
43 +> Ticketmaster (couvert par le connecteur ticketmaster, API
44 +> officielle priceRanges). Complément honnête : étiquettes
45 +> explicites « gratuit » dans event_tag / promotional_tag →
46 +> is_free=True. On n'invente rien.
38 47
39 48 </details>
40 49
41 50 ## Champs récupérés → schéma
42 51
43 −Complétude mesurée sur les **413 événements actifs** de la source ; exemples tirés d'un événement réel (`evenko:e008639`).
52 +Complétude mesurée sur les **621 événements actifs** de la source ; exemples tirés d'un événement réel (`evenko:e008238`).
44 53
45 54 | Champ (`events.*`) | Description | Complétude | Exemple réel |
46 55 |---|---|---|---|
47 −| `title` — Titre | titre de l'événement | 100 % | Hazlett |
56 +| `title` — Titre | titre de l'événement | 100 % | Bunt. |
48 57 | `description` — Description | texte descriptif (source ou fiche détail) | 99 % | Portes : 19 h 00 |
49 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.ticketmaster.ca/event/310064CBAD997B95?lang=fr-ca&brand=evenko |
58 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.ticketmaster.ca/event/310064A1D614A5D4?lang=fr-ca&brand=mtelus |
50 59 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
51 −| `venue` — Salle (venue) | nom du lieu | 100 % | Théâtre Beanfield |
60 +| `venue` — Salle (venue) | nom du lieu | 100 % | MTELUS |
52 61 | `address` — Adresse | adresse civique | 0 % | ∅ |
53 62 | `city` — Ville | municipalité | 100 % | Montréal |
54 −| `region` — Région | région administrative | 100 % | Montréal |
55 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 97 % | 45.4828925, -73.5750666 |
56 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-09 |
57 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-09 |
58 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 99 % | 20:00 |
63 +| `region` — Région | région administrative | 99 % | Montréal |
64 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 84 % | 45.5106101, -73.5633963 |
65 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-17 |
66 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-17 |
67 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:00 |
59 68 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
60 −| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 1 % | postponed |
69 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 1 % | soldout |
61 70 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
62 71 | `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
63 72 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
64 −| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 99 % | ["Hazlett"] |
73 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 100 % | ["BUNT."] |
65 74 | `organizer` — Organisateur | promoteur / organisme | 100 % | evenko |
66 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://images.ctfassets.net/3yxl57nu0yl4/mN9LeqmCUHLpse7u5H6qZ/886d0a285be58605b448a73f9080464f… |
75 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://images.ctfassets.net/3yxl57nu0yl4/5KTn3urKJKaNNhQIDQLe7H/257aab96b07a1556d5d5cc1c92dfd6f… |
67 76 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
68 77
69 78 ## Fréquence & budgets
@@ -73,19 +82,19 @@ Complétude mesurée sur les **413 événements actifs** de la source ; exemples
73 82
74 83 ## Volumétrie & complétude live
75 84
76 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
77 −
78 −- **Événements en base** : 426 (dont **413 actifs**)
79 −- **GPS** : 400/413 actifs (97 %)
80 −- **Prix** : 0/413 actifs (0 %)
81 −- **Heure (start_time)** : 410/413 actifs (99 %)
82 −- **Heure de fin** : 0/413 actifs (0 %)
83 −- **Statut (annulé/reporté/complet)** : 4/413 actifs (1 %)
84 −- **Artistes** : 410/413 actifs (99 %)
85 −- **Image** : 413/413 actifs (100 %)
86 −- **Salle (venue)** : 413/413 actifs (100 %)
87 −- **Dernier sync** : 2026-08-22 23:02 — fetched 413, +0 ajoutés, ~0 mis à jour, -0 retirés
88 −- **Syncs enregistrés** (sync_log) : 121
85 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
86 +
87 +- **Événements en base** : 646 (dont **621 actifs**)
88 +- **GPS** : 520/621 actifs (84 %)
89 +- **Prix** : 0/621 actifs (0 %)
90 +- **Heure (start_time)** : 618/621 actifs (100 %)
91 +- **Heure de fin** : 0/621 actifs (0 %)
92 +- **Statut (annulé/reporté/complet)** : 4/621 actifs (1 %)
93 +- **Artistes** : 618/621 actifs (100 %)
94 +- **Image** : 621/621 actifs (100 %)
95 +- **Salle (venue)** : 621/621 actifs (100 %)
96 +- **Dernier sync** : 2026-08-25 18:00 — fetched 621, +0 ajoutés, ~0 mis à jour, -0 retirés
97 +- **Syncs enregistrés** (sync_log) : 176
89 98
90 99 ## Erreurs & dépannage
91 100
modified docs/connecteurs/eventbrite.md +22 −22
@@ -1,6 +1,6 @@
1 1 # Eventbrite — billetterie libre-service (Québec) (`eventbrite`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/eventbrite.py`
6 6
@@ -46,30 +46,30 @@
46 46
47 47 ## Champs récupérés → schéma
48 48
49 −Complétude mesurée sur les **435 événements actifs** de la source ; exemples tirés d'un événement réel (`eventbrite:1993799466877`).
49 +Complétude mesurée sur les **435 événements actifs** de la source ; exemples tirés d'un événement réel (`eventbrite:1984325171003`).
50 50
51 51 | Champ (`events.*`) | Description | Complétude | Exemple réel |
52 52 |---|---|---|---|
53 −| `title` — Titre | titre de l'événement | 100 % | FUBAR MONTREAL ( Round 4) |
54 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | FUBAR MONTREAL: Une soirée de folie où tout peut arriver! |
55 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.eventbrite.com/e/fubar-montreal-round-4-tickets-1993799466877 |
53 +| `title` — Titre | titre de l'événement | 100 % | Montréal : Vendredi 11 septembre 2026 - 19h30 : Hommage à Ginette Reno |
54 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Montréal : Vendredi 11 septembre 2026 à 19h30 - Spectacle hommage à Ginette Reno |
55 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.eventbrite.ca/e/billets-montreal-vendredi-11-septembre-2026-19h30-hommage-a-ginette-… |
56 56 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
57 −| `venue` — Salle (venue) | nom du lieu | 100 % | Sainte-Catherine Hall |
58 −| `address` — Adresse | adresse civique | 100 % | 917 Rue Sainte-Catherine Est |
57 +| `venue` — Salle (venue) | nom du lieu | 100 % | Église catholique Sainte-Marthe |
58 +| `address` — Adresse | adresse civique | 100 % | 7580 Boulevard Gouin Est |
59 59 | `city` — Ville | municipalité | 100 % | Montréal |
60 60 | `region` — Région | région administrative | 99 % | Montréal |
61 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 45.516717, -73.558191 |
62 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
63 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-23 |
64 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 22:00 |
65 −| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 100 % | 03:00 |
61 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 45.636778, -73.605884 |
62 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-11 |
63 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-11 |
64 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 19:30 |
65 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 100 % | 21:00 |
66 66 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
67 67 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 88 % | 0 |
68 −| `price_min` — Prix min | tarif minimal réel ($) | 88 % | 43.93 |
69 −| `price_label` — Libellé prix | étiquette tarifaire affichable | 88 % | 43.93 $ à 1172.84 $ (Eventbrite) |
68 +| `price_min` — Prix min | tarif minimal réel ($) | 87 % | 60.78 |
69 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 88 % | 60.78 $ (Eventbrite) |
70 70 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
71 −| `organizer` — Organisateur | promoteur / organisme | 97 % | Hangover Global / BlackBird |
72 −| `image` — Image | visuel (og:image ou média source) | 98 % | https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.com%2Fimages%2F1191069433%2F214898832986%2F1%2Fori… |
71 +| `organizer` — Organisateur | promoteur / organisme | 97 % | Productions Marc Gervais Conférence / Spectacle |
72 +| `image` — Image | visuel (og:image ou média source) | 98 % | https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.com%2Fimages%2F1191872529%2F242151267944%2F1%2Fori… |
73 73 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
74 74
75 75 ## Fréquence & budgets
@@ -84,19 +84,19 @@ Complétude mesurée sur les **435 événements actifs** de la source ; exemples
84 84
85 85 ## Volumétrie & complétude live
86 86
87 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
87 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
88 88
89 −- **Événements en base** : 571 (dont **435 actifs**)
89 +- **Événements en base** : 674 (dont **435 actifs**)
90 90 - **GPS** : 435/435 actifs (100 %)
91 −- **Prix** : 384/435 actifs (88 %)
91 +- **Prix** : 381/435 actifs (88 %)
92 92 - **Heure (start_time)** : 435/435 actifs (100 %)
93 93 - **Heure de fin** : 435/435 actifs (100 %)
94 94 - **Statut (annulé/reporté/complet)** : 0/435 actifs (0 %)
95 95 - **Artistes** : 0/435 actifs (0 %)
96 −- **Image** : 426/435 actifs (98 %)
96 +- **Image** : 427/435 actifs (98 %)
97 97 - **Salle (venue)** : 435/435 actifs (100 %)
98 −- **Dernier sync** : 2026-08-22 23:03 — fetched 429, +1 ajoutés, ~1 mis à jour, -2 retirés
99 −- **Syncs enregistrés** (sync_log) : 103
98 +- **Dernier sync** : 2026-08-25 18:01 — fetched 430, +0 ajoutés, ~6 mis à jour, -5 retirés
99 +- **Syncs enregistrés** (sync_log) : 158
100 100
101 101 ## Erreurs & dépannage
102 102
modified docs/connecteurs/gaspesie.md +35 −31
@@ -1,6 +1,6 @@
1 1 # Tourisme Gaspésie — événements (ATR) (`gaspesie`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/gaspesie.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Tourisme Gaspésie — événements (ATR)](https://www.tourisme-gaspesie.com/fr/festival-et-evenements/)
10 10 - **Couverture** : Gaspésie–Îles-de-la-Madeleine
11 −- **Extraction** : Scrapfly (asp, country=ca, sans render_js — HTML rendu serveur) : liste (1 crédit) + fiches incrémentales (cache, GASP_FICHE_MAX=8/sync, re-visite 14 j) — titre h1, plage de dates FR, adresse/ville/CP (bloc coordonnées), og:description/og:image. Fiches sans date écartées (festivals annuels non datés).
11 +- **Extraction** : Scrapfly (asp, country=ca, sans render_js) : liste (1 crédit) + fiches incrémentales (GASP_FICHE_MAX/sync, re-visite 14 j, cache v2) — titre h1, plage FR, og:description/og:image + JSON-LD Organization → GPS exact, adresse/ville/CP structurés, site web officiel
12 12 - **Note** : RÉACTIVÉE 2026-08-21 (Phase 4) : la vague Scrapfly consignée annoncée en Phase 3. Ancienne URL /evenements-gaspesie.html morte (404) → /fr/festival-et-evenements/ (28 fiches, ~6 datées à l'amorce).
13 13
14 14 ## Accès
@@ -30,13 +30,17 @@
30 30 > (CMS Umbraco). Le site est derrière un WAF : 403 sur GET
31 31 > identifié (revérifié 2026-08-21) → extraction via SCRAPFLY
32 32 > (asp, country=ca, sans render_js — le HTML est rendu serveur),
33 −> usage consigné et budgeté (GASP_FICHE_MAX/sync).
33 +> usage consigné et budgeté (GASP_FICHE_MAX/sync). La liste
34 +> publie les 27 fiches d'un coup (pagination purement côté
35 +> client, 12/page — revérifié 2026-08-25) : rien de caché.
34 36 > Extraction: liste (1 crédit) → fiches détail incrémentales (cache
35 37 > data/gaspesie_cache.json, GASP_FICHE_MAX/sync, re-visite 14 j
36 −> — festivals annuels stables) : titre <h1>, plage de dates FR
37 −> (« 18 au 21 juin 2026 »), ville (« Carleton-sur-Mer, Québec »),
38 −> og:description, og:image. Pas d'heure ni de tarif structurés
39 −> → jamais inventés.
38 +> — festivals annuels stables, version v=2) : titre <h1>, plage
39 +> de dates FR (« 18 au 21 juin 2026 »), og:description, og:image.
40 +> ENRICHI (2026-08-25) : JSON-LD Organization de la fiche → GPS
41 +> exact (GeoCoordinates), adresse civique/ville/code postal
42 +> structurés, site web officiel (sameAs non-réseau-social).
43 +> Pas d'heure ni de tarif structurés → jamais inventés.
40 44 > Accès : contenu public de l'ATR ; anti-bot contourné avec modération
41 45 > (≤ 1 + GASP_FICHE_MAX crédits Scrapfly/sync, re-visite 14 j),
42 46 > attribution et lien vers la fiche officielle conservés.
@@ -45,21 +49,21 @@
45 49
46 50 ## Champs récupérés → schéma
47 51
48 −Complétude mesurée sur les **10 événements actifs** de la source ; exemples tirés d'un événement réel (`gaspesie:les-perceides-festival-international-de-cinema-et-dart-de-perce`).
52 +Complétude mesurée sur les **7 événements actifs** de la source ; exemples tirés d'un événement réel (`gaspesie:noel-en-gaspesie`).
49 53
50 54 | Champ (`events.*`) | Description | Complétude | Exemple réel |
51 55 |---|---|---|---|
52 −| `title` — Titre | titre de l'événement | 100 % | Les Percéides - Festival international de cinéma et d'art de Percé |
53 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Les Percéides en tant qu’unique festival de cinéma d’auteur en Gaspésie se distingue par sa libe… |
54 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.tourisme-gaspesie.com/fr/festival-et-evenements/les-perceides-festival-international… |
55 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["festival"] |
56 +| `title` — Titre | titre de l'événement | 100 % | Noël en Gaspésie |
57 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Événement proposant un temps des fêtes inoubliable dans les décors magnifiques de la Baie-des-Ch… |
58 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.tourisme-gaspesie.com/fr/festival-et-evenements/noel-en-gaspesie/ |
59 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
56 60 | `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
57 −| `address` — Adresse | adresse civique | 100 % | 162, route 132 Ouest |
58 −| `city` — Ville | municipalité | 100 % | Percé |
61 +| `address` — Adresse | adresse civique | 100 % | 99, place Suzanne-Guité |
62 +| `city` — Ville | municipalité | 100 % | New Richmond |
59 63 | `region` — Région | région administrative | 100 % | Gaspésie–Îles-de-la-Madeleine |
60 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 90 % | 48.5217344, -64.2143739 |
61 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-19 |
62 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-23 |
64 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 86 % | 48.1618644, -65.8618622 |
65 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-11-21 |
66 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-12-31 |
63 67 | `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 68 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
65 69 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
@@ -68,7 +72,7 @@ Complétude mesurée sur les **10 événements actifs** de la source ; exemples
68 72 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
69 73 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
70 74 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
71 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.tourisme-gaspesie.com/media/joydhvzd/les-perceides-festival-international-de-cinema-… |
75 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.tourisme-gaspesie.com/media/goglqxb2/noel-en-gaspesie-evenement-1-_mainpicture.jpg?w… |
72 76 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
73 77
74 78 ## Fréquence & budgets
@@ -83,19 +87,19 @@ Complétude mesurée sur les **10 événements actifs** de la source ; exemples
83 87
84 88 ## Volumétrie & complétude live
85 89
86 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
87 −
88 −- **Événements en base** : 26 (dont **10 actifs**)
89 −- **GPS** : 9/10 actifs (90 %)
90 −- **Prix** : 0/10 actifs (0 %)
91 −- **Heure (start_time)** : 0/10 actifs (0 %)
92 −- **Heure de fin** : 0/10 actifs (0 %)
93 −- **Statut (annulé/reporté/complet)** : 0/10 actifs (0 %)
94 −- **Artistes** : 0/10 actifs (0 %)
95 −- **Image** : 10/10 actifs (100 %)
96 −- **Salle (venue)** : 0/10 actifs (0 %)
97 −- **Dernier sync** : 2026-08-22 23:03 — fetched 26, +0 ajoutés, ~0 mis à jour, -0 retirés
98 −- **Syncs enregistrés** (sync_log) : 26
90 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
91 +
92 +- **Événements en base** : 26 (dont **7 actifs**)
93 +- **GPS** : 6/7 actifs (86 %)
94 +- **Prix** : 0/7 actifs (0 %)
95 +- **Heure (start_time)** : 0/7 actifs (0 %)
96 +- **Heure de fin** : 0/7 actifs (0 %)
97 +- **Statut (annulé/reporté/complet)** : 0/7 actifs (0 %)
98 +- **Artistes** : 0/7 actifs (0 %)
99 +- **Image** : 7/7 actifs (100 %)
100 +- **Salle (venue)** : 0/7 actifs (0 %)
101 +- **Dernier sync** : 2026-08-25 18:01 — fetched 25, +0 ajoutés, ~0 mis à jour, -0 retirés
102 +- **Syncs enregistrés** (sync_log) : 81
99 103
100 104 ## Erreurs & dépannage
101 105
modified docs/connecteurs/gatineau.md +76 −13
@@ -1,46 +1,109 @@
1 −# Ville de Gatineau — Événements (`gatineau`)
1 +# Ville de Gatineau — Calendrier officiel (`gatineau`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 −**Statut : abandonnee** · Type : `municipal` · Connecteur : *aucun (source sans connecteur runtime)*
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/gatineau.py`
6 6
7 7 ## Description
8 8
9 −- **Source** : [Ville de Gatineau — Événements](https://www.gatineau.ca/)
9 +- **Source** : [Ville de Gatineau — Calendrier officiel](https://calendrier.gatineau.cloud)
10 10 - **Couverture** : Gatineau (Outaouais)
11 −- **Extraction** : n/d
12 −- **Note** : Aucun jeu de données « événements » sur Données Québec (portail Gatineau : géomatique seulement) et site 100 % JavaScript sans API découverte (vérifié 2026-08-18)
11 +- **Extraction** : API JSON interne POST /calendrier/json/evenements.aspx (1 requête) — dates UTC→locale, lieux et images ~100 %. RÉACTIVÉE 2026-08-25 (l'abandon visait l'open data, mort ; le calendrier applicatif est vivant)
13 12
14 13 ## Accès
15 14
16 15 - **Type** : municipal
17 −- **Endpoint** : voir URL source (https://www.gatineau.ca/)
16 +- **Endpoint** (`BASE`) : `https://calendrier.gatineau.cloud`
18 17 - **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 −- **Accès légal** : n/d
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : application municipale publique, robots: all
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/gatineau.py : connecteur Ville de Gatineau — calendrier officiel
26 +> Source : https://calendrier.gatineau.cloud — application calendrier
27 +> dédiée de la Ville (gatineau.ca/calendrier y redirige).
28 +> Extraction: API JSON interne POST /calendrier/json/evenements.aspx
29 +> (même appel que le frontend, découvert dans scripts.js) —
30 +> ~160 événements / ~250 occurrences en 1 requête : titre,
31 +> description, image, lien officiel, occurrences horodatées
32 +> (UTC → heure locale via utc_to_local), emplacements
33 +> (nom, adresse, secteur). Vérifié 2026-08-25.
34 +> Accès : application municipale publique, robots.txt « robots: all »
35 +> sur la page, GET/POST directs, identification honnête.
36 +> Prix : non publié par l'API → jamais inventé (les liens « Acheter
37 +> des billets » sont conservés dans website).
38 +
39 +</details>
20 40
21 41 ## Champs récupérés → schéma
22 42
23 −*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
43 +Complétude mesurée sur les **254 événements actifs** de la source ; exemples tirés d'un événement réel (`gatineau:46388-66780`).
44 +
45 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
46 +|---|---|---|---|
47 +| `title` — Titre | titre de l'événement | 100 % | Recycl'Art 2026 |
48 +| `description` — Description | texte descriptif (source ou fiche détail) | 54 % | ∅ |
49 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://calendrier.gatineau.cloud/calendrier/evenement.aspx?langue=fr-ca&id_evenement=46388 |
50 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
51 +| `venue` — Salle (venue) | nom du lieu | 100 % | Ruisseau de la Brasserie |
52 +| `address` — Adresse | adresse civique | 87 % | ∅ |
53 +| `city` — Ville | municipalité | 100 % | Gatineau |
54 +| `region` — Région | région administrative | 100 % | Outaouais |
55 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 87 % | 45.4430331, -75.7221264 |
56 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-07-04 |
57 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-30 |
58 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 99 % | 14:00 |
59 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 99 % | 17:00 |
60 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
61 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
62 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
63 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
64 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
65 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Gatineau |
66 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://calendrier.gatineau.cloud/upload/calendrier/4abbe660-81d1-48f8-b508-2093082bf2aa.jpg |
67 +| `website` — Site web | site officiel de l'événement | 100 % | https://calendrier.gatineau.cloud/calendrier/lien.aspx?langue=fr-ca&id_evenement=46388&url_retou… |
24 68
25 69 ## Fréquence & budgets
26 70
27 −- **Cadence** : n/d
71 +- **Cadence** : quotidien
72 +- **Budgets / plafonds (variables d'environnement)** :
73 +
74 +| Variable | Défaut | Rôle |
75 +|---|---|---|
76 +| `GATINEAU_LIGNES` | `500` | plafond de lignes |
28 77 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
29 78
30 79 ## Volumétrie & complétude live
31 80
32 −*Source sans connecteur runtime : aucune donnée en base.*
81 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
82 +
83 +- **Événements en base** : 254 (dont **254 actifs**)
84 +- **GPS** : 221/254 actifs (87 %)
85 +- **Prix** : 0/254 actifs (0 %)
86 +- **Heure (start_time)** : 251/254 actifs (99 %)
87 +- **Heure de fin** : 251/254 actifs (99 %)
88 +- **Statut (annulé/reporté/complet)** : 0/254 actifs (0 %)
89 +- **Artistes** : 0/254 actifs (0 %)
90 +- **Image** : 254/254 actifs (100 %)
91 +- **Salle (venue)** : 254/254 actifs (100 %)
92 +- **Dernier sync** : 2026-08-25 18:28 — fetched 254, +254 ajoutés, ~0 mis à jour, -0 retirés
93 +- **Syncs enregistrés** (sync_log) : 1
33 94
34 95 ## Erreurs & dépannage
35 96
97 +Aucune erreur dans `sync_log` pour cette source. ✅
98 +
99 +- Rejouer un sync isolé : `python3 run.py sync --source gatineau` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='gatineau' ORDER BY ts DESC LIMIT 5;"`
36 100
37 101 ## Licence / attribution
38 102
39 −- n/d
103 +- application municipale publique, robots: all
40 104
41 105 ## Historique
42 106
43 −- **Source abandonnée** — Aucun jeu de données « événements » sur Données Québec (portail Gatineau : géomatique seulement) et site 100 % JavaScript sans API découverte (vérifié 2026-08-18)
44 107 - **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
45 108 - **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
46 109 - **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/granby.md +105 −0
@@ -0,0 +1,105 @@
1 +# Ville de Granby — Calendrier des événements (liste éditoriale) (`granby`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/granby.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Granby — Calendrier des événements (liste éditoriale)](https://www.granby.ca/fr/calendrier-evenements)
10 +- **Couverture** : Granby (Estrie)
11 +- **Extraction** : HTML serveur (blocs éditoriaux h5 + dates FR + lien externe), parsing tolérant, 1 requête — aucun calendrier structuré à la source (jsonapi désactivé, listing Drupal vide côté serveur)
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`LIST_URL`) : `https://www.granby.ca/fr/calendrier-evenements`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Accès légal** : site municipal public, robots.txt permissif
21 +
22 +<details><summary>En-tête documentaire du connecteur</summary>
23 +
24 +> connectors/granby.py : connecteur Ville de Granby — calendrier des événements
25 +> Source : https://www.granby.ca/fr/calendrier-evenements (Drupal).
26 +> Extraction: la Ville ne publie PAS de calendrier structuré (jsonapi
27 +> désactivé, page « actualités/événements » vide côté serveur,
28 +> vérifié 2026-08-25) — la page calendrier est une liste
29 +> ÉDITORIALE rendue serveur : blocs riches <h5><strong>Titre
30 +> </strong></h5> suivi d'un <p> de dates FR (« Les 22 et 23 mai
31 +> 2026 », « Du 28 au 30 mai 2026 ») et d'un lien « Découvrez
32 +> l'événement » (souvent externe, vers l'organisateur).
33 +> Parsing tolérant : un bloc sans date parseable est ignoré.
34 +> Volume faible (~15-25 événements saisonniers), 1 requête.
35 +> Accès : site municipal public, GET direct throttlé, identification
36 +> honnête ; robots.txt permissif.
37 +> Prix : non publié par la source → jamais inventé. Lieu/heure non
38 +> publiés dans la liste → laissés vides.
39 +
40 +</details>
41 +
42 +## Champs récupérés → schéma
43 +
44 +Complétude mesurée sur les **8 événements actifs** de la source ; exemples tirés d'un événement réel (`granby:rendez-vous-culturels-tournee-des-parcs-2026-08-11`).
45 +
46 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
47 +|---|---|---|---|
48 +| `title` — Titre | titre de l'événement | 100 % | Rendez-vous culturels – Tournée des parcs |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.vccgranby.org/rdvgranby |
51 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["plein-air"] |
52 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
53 +| `address` — Adresse | adresse civique | 0 % | ∅ |
54 +| `city` — Ville | municipalité | 100 % | Granby |
55 +| `region` — Région | région administrative | 100 % | Estrie |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 0 % | ∅ |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-11 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
59 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
60 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
61 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
62 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
63 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
64 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
65 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Granby |
67 +| `image` — Image | visuel (og:image ou média source) | 0 % | ∅ |
68 +| `website` — Site web | site officiel de l'événement | 75 % | https://www.vccgranby.org/rdvgranby |
69 +
70 +## Fréquence & budgets
71 +
72 +- **Cadence** : hebdomadaire
73 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
74 +
75 +## Volumétrie & complétude live
76 +
77 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
78 +
79 +- **Événements en base** : 20 (dont **8 actifs**)
80 +- **GPS** : 0/8 actifs (0 %)
81 +- **Prix** : 0/8 actifs (0 %)
82 +- **Heure (start_time)** : 0/8 actifs (0 %)
83 +- **Heure de fin** : 0/8 actifs (0 %)
84 +- **Statut (annulé/reporté/complet)** : 0/8 actifs (0 %)
85 +- **Artistes** : 0/8 actifs (0 %)
86 +- **Image** : 0/8 actifs (0 %)
87 +- **Salle (venue)** : 0/8 actifs (0 %)
88 +- **Dernier sync** : 2026-08-25 18:19 — fetched 20, +20 ajoutés, ~0 mis à jour, -0 retirés
89 +- **Syncs enregistrés** (sync_log) : 1
90 +
91 +## Erreurs & dépannage
92 +
93 +Aucune erreur dans `sync_log` pour cette source. ✅
94 +
95 +- Rejouer un sync isolé : `python3 run.py sync --source granby` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='granby' ORDER BY ts DESC LIMIT 5;"`
96 +
97 +## Licence / attribution
98 +
99 +- site municipal public, robots.txt permissif
100 +
101 +## Historique
102 +
103 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
104 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
105 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/ilesdelamadeleine.md +88 −0
@@ -0,0 +1,88 @@
1 +# Tourisme Îles-de-la-Madeleine — festivals et événements (ATR) (`ilesdelamadeleine`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/ilesdelamadeleine.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Tourisme Îles-de-la-Madeleine — festivals et événements (ATR)](https://www.tourismeilesdelamadeleine.com/fr/decouvrir-les-iles/experiences/culture/festivals-et-evenements/)
10 +- **Couverture** : Îles-de-la-Madeleine
11 +- **Extraction** : Cartes SSR de la liste (titre, dates FR, catégorie, image) + fiches détail incrémentales (microdonnées schema.org : adresse/ville/CP, description, dates ; cache, IDM_FICHE_MAX=10, re-visite 7 j)
12 +
13 +## Accès
14 +
15 +- **Type** : tourisme-regional
16 +- **Endpoint** (`BASE`) : `https://www.tourismeilesdelamadeleine.com`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : Pages publiques de l'ATR, robots.txt permissif — GET throttlés, UA honnête, lien vers la fiche originale
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/ilesdelamadeleine.py : Tourisme Îles-de-la-Madeleine (ATR)
26 +> Source : tourismeilesdelamadeleine.com/fr/decouvrir-les-iles/experiences/
27 +> culture/festivals-et-evenements/ — calendrier officiel de l'ATR
28 +> (plateforme PHP maison, rendue serveur ; vérifié 2026-08-25 :
29 +> ~10 cartes avec titre, plage de dates FR « Du 22 février au
30 +> 1 mars 2026 », catégorie, image).
31 +> Extraction: 1 GET liste → cartes (lien /fr/<slug>/festivals-et-evenements/,
32 +> image listesite_, titre, dates FR, catégorie). Fiches détail
33 +> incrémentales (cache data/ilesdelamadeleine_cache.json,
34 +> plafond IDM_FICHE_MAX/sync, re-visite 7 j) : microdonnées
35 +> schema.org (streetAddress, addressLocality, postalCode,
36 +> telephone) + description du bloc principal (itemprop) +
37 +> plage de dates de la fiche (souvent plus précise).
38 +> Accès : robots.txt « Allow: / » (hors utilitaires) — GET throttlés,
39 +> identification honnête, lien vers la fiche originale.
40 +> Ni prix ni heure publiés de façon structurée → jamais inventés.
41 +
42 +</details>
43 +
44 +## Champs récupérés → schéma
45 +
46 +*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
47 +
48 +## Fréquence & budgets
49 +
50 +- **Cadence** : hebdomadaire
51 +- **Budgets / plafonds (variables d'environnement)** :
52 +
53 +| Variable | Défaut | Rôle |
54 +|---|---|---|
55 +| `IDM_FICHE_MAX` | `10` | — |
56 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
57 +
58 +## Volumétrie & complétude live
59 +
60 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
61 +
62 +- **Événements en base** : 9 (dont **0 actifs**)
63 +- **GPS** : 0/0 actifs (—)
64 +- **Prix** : 0/0 actifs (—)
65 +- **Heure (start_time)** : 0/0 actifs (—)
66 +- **Heure de fin** : 0/0 actifs (—)
67 +- **Statut (annulé/reporté/complet)** : 0/0 actifs (—)
68 +- **Artistes** : 0/0 actifs (—)
69 +- **Image** : 0/0 actifs (—)
70 +- **Salle (venue)** : 0/0 actifs (—)
71 +- **Dernier sync** : 2026-08-25 18:18 — fetched 9, +9 ajoutés, ~0 mis à jour, -0 retirés
72 +- **Syncs enregistrés** (sync_log) : 1
73 +
74 +## Erreurs & dépannage
75 +
76 +Aucune erreur dans `sync_log` pour cette source. ✅
77 +
78 +- Rejouer un sync isolé : `python3 run.py sync --source ilesdelamadeleine` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='ilesdelamadeleine' ORDER BY ts DESC LIMIT 5;"`
79 +
80 +## Licence / attribution
81 +
82 +- Pages publiques de l'ATR, robots.txt permissif — GET throttlés, UA honnête, lien vers la fiche originale
83 +
84 +## Historique
85 +
86 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
87 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
88 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/joliette.md +111 −0
@@ -0,0 +1,111 @@
1 +# Ville de Joliette — Événements (`joliette`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/joliette.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Joliette — Événements](https://www.joliette.ca/evenements)
10 +- **Couverture** : Joliette (Lanaudière)
11 +- **Extraction** : HTML serveur OctoberCMS/Blanko /evenements/N + fiches (heures, « Gratuit ») en cache
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`LIST_URL`) : `https://www.joliette.ca/evenements`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : par numéro de page
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : site municipal public, robots.txt permissif
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/joliette.py : connecteur Ville de Joliette — événements
26 +> Source : https://www.joliette.ca/evenements (OctoberCMS/Blanko,
27 +> rendu serveur).
28 +> Extraction: pages liste /evenements puis /evenements/2, /3... (arrêt à la
29 +> première page sans carte) → cartes « c-event-card » :
30 +> catégorie, titre, date FR (simple ou plage « Tous les
31 +> dimanches du 26 juillet au 30 août 2026 »), lieu, url fiche,
32 +> image. Enrichissement par fiche /evenement/<cat>/<slug>
33 +> (heures « 19 h à 21 h », coût « Gratuit ») — CACHE
34 +> INCRÉMENTAL disque, plafond JOL_FICHES par sync.
35 +> Accès : site municipal public, GET directs throttlés, identification
36 +> honnête ; robots.txt permissif.
37 +> Prix : la fiche publie le coût (souvent « Gratuit ») → price_label
38 +> fidèle, jamais inventé.
39 +
40 +</details>
41 +
42 +## Champs récupérés → schéma
43 +
44 +Complétude mesurée sur les **10 événements actifs** de la source ; exemples tirés d'un événement réel (`joliette:culture-et-divertissement-festival-des-nations-2026-08-29`).
45 +
46 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
47 +|---|---|---|---|
48 +| `title` — Titre | titre de l'événement | 100 % | Festival des nations |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.joliette.ca/evenement/culture-et-divertissement/festival-des-nations |
51 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
52 +| `venue` — Salle (venue) | nom du lieu | 100 % | Parc Louis-Querbes |
53 +| `address` — Adresse | adresse civique | 0 % | ∅ |
54 +| `city` — Ville | municipalité | 100 % | Joliette |
55 +| `region` — Région | région administrative | 100 % | Lanaudière |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 70 % | 46.030199, -73.436039 |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-29 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-30 |
59 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 10:00 |
60 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 60 % | 19:00 |
61 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
62 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 60 % | 1 |
63 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
64 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 60 % | Gratuit |
65 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Joliette |
67 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.joliette.ca/storage/app/resources/resize/860_450_0_0_crop/img_0a54244c724d15eb1710bb… |
68 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
69 +
70 +## Fréquence & budgets
71 +
72 +- **Cadence** : quotidien
73 +- **Budgets / plafonds (variables d'environnement)** :
74 +
75 +| Variable | Défaut | Rôle |
76 +|---|---|---|
77 +| `JOL_PAGES` | `8` | — |
78 +| `JOL_FICHES` | `40` | — |
79 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
80 +
81 +## Volumétrie & complétude live
82 +
83 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
84 +
85 +- **Événements en base** : 10 (dont **10 actifs**)
86 +- **GPS** : 7/10 actifs (70 %)
87 +- **Prix** : 6/10 actifs (60 %)
88 +- **Heure (start_time)** : 10/10 actifs (100 %)
89 +- **Heure de fin** : 6/10 actifs (60 %)
90 +- **Statut (annulé/reporté/complet)** : 0/10 actifs (0 %)
91 +- **Artistes** : 0/10 actifs (0 %)
92 +- **Image** : 10/10 actifs (100 %)
93 +- **Salle (venue)** : 10/10 actifs (100 %)
94 +- **Dernier sync** : 2026-08-25 18:19 — fetched 10, +10 ajoutés, ~0 mis à jour, -0 retirés
95 +- **Syncs enregistrés** (sync_log) : 1
96 +
97 +## Erreurs & dépannage
98 +
99 +Aucune erreur dans `sync_log` pour cette source. ✅
100 +
101 +- Rejouer un sync isolé : `python3 run.py sync --source joliette` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='joliette' ORDER BY ts DESC LIMIT 5;"`
102 +
103 +## Licence / attribution
104 +
105 +- site municipal public, robots.txt permissif
106 +
107 +## Historique
108 +
109 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
110 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
111 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/lanaudiere.md +29 −24
@@ -1,6 +1,6 @@
1 1 # Tourisme Lanaudière — événements (ATR) (`lanaudiere`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/lanaudiere.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Tourisme Lanaudière — événements (ATR)](https://lanaudiere.ca/fr/evenements-lanaudiere)
10 10 - **Couverture** : Festivals et événements, Lanaudière
11 −- **Extraction** : sitemap.xml (1 GET) → fiches /fr/evenements-lanaudiere/<slug>/ incrémentales (cache data/lanaudiere_cache.json, LAN_FICHE_MAX/sync, re-visite 7 j) : titre, plage de dates FR, municipalité, og:image/og:description ; médias Tourinsoft → recoupement SITQ géré par dedup_key
11 +- **Extraction** : sitemap.xml (1 GET, ~85 fiches FR) → fiches incrémentales (LAN_FICHE_MAX/sync, re-visite 7 j, cache v2, Crawl-delay 2 s honoré) : en-tête member-title (titre, plage FR, municipalité), description expandable-intro, adresse+CP member-coords, organisateur, site web member-website, audience « Clientèle admise », og:image
12 12
13 13 ## Accès
14 14
@@ -28,13 +28,18 @@
28 28 > le SIT Québec, recoupement géré par dedup_key). La liste rendue
29 29 > serveur ne montre que ~10 cartes (le reste charge en JS) → on
30 30 > passe par le SITEMAP officiel (robots.txt), qui publie TOUTES
31 −> les fiches /fr/evenements-lanaudiere/<slug>/ (~44 à venir,
32 −> vérifié 2026-08-19).
31 +> les fiches /fr/evenements-lanaudiere/<slug>/ (~85 URLs,
32 +> revérifié 2026-08-25).
33 33 > Extraction: sitemap.xml (1 GET) → fiches détail incrémentales (cache
34 −> data/lanaudiere_cache.json, LAN_FICHE_MAX/sync, re-visite 7 j) :
35 −> en-tête structuré member-title-1-1 (titre, plage de dates FR
36 −> « Du 17 au 26 juillet 2026 », municipalité) + og:image +
37 −> og:description. Pas d'heure ni tarif structurés → jamais inventés.
34 +> data/lanaudiere_cache.json, LAN_FICHE_MAX/sync, re-visite 7 j,
35 +> version v=2) : en-tête structuré member-title-1-1 (titre,
36 +> plage de dates FR « Du 17 au 26 juillet 2026 », municipalité)
37 +> + og:image. ENRICHI (2026-08-25) : description longue
38 +> (expandable-intro, fallback og:description), adresse civique +
39 +> code postal (member-coords-1-1__address), organisateur
40 +> (member-coords-1-1__name), site web (member-website-1-1) et
41 +> audience (« Clientèle admise »). Pas d'heure ni tarif
42 +> structurés → jamais inventés.
38 43 > Accès : robots.txt : « Disallow: /*? » (aucune URL à paramètres
39 44 > requêtée) + « Crawl-delay: 2 » → GET séquentiels 2,1 s.
40 45
@@ -42,12 +47,12 @@
42 47
43 48 ## Champs récupérés → schéma
44 49
45 −Complétude mesurée sur les **44 événements actifs** de la source ; exemples tirés d'un événement réel (`lanaudiere:1804-evenement`).
50 +Complétude mesurée sur les **35 événements actifs** de la source ; exemples tirés d'un événement réel (`lanaudiere:1804-evenement`).
46 51
47 52 | Champ (`events.*`) | Description | Complétude | Exemple réel |
48 53 |---|---|---|---|
49 54 | `title` — Titre | titre de l'événement | 100 % | 1804, l'événement |
50 −| `description` — Description | texte descriptif (source ou fiche détail) | 5 % | ∅ |
55 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Faites un voyage dans le temps avec les voyageurs de la compagnie du Nord-Ouest à l’époque de la… |
51 56 | `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://lanaudiere.ca/fr/evenements-lanaudiere/1804-evenement/ |
52 57 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
53 58 | `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
@@ -65,7 +70,7 @@ Complétude mesurée sur les **44 événements actifs** de la source ; exemples
65 70 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
66 71 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
67 72 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
68 −| `image` — Image | visuel (og:image ou média source) | 93 % | https://mto.media.tourinsoft.eu/upload/Corpo---SODECT-1804-2019-171.JPG |
73 +| `image` — Image | visuel (og:image ou média source) | 91 % | https://mto.media.tourinsoft.eu/upload/Corpo---SODECT-1804-2019-171.JPG |
69 74 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
70 75
71 76 ## Fréquence & budgets
@@ -80,19 +85,19 @@ Complétude mesurée sur les **44 événements actifs** de la source ; exemples
80 85
81 86 ## Volumétrie & complétude live
82 87
83 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
84 −
85 −- **Événements en base** : 81 (dont **44 actifs**)
86 −- **GPS** : 0/44 actifs (0 %)
87 −- **Prix** : 0/44 actifs (0 %)
88 −- **Heure (start_time)** : 0/44 actifs (0 %)
89 −- **Heure de fin** : 0/44 actifs (0 %)
90 −- **Statut (annulé/reporté/complet)** : 0/44 actifs (0 %)
91 −- **Artistes** : 0/44 actifs (0 %)
92 −- **Image** : 41/44 actifs (93 %)
93 −- **Salle (venue)** : 0/44 actifs (0 %)
94 −- **Dernier sync** : 2026-08-22 23:03 — fetched 81, +0 ajoutés, ~0 mis à jour, -0 retirés
95 −- **Syncs enregistrés** (sync_log) : 82
88 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
89 +
90 +- **Événements en base** : 81 (dont **35 actifs**)
91 +- **GPS** : 0/35 actifs (0 %)
92 +- **Prix** : 0/35 actifs (0 %)
93 +- **Heure (start_time)** : 0/35 actifs (0 %)
94 +- **Heure de fin** : 0/35 actifs (0 %)
95 +- **Statut (annulé/reporté/complet)** : 0/35 actifs (0 %)
96 +- **Artistes** : 0/35 actifs (0 %)
97 +- **Image** : 32/35 actifs (91 %)
98 +- **Salle (venue)** : 0/35 actifs (0 %)
99 +- **Dernier sync** : 2026-08-25 18:01 — fetched 81, +0 ajoutés, ~81 mis à jour, -0 retirés
100 +- **Syncs enregistrés** (sync_log) : 137
96 101
97 102 ## Erreurs & dépannage
98 103
modified docs/connecteurs/laurentides.md +85 −14
@@ -1,46 +1,117 @@
1 −# Tourisme Laurentides — événements (ATR) (`laurentides`)
1 +# Tourisme Laurentides — calendrier des événements (ATR) (`laurentides`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 −**Statut : abandonnee** · Type : `tourisme-regional` · Connecteur : *aucun (source sans connecteur runtime)*
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/laurentides.py`
6 6
7 7 ## Description
8 8
9 −- **Source** : [Tourisme Laurentides — événements (ATR)](https://www.laurentides.com/fr/quoifaire/evenements)
9 +- **Source** : [Tourisme Laurentides — calendrier des événements (ATR)](https://www.laurentides.com/evenements/)
10 10 - **Couverture** : Laurentides
11 −- **Extraction** : n/d
12 −- **Note** : ÉCARTÉE 2026-08-19 (Phase 3) : WAF — 403 sur GET identifié (UA honnête). Scrapfly possible mais non justifié (la région est déjà la 4ᵉ mieux couverte : 620 fiches au baseline). À re-sonder.
11 +- **Extraction** : API REST /wp-json/wp/v2/mec-events (~228 fiches : titre, description, image) via auto-escalade anti-bot (WAF 403 → Scrapfly ASP) ; dates+heures réelles, tarif, salle et adresse via fiches détail MEC incrémentales pilotées par « modified » (cache, LAU_FICHE_MAX=10/sync). RÉHABILITÉE 2026-08-25 (écartée Phase 3 pour WAF)
13 12
14 13 ## Accès
15 14
16 15 - **Type** : tourisme-regional
17 −- **Endpoint** : voir URL source (https://www.laurentides.com/fr/quoifaire/evenements)
18 −- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 −- **Accès légal** : n/d
16 +- **Endpoint** (`BASE`) : `https://www.laurentides.com`
17 +- **Auth** : Aucune côté source ; `SCRAPFLY_API_KEY` requis pour le backend Scrapfly
18 +- **Pagination** : par numéro de page, per_page
19 +- **Backend** : Scrapfly (anti-bot / rendu JS) via `base.get_scrapfly()`
20 +- **Throttle** : 1.0 s entre requêtes
21 +- **Accès légal** : API REST publique ; WAF contourné par proxy/Scrapfly avec requêtes throttlées et plafonnées — UA honnête, lien vers la fiche originale
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/laurentides.py : Tourisme Laurentides (ATR) — RÉHABILITÉE (vague
26 +> ATR 2026-08 ; écartée Phase 3 pour WAF 403 : la chaîne anti-bot passe)
27 +> Source : laurentides.com/evenements — calendrier officiel de l'ATR
28 +> (WordPress + Modern Events Calendar « mec-events »).
29 +> Extraction: API REST WordPress /wp-json/wp/v2/mec-events (paginée
30 +> per_page=100, ~228 fiches, X-WP-TotalPages vérifié 2026-08-25)
31 +> via BaseConnector.get() dont l'AUTO-ESCALADE anti-bot
32 +> (Oxylabs → Scrapfly ASP → Bright Data) perce le WAF qui bloque
33 +> le GET direct (403). La liste donne titre, description
34 +> (content), image (_embedded featuredmedia), lien, modified.
35 +> MEC n'expose PAS les dates en REST → fiches détail
36 +> incrémentales (cache data/laurentides_cache.json, plafond
37 +> LAU_FICHE_MAX/sync, re-visite si `modified` change) : dates
38 +> (mec-start/end-date-label « Déc 05 2026 »), tarif
39 +> (mec-events-event-cost), lieu + adresse
40 +> (mec-single-event-location).
41 +> Accès : API REST publique (aucune auth) ; le WAF de l'hébergeur
42 +> bloque les UA non-navigateur → escalade proxy/Scrapfly,
43 +> requêtes throttlées et plafonnées (coût par requête), lien
44 +> vers la fiche originale. Prix : texte source, jamais inventé.
45 +
46 +</details>
20 47
21 48 ## Champs récupérés → schéma
22 49
23 −*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
50 +Complétude mesurée sur les **228 événements actifs** de la source ; exemples tirés d'un événement réel (`laurentides:86265`).
51 +
52 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
53 +|---|---|---|---|
54 +| `title` — Titre | titre de l'événement | 100 % | Marché de Noël du Lac-Supérieur |
55 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Plongez dans l’ambiance féérique du temps des Fêtes lors du Marché de Noël de Lac-Supérieur, qui… |
56 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.laurentides.com/evenement/marche-de-noel-du-lac-superieur/ |
57 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["marche-foire"] |
58 +| `venue` — Salle (venue) | nom du lieu | 5 % | Hôtel de ville à Lac-Supérieur |
59 +| `address` — Adresse | adresse civique | 5 % | 1281 Chemin du Lac-Supérieur, Lac-Supérieur, QC, J0T 1J0 |
60 +| `city` — Ville | municipalité | 2 % | Lac-Supérieur |
61 +| `region` — Région | région administrative | 100 % | Laurentides |
62 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 2 % | 46.1637638, -74.4703258 |
63 +| `start_date` — Date début | date locale AAAA-MM-JJ | 5 % | 2026-12-05 |
64 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 5 % | 2026-12-05 |
65 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 5 % | 10:00 |
66 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 5 % | 16:00 |
67 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
68 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 4 % | 1 |
69 +| `price_min` — Prix min | tarif minimal réel ($) | 1 % | ∅ |
70 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 4 % | Gratuite |
71 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
72 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
73 +| `image` — Image | visuel (og:image ou média source) | 92 % | https://www.laurentides.com/wp-content/uploads/2026/08/marchenoellacsuperieur-e1787503165656.jpg |
74 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
24 75
25 76 ## Fréquence & budgets
26 77
27 −- **Cadence** : n/d
78 +- **Cadence** : quotidienne (backfill des fiches ~3 semaines, puis incrémental)
79 +- **Budgets / plafonds (variables d'environnement)** :
80 +
81 +| Variable | Défaut | Rôle |
82 +|---|---|---|
83 +| `LAU_PAGE_MAX` | `6` | — |
84 +| `LAU_FICHE_MAX` | `10` | — |
28 85 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
29 86
30 87 ## Volumétrie & complétude live
31 88
32 −*Source sans connecteur runtime : aucune donnée en base.*
89 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
90 +
91 +- **Événements en base** : 228 (dont **228 actifs**)
92 +- **GPS** : 4/228 actifs (2 %)
93 +- **Prix** : 9/228 actifs (4 %)
94 +- **Heure (start_time)** : 11/228 actifs (5 %)
95 +- **Heure de fin** : 11/228 actifs (5 %)
96 +- **Statut (annulé/reporté/complet)** : 0/228 actifs (0 %)
97 +- **Artistes** : 0/228 actifs (0 %)
98 +- **Image** : 210/228 actifs (92 %)
99 +- **Salle (venue)** : 12/228 actifs (5 %)
100 +- **Dernier sync** : 2026-08-25 18:46 — fetched 228, +228 ajoutés, ~0 mis à jour, -0 retirés
101 +- **Syncs enregistrés** (sync_log) : 1
33 102
34 103 ## Erreurs & dépannage
35 104
105 +Aucune erreur dans `sync_log` pour cette source. ✅
106 +
107 +- Rejouer un sync isolé : `python3 run.py sync --source laurentides` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='laurentides' ORDER BY ts DESC LIMIT 5;"`
36 108
37 109 ## Licence / attribution
38 110
39 −- n/d
111 +- API REST publique ; WAF contourné par proxy/Scrapfly avec requêtes throttlées et plafonnées — UA honnête, lien vers la fiche originale
40 112
41 113 ## Historique
42 114
43 −- **Source abandonnée** — ÉCARTÉE 2026-08-19 (Phase 3) : WAF — 403 sur GET identifié (UA honnête). Scrapfly possible mais non justifié (la région est déjà la 4ᵉ mieux couverte : 620 fiches au baseline). À re-sonder.
44 115 - **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
45 116 - **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
46 117 - **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/laval.md +24 −24
@@ -1,6 +1,6 @@
1 1 # Ville de Laval — Calendrier des activités (`laval`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : morte-a-la-source** · Type : `donnees-ouvertes` · Connecteur : `sortika/connectors/laval.py`
6 6
@@ -34,22 +34,22 @@
34 34
35 35 ## Champs récupérés → schéma
36 36
37 −Complétude mesurée sur les **17 événements actifs** de la source ; exemples tirés d'un événement réel (`laval:biblio-jeunesse-educazoo-saint-valentin-2025-02-15`).
37 +Complétude mesurée sur les **1 événements actifs** de la source ; exemples tirés d'un événement réel (`laval:mois-histoire-noirs-visages-histoire-exposition-2025-02-04`).
38 38
39 39 | Champ (`events.*`) | Description | Complétude | Exemple réel |
40 40 |---|---|---|---|
41 −| `title` — Titre | titre de l'événement | 100 % | Éducazoo |
42 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Cadeaux, parades, chants langoureux et techniques parfois étranges : les animaux déballent un ar… |
43 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.laval.ca/Pages/Fr/Calendrier/biblio-jeunesse-educazoo-saint-valentin.aspx |
44 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["conference", "musique", "famille"] |
45 −| `venue` — Salle (venue) | nom du lieu | 94 % | Bibliothèque Émile-Nelligan |
41 +| `title` — Titre | titre de l'événement | 100 % | Visages de l'histoire : exposition |
42 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Tout au long du mois de février, dans le cadre du Mois de l’histoire des Noirs, découvrez l'expo… |
43 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.laval.ca/Pages/Fr/Calendrier/mois-histoire-noirs-visages-histoire-exposition.aspx |
44 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["communautaire", "patrimoine"] |
45 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
46 46 | `address` — Adresse | adresse civique | 0 % | ∅ |
47 47 | `city` — Ville | municipalité | 100 % | Laval |
48 48 | `region` — Région | région administrative | 100 % | Laval |
49 49 | `lat` — GPS (lat/lng) | coordonnées géographiques | 0 % | ∅ |
50 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2025-02-15 |
51 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2025-02-15 |
52 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 14:45 |
50 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2025-02-04 |
51 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2034-08-29 |
52 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 08:30 |
53 53 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
54 54 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
55 55 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
@@ -57,7 +57,7 @@ Complétude mesurée sur les **17 événements actifs** de la source ; exemples
57 57 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
58 58 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
59 59 | `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Laval |
60 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.laval.ca/PublishingImages/Calendrier-des-activites/bibliotheque/jeunesse-educazoo-sa… |
60 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.laval.ca/PublishingImages/Calendrier-des-activites/visage-histoire-335x215.jpg |
61 61 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
62 62
63 63 ## Fréquence & budgets
@@ -67,19 +67,19 @@ Complétude mesurée sur les **17 événements actifs** de la source ; exemples
67 67
68 68 ## Volumétrie & complétude live
69 69
70 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
71 −
72 −- **Événements en base** : 277 (dont **17 actifs**)
73 −- **GPS** : 0/17 actifs (0 %)
74 −- **Prix** : 0/17 actifs (0 %)
75 −- **Heure (start_time)** : 17/17 actifs (100 %)
76 −- **Heure de fin** : 0/17 actifs (0 %)
77 −- **Statut (annulé/reporté/complet)** : 0/17 actifs (0 %)
78 −- **Artistes** : 0/17 actifs (0 %)
79 −- **Image** : 17/17 actifs (100 %)
80 −- **Salle (venue)** : 16/17 actifs (94 %)
81 −- **Dernier sync** : 2026-08-22 23:03 — fetched 293, +0 ajoutés, ~32 mis à jour, -0 retirés
82 −- **Syncs enregistrés** (sync_log) : 124
70 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
71 +
72 +- **Événements en base** : 277 (dont **1 actifs**)
73 +- **GPS** : 0/1 actifs (0 %)
74 +- **Prix** : 0/1 actifs (0 %)
75 +- **Heure (start_time)** : 1/1 actifs (100 %)
76 +- **Heure de fin** : 0/1 actifs (0 %)
77 +- **Statut (annulé/reporté/complet)** : 0/1 actifs (0 %)
78 +- **Artistes** : 0/1 actifs (0 %)
79 +- **Image** : 1/1 actifs (100 %)
80 +- **Salle (venue)** : 0/1 actifs (0 %)
81 +- **Dernier sync** : 2026-08-25 18:01 — fetched 293, +0 ajoutés, ~32 mis à jour, -0 retirés
82 +- **Syncs enregistrés** (sync_log) : 179
83 83
84 84 ## Erreurs & dépannage
85 85
modified docs/connecteurs/lavalweb.md +24 −24
@@ -1,6 +1,6 @@
1 1 # Ville de Laval — calendrier vivant laval.ca (`lavalweb`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/lavalweb.py`
6 6
@@ -41,22 +41,22 @@
41 41
42 42 ## Champs récupérés → schéma
43 43
44 −Complétude mesurée sur les **443 événements actifs** de la source ; exemples tirés d'un événement réel (`lavalweb:halloween-exposition-de-plantes-carnivores-et-dautres-etrangetes`).
44 +Complétude mesurée sur les **427 événements actifs** de la source ; exemples tirés d'un événement réel (`lavalweb:famille-fete`).
45 45
46 46 | Champ (`events.*`) | Description | Complétude | Exemple réel |
47 47 |---|---|---|---|
48 −| `title` — Titre | titre de l'événement | 100 % | Halloween : exposition de plantes carnivores et d’autres étrangetés |
49 −| `description` — Description | texte descriptif (source ou fiche détail) | 49 % | ∅ |
50 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.laval.ca/calendrier-activites/halloween-exposition-de-plantes-carnivores-et-dautres-… |
51 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["exposition-musee"] |
52 −| `venue` — Salle (venue) | nom du lieu | 100 % | Centre de la Nature |
48 +| `title` — Titre | titre de l'événement | 100 % | Famille à la fête |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 51 % | Participez à une fête de quartier intergénérationnelle mettant en valeur le patrimoine et l’hist… |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.laval.ca/calendrier-activites/famille-fete/ |
51 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["famille"] |
52 +| `venue` — Salle (venue) | nom du lieu | 100 % | Parc de la Renaissance |
53 53 | `address` — Adresse | adresse civique | 0 % | ∅ |
54 54 | `city` — Ville | municipalité | 100 % | Laval |
55 55 | `region` — Région | région administrative | 100 % | Laval |
56 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 75 % | 45.6059801, -73.6603569 |
57 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-10-10 |
58 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-10-10 |
59 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 98 % | 09:30 |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 89 % | 45.602079, -73.7961698 |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-05 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-05 |
59 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 98 % | 11:00 |
60 60 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
61 61 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
62 62 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
@@ -80,19 +80,19 @@ Complétude mesurée sur les **443 événements actifs** de la source ; exemples
80 80
81 81 ## Volumétrie & complétude live
82 82
83 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
84 −
85 −- **Événements en base** : 555 (dont **443 actifs**)
86 −- **GPS** : 331/443 actifs (75 %)
87 −- **Prix** : 0/443 actifs (0 %)
88 −- **Heure (start_time)** : 434/443 actifs (98 %)
89 −- **Heure de fin** : 0/443 actifs (0 %)
90 −- **Statut (annulé/reporté/complet)** : 0/443 actifs (0 %)
91 −- **Artistes** : 0/443 actifs (0 %)
92 −- **Image** : 0/443 actifs (0 %)
93 −- **Salle (venue)** : 443/443 actifs (100 %)
94 −- **Dernier sync** : 2026-08-22 22:26 — fetched 441, +0 ajoutés, ~0 mis à jour, -0 retirés
95 −- **Syncs enregistrés** (sync_log) : 81
83 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
84 +
85 +- **Événements en base** : 563 (dont **427 actifs**)
86 +- **GPS** : 382/427 actifs (89 %)
87 +- **Prix** : 0/427 actifs (0 %)
88 +- **Heure (start_time)** : 418/427 actifs (98 %)
89 +- **Heure de fin** : 0/427 actifs (0 %)
90 +- **Statut (annulé/reporté/complet)** : 0/427 actifs (0 %)
91 +- **Artistes** : 0/427 actifs (0 %)
92 +- **Image** : 0/427 actifs (0 %)
93 +- **Salle (venue)** : 427/427 actifs (100 %)
94 +- **Dernier sync** : 2026-08-25 18:04 — fetched 426, +0 ajoutés, ~0 mis à jour, -1 retirés
95 +- **Syncs enregistrés** (sync_log) : 136
96 96
97 97 ## Erreurs & dépannage
98 98
modified docs/connecteurs/lavitrine.md +41 −29
@@ -1,6 +1,6 @@
1 1 # La Vitrine culturelle (`lavitrine`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/lavitrine.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [La Vitrine culturelle](https://www.lavitrine.com/)
10 10 - **Couverture** : Spectacles et sorties culturelles, Grand Montréal
11 −- **Extraction** : GET directs — sitemap.xml (2 000 fiches evenement + 185 exposition) + fiches HTML (bloc event_calendar), cache incrémental, 150 fiches/sync, re-visite 7 j
11 +- **Extraction** : sitemap.xml + fiches rendues serveur (carrousel dates/heures/salles, description, og:image) + pages REPRÉSENTATION (…/<ficheId>/<dateId>) : JSON-LD Event complet → adresse, code postal, GPS bruts de la source (100 % constaté), prix billetterie (73 %) ; date+heure locales prises du fil d'Ariane (le startDate JSON-LD est en UTC et change de jour — piège évité) ; incrémental versionné v3, plafond LV_MAX_FETCH/sync
12 12
13 13 ## Accès
14 14
@@ -37,28 +37,40 @@
37 37 > Accès : robots.txt « User-Agent: * / Allow: / » + Content-Signal
38 38 > « search=yes, use=reference » → usage index + lien vers la
39 39 > fiche originale, conforme aux signaux publiés (CLAUDE.md §15).
40 −> Prix : non exposés sur la fiche → is_free inconnu (jamais inventé).
40 +> Prix/GPS : les pages FICHE ne publient ni tarif, ni JSON-LD, ni adresse.
41 +> MAIS le sitemap contient aussi ~1 000 pages « représentation »
42 +> (/fr/evenement/<slug>/<ficheId>/<dateId>) qui publient un
43 +> JSON-LD Event complet (vérifié live 2026-08-25, 14/14 pages :
44 +> Place name + PostalAddress + GeoCoordinates 100 %, offers.price
45 +> ~80 %) → parseur v3 : adresse civique, code postal, lat/lng et
46 +> prix billetterie attachés à LA représentation correspondante
47 +> (identifier = dateId de la slide), sans AUCUN GET de plus (ces
48 +> pages étaient déjà téléchargées et ignorées). Les autres
49 +> représentations restent au géocodage aval salle+ville
50 +> (sortika/venues.py + geocode.py). isAccessibleForFree vaut
51 +> false par défaut chez la source → jamais utilisé seul ;
52 +> is_free=False seulement si un prix > 0 est publié.
41 53
42 54 </details>
43 55
44 56 ## Champs récupérés → schéma
45 57
46 −Complétude mesurée sur les **5039 événements actifs** de la source ; exemples tirés d'un événement réel (`lavitrine:38808-2027-03-19`).
58 +Complétude mesurée sur les **5008 événements actifs** de la source ; exemples tirés d'un événement réel (`lavitrine:16144-2027-03-25`).
47 59
48 60 | Champ (`events.*`) | Description | Complétude | Exemple réel |
49 61 |---|---|---|---|
50 −| `title` — Titre | titre de l'événement | 100 % | Guy Nantel - Bienveillant En rodage |
51 −| `description` — Description | texte descriptif (source ou fiche détail) | 99 % | Alors que l’actualité regorge de sujets explosifs, d’opinions tranchées et de contradictions bie… |
52 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.lavitrine.com/fr/evenement/guy-nantel-bienveillant-en-rodage/38808 |
53 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
54 −| `venue` — Salle (venue) | nom du lieu | 100 % | Théâtre Le Patriote |
62 +| `title` — Titre | titre de l'événement | 100 % | Glam Rock Show |
63 +| `description` — Description | texte descriptif (source ou fiche détail) | 99 % | « Glam Rock Show », un spectacle qui vous fera revivre la belle époque du « Glam Rock » des anné… |
64 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.lavitrine.com/fr/evenement/glam-rock-show/16144 |
65 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
66 +| `venue` — Salle (venue) | nom du lieu | 100 % | Le Théâtre Capitole |
55 67 | `address` — Adresse | adresse civique | 0 % | ∅ |
56 −| `city` — Ville | municipalité | 100 % | Sainte-Agathe-des-Monts |
57 −| `region` — Région | région administrative | 100 % | Laurentides |
58 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 69 % | 46.0308044, -74.284975 |
59 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2027-03-19 |
60 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2027-03-19 |
61 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:00 |
68 +| `city` — Ville | municipalité | 100 % | Québec |
69 +| `region` — Région | région administrative | 100 % | Capitale-Nationale |
70 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 72 % | 46.812805, -71.213904 |
71 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2027-03-25 |
72 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2027-03-25 |
73 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:30 |
62 74 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
63 75 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
64 76 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
@@ -66,7 +78,7 @@ Complétude mesurée sur les **5039 événements actifs** de la source ; exemple
66 78 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
67 79 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
68 80 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
69 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://lvc-medias.lavitrine.com/adfa7a36-cd01-5248-b4bf-ffada84d38e9.png |
81 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://lvc-medias.lavitrine.com/8de4d179-3629-5c8c-b088-606a5904bf17.jpg |
70 82 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
71 83
72 84 ## Fréquence & budgets
@@ -81,19 +93,19 @@ Complétude mesurée sur les **5039 événements actifs** de la source ; exemple
81 93
82 94 ## Volumétrie & complétude live
83 95
84 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
85 −
86 −- **Événements en base** : 5173 (dont **5039 actifs**)
87 −- **GPS** : 3473/5039 actifs (69 %)
88 −- **Prix** : 0/5039 actifs (0 %)
89 −- **Heure (start_time)** : 5039/5039 actifs (100 %)
90 −- **Heure de fin** : 0/5039 actifs (0 %)
91 −- **Statut (annulé/reporté/complet)** : 0/5039 actifs (0 %)
92 −- **Artistes** : 0/5039 actifs (0 %)
93 −- **Image** : 5039/5039 actifs (100 %)
94 −- **Salle (venue)** : 5037/5039 actifs (100 %)
95 −- **Dernier sync** : 2026-08-22 22:26 — fetched 5120, +0 ajoutés, ~0 mis à jour, -0 retirés
96 −- **Syncs enregistrés** (sync_log) : 114
96 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
97 +
98 +- **Événements en base** : 5205 (dont **5008 actifs**)
99 +- **GPS** : 3598/5008 actifs (72 %)
100 +- **Prix** : 0/5008 actifs (0 %)
101 +- **Heure (start_time)** : 5008/5008 actifs (100 %)
102 +- **Heure de fin** : 0/5008 actifs (0 %)
103 +- **Statut (annulé/reporté/complet)** : 0/5008 actifs (0 %)
104 +- **Artistes** : 0/5008 actifs (0 %)
105 +- **Image** : 5008/5008 actifs (100 %)
106 +- **Salle (venue)** : 5006/5008 actifs (100 %)
107 +- **Dernier sync** : 2026-08-25 18:04 — fetched 5127, +0 ajoutés, ~0 mis à jour, -0 retirés
108 +- **Syncs enregistrés** (sync_log) : 169
97 109
98 110 ## Erreurs & dépannage
99 111
modified docs/connecteurs/lepointdevente.md +43 −39
@@ -1,6 +1,6 @@
1 1 # Le point de vente — billetterie québécoise (`lepointdevente`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/lepointdevente.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Le point de vente — billetterie québécoise](https://lepointdevente.com/billets)
10 10 - **Couverture** : Concerts, humour, théâtre, festivals — partout au Québec (~2 500 événements à venir)
11 −- **Extraction** : pages de liste HTML rendues serveur (paginées), parse des cartes (titre, date FR + heure « 17 août 2026, 20h00 » → start_time, salle, ville, image) + fiches détail /billets/<id> incrémentales (cache data/lepointdevente_cache.json, 150 fiches/sync, re-visite 7 j) : description og:description, heure précise, tarifs JSON réels via /plugins/rates/<id> → price_min/price_label/is_free ; artistes extraits des titres par patrons sûrs seulement (« Artiste - Salle », « X en concert »)
11 +- **Extraction** : pages de liste HTML rendues serveur (paginées), parse des cartes (titre, date FR + heure « 17 août 2026, 20h00 » → start_time, salle, ville, image) + fiches détail /billets/<id> incrémentales (cache data/lepointdevente_cache.json, 150 fiches/sync, re-visite 7 j) : description og:description, heure précise, tarifs JSON réels via /plugins/rates/<id> → price_min/price_label/is_free ; artistes extraits des titres par patrons sûrs seulement (« Artiste - Salle », « X en concert »). MAJ 2026-08-25 (fiches v2, LPDV_DETAIL_MAX=150/sync) : adresse civique en microdonnées schema.org (streetAddress + addressLocality, 84 % des fiches) → géocodage aval
12 12
13 13 ## Accès
14 14
@@ -31,43 +31,47 @@
31 31 > Accès : robots.txt = « User-Agent: * / Allow: * » (scraping permis) ;
32 32 > GET throttlés 0,6 s, identification honnête, ~65 requêtes/sync.
33 33 > Détail : enrichissement incrémental par fiches /billets/<id> (modèle
34 −> lavitrine) — cache disque data/lepointdevente_cache.json,
35 −> plafond LPDV_DETAIL_MAX fiches/sync (défaut 150), re-visite
36 −> après 7 jours, throttle poli 0,8 s. Chaque fiche donne :
37 −> description (og:description), heure précise (data-event-name)
38 −> et l'id interne du widget billetterie (data-reference) →
39 −> tarifs JSON réels via /plugins/rates/<id> (prix TTC affichés
40 −> par la billetterie elle-même : price_min / price_label /
41 −> is_free si tous les tarifs sont à 0 $). Jamais de prix inventé.
34 +> lavitrine, version de parse « v ») — cache disque
35 +> data/lepointdevente_cache.json, plafond LPDV_DETAIL_MAX
36 +> fiches/sync (défaut 150), re-visite après 7 jours, throttle
37 +> poli 0,8 s. Chaque fiche donne : description (og:description),
38 +> heure précise (data-event-name), ADRESSE CIVIQUE du lieu
39 +> (microdonnées schema.org PostalAddress : streetAddress +
40 +> addressLocality, vérifié 2026-08-25 → géocodage aval, la
41 +> liste ne donnait ni adresse ni GPS) et l'id interne du widget
42 +> billetterie (data-reference) → tarifs JSON réels via
43 +> /plugins/rates/<id> (prix TTC affichés par la billetterie
44 +> elle-même : price_min / price_label / is_free si tous les
45 +> tarifs sont à 0 $). Jamais de prix inventé.
42 46
43 47 </details>
44 48
45 49 ## Champs récupérés → schéma
46 50
47 −Complétude mesurée sur les **1790 événements actifs** de la source ; exemples tirés d'un événement réel (`lepointdevente:7gw260926001`).
51 +Complétude mesurée sur les **1734 événements actifs** de la source ; exemples tirés d'un événement réel (`lepointdevente:tetu-levis`).
48 52
49 53 | Champ (`events.*`) | Description | Complétude | Exemple réel |
50 54 |---|---|---|---|
51 −| `title` — Titre | titre de l'événement | 100 % | Sam Vigneault en rodage |
52 −| `description` — Description | texte descriptif (source ou fiche détail) | 87 % | Sam Vigneault en rodage– HumoristeSamedi 26 septembre 2026 à la Chapelle du LacOriginaire de Ple… |
53 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://lepointdevente.com/billets/7gw260926001 |
55 +| `title` — Titre | titre de l'événement | 100 % | TÊTU - En rodage |
56 +| `description` — Description | texte descriptif (source ou fiche détail) | 87 % | Têtu – En rodageAvec son humour incisif et ses anecdotes percutantes, Biko observe le quotidien,… |
57 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://lepointdevente.com/billets/tetu-levis |
54 58 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
55 −| `venue` — Salle (venue) | nom du lieu | 99 % | Chapelle du Lac |
59 +| `venue` — Salle (venue) | nom du lieu | 99 % | Vignoble Le Nordet |
56 60 | `address` — Adresse | adresse civique | 0 % | ∅ |
57 −| `city` — Ville | municipalité | 100 % | Fossambault-sur-le-Lac |
58 −| `region` — Région | région administrative | 98 % | Capitale-Nationale |
59 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 30 % | 46.8903229, -71.6121222 |
60 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-26 |
61 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-26 |
62 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 93 % | 20:00 |
61 +| `city` — Ville | municipalité | 100 % | Lévis |
62 +| `region` — Région | région administrative | 98 % | Chaudière-Appalaches |
63 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 34 % | 46.7239469, -71.1296216 |
64 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-27 |
65 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-27 |
66 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 94 % | 20:00 |
63 67 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 68 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
65 −| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 92 % | 0 |
66 −| `price_min` — Prix min | tarif minimal réel ($) | 90 % | 30.0 |
67 −| `price_label` — Libellé prix | étiquette tarifaire affichable | 92 % | Table basse et chaise 30 $ · Table-bar et tabouret avec dossier 30 $ · Table-bistro et tabouret … |
68 −| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 1 % | ["Sam Vigneault"] |
69 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 93 % | 0 |
70 +| `price_min` — Prix min | tarif minimal réel ($) | 90 % | 21.95 |
71 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 93 % | Prévente 21.95 $ · Admission générale 27.22 $ |
72 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 1 % | ["TÊTU"] |
69 73 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
70 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://tpos.s3.amazonaws.com/events/7GW/26/09/26/001/7gw260926001-1152x648-fr-6984c862.png |
74 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://tpos.s3.amazonaws.com/events/PMR/26/07/27/001/pmr260727001-1152x648-fr-6a4d50b7.png |
71 75 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
72 76
73 77 ## Fréquence & budgets
@@ -82,19 +86,19 @@ Complétude mesurée sur les **1790 événements actifs** de la source ; exemple
82 86
83 87 ## Volumétrie & complétude live
84 88
85 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
86 −
87 −- **Événements en base** : 4446 (dont **1790 actifs**)
88 −- **GPS** : 533/1790 actifs (30 %)
89 −- **Prix** : 1645/1790 actifs (92 %)
90 −- **Heure (start_time)** : 1673/1790 actifs (93 %)
91 −- **Heure de fin** : 0/1790 actifs (0 %)
92 −- **Statut (annulé/reporté/complet)** : 0/1790 actifs (0 %)
93 −- **Artistes** : 16/1790 actifs (1 %)
94 −- **Image** : 1790/1790 actifs (100 %)
95 −- **Salle (venue)** : 1781/1790 actifs (99 %)
96 −- **Dernier sync** : 2026-08-22 22:27 — fetched 1745, +1 ajoutés, ~5 mis à jour, -21 retirés
97 −- **Syncs enregistrés** (sync_log) : 124
89 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
90 +
91 +- **Événements en base** : 4565 (dont **1734 actifs**)
92 +- **GPS** : 585/1734 actifs (34 %)
93 +- **Prix** : 1610/1734 actifs (93 %)
94 +- **Heure (start_time)** : 1635/1734 actifs (94 %)
95 +- **Heure de fin** : 0/1734 actifs (0 %)
96 +- **Statut (annulé/reporté/complet)** : 0/1734 actifs (0 %)
97 +- **Artistes** : 17/1734 actifs (1 %)
98 +- **Image** : 1734/1734 actifs (100 %)
99 +- **Salle (venue)** : 1725/1734 actifs (99 %)
100 +- **Dernier sync** : 2026-08-25 18:05 — fetched 1732, +0 ajoutés, ~9 mis à jour, -7 retirés
101 +- **Syncs enregistrés** (sync_log) : 179
98 102
99 103 ## Erreurs & dépannage
100 104
modified docs/connecteurs/levis.md +74 −13
@@ -1,46 +1,107 @@
1 −# Ville de Lévis — Calendrier (`levis`)
1 +# Ville de Lévis — Calendrier des événements (`levis`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 −**Statut : abandonnee** · Type : `municipal` · Connecteur : *aucun (source sans connecteur runtime)*
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/levis.py`
6 6
7 7 ## Description
8 8
9 −- **Source** : [Ville de Lévis — Calendrier](https://www.ville.levis.qc.ca/)
9 +- **Source** : [Ville de Lévis — Calendrier des événements](https://levis.ca/fr/calendrier-des-evenements)
10 10 - **Couverture** : Lévis (Chaudière-Appalaches)
11 −- **Extraction** : n/d
12 −- **Note** : Site derrière un WAF (403 sur GET identifiés) et aucun jeu « événements » sur Données Québec (vérifié 2026-08-18)
11 +- **Extraction** : GraphQL public du site (queryToken de lecture extrait de la page à chaque run). RÉACTIVÉE 2026-08-25 (l'abandon visait le WAF sur GET — l'API GraphQL publique passe)
13 12
14 13 ## Accès
15 14
16 15 - **Type** : municipal
17 −- **Endpoint** : voir URL source (https://www.ville.levis.qc.ca/)
16 +- **Endpoint** (`PAGE`) : `https://levis.ca/fr/calendrier-des-evenements`
17 +- **Endpoint** (`GRAPHQL`) : `https://levis.ca/graphql/`
18 18 - **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 −- **Accès légal** : n/d
19 +- **Pagination** : offset/limit
20 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
21 +- **Throttle** : 0.8 s entre requêtes
22 +- **Accès légal** : API du site municipal public, jeton public, robots.txt permissif
23 +
24 +<details><summary>En-tête documentaire du connecteur</summary>
25 +
26 +> connectors/levis.py : connecteur Ville de Lévis — calendrier des événements
27 +> Source : https://levis.ca/fr/calendrier-des-evenements (nouveau site
28 +> Nuxt/Craft CMS lancé fin 2025 ; l'ancien ville.levis.qc.ca et
29 +> son WAF 403 — cause de l'abandon 2026-08-18 — y redirige).
30 +> Extraction: API GraphQL publique https://levis.ca/graphql/ avec le
31 +> queryToken PUBLIC exposé dans window.__NUXT__.config de la
32 +> page (extrait à chaque run, constante de repli) — requête
33 +> calquée sur getListingEvenements du bundle : occurrences
34 +> datées (startDateLocalized), fiche (titre, url, accroche,
35 +> image, lieu, types, secteur). ~160 événements à venir,
36 +> vérifié 2026-08-25.
37 +> Accès : API du site municipal public, jeton de LECTURE publié dans
38 +> le HTML à tous les visiteurs ; robots.txt n'interdit pas
39 +> /graphql/. POST throttlés, identification honnête.
40 +> Prix : non publié par la source → jamais inventé.
41 +
42 +</details>
20 43
21 44 ## Champs récupérés → schéma
22 45
23 −*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
46 +Complétude mesurée sur les **153 événements actifs** de la source ; exemples tirés d'un événement réel (`levis:639dd2a5-3480-47bb-bbba-3dd58e83e822-2026-08-29`).
47 +
48 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
49 +|---|---|---|---|
50 +| `title` — Titre | titre de l'événement | 100 % | Spectacle de cirque dans la fontaine |
51 +| `description` — Description | texte descriptif (source ou fiche détail) | 93 % | À l’image des Lavigueur débarquent , Flip Fabrique vous présente une famille haute en couleur qu… |
52 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://levis.ca/fr/calendrier-des-evenements/spectacle-de-cirque-dans-la-fontaine |
53 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
54 +| `venue` — Salle (venue) | nom du lieu | 12 % | Quai Paquet |
55 +| `address` — Adresse | adresse civique | 0 % | ∅ |
56 +| `city` — Ville | municipalité | 100 % | Lévis |
57 +| `region` — Région | région administrative | 100 % | Chaudière-Appalaches |
58 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 9 % | 46.8121792, -71.1870052 |
59 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-29 |
60 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-29 |
61 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 74 % | 20:15 |
62 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 73 % | 21:15 |
63 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
64 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
65 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
66 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
67 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
68 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Lévis |
69 +| `image` — Image | visuel (og:image ou média source) | 97 % | https://levis-website-resources.s3.bhs.io.cloud.ovh.net/uploads/Photos/Spectacle-de-cirque-dans-… |
70 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
24 71
25 72 ## Fréquence & budgets
26 73
27 −- **Cadence** : n/d
74 +- **Cadence** : quotidien
28 75 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
29 76
30 77 ## Volumétrie & complétude live
31 78
32 −*Source sans connecteur runtime : aucune donnée en base.*
79 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
80 +
81 +- **Événements en base** : 153 (dont **153 actifs**)
82 +- **GPS** : 14/153 actifs (9 %)
83 +- **Prix** : 0/153 actifs (0 %)
84 +- **Heure (start_time)** : 113/153 actifs (74 %)
85 +- **Heure de fin** : 111/153 actifs (73 %)
86 +- **Statut (annulé/reporté/complet)** : 0/153 actifs (0 %)
87 +- **Artistes** : 0/153 actifs (0 %)
88 +- **Image** : 148/153 actifs (97 %)
89 +- **Salle (venue)** : 19/153 actifs (12 %)
90 +- **Dernier sync** : 2026-08-25 18:28 — fetched 153, +153 ajoutés, ~0 mis à jour, -0 retirés
91 +- **Syncs enregistrés** (sync_log) : 1
33 92
34 93 ## Erreurs & dépannage
35 94
95 +Aucune erreur dans `sync_log` pour cette source. ✅
96 +
97 +- Rejouer un sync isolé : `python3 run.py sync --source levis` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='levis' ORDER BY ts DESC LIMIT 5;"`
36 98
37 99 ## Licence / attribution
38 100
39 −- n/d
101 +- API du site municipal public, jeton public, robots.txt permissif
40 102
41 103 ## Historique
42 104
43 −- **Source abandonnée** — Site derrière un WAF (403 sur GET identifiés) et aucun jeu « événements » sur Données Québec (vérifié 2026-08-18)
44 105 - **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
45 106 - **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
46 107 - **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/lhjmq.md +25 −25
@@ -1,6 +1,6 @@
1 1 # LHJMQ — matchs des équipes québécoises (`lhjmq`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `sport` · Connecteur : `sortika/connectors/lhjmq.py`
6 6
@@ -44,22 +44,22 @@
44 44
45 45 ## Champs récupérés → schéma
46 46
47 −Complétude mesurée sur les **417 événements actifs** de la source ; exemples tirés d'un événement réel (`lhjmq:32606`).
47 +Complétude mesurée sur les **410 événements actifs** de la source ; exemples tirés d'un événement réel (`lhjmq:32616`).
48 48
49 49 | Champ (`events.*`) | Description | Complétude | Exemple réel |
50 50 |---|---|---|---|
51 −| `title` — Titre | titre de l'événement | 100 % | LHJMQ : Cataractes (Shawinigan) c. Voltigeurs (Drummondville) |
52 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Match de la Ligue de hockey junior Maritimes Québec (LHJMQ) — Cataractes (Shawinigan) en visite … |
53 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://theqmjhl.ca/gamecentre/32606 |
51 +| `title` — Titre | titre de l'événement | 100 % | LHJMQ : Armada (Blainville-Boisbriand) c. Olympiques (Gatineau) |
52 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Match de la Ligue de hockey junior Maritimes Québec (LHJMQ) — Armada (Blainville-Boisbriand) en … |
53 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://theqmjhl.ca/gamecentre/32616 |
54 54 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["sport"] |
55 −| `venue` — Salle (venue) | nom du lieu | 100 % | Centre sportif Girardin - Drummondville (QC) |
55 +| `venue` — Salle (venue) | nom du lieu | 100 % | Complexe Branchaud-Brière - Gatineau |
56 56 | `address` — Adresse | adresse civique | 0 % | ∅ |
57 −| `city` — Ville | municipalité | 100 % | Drummondville |
58 −| `region` — Région | région administrative | 100 % | Centre-du-Québec |
59 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 99 % | 45.8744982, -72.5000621 |
60 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
61 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
62 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 14:00 |
57 +| `city` — Ville | municipalité | 100 % | Gatineau |
58 +| `region` — Région | région administrative | 100 % | Outaouais |
59 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 99 % | 45.4972217, -75.6444245 |
60 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-26 |
61 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-26 |
62 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 19:00 |
63 63 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 64 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
65 65 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
@@ -77,19 +77,19 @@ Complétude mesurée sur les **417 événements actifs** de la source ; exemples
77 77
78 78 ## Volumétrie & complétude live
79 79
80 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
81 −
82 −- **Événements en base** : 423 (dont **417 actifs**)
83 −- **GPS** : 411/417 actifs (99 %)
84 −- **Prix** : 0/417 actifs (0 %)
85 −- **Heure (start_time)** : 417/417 actifs (100 %)
86 −- **Heure de fin** : 0/417 actifs (0 %)
87 −- **Statut (annulé/reporté/complet)** : 0/417 actifs (0 %)
88 −- **Artistes** : 0/417 actifs (0 %)
89 −- **Image** : 0/417 actifs (0 %)
90 −- **Salle (venue)** : 417/417 actifs (100 %)
91 −- **Dernier sync** : 2026-08-22 22:27 — fetched 423, +0 ajoutés, ~0 mis à jour, -0 retirés
92 −- **Syncs enregistrés** (sync_log) : 81
80 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
81 +
82 +- **Événements en base** : 423 (dont **410 actifs**)
83 +- **GPS** : 404/410 actifs (99 %)
84 +- **Prix** : 0/410 actifs (0 %)
85 +- **Heure (start_time)** : 410/410 actifs (100 %)
86 +- **Heure de fin** : 0/410 actifs (0 %)
87 +- **Statut (annulé/reporté/complet)** : 0/410 actifs (0 %)
88 +- **Artistes** : 0/410 actifs (0 %)
89 +- **Image** : 0/410 actifs (0 %)
90 +- **Salle (venue)** : 410/410 actifs (100 %)
91 +- **Dernier sync** : 2026-08-25 18:05 — fetched 423, +0 ajoutés, ~0 mis à jour, -0 retirés
92 +- **Syncs enregistrés** (sync_log) : 136
93 93
94 94 ## Erreurs & dépannage
95 95
modified docs/connecteurs/longueuil.md +4 −4
@@ -1,6 +1,6 @@
1 1 # Ville de Longueuil — Événements (nouveau portail) (`longueuil`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/longueuil.py`
6 6
@@ -81,7 +81,7 @@ Complétude mesurée sur les **4 événements actifs** de la source ; exemples t
81 81
82 82 ## Volumétrie & complétude live
83 83
84 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
84 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
85 85
86 86 - **Événements en base** : 9 (dont **4 actifs**)
87 87 - **GPS** : 3/4 actifs (75 %)
@@ -92,8 +92,8 @@ Complétude mesurée sur les **4 événements actifs** de la source ; exemples t
92 92 - **Artistes** : 0/4 actifs (0 %)
93 93 - **Image** : 4/4 actifs (100 %)
94 94 - **Salle (venue)** : 0/4 actifs (0 %)
95 −- **Dernier sync** : 2026-08-22 22:27 — fetched 9, +0 ajoutés, ~0 mis à jour, -0 retirés
96 −- **Syncs enregistrés** (sync_log) : 101
95 +- **Dernier sync** : 2026-08-25 18:05 — fetched 9, +0 ajoutés, ~0 mis à jour, -0 retirés
96 +- **Syncs enregistrés** (sync_log) : 156
97 97
98 98 ## Erreurs & dépannage
99 99
added docs/connecteurs/magog.md +107 −0
@@ -0,0 +1,107 @@
1 +# Ville de Magog — Événements et activités (agenda Google public) (`magog`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/magog.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Magog — Événements et activités (agenda Google public)](https://www.ville.magog.qc.ca/calendrier/evenements-et-activites/)
10 +- **Couverture** : Magog (Estrie)
11 +- **Extraction** : flux iCal Google public embarqué par la page municipale (1 232 VEVENT, 1 requête) : conversion UTC/TZID/VALUE=DATE + expansion RRULE DAILY/WEEKLY (COUNT/UNTIL/BYDAY) plafonnée à 365 j
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`PAGE`) : `https://www.ville.magog.qc.ca/calendrier/evenements-et-activites/`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Accès légal** : calendrier Google publié publiquement par la Ville sur sa page
21 +
22 +<details><summary>En-tête documentaire du connecteur</summary>
23 +
24 +> connectors/magog.py : connecteur Ville de Magog — événements et activités
25 +> Source : https://www.ville.magog.qc.ca/calendrier/evenements-et-activites/
26 +> — la page municipale embarque un calendrier Google public
27 +> (plugin « Simple Google Calendar », id découvert dans le HTML).
28 +> Extraction: flux iCal PUBLIC du calendrier Google
29 +> fr7c02sjcaibul422352d811oc@group.calendar.google.com
30 +> (~1 230 VEVENT dont l'historique depuis 2017) : SUMMARY,
31 +> DTSTART/DTEND UTC → date+heure locales (utc_to_local),
32 +> LOCATION (« Salle, adresse, ville... »), DESCRIPTION (contient
33 +> l'URL de la fiche ville.magog.qc.ca), STATUS. Récurrences
34 +> Google observées (2026-08-25) : FREQ=DAILY/WEEKLY avec COUNT/
35 +> UNTIL/BYDAY → mini-expanseur déterministe (aucune invention),
36 +> occurrences plafonnées à HORIZON_DAYS. 1 requête par sync.
37 +> Accès : flux iCal publié publiquement par la Ville (bouton « + Google
38 +> Agenda » de sa page) ; conditions Google : accès lecture aux
39 +> calendriers publics. GET direct, identification honnête.
40 +> Prix : non publié par le flux → jamais inventé.
41 +
42 +</details>
43 +
44 +## Champs récupérés → schéma
45 +
46 +Complétude mesurée sur les **190 événements actifs** de la source ; exemples tirés d'un événement réel (`magog:0tq9dk7991sipi07r64jecmqgp-2026-08-25`).
47 +
48 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
49 +|---|---|---|---|
50 +| `title` — Titre | titre de l'événement | 100 % | Patinage libre |
51 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
52 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.ville.magog.qc.ca/evenement/patinage-libre-horaire-regulier/ |
53 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
54 +| `venue` — Salle (venue) | nom du lieu | 100 % | Aréna de Magog |
55 +| `address` — Adresse | adresse civique | 100 % | 100 Rue Saint-Alphonse Nord, Magog, QC J1X 3Y5, Canada |
56 +| `city` — Ville | municipalité | 100 % | Magog |
57 +| `region` — Région | région administrative | 100 % | Estrie |
58 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 99 % | 45.2636204, -72.1303987 |
59 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
60 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
61 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 09:00 |
62 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 100 % | 10:50 |
63 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
64 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
65 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
66 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
67 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
68 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Magog |
69 +| `image` — Image | visuel (og:image ou média source) | 0 % | ∅ |
70 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
71 +
72 +## Fréquence & budgets
73 +
74 +- **Cadence** : quotidien
75 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
76 +
77 +## Volumétrie & complétude live
78 +
79 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
80 +
81 +- **Événements en base** : 198 (dont **190 actifs**)
82 +- **GPS** : 189/190 actifs (99 %)
83 +- **Prix** : 0/190 actifs (0 %)
84 +- **Heure (start_time)** : 190/190 actifs (100 %)
85 +- **Heure de fin** : 190/190 actifs (100 %)
86 +- **Statut (annulé/reporté/complet)** : 0/190 actifs (0 %)
87 +- **Artistes** : 0/190 actifs (0 %)
88 +- **Image** : 0/190 actifs (0 %)
89 +- **Salle (venue)** : 190/190 actifs (100 %)
90 +- **Dernier sync** : 2026-08-25 18:25 — fetched 198, +198 ajoutés, ~0 mis à jour, -0 retirés
91 +- **Syncs enregistrés** (sync_log) : 1
92 +
93 +## Erreurs & dépannage
94 +
95 +Aucune erreur dans `sync_log` pour cette source. ✅
96 +
97 +- Rejouer un sync isolé : `python3 run.py sync --source magog` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='magog' ORDER BY ts DESC LIMIT 5;"`
98 +
99 +## Licence / attribution
100 +
101 +- calendrier Google publié publiquement par la Ville sur sa page
102 +
103 +## Historique
104 +
105 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
106 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
107 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/mauricie.md +82 −14
@@ -1,46 +1,114 @@
1 −# Tourisme Mauricie — événements (ATR) (`mauricie`)
1 +# Tourisme Mauricie — calendrier des événements (ATR) (`mauricie`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 −**Statut : abandonnee** · Type : `tourisme-regional` · Connecteur : *aucun (source sans connecteur runtime)*
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/mauricie.py`
6 6
7 7 ## Description
8 8
9 −- **Source** : [Tourisme Mauricie — événements (ATR)](https://tourismemauricie.com/evenements)
10 −- **Couverture** : Mauricie
11 −- **Extraction** : n/d
12 −- **Note** : ÉCARTÉE 2026-08-19 (Phase 3) : liste /evenements rendue JS — 1 seule carte SSR par page (vérifié pages 1 et 2), cartes chargées par admin-ajax non documenté, API REST /wp-json verrouillée (401 rest_not_logged_in). À re-sonder (percer la facette admin-ajax) dans une vague ultérieure.
9 +- **Source** : [Tourisme Mauricie — calendrier des événements (ATR)](https://tourismemauricie.com/evenements)
10 +- **Couverture** : Mauricie (+ villes limitrophes du Centre-du-Québec)
11 +- **Extraction** : API JSON interne du thème (GET /wp-json/acolyte/v1/get-events-data?lang=fr&page=N, ~174 événements : titre, url, ville, salle, catégories, date FR, GPS, images) + fiches détail incrémentales (JSON-LD Event : date ISO, adresse, description ; cache, MAU_FICHE_MAX=12, re-visite 7 j) ; région par la VILLE (l'ATR couvre des villes du Centre-du-Québec). RÉHABILITÉE 2026-08-25 (écartée Phase 3 pour rendu JS — l'endpoint XHR interne est percé)
13 12
14 13 ## Accès
15 14
16 15 - **Type** : tourisme-regional
17 −- **Endpoint** : voir URL source (https://tourismemauricie.com/evenements)
16 +- **Endpoint** (`BASE`) : `https://tourismemauricie.com`
18 17 - **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 −- **Accès légal** : n/d
18 +- **Pagination** : par numéro de page
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : API REST WordPress publique (aucune auth), robots.txt sans interdiction — GET throttlés, UA honnête
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/mauricie.py : Tourisme Mauricie (ATR) — RÉHABILITÉE (vague ATR
26 +> 2026-08 ; écartée Phase 3 « liste rendue JS », l'endpoint XHR est percé)
27 +> Source : tourismemauricie.com/evenements — calendrier officiel de l'ATR
28 +> (thème WordPress « Acolyte », listing Vue.js).
29 +> Extraction: API JSON interne du thème (celle que le composant Vue appelle,
30 +> vérifiée 2026-08-25) :
31 +> GET /wp-json/acolyte/v1/get-events-data?lang=fr&page=N
32 +> → ~174 événements paginés 9/page : id, titre, url, images CDN,
33 +> ville, salle (venue_name), catégories, date FR, GPS. Fiches
34 +> détail incrémentales (cache data/mauricie_cache.json, plafond
35 +> MAU_FICHE_MAX/sync, re-visite 7 j) : JSON-LD Event → date de
36 +> début ISO, adresse civique, code postal, description.
37 +> Accès : API REST WordPress publique du thème (aucune auth), robots.txt
38 +> sans interdiction — GET throttlés, UA honnête. Ni prix ni heure
39 +> structurés publiés → jamais inventés. Région administrative
40 +> résolue par la VILLE (l'ATR couvre aussi des villes du
41 +> Centre-du-Québec : Bécancour, Nicolet…), repli « Mauricie ».
42 +
43 +</details>
20 44
21 45 ## Champs récupérés → schéma
22 46
23 −*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
47 +Complétude mesurée sur les **157 événements actifs** de la source ; exemples tirés d'un événement réel (`mauricie:32919`).
48 +
49 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
50 +|---|---|---|---|
51 +| `title` — Titre | titre de l'événement | 100 % | Quai en fête Bécancour |
52 +| `description` — Description | texte descriptif (source ou fiche détail) | 10 % | Le festival Quai en fête proposera près d'une cinquantaine de spectacles au Quai de Bécancour! |
53 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://tourismemauricie.com/evenements/quai-en-fete-becancour |
54 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique", "festival"] |
55 +| `venue` — Salle (venue) | nom du lieu | 98 % | Quai en fête |
56 +| `address` — Adresse | adresse civique | 8 % | 100 Avenue des Nénuphars |
57 +| `city` — Ville | municipalité | 85 % | Bécancour |
58 +| `region` — Région | région administrative | 100 % | Centre-du-Québec |
59 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 94 % | 46.335655, -72.517079 |
60 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
61 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
62 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
63 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
65 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
66 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
67 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
68 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
69 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
70 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://cdn.footlight.io/iiif/3/7bc78d76-9a04-419d-9846-d48a61e34fd7.png/full/820,/0/default.png |
71 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
24 72
25 73 ## Fréquence & budgets
26 74
27 −- **Cadence** : n/d
75 +- **Cadence** : quotidienne
76 +- **Budgets / plafonds (variables d'environnement)** :
77 +
78 +| Variable | Défaut | Rôle |
79 +|---|---|---|
80 +| `MAU_PAGE_MAX` | `40` | — |
81 +| `MAU_FICHE_MAX` | `12` | — |
28 82 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
29 83
30 84 ## Volumétrie & complétude live
31 85
32 −*Source sans connecteur runtime : aucune donnée en base.*
86 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
87 +
88 +- **Événements en base** : 157 (dont **157 actifs**)
89 +- **GPS** : 148/157 actifs (94 %)
90 +- **Prix** : 0/157 actifs (0 %)
91 +- **Heure (start_time)** : 0/157 actifs (0 %)
92 +- **Heure de fin** : 0/157 actifs (0 %)
93 +- **Statut (annulé/reporté/complet)** : 0/157 actifs (0 %)
94 +- **Artistes** : 0/157 actifs (0 %)
95 +- **Image** : 157/157 actifs (100 %)
96 +- **Salle (venue)** : 154/157 actifs (98 %)
97 +- **Dernier sync** : 2026-08-25 18:24 — fetched 157, +157 ajoutés, ~0 mis à jour, -0 retirés
98 +- **Syncs enregistrés** (sync_log) : 1
33 99
34 100 ## Erreurs & dépannage
35 101
102 +Aucune erreur dans `sync_log` pour cette source. ✅
103 +
104 +- Rejouer un sync isolé : `python3 run.py sync --source mauricie` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='mauricie' ORDER BY ts DESC LIMIT 5;"`
36 105
37 106 ## Licence / attribution
38 107
39 −- n/d
108 +- API REST WordPress publique (aucune auth), robots.txt sans interdiction — GET throttlés, UA honnête
40 109
41 110 ## Historique
42 111
43 −- **Source abandonnée** — ÉCARTÉE 2026-08-19 (Phase 3) : liste /evenements rendue JS — 1 seule carte SSR par page (vérifié pages 1 et 2), cartes chargées par admin-ajax non documenté, API REST /wp-json verrouillée (401 rest_not_logged_in). À re-sonder (percer la facette admin-ajax) dans une vague ultérieure.
44 112 - **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
45 113 - **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
46 114 - **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/monteregie.md +31 −27
@@ -1,6 +1,6 @@
1 1 # Tourisme Montérégie — événements (ATR) (`monteregie`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/monteregie.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Tourisme Montérégie — événements (ATR)](https://www.tourisme-monteregie.qc.ca/membre/categorie/quoi-faire/festivals-et-evenements/)
10 10 - **Couverture** : Montérégie (~78 fiches à venir, vérifié 2026-08-21)
11 −- **Extraction** : API REST WordPress publique /wp-json/wp/v2/cpt_event (paginée per_page=100) — titre, description, plage de dates FR, lat/long, site web, image (gisement Tourinsoft, acf.syndic_object_id EVENMTOV… ; recoupement SITQ géré par dedup_key). Fiches détail incrémentales (MTG_FICHE_MAX=15/sync, re-visite 7 j) : JSON-LD Event → adresse civique, ville, code postal.
11 +- **Extraction** : API REST WP /wp-json/wp/v2/cpt_event (per_page=100) + taxonomie /event-categorie (1 GET → catégories brutes) — titre, description (content, fallback excerpt), plage FR, GPS ACF, site web, image ; fiches incrémentales (MTG_FICHE_MAX/sync, re-visite 7 j) : JSON-LD Event → adresse, ville, CP
12 12
13 13 ## Accès
14 14
@@ -24,18 +24,22 @@
24 24
25 25 > connectors/monteregie.py : Tourisme Montérégie — événements (ATR, Phase 4)
26 26 > Source : https://www.tourisme-monteregie.qc.ca — WordPress dont l'API
27 −> REST publique expose le CPT `cpt_event` (~78 fiches à venir,
28 −> vérifié 2026-08-21). Gisement Tourinsoft (acf.syndic_object_id
29 −> EVENMTOV… — même famille que le SIT Québec ; recoupement géré
30 −> par dedup_key, mais l'ATR ajoute GPS, image, site web et
31 −> description que le flux SITQ ne publie pas).
27 +> REST publique expose le CPT `cpt_event` (78 fiches publiées,
28 +> X-WP-TotalPages=1, revérifié 2026-08-25). Gisement Tourinsoft
29 +> (acf.syndic_object_id EVENMTOV… — même famille que le SIT
30 +> Québec ; recoupement géré par dedup_key, mais l'ATR ajoute
31 +> GPS, image, site web et description que le flux SITQ ne
32 +> publie pas).
32 33 > Extraction: API JSON /wp-json/wp/v2/cpt_event (paginée per_page=100) →
33 −> titre, description, plage de dates FR (« Du 18 septembre au
34 −> 20 septembre 2026 »), lat/long, site web, image. Fiches détail
34 +> titre, description (content, fallback excerpt), plage de
35 +> dates FR (« Du 18 septembre au 20 septembre 2026 »), lat/long,
36 +> site web, image. ENRICHI (2026-08-25) : taxonomie
37 +> event-categorie (1 GET/sync) → catégories brutes (« Marchés
38 +> de Noël », « Festival et autres événements »). Fiches détail
35 39 > incrémentales (cache data/monteregie_cache.json,
36 40 > MTG_FICHE_MAX/sync, re-visite 7 j) : bloc JSON-LD Event →
37 −> adresse civique, ville, code postal. Pas d'heure ni de tarif
38 −> structurés → jamais inventés.
41 +> adresse civique, ville, code postal. Ni heure ni tarif
42 +> structurés publiés (vérifié sur fiches) → jamais inventés.
39 43 > Accès : API REST WordPress publique (aucune auth), robots.txt sans
40 44 > interdiction sur /wp-json/ — GET throttlés, UA honnête.
41 45
@@ -43,7 +47,7 @@
43 47
44 48 ## Champs récupérés → schéma
45 49
46 −Complétude mesurée sur les **28 événements actifs** de la source ; exemples tirés d'un événement réel (`monteregie:89015`).
50 +Complétude mesurée sur les **26 événements actifs** de la source ; exemples tirés d'un événement réel (`monteregie:89015`).
47 51
48 52 | Champ (`events.*`) | Description | Complétude | Exemple réel |
49 53 |---|---|---|---|
@@ -52,7 +56,7 @@ Complétude mesurée sur les **28 événements actifs** de la source ; exemples
52 56 | `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.tourisme-monteregie.qc.ca/evenement/marche-de-noel-de-marieville/ |
53 57 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
54 58 | `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
55 −| `address` — Adresse | adresse civique | 93 % | rue du Pont |
59 +| `address` — Adresse | adresse civique | 92 % | rue du Pont |
56 60 | `city` — Ville | municipalité | 100 % | Marieville |
57 61 | `region` — Région | région administrative | 100 % | Montérégie |
58 62 | `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 45.432743, -73.159756 |
@@ -67,7 +71,7 @@ Complétude mesurée sur les **28 événements actifs** de la source ; exemples
67 71 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
68 72 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
69 73 | `image` — Image | visuel (og:image ou média source) | 96 % | https://www.tourisme-monteregie.qc.ca/wp-content/uploads/2025/03/marche-de-noel-de-marieville-tr… |
70 −| `website` — Site web | site officiel de l'événement | 75 % | ∅ |
74 +| `website` — Site web | site officiel de l'événement | 77 % | ∅ |
71 75
72 76 ## Fréquence & budgets
73 77
@@ -81,19 +85,19 @@ Complétude mesurée sur les **28 événements actifs** de la source ; exemples
81 85
82 86 ## Volumétrie & complétude live
83 87
84 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
85 −
86 −- **Événements en base** : 78 (dont **28 actifs**)
87 −- **GPS** : 28/28 actifs (100 %)
88 −- **Prix** : 0/28 actifs (0 %)
89 −- **Heure (start_time)** : 0/28 actifs (0 %)
90 −- **Heure de fin** : 0/28 actifs (0 %)
91 −- **Statut (annulé/reporté/complet)** : 0/28 actifs (0 %)
92 −- **Artistes** : 0/28 actifs (0 %)
93 −- **Image** : 27/28 actifs (96 %)
94 −- **Salle (venue)** : 0/28 actifs (0 %)
95 −- **Dernier sync** : 2026-08-22 22:27 — fetched 78, +0 ajoutés, ~0 mis à jour, -0 retirés
96 −- **Syncs enregistrés** (sync_log) : 28
88 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
89 +
90 +- **Événements en base** : 78 (dont **26 actifs**)
91 +- **GPS** : 26/26 actifs (100 %)
92 +- **Prix** : 0/26 actifs (0 %)
93 +- **Heure (start_time)** : 0/26 actifs (0 %)
94 +- **Heure de fin** : 0/26 actifs (0 %)
95 +- **Statut (annulé/reporté/complet)** : 0/26 actifs (0 %)
96 +- **Artistes** : 0/26 actifs (0 %)
97 +- **Image** : 25/26 actifs (96 %)
98 +- **Salle (venue)** : 0/26 actifs (0 %)
99 +- **Dernier sync** : 2026-08-25 18:05 — fetched 78, +0 ajoutés, ~0 mis à jour, -0 retirés
100 +- **Syncs enregistrés** (sync_log) : 83
97 101
98 102 ## Erreurs & dépannage
99 103
modified docs/connecteurs/montreal.md +34 −31
@@ -1,6 +1,6 @@
1 1 # Ville de Montréal — Événements publics (`montreal`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `donnees-ouvertes` · Connecteur : `sortika/connectors/montreal.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Ville de Montréal — Événements publics](https://donnees.montreal.ca/dataset/evenements-publics)
10 10 - **Couverture** : Événements municipaux, île de Montréal
11 −- **Extraction** : API CKAN datastore_search (JSON, paginée) — venue = emplacement (si vrai nom de lieu) ou titre_adresse ; address = adresse_principale + adresse_secondaire ; le jeu de données ne publie pas l'heure → heure locale captée dans le JSON-LD des fiches montreal.ca (« startDate »:"…T19:30:00-04:00") lors de l'enrichissement og:image incrémental (cache, plafond MTL_IMG_MAX/sync)
11 +- **Extraction** : API CKAN datastore_search (JSON, paginée) — venue = emplacement (si vrai nom de lieu) ou titre_adresse ; address = adresse_principale + adresse_secondaire ; le jeu de données ne publie pas l'heure → heure locale captée dans le JSON-LD des fiches montreal.ca (« startDate »:"…T19:30:00-04:00") lors de l'enrichissement og:image incrémental (cache, plafond MTL_IMG_MAX/sync). MAJ 2026-08-25 : plafond MTL_IMG_MAX doublé (600/sync), collecte complète puis priorisation jamais-visités/date proche — convergence atteinte : image 100 % et heure 99 % des événements à venir
12 12
13 13 ## Accès
14 14
@@ -30,36 +30,39 @@
30 30 > sport, marchés) avec dates, coût, arrondissement, lat/lng.
31 31 > Images : absentes du jeu de données → enrichissement incrémental par
32 32 > og:image des fiches montreal.ca (cache disque, plafonné par
33 −> sync via MTL_IMG_MAX, défaut 300).
33 +> sync via MTL_IMG_MAX, défaut 600 — doublé 2026-08-25 pour un
34 +> rattrapage plus rapide ; candidats collectés en entier PUIS
35 +> triés — jamais visités d'abord, ensuite par date de début la
36 +> plus proche — avant d'appliquer le plafond).
34 37 > https://donnees.montreal.ca/dataset/evenements-publics
35 38
36 39 </details>
37 40
38 41 ## Champs récupérés → schéma
39 42
40 −Complétude mesurée sur les **3522 événements actifs** de la source ; exemples tirés d'un événement réel (`montreal:schulichbon-pasteur-nelson-lee-denise-djokic-et-kyoko-hashimoto-118821`).
43 +Complétude mesurée sur les **3691 événements actifs** de la source ; exemples tirés d'un événement réel (`montreal:marche-fermier-laurier-110385`).
41 44
42 45 | Champ (`events.*`) | Description | Complétude | Exemple réel |
43 46 |---|---|---|---|
44 −| `title` — Titre | titre de l'événement | 100 % | Schulich@Bon-Pasteur - Nelson Lee, Denise Djokic et Kyoko Hashimoto |
45 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Ne manquez ce concert des membres du corps professoral de l’École de musique Schulich de l’Unive… |
46 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://montreal.ca/evenements/schulichbon-pasteur-nelson-lee-denise-djokic-et-kyoko-hashimoto-1… |
47 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
48 −| `venue` — Salle (venue) | nom du lieu | 4 % | Salle Paul-Desmarais |
49 −| `address` — Adresse | adresse civique | 4 % | 1920, rue Baile, Centre Canadien d'Architecture |
47 +| `title` — Titre | titre de l'événement | 100 % | Marché fermier Laurier |
48 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Chaque année, Le Plateau vous propose des march��s publics de quartier pour vous procurer des al… |
49 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://montreal.ca/evenements/marche-fermier-laurier-110385 |
50 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["marche-foire"] |
51 +| `venue` — Salle (venue) | nom du lieu | 4 % | Place du Coteau-Saint-Louis (sortie nord du métro Laurier) |
52 +| `address` — Adresse | adresse civique | 4 % | 465 avenue Laurier est |
50 53 | `city` — Ville | municipalité | 100 % | Montréal |
51 54 | `region` — Région | région administrative | 100 % | Montréal |
52 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 99 % | 45.491008, -73.578662 |
53 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-12-09 |
54 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-12-09 |
55 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 99 % | 19:30 |
56 −| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 99 % | 20:45 |
55 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 99 % | 45.52801, -73.58902 |
56 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-27 |
57 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-27 |
58 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 99 % | 15:00 |
59 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 99 % | 19:00 |
57 60 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
58 61 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 100 % | 1 |
59 62 | `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
60 −| `price_label` — Libellé prix | étiquette tarifaire affichable | 100 % | Gratuit — Avec billet |
63 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 100 % | Gratuit — Entrée libre |
61 64 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
62 −| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Montréal — Ville de Montréal |
65 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Montréal — Le Plateau-Mont-Royal |
63 66 | `image` — Image | visuel (og:image ou média source) | 100 % | https://res.cloudinary.com/villemontreal/image/upload/w_1200,h_630,c_fill/f_auto,dpr_auto,q_auto… |
64 67 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
65 68
@@ -70,24 +73,24 @@ Complétude mesurée sur les **3522 événements actifs** de la source ; exemple
70 73
71 74 | Variable | Défaut | Rôle |
72 75 |---|---|---|
73 −| `MTL_IMG_MAX` | `300` | — |
76 +| `MTL_IMG_MAX` | `600` | — |
74 77 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
75 78
76 79 ## Volumétrie & complétude live
77 80
78 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
79 −
80 −- **Événements en base** : 6207 (dont **3522 actifs**)
81 −- **GPS** : 3486/3522 actifs (99 %)
82 −- **Prix** : 3522/3522 actifs (100 %)
83 −- **Heure (start_time)** : 3503/3522 actifs (99 %)
84 −- **Heure de fin** : 3503/3522 actifs (99 %)
85 −- **Statut (annulé/reporté/complet)** : 0/3522 actifs (0 %)
86 −- **Artistes** : 0/3522 actifs (0 %)
87 −- **Image** : 3522/3522 actifs (100 %)
88 −- **Salle (venue)** : 154/3522 actifs (4 %)
89 −- **Dernier sync** : 2026-08-22 22:27 — fetched 5757, +0 ajoutés, ~0 mis à jour, -0 retirés
90 −- **Syncs enregistrés** (sync_log) : 124
81 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
82 +
83 +- **Événements en base** : 6542 (dont **3691 actifs**)
84 +- **GPS** : 3655/3691 actifs (99 %)
85 +- **Prix** : 3691/3691 actifs (100 %)
86 +- **Heure (start_time)** : 3672/3691 actifs (99 %)
87 +- **Heure de fin** : 3672/3691 actifs (99 %)
88 +- **Statut (annulé/reporté/complet)** : 0/3691 actifs (0 %)
89 +- **Artistes** : 0/3691 actifs (0 %)
90 +- **Image** : 3691/3691 actifs (100 %)
91 +- **Salle (venue)** : 146/3691 actifs (4 %)
92 +- **Dernier sync** : 2026-08-25 18:05 — fetched 5995, +0 ajoutés, ~0 mis à jour, -0 retirés
93 +- **Syncs enregistrés** (sync_log) : 179
91 94
92 95 ## Erreurs & dépannage
93 96
added docs/connecteurs/mtlorg.md +114 −0
@@ -0,0 +1,114 @@
1 +# Tourisme Montréal (mtl.org) — festivals et événements (ATR) (`mtlorg`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/mtlorg.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Tourisme Montréal (mtl.org) — festivals et événements (ATR)](https://www.mtl.org/fr/quoi-faire/festivals-et-evenements)
10 +- **Couverture** : Montréal
11 +- **Extraction** : Index Algolia public du site (prod_fr, facettes _sections:Festivals et événements + content_type:product) : 1 requête/sync → titre, description, plages de dates structurées _open_dates, adresse, GPS, sous-catégories, image CDN ; les fiches sans date (salles/attraits) sont écartées
12 +
13 +## Accès
14 +
15 +- **Type** : tourisme-regional
16 +- **Endpoint** (`BASE`) : `https://www.mtl.org`
17 +- **Endpoint** (`ALGOLIA_URL`) : `https://XGNQMFEPVY-dsn.algolia.net/1/indexes/prod_fr/query`
18 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 +- **Pagination** : par numéro de page
20 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
21 +- **Throttle** : 0.8 s entre requêtes
22 +- **Accès légal** : Clé Algolia « search-only » publiée par le site lui-même — requêtes throttlées, UA honnête, lien vers la fiche originale
23 +
24 +<details><summary>En-tête documentaire du connecteur</summary>
25 +
26 +> connectors/mtlorg.py : Tourisme Montréal — mtl.org (ATR, vague ATR 2026-08)
27 +> Source : mtl.org/fr/quoi-faire/festivals-et-evenements — répertoire
28 +> officiel de Tourisme Montréal (frontend SvelteKit + Drupal,
29 +> listing servi par le même index Algolia que le site).
30 +> Extraction: index Algolia « prod_fr » (appId/clé de recherche publiques,
31 +> embarquées dans le JS du site — vérifié 2026-08-25), filtre
32 +> facettes _sections:« Festivals et événements » +
33 +> content_type:product → ~650 fiches : titre, surtitre,
34 +> description, plages de dates STRUCTURÉES _open_dates
35 +> [{start,end}], adresse complète (_address), GPS (_geoloc),
36 +> sous-catégories, image CDN, URL. 1 requête/sync
37 +> (hitsPerPage=1000). Les fiches SANS _open_dates (salles /
38 +> attraits permanents listés dans la section) sont écartées :
39 +> un événement sans date publiée n'est pas émis (jamais inventé).
40 +> Accès : clé Algolia « search-only » publiée par le site lui-même —
41 +> requêtes throttlées, identification honnête, lien vers la
42 +> fiche originale. Ni prix ni heure publiés → jamais inventés.
43 +
44 +</details>
45 +
46 +## Champs récupérés → schéma
47 +
48 +Complétude mesurée sur les **546 événements actifs** de la source ; exemples tirés d'un événement réel (`mtlorg:93794`).
49 +
50 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
51 +|---|---|---|---|
52 +| `title` — Titre | titre de l'événement | 100 % | Edward aux mains d'argent en concert |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | «Edward aux mains d'argent en concert» présente le célèbre conte moderne de Tim Burton dans une … |
54 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.mtl.org/fr/quoi-faire/festivals-et-evenements/edward-mains-argent-concert |
55 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
56 +| `venue` — Salle (venue) | nom du lieu | 92 % | Salle Wilfrid-Pelletier |
57 +| `address` — Adresse | adresse civique | 87 % | Place des Arts, 175, rue Sainte-Catherine Ouest |
58 +| `city` — Ville | municipalité | 94 % | Montréal |
59 +| `region` — Région | région administrative | 100 % | Montréal |
60 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 95 % | 45.508758, -73.567352 |
61 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-10-10 |
62 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-10-10 |
63 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
65 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
66 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
67 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
68 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
69 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
70 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
71 +| `image` — Image | visuel (og:image ou média source) | 79 % | https://cms.mtl.org/files/default/styles/square_600_600/public/2026-08/64070__C5DB5FA9-3100-4C1D… |
72 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
73 +
74 +## Fréquence & budgets
75 +
76 +- **Cadence** : quotidienne
77 +- **Budgets / plafonds (variables d'environnement)** :
78 +
79 +| Variable | Défaut | Rôle |
80 +|---|---|---|
81 +| `MTL_PAGE_MAX` | `5` | — |
82 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
83 +
84 +## Volumétrie & complétude live
85 +
86 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
87 +
88 +- **Événements en base** : 650 (dont **546 actifs**)
89 +- **GPS** : 516/546 actifs (95 %)
90 +- **Prix** : 0/546 actifs (0 %)
91 +- **Heure (start_time)** : 0/546 actifs (0 %)
92 +- **Heure de fin** : 0/546 actifs (0 %)
93 +- **Statut (annulé/reporté/complet)** : 0/546 actifs (0 %)
94 +- **Artistes** : 0/546 actifs (0 %)
95 +- **Image** : 432/546 actifs (79 %)
96 +- **Salle (venue)** : 501/546 actifs (92 %)
97 +- **Dernier sync** : 2026-08-25 18:25 — fetched 650, +650 ajoutés, ~0 mis à jour, -0 retirés
98 +- **Syncs enregistrés** (sync_log) : 1
99 +
100 +## Erreurs & dépannage
101 +
102 +Aucune erreur dans `sync_log` pour cette source. ✅
103 +
104 +- Rejouer un sync isolé : `python3 run.py sync --source mtlorg` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='mtlorg' ORDER BY ts DESC LIMIT 5;"`
105 +
106 +## Licence / attribution
107 +
108 +- Clé Algolia « search-only » publiée par le site lui-même — requêtes throttlées, UA honnête, lien vers la fiche originale
109 +
110 +## Historique
111 +
112 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
113 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
114 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/outaouais.md +32 −21
@@ -1,6 +1,6 @@
1 1 # Tourisme Outaouais — calendrier des événements (ATR) (`outaouais`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/outaouais.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Tourisme Outaouais — calendrier des événements (ATR)](https://tourismeoutaouais.com/calendrier-des-evenements)
10 10 - **Couverture** : Festivals et événements, Outaouais (337 fiches seulement au baseline régional)
11 −- **Extraction** : props JSON de l'îlot Astro (déballage [type, valeur]) : id, titre, url (interne ou site officiel), municipalité, territoire, photo CDN, startDate/endDate ISO ; ni heure ni tarif publiés → jamais inventés
11 +- **Extraction** : props JSON de l'îlot Astro (1 GET, tout le calendrier) + fiches internes /attraits/<slug> des événements À VENIR, incrémentales (OUT_FICHE_MAX/sync, re-visite 7 j) : description, adresse+ville (div.address), site web officiel ; ni heure ni tarif → jamais inventés
12 12
13 13 ## Accès
14 14
@@ -27,13 +27,19 @@
27 27 > Source : https://tourismeoutaouais.com/calendrier-des-evenements — site
28 28 > Astro : la page embarque TOUT le calendrier dans les props
29 29 > sérialisées de l'îlot <astro-island CalendrierDesEvenements>
30 −> (162 événements dont 25 à venir, vérifié 2026-08-19) →
31 −> UNE SEULE requête par sync.
30 +> (159 événements dont ~22 à venir, revérifié 2026-08-25) →
31 +> UNE SEULE requête pour la liste.
32 32 > Extraction: props JSON (format Svelte [type, valeur] déballé) : id, titre,
33 33 > slug/url (interne /attraits/<slug> ou site officiel externe),
34 34 > municipalité, territoire, photo (CDN admin.tourismeoutaouais.com),
35 −> startDate/endDate ISO. Pas d'heure ni de tarif publiés → jamais
36 −> inventés. GPS via annuaire de salles/géocodeur central.
35 +> startDate/endDate ISO. ENRICHISSEMENT (2026-08-25) : les fiches
36 +> internes /attraits/<slug> des événements à venir sont visitées
37 +> incrémentalement (cache data/outaouais_cache.json,
38 +> OUT_FICHE_MAX/sync, re-visite 7 j) : description (meta
39 +> description / div.description), adresse civique + ville
40 +> (div.address « 988, rue Saint-louis, Gatineau (Québec) »),
41 +> site web officiel (« Visitez le site web »). Pas d'heure ni de
42 +> tarif publiés → jamais inventés. GPS via annuaire/géocodeur.
37 43 > Accès : robots.txt « Allow: / » — GET direct, identification honnête.
38 44 > Décision : comble l'échec Gatineau (site 100 % JS, consigné 2026-08-18) —
39 45 > l'Outaouais n'avait que 337 fiches au baseline Phase 3.
@@ -42,7 +48,7 @@
42 48
43 49 ## Champs récupérés → schéma
44 50
45 −Complétude mesurée sur les **24 événements actifs** de la source ; exemples tirés d'un événement réel (`outaouais:6a756bca300d5843f38a1a25`).
51 +Complétude mesurée sur les **23 événements actifs** de la source ; exemples tirés d'un événement réel (`outaouais:6a756bca300d5843f38a1a25`).
46 52
47 53 | Champ (`events.*`) | Description | Complétude | Exemple réel |
48 54 |---|---|---|---|
@@ -52,7 +58,7 @@ Complétude mesurée sur les **24 événements actifs** de la source ; exemples
52 58 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
53 59 | `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
54 60 | `address` — Adresse | adresse civique | 0 % | ∅ |
55 −| `city` — Ville | municipalité | 58 % | Gatineau |
61 +| `city` — Ville | municipalité | 61 % | Gatineau |
56 62 | `region` — Région | région administrative | 100 % | Outaouais |
57 63 | `lat` — GPS (lat/lng) | coordonnées géographiques | 0 % | ∅ |
58 64 | `start_date` — Date début | date locale AAAA-MM-JJ | 96 % | ∅ |
@@ -71,23 +77,28 @@ Complétude mesurée sur les **24 événements actifs** de la source ; exemples
71 77 ## Fréquence & budgets
72 78
73 79 - **Cadence** : horaire (watcher PM2)
80 +- **Budgets / plafonds (variables d'environnement)** :
81 +
82 +| Variable | Défaut | Rôle |
83 +|---|---|---|
84 +| `OUT_FICHE_MAX` | `20` | — |
74 85 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
75 86
76 87 ## Volumétrie & complétude live
77 88
78 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
79 −
80 −- **Événements en base** : 162 (dont **24 actifs**)
81 −- **GPS** : 0/24 actifs (0 %)
82 −- **Prix** : 0/24 actifs (0 %)
83 −- **Heure (start_time)** : 0/24 actifs (0 %)
84 −- **Heure de fin** : 0/24 actifs (0 %)
85 −- **Statut (annulé/reporté/complet)** : 0/24 actifs (0 %)
86 −- **Artistes** : 0/24 actifs (0 %)
87 −- **Image** : 24/24 actifs (100 %)
88 −- **Salle (venue)** : 0/24 actifs (0 %)
89 −- **Dernier sync** : 2026-08-22 22:27 — fetched 159, +0 ajoutés, ~0 mis à jour, -0 retirés
90 −- **Syncs enregistrés** (sync_log) : 82
89 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
90 +
91 +- **Événements en base** : 162 (dont **23 actifs**)
92 +- **GPS** : 0/23 actifs (0 %)
93 +- **Prix** : 0/23 actifs (0 %)
94 +- **Heure (start_time)** : 0/23 actifs (0 %)
95 +- **Heure de fin** : 0/23 actifs (0 %)
96 +- **Statut (annulé/reporté/complet)** : 0/23 actifs (0 %)
97 +- **Artistes** : 0/23 actifs (0 %)
98 +- **Image** : 23/23 actifs (100 %)
99 +- **Salle (venue)** : 0/23 actifs (0 %)
100 +- **Dernier sync** : 2026-08-25 18:05 — fetched 159, +0 ajoutés, ~0 mis à jour, -0 retirés
101 +- **Syncs enregistrés** (sync_log) : 137
91 102
92 103 ## Erreurs & dépannage
93 104
modified docs/connecteurs/ovation.md +39 −31
@@ -1,6 +1,6 @@
1 1 # Réseau Ovation — billetterie québécoise multi-salles (`ovation`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/ovation.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Réseau Ovation — billetterie québécoise multi-salles](https://reseau.ovation.ca/)
10 10 - **Couverture** : 26 organisations 100 % QC : Salle Albert-Rousseau, Petit Champlain, Diffusion Saguenay, Gatineau (Odyssée, Jean-Despréz), Vieux Terrebonne, Palace de Granby, Vieux Clocher Magog/Waterloo, La Tuque, Festival Western de St-Tite, clubs LHJMQ/LNAH…
11 −- **Extraction** : Sous-site mobile m.ovation.ca (HTML rendu serveur, AUCUN anti-bot) : par organisation (codes base36 validés par balayage, config data/ovation_orgs.json), /fr/Search/AllEvents paginé (?pageNumber=N) → 1 eventId = 1 représentation ; fiches fragment EventDescription (≈5 Ko, OVA_FICHE_MAX=40/sync) : date pleine FR + heure, catégories, ARTISTES (liens EventsByPerformer), description, salle + adresse + ville + code postal. Prix non exposé → jamais inventé.
11 +- **Extraction** : Sous-site mobile m.ovation.ca (HTML rendu serveur, AUCUN anti-bot) : par organisation (codes base36 validés par balayage, config data/ovation_orgs.json), /fr/Search/AllEvents paginé (?pageNumber=N) → 1 eventId = 1 représentation ; fiches fragment EventDescription (≈5 Ko, OVA_FICHE_MAX=40/sync) : date pleine FR + heure, catégories, ARTISTES (liens EventsByPerformer), description, salle + adresse + ville + code postal. Prix non exposé → jamais inventé. MAJ 2026-08-25 : prix NON PUBLIÉS (pages Event mobile+desktop sans aucun montant ni JSON-LD offers ; tarifs uniquement sous /event/tickets, Disallow robots.txt avec /api/) → jamais requêté, jamais inventé ; re-balayage des organisations sans nouveauté
12 12
13 13 ## Accès
14 14
@@ -38,8 +38,16 @@
38 38 > OVA_FICHE_MAX/sync, re-visite 7 j) : date pleine FR
39 39 > (« mercredi 26 août 2026, 20h00 »), catégories, artistes
40 40 > (EventsByPerformer), description, salle + adresse + ville +
41 −> code postal. Prix non exposé par le sous-site mobile →
42 −> jamais inventé.
41 +> code postal.
42 +> Prix : NON PUBLIÉS par la source sur ses surfaces crawlables —
43 +> re-sondé live 2026-08-25 : la page Event complète mobile
44 +> (77 Ko) et la page desktop www.ovation.ca (ForceWebsite-
45 +> Version=Desktop, 72 Ko) ne contiennent AUCUN montant ni
46 +> JSON-LD offers (les seules mentions « prix » = politique de
47 +> cookies) ; aucun endpoint JSON public. Les tarifs
48 +> n'apparaissent qu'au choix des sièges (/event/tickets),
49 +> chemin Disallow par robots.txt (mobile ET desktop, avec
50 +> /api/) → jamais requêté, prix jamais inventé.
43 51 > Accès : billetterie publique, HTML public sans auth. robots.txt
44 52 > (lu 2026-08-21) : User-agent * → Crawl-delay: 30 +
45 53 > Disallow /event/tickets (jamais requêté) → GET espacés de
@@ -53,30 +61,30 @@
53 61
54 62 ## Champs récupérés → schéma
55 63
56 −Complétude mesurée sur les **311 événements actifs** de la source ; exemples tirés d'un événement réel (`ovation:00000P-0104ZJ`).
64 +Complétude mesurée sur les **459 événements actifs** de la source ; exemples tirés d'un événement réel (`ovation:00000Q-1208JO`).
57 65
58 66 | Champ (`events.*`) | Description | Complétude | Exemple réel |
59 67 |---|---|---|---|
60 −| `title` — Titre | titre de l'événement | 100 % | Maxim Martin — Bon chien |
61 −| `description` — Description | texte descriptif (source ou fiche détail) | 75 % | Même après cinq one-man-show, un livre et une série sur son quotidien avec sa fille, Maxim Marti… |
62 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://m.ovation.ca/00000P/fr/Event/?eventId=0104ZJ |
68 +| `title` — Titre | titre de l'événement | 100 % | LAMA : D’aventures en aventures — Le spectacle musical biographique autorisé par SERGE LAMA |
69 +| `description` — Description | texte descriptif (source ou fiche détail) | 78 % | «(…) ce que je suis me reste obscur.» Serge Lama |
70 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://m.ovation.ca/00000Q/fr/Event/?eventId=1208JO |
63 71 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["arts-scene"] |
64 −| `venue` — Salle (venue) | nom du lieu | 100 % | Maison des arts Desjardins Drummondville - Salle principale |
65 −| `address` — Adresse | adresse civique | 100 % | 175, rue Ringuet |
66 −| `city` — Ville | municipalité | 100 % | Drummondville |
67 −| `region` — Région | région administrative | 100 % | Centre-du-Québec |
68 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 45.8771456, -72.5006843 |
69 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2027-01-08 |
70 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2027-01-08 |
72 +| `venue` — Salle (venue) | nom du lieu | 100 % | Salle Albert-Rousseau |
73 +| `address` — Adresse | adresse civique | 100 % | 2410, Chemin Sainte-Foy |
74 +| `city` — Ville | municipalité | 100 % | Québec |
75 +| `region` — Région | région administrative | 100 % | Capitale-Nationale |
76 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 46.7866655, -71.2862695 |
77 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-24 |
78 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-24 |
71 79 | `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:00 |
72 80 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
73 81 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
74 82 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
75 83 | `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
76 84 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
77 −| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 90 % | ["Maxim Martin"] |
78 −| `organizer` — Organisateur | promoteur / organisme | 100 % | Maison des Arts Desjardins Drummondville |
79 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://m.ovation.ca/product-catalog-images/thumbnails/28ea40e8-efc8-4d42-a967-06a97817417a.jpg |
85 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 90 % | ["Stéphan Côté", "Élizabeth Duperré", "Serge Lama", "Eric Paulhus", "Gaële Tavernier"] |
86 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Salle Albert-Rousseau |
87 +| `image` — Image | visuel (og:image ou média source) | 99 % | https://m.ovation.ca/product-catalog-images/thumbnails/45b1cc48-d3c2-4dfa-9ad8-2efcd8296c7a.jpg |
80 88 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
81 89
82 90 ## Fréquence & budgets
@@ -93,19 +101,19 @@ Complétude mesurée sur les **311 événements actifs** de la source ; exemples
93 101
94 102 ## Volumétrie & complétude live
95 103
96 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
97 −
98 −- **Événements en base** : 312 (dont **311 actifs**)
99 −- **GPS** : 311/311 actifs (100 %)
100 −- **Prix** : 0/311 actifs (0 %)
101 −- **Heure (start_time)** : 311/311 actifs (100 %)
102 −- **Heure de fin** : 0/311 actifs (0 %)
103 −- **Statut (annulé/reporté/complet)** : 0/311 actifs (0 %)
104 −- **Artistes** : 279/311 actifs (90 %)
105 −- **Image** : 310/311 actifs (100 %)
106 −- **Salle (venue)** : 311/311 actifs (100 %)
107 −- **Dernier sync** : 2026-08-22 22:27 — fetched 311, +0 ajoutés, ~0 mis à jour, -0 retirés
108 −- **Syncs enregistrés** (sync_log) : 22
104 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
105 +
106 +- **Événements en base** : 461 (dont **459 actifs**)
107 +- **GPS** : 459/459 actifs (100 %)
108 +- **Prix** : 0/459 actifs (0 %)
109 +- **Heure (start_time)** : 458/459 actifs (100 %)
110 +- **Heure de fin** : 0/459 actifs (0 %)
111 +- **Statut (annulé/reporté/complet)** : 0/459 actifs (0 %)
112 +- **Artistes** : 414/459 actifs (90 %)
113 +- **Image** : 456/459 actifs (99 %)
114 +- **Salle (venue)** : 459/459 actifs (100 %)
115 +- **Dernier sync** : 2026-08-25 18:05 — fetched 459, +0 ajoutés, ~0 mis à jour, -0 retirés
116 +- **Syncs enregistrés** (sync_log) : 77
109 117
110 118 ## Erreurs & dépannage
111 119
modified docs/connecteurs/placedesarts.md +28 −28
@@ -1,6 +1,6 @@
1 1 # Place des Arts — calendrier multi-salles (`placedesarts`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `salle-de-spectacle` · Connecteur : `sortika/connectors/placedesarts.py`
6 6
@@ -44,30 +44,30 @@
44 44
45 45 ## Champs récupérés → schéma
46 46
47 −Complétude mesurée sur les **346 événements actifs** de la source ; exemples tirés d'un événement réel (`placedesarts:24-poses-portraits-2026-09-02-19:30`).
47 +Complétude mesurée sur les **344 événements actifs** de la source ; exemples tirés d'un événement réel (`placedesarts:hubris-une-histoire-des-annees-2010-2026-09-24-20:00`).
48 48
49 49 | Champ (`events.*`) | Description | Complétude | Exemple réel |
50 50 |---|---|---|---|
51 −| `title` — Titre | titre de l'événement | 100 % | 24 poses (portraits) |
51 +| `title` — Titre | titre de l'événement | 100 % | Hubris - Une histoire des années 2010 |
52 52 | `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
53 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.placedesarts.com/evenement/24-poses-portraits |
54 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["arts-scene"] |
55 −| `venue` — Salle (venue) | nom du lieu | 100 % | Théâtre Jean-Duceppe |
53 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.placedesarts.com/evenement/hubris-une-histoire-des-annees-2010 |
54 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
55 +| `venue` — Salle (venue) | nom du lieu | 100 % | Cinquième Salle |
56 56 | `address` — Adresse | adresse civique | 0 % | ∅ |
57 57 | `city` — Ville | municipalité | 100 % | Montréal |
58 58 | `region` — Région | région administrative | 100 % | Montréal |
59 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 94 % | 45.5084325, -73.5658186 |
60 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-02 |
61 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-02 |
62 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 19:30 |
59 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 94 % | 45.5079734, -73.567285 |
60 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-24 |
61 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-24 |
62 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:00 |
63 63 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 64 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
65 −| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 73 % | 0 |
66 −| `price_min` — Prix min | tarif minimal réel ($) | 73 % | 33.0 |
67 −| `price_label` — Libellé prix | étiquette tarifaire affichable | 73 % | 33 $ à 97,50 $ |
65 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 74 % | 0 |
66 +| `price_min` — Prix min | tarif minimal réel ($) | 74 % | 42.0 |
67 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 74 % | 42 $ |
68 68 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
69 −| `organizer` — Organisateur | promoteur / organisme | 100 % | Duceppe |
70 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://cdn.placedesarts.com/tranforms/_300x140_crop_center-center_none_ns/1657294/24POSES_DU252… |
69 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Festival international de la littérature |
70 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://cdn.placedesarts.com/tranforms/_300x140_crop_center-center_none_ns/1992545/Hubris_©Chris… |
71 71 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
72 72
73 73 ## Fréquence & budgets
@@ -82,19 +82,19 @@ Complétude mesurée sur les **346 événements actifs** de la source ; exemples
82 82
83 83 ## Volumétrie & complétude live
84 84
85 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
86 −
87 −- **Événements en base** : 356 (dont **346 actifs**)
88 −- **GPS** : 324/346 actifs (94 %)
89 −- **Prix** : 252/346 actifs (73 %)
90 −- **Heure (start_time)** : 345/346 actifs (100 %)
91 −- **Heure de fin** : 0/346 actifs (0 %)
92 −- **Statut (annulé/reporté/complet)** : 0/346 actifs (0 %)
93 −- **Artistes** : 0/346 actifs (0 %)
94 −- **Image** : 346/346 actifs (100 %)
95 −- **Salle (venue)** : 346/346 actifs (100 %)
96 −- **Dernier sync** : 2026-08-22 22:27 — fetched 354, +0 ajoutés, ~0 mis à jour, -0 retirés
97 −- **Syncs enregistrés** (sync_log) : 81
85 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
86 +
87 +- **Événements en base** : 358 (dont **344 actifs**)
88 +- **GPS** : 322/344 actifs (94 %)
89 +- **Prix** : 253/344 actifs (74 %)
90 +- **Heure (start_time)** : 343/344 actifs (100 %)
91 +- **Heure de fin** : 0/344 actifs (0 %)
92 +- **Statut (annulé/reporté/complet)** : 0/344 actifs (0 %)
93 +- **Artistes** : 0/344 actifs (0 %)
94 +- **Image** : 344/344 actifs (100 %)
95 +- **Salle (venue)** : 344/344 actifs (100 %)
96 +- **Dernier sync** : 2026-08-25 18:05 — fetched 356, +0 ajoutés, ~0 mis à jour, -0 retirés
97 +- **Syncs enregistrés** (sync_log) : 136
98 98
99 99 ## Erreurs & dépannage
100 100
modified docs/connecteurs/quebecanimee.md +31 −27
@@ -1,6 +1,6 @@
1 1 # Québec animée — Ville de Québec (`quebecanimee`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/quebecanimee.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Québec animée — Ville de Québec](https://animee.ville.quebec.qc.ca/activites/)
10 10 - **Couverture** : Activités gratuites/à faible coût, Ville de Québec (Capitale-Nationale)
11 −- **Extraction** : liste /activites/ (1 GET, attributs data-* : dates ISO, arrondissement, catégorie, prix) + fiches incrémentales (cache data/quebecanimee_cache.json, QA_FICHE_MAX/sync, re-visite 7 j) : og:description, lieu ; une carte par occurrence datée → une fiche par séance
11 +- **Extraction** : liste HTML rendue serveur + fiches détail incrémentales (QA_FICHE_MAX/sync, cache v2) : heure via boutons data-date/data-heure (62 %), salle réelle .lieu-nom (74 %, zéro bruit), adresse civique .lieu-adresse (72 %) pour le géocodage aval ; venue/heures des entrées v1 muselés (bruit « Lieux » du menu) en attendant le backfill
12 12
13 13 ## Accès
14 14
@@ -32,8 +32,12 @@
32 32 > data-date-debut / data-date-fin ISO, data-arrondissement,
33 33 > data-categorie, data-prix (« Gratuit ») + titre/image de la
34 34 > carte ; fiches détail incrémentales (cache
35 −> data/quebecanimee_cache.json, QA_FICHE_MAX/sync, re-visite 7 j) :
36 −> description, lieu (nom de salle/parc), heures si publiées.
35 +> data/quebecanimee_cache.json, QA_FICHE_MAX/sync, re-visite 7 j,
36 +> version de parse « v » — patron lavitrine) : description,
37 +> lieu réel (.lieu-nom) + adresse civique (.lieu-adresse) pour
38 +> le géocodage aval, et HEURES par représentation (attributs
39 +> data-date / data-heure des boutons « Ajouter à mon horaire »,
40 +> vérifié 2026-08-25) → start_time par occurrence datée.
37 41 > Accès : site municipal public rendu serveur — GET throttlés 0,8 s,
38 42 > identification honnête, lien vers la fiche officielle.
39 43
@@ -41,21 +45,21 @@
41 45
42 46 ## Champs récupérés → schéma
43 47
44 −Complétude mesurée sur les **102 événements actifs** de la source ; exemples tirés d'un événement réel (`quebecanimee:videoprojection-sur-la-bibliotheque-gabrielle-roy-avec-irina-gonzalez-2026-09-04`).
48 +Complétude mesurée sur les **95 événements actifs** de la source ; exemples tirés d'un événement réel (`quebecanimee:cinema-en-plein-air-dans-saint-roch-richelieu-2026-08-27`).
45 49
46 50 | Champ (`events.*`) | Description | Complétude | Exemple réel |
47 51 |---|---|---|---|
48 −| `title` — Titre | titre de l'événement | 100 % | Vidéoprojection sur la bibliothèque Gabrielle-Roy avec Irina González |
49 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | 4 septembre 2026, Place Jacques-Cartier |
50 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://animee.ville.quebec.qc.ca/activites/videoprojection-sur-la-bibliotheque-gabrielle-roy-av… |
51 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["conference", "exposition-musee"] |
52 −| `venue` — Salle (venue) | nom du lieu | 100 % | Lieux |
52 +| `title` — Titre | titre de l'événement | 100 % | Cinéma en plein air dans Saint-Roch : Richelieu |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | 27 août 2026, Parc des Pékans |
54 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://animee.ville.quebec.qc.ca/activites/cinema-en-plein-air-dans-saint-roch-richelieu/ |
55 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["cinema"] |
56 +| `venue` — Salle (venue) | nom du lieu | 74 % | Parc des Pékans |
53 57 | `address` — Adresse | adresse civique | 0 % | ∅ |
54 58 | `city` — Ville | municipalité | 100 % | Québec |
55 59 | `region` — Région | région administrative | 100 % | Capitale-Nationale |
56 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 48.4527492, -71.0736674 |
57 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-04 |
58 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-04 |
60 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 40 % | 46.8156314, -71.2245322 |
61 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-27 |
62 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-27 |
59 63 | `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
60 64 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
61 65 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
@@ -64,7 +68,7 @@ Complétude mesurée sur les **102 événements actifs** de la source ; exemples
64 68 | `price_label` — Libellé prix | étiquette tarifaire affichable | 100 % | Gratuit |
65 69 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 70 | `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Québec |
67 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://animee.ville.quebec.qc.ca/media/zpmo3k2f/lueursmemoire_irina_16x9-22.jpg?width=320&amp;h… |
71 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://animee.ville.quebec.qc.ca/media/24sjy1i3/cinema.jpg?width=320&amp;height=180&amp;v=1dc5d… |
68 72 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
69 73
70 74 ## Fréquence & budgets
@@ -79,19 +83,19 @@ Complétude mesurée sur les **102 événements actifs** de la source ; exemples
79 83
80 84 ## Volumétrie & complétude live
81 85
82 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
83 −
84 −- **Événements en base** : 131 (dont **102 actifs**)
85 −- **GPS** : 102/102 actifs (100 %)
86 −- **Prix** : 102/102 actifs (100 %)
87 −- **Heure (start_time)** : 0/102 actifs (0 %)
88 −- **Heure de fin** : 0/102 actifs (0 %)
89 −- **Statut (annulé/reporté/complet)** : 0/102 actifs (0 %)
90 −- **Artistes** : 0/102 actifs (0 %)
91 −- **Image** : 102/102 actifs (100 %)
92 −- **Salle (venue)** : 102/102 actifs (100 %)
93 −- **Dernier sync** : 2026-08-22 22:27 — fetched 102, +0 ajoutés, ~0 mis à jour, -0 retirés
94 −- **Syncs enregistrés** (sync_log) : 81
86 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
87 +
88 +- **Événements en base** : 136 (dont **95 actifs**)
89 +- **GPS** : 38/95 actifs (40 %)
90 +- **Prix** : 95/95 actifs (100 %)
91 +- **Heure (start_time)** : 0/95 actifs (0 %)
92 +- **Heure de fin** : 0/95 actifs (0 %)
93 +- **Statut (annulé/reporté/complet)** : 0/95 actifs (0 %)
94 +- **Artistes** : 0/95 actifs (0 %)
95 +- **Image** : 95/95 actifs (100 %)
96 +- **Salle (venue)** : 70/95 actifs (74 %)
97 +- **Dernier sync** : 2026-08-25 18:05 — fetched 95, +0 ajoutés, ~95 mis à jour, -0 retirés
98 +- **Syncs enregistrés** (sync_log) : 136
95 99
96 100 ## Erreurs & dépannage
97 101
added docs/connecteurs/quebeccite.md +114 −0
@@ -0,0 +1,114 @@
1 +# Québec cité — calendrier des événements (ATR) (`quebeccite`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/quebeccite.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Québec cité — calendrier des événements (ATR)](https://www.quebec-cite.com/fr/quoi-faire-quebec/evenements)
10 +- **Couverture** : Région de Québec (→ Capitale-Nationale)
11 +- **Extraction** : Index Algolia public du site (default_fr_event_date_start_asc, clé search-only du thème Drupal) : 1 requête/sync (event_date_end>=hier) → titre, résumé, salle, GPS, prix, gratuité, catégories, image, lien officiel ; dates à 12:00 UTC sur tout l'index = jour entier → aucune heure émise
12 +
13 +## Accès
14 +
15 +- **Type** : tourisme-regional
16 +- **Endpoint** (`BASE`) : `https://www.quebec-cite.com`
17 +- **Endpoint** (`ALGOLIA_URL`) : `https://WOZPDCK6IL-dsn.algolia.net/1/indexes/default_fr_event_date_start_asc/query`
18 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 +- **Pagination** : par numéro de page
20 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
21 +- **Throttle** : 0.8 s entre requêtes
22 +- **Accès légal** : Clé Algolia « search-only » publiée par le site lui-même — requêtes throttlées, UA honnête, lien vers la fiche originale
23 +
24 +<details><summary>En-tête documentaire du connecteur</summary>
25 +
26 +> connectors/quebeccite.py : Québec cité / Destination Québec cité (ATR,
27 +> vague ATR 2026-08)
28 +> Source : quebec-cite.com/fr/quoi-faire-quebec/evenements — calendrier
29 +> officiel de l'office de tourisme de la région de Québec
30 +> (Drupal, listing servi par le même index Algolia que le site).
31 +> Extraction: index Algolia « default_fr_event_date_start_asc » (appId/clé
32 +> de recherche publiques, embarquées dans le JS du thème —
33 +> vérifié 2026-08-25 : ~840 fiches avec titre, résumé, lieu
34 +> (event_location), GPS, prix texte (« À partir de 98,73 $ »),
35 +> gratuité event_free, dates start/end en timestamps Unix,
36 +> type/sujets/thèmes, image, lien externe officiel). 1-2
37 +> requêtes/sync (hitsPerPage=1000, filtre event_date_end >=
38 +> hier). Aucune fiche détail nécessaire.
39 +> Accès : clé Algolia « search-only » publiée par le site lui-même —
40 +> requêtes throttlées, identification honnête, lien vers la
41 +> fiche originale. is_free seulement si event_free=true ;
42 +> price_label = texte source (jamais inventé).
43 +
44 +</details>
45 +
46 +## Champs récupérés → schéma
47 +
48 +Complétude mesurée sur les **469 événements actifs** de la source ; exemples tirés d'un événement réel (`quebeccite:72091`).
49 +
50 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
51 +|---|---|---|---|
52 +| `title` — Titre | titre de l'événement | 100 % | Le Petit Roi pour toujours |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 97 % | Jean-Pierre Ferland, icône de la chanson québécoise, restera à jamais Le Petit Roi. |
54 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.quebec-cite.com/fr/quoi-faire-quebec/evenements/le-petit-roi-pour-toujours |
55 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["arts-scene"] |
56 +| `venue` — Salle (venue) | nom du lieu | 97 % | Grand Théâtre de Québec |
57 +| `address` — Adresse | adresse civique | 0 % | ∅ |
58 +| `city` — Ville | municipalité | 0 % | ∅ |
59 +| `region` — Région | région administrative | 100 % | Capitale-Nationale |
60 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 97 % | 46.806228, -71.221983 |
61 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-25 |
62 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-25 |
63 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
65 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
66 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 89 % | 0 |
67 +| `price_min` — Prix min | tarif minimal réel ($) | 77 % | 98.73 |
68 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 81 % | À partir de 98,73 $ |
69 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
70 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
71 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.quebec-cite.com/sites/otq/files/media/image/1140x746_LePetitRoi.max-1140x746.png |
72 +| `website` — Site web | site officiel de l'événement | 100 % | https://grandtheatre.qc.ca/programmation/le-petit-roi/ |
73 +
74 +## Fréquence & budgets
75 +
76 +- **Cadence** : quotidienne
77 +- **Budgets / plafonds (variables d'environnement)** :
78 +
79 +| Variable | Défaut | Rôle |
80 +|---|---|---|
81 +| `QBC_PAGE_MAX` | `5` | — |
82 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
83 +
84 +## Volumétrie & complétude live
85 +
86 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
87 +
88 +- **Événements en base** : 469 (dont **469 actifs**)
89 +- **GPS** : 456/469 actifs (97 %)
90 +- **Prix** : 421/469 actifs (90 %)
91 +- **Heure (start_time)** : 0/469 actifs (0 %)
92 +- **Heure de fin** : 0/469 actifs (0 %)
93 +- **Statut (annulé/reporté/complet)** : 0/469 actifs (0 %)
94 +- **Artistes** : 0/469 actifs (0 %)
95 +- **Image** : 469/469 actifs (100 %)
96 +- **Salle (venue)** : 457/469 actifs (97 %)
97 +- **Dernier sync** : 2026-08-25 18:24 — fetched 469, +469 ajoutés, ~0 mis à jour, -0 retirés
98 +- **Syncs enregistrés** (sync_log) : 1
99 +
100 +## Erreurs & dépannage
101 +
102 +Aucune erreur dans `sync_log` pour cette source. ✅
103 +
104 +- Rejouer un sync isolé : `python3 run.py sync --source quebeccite` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='quebeccite' ORDER BY ts DESC LIMIT 5;"`
105 +
106 +## Licence / attribution
107 +
108 +- Clé Algolia « search-only » publiée par le site lui-même — requêtes throttlées, UA honnête, lien vers la fiche originale
109 +
110 +## Historique
111 +
112 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
113 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
114 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/quoifaire.md +115 −0
@@ -0,0 +1,115 @@
1 +# Quoi faire — agrégateur de sorties québécois (`quoifaire`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `agregateur` · Connecteur : `sortika/connectors/quoifaire.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Quoi faire — agrégateur de sorties québécois](https://quoifaire.com/)
10 +- **Couverture** : centaines de fiches à venir (Québec, Montréal, Saguenay, Outaouais, Mauricie) — site lent, sections parfois 502 (tolérées)
11 +- **Extraction** : listes HTML rendues serveur /<ville>/evenements (+?page=N, 5 sections villes) puis fiches serveur : dates FR, heure (À confirmer→None), prix publié brut, région touristique, lieu/adresse, lat/lng Google Maps, auditoire, og:image ; incrémental cache 7 j, plafond QUOIFAIRE_DETAIL_MAX=150
12 +
13 +## Accès
14 +
15 +- **Type** : agregateur
16 +- **Endpoint** (`BASE`) : `https://quoifaire.com`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : par numéro de page, parcours de sitemap XML
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : robots.txt Allow: / (seuls /search et admin exclus, non visités), GET throttlés 0,8 s, identification honnête
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/quoifaire.py : connecteur Quoi faire (quoifaire.com) — agrégateur
26 +> Source : agrégateur de sorties québécois (sections villes : Québec,
27 +> Montréal, Saguenay, Outaouais, Mauricie — les autres sections
28 +> renvoyaient 502 à la sonde du 2026-08-25, ignorées proprement).
29 +> Extraction: listes HTML rendues serveur /<ville>/evenements (+ ?page=N)
30 +> → URLs de fiches À VENIR seulement (le sitemap remonte 15 ans
31 +> d'archives, on ne s'en sert pas). Fiches rendues serveur :
32 +> h1, résumé, plage de dates FR (next-date__date), prix publié
33 +> (next-date__price « min. X$ | max. Y$ »), heure (data-meta-time,
34 +> « À confirmer » → None), Région touristique / Lieu / adresse,
35 +> lat/lng du lien Google Maps, auditoire, catégorie, og:image.
36 +> INCRÉMENTAL avec cache disque (data/quoifaire_cache.json) :
37 +> nouvelles fiches d'abord, re-visite après 7 jours, plafonné à
38 +> QUOIFAIRE_DETAIL_MAX fiches/sync.
39 +> Accès : robots.txt « Allow: / » (seuls /search et l'admin sont exclus,
40 +> on n'y touche pas) ; GET throttlés 0,8 s, identification honnête.
41 +> Prix : price_label = texte publié (« min. 40.00$ | max. 150.00$ ») →
42 +> parse_price en aval (price_min = plus bas montant). Jamais inventé.
43 +
44 +</details>
45 +
46 +## Champs récupérés → schéma
47 +
48 +Complétude mesurée sur les **113 événements actifs** de la source ; exemples tirés d'un événement réel (`quoifaire:61e-coupe-vanier-greenshield`).
49 +
50 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
51 +|---|---|---|---|
52 +| `title` — Titre | titre de l'événement | 100 % | 61e Coupe Vanier GreenShield |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | La grande finale du football universitaire canadien est de retour à Québec! |
54 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://quoifaire.com/quebec/evenements/61e-coupe-vanier-greenshield |
55 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["sport"] |
56 +| `venue` — Salle (venue) | nom du lieu | 42 % | PEPS (Université Laval) |
57 +| `address` — Adresse | adresse civique | 42 % | 2300 Rue de la Terrasse, Saint-Thomas-d'Aquin, Cité-Universitaire, Sainte-Foy–Sillery–Cap-Rouge,… |
58 +| `city` — Ville | municipalité | 41 % | Québec |
59 +| `region` — Région | région administrative | 100 % | Capitale-Nationale |
60 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 46.784796, -71.276909 |
61 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-11-28 |
62 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-11-28 |
63 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 56 % | 15:00 |
64 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
65 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
66 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 100 % | 0 |
67 +| `price_min` — Prix min | tarif minimal réel ($) | 75 % | 20.0 |
68 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 100 % | min. 20.00$ \| max. 26.00$ |
69 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
70 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
71 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://quoifaire.tor1.digitaloceanspaces.com/prod/events/Xp3IjJQGTc7mgqU5TYcX1OiWuNlaqeJyJAtCe0… |
72 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
73 +
74 +## Fréquence & budgets
75 +
76 +- **Cadence** : hebdomadaire
77 +- **Budgets / plafonds (variables d'environnement)** :
78 +
79 +| Variable | Défaut | Rôle |
80 +|---|---|---|
81 +| `QUOIFAIRE_CITIES` | `quebec,montreal,saguenay,outaouais,mauricie` | — |
82 +| `QUOIFAIRE_DETAIL_MAX` | `150` | — |
83 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
84 +
85 +## Volumétrie & complétude live
86 +
87 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
88 +
89 +- **Événements en base** : 113 (dont **113 actifs**)
90 +- **GPS** : 113/113 actifs (100 %)
91 +- **Prix** : 113/113 actifs (100 %)
92 +- **Heure (start_time)** : 63/113 actifs (56 %)
93 +- **Heure de fin** : 0/113 actifs (0 %)
94 +- **Statut (annulé/reporté/complet)** : 0/113 actifs (0 %)
95 +- **Artistes** : 0/113 actifs (0 %)
96 +- **Image** : 113/113 actifs (100 %)
97 +- **Salle (venue)** : 47/113 actifs (42 %)
98 +- **Dernier sync** : 2026-08-25 18:52 — fetched 113, +113 ajoutés, ~0 mis à jour, -0 retirés
99 +- **Syncs enregistrés** (sync_log) : 1
100 +
101 +## Erreurs & dépannage
102 +
103 +Aucune erreur dans `sync_log` pour cette source. ✅
104 +
105 +- Rejouer un sync isolé : `python3 run.py sync --source quoifaire` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='quoifaire' ORDER BY ts DESC LIMIT 5;"`
106 +
107 +## Licence / attribution
108 +
109 +- robots.txt Allow: / (seuls /search et admin exclus, non visités), GET throttlés 0,8 s, identification honnête
110 +
111 +## Historique
112 +
113 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
114 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
115 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/repentigny.md +1 −1
@@ -1,6 +1,6 @@
1 1 # Ville de Repentigny — Événements (`repentigny`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : abandonnee** · Type : `donnees-ouvertes` · Connecteur : *aucun (source sans connecteur runtime)*
6 6
added docs/connecteurs/rimouski.md +101 −0
@@ -0,0 +1,101 @@
1 +# Ville de Rimouski — Événements (`rimouski`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/rimouski.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Rimouski — Événements](https://rimouski.ca/loisirs-culture/activites/evenements)
10 +- **Couverture** : Rimouski (Bas-Saint-Laurent)
11 +- **Extraction** : flux RSS étendu du site (1 requête, balises maison startDate/eventTime/location — salle et adresse séparées)
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`FEED`) : `https://rimouski.ca/rss/loisirs-culture/activites/evenements/`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : parcours de sitemap XML
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Accès légal** : flux RSS public du site municipal
21 +
22 +<details><summary>En-tête documentaire du connecteur</summary>
23 +
24 +> connectors/rimouski.py : connecteur Ville de Rimouski — événements officiels
25 +> Source : https://rimouski.ca/loisirs-culture/activites/evenements
26 +> Extraction: flux RSS ÉTENDU du calendrier (découvert sur la page) :
27 +> /rss/loisirs-culture/activites/evenements/ — ~240 items avec
28 +> balises maison <startDate> (ISO), <endDate>, <eventTime>
29 +> (« 13 h 30 »), <category>, <organizer>, <location>
30 +> (« Salle (adresse civique) »), <image>, <description>.
31 +> 1 seule requête. Vérifié 2026-08-25.
32 +> Accès : flux RSS public du site municipal, robots.txt permissif
33 +> (sitemap seulement). GET direct, identification honnête.
34 +> Prix : non publié par le flux → jamais inventé.
35 +
36 +</details>
37 +
38 +## Champs récupérés → schéma
39 +
40 +Complétude mesurée sur les **56 événements actifs** de la source ; exemples tirés d'un événement réel (`rimouski:les-midis-dete-au-bord-du-fleuve-copie-7-2026-08-27`).
41 +
42 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
43 +|---|---|---|---|
44 +| `title` — Titre | titre de l'événement | 100 % | ANNULÉ - Les midis d’été au bord du fleuve |
45 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Dernier rendez-vous de l'été.... Tous les jeudis de l’été, offrez-vous une pause au cœur de la j… |
46 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://rimouski.ca/loisirs-culture/activites/evenement/les-midis-dete-au-bord-du-fleuve-copie-7 |
47 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["arts-scene"] |
48 +| `venue` — Salle (venue) | nom du lieu | 100 % | Place des Anciens-Combattants |
49 +| `address` — Adresse | adresse civique | 82 % | ∅ |
50 +| `city` — Ville | municipalité | 100 % | Rimouski |
51 +| `region` — Région | région administrative | 100 % | Bas-Saint-Laurent |
52 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 96 % | 48.451077, -68.52938 |
53 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-27 |
54 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-27 |
55 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 89 % | 11:30 |
56 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
57 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
58 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
59 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
60 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
61 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
62 +| `organizer` — Organisateur | promoteur / organisme | 84 % | Ville de Rimouski |
63 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://rimouski.ca/storage/app/uploads/public/6a3/e98/b86/6a3e98b8639cb171905645.jpg |
64 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
65 +
66 +## Fréquence & budgets
67 +
68 +- **Cadence** : quotidien
69 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
70 +
71 +## Volumétrie & complétude live
72 +
73 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
74 +
75 +- **Événements en base** : 237 (dont **56 actifs**)
76 +- **GPS** : 54/56 actifs (96 %)
77 +- **Prix** : 0/56 actifs (0 %)
78 +- **Heure (start_time)** : 50/56 actifs (89 %)
79 +- **Heure de fin** : 0/56 actifs (0 %)
80 +- **Statut (annulé/reporté/complet)** : 0/56 actifs (0 %)
81 +- **Artistes** : 0/56 actifs (0 %)
82 +- **Image** : 56/56 actifs (100 %)
83 +- **Salle (venue)** : 56/56 actifs (100 %)
84 +- **Dernier sync** : 2026-08-25 18:28 — fetched 237, +237 ajoutés, ~0 mis à jour, -0 retirés
85 +- **Syncs enregistrés** (sync_log) : 1
86 +
87 +## Erreurs & dépannage
88 +
89 +Aucune erreur dans `sync_log` pour cette source. ✅
90 +
91 +- Rejouer un sync isolé : `python3 run.py sync --source rimouski` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='rimouski' ORDER BY ts DESC LIMIT 5;"`
92 +
93 +## Licence / attribution
94 +
95 +- flux RSS public du site municipal
96 +
97 +## Historique
98 +
99 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
100 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
101 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/rouynnoranda.md +111 −0
@@ -0,0 +1,111 @@
1 +# Ville de Rouyn-Noranda — Événements (`rouynnoranda`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/rouynnoranda.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Rouyn-Noranda — Événements](https://www.rouyn-noranda.ca/evenements)
10 +- **Couverture** : Rouyn-Noranda (Abitibi-Témiscamingue)
11 +- **Extraction** : HTML serveur OctoberCMS + fiches (heures/lieu) en cache ; dates publiées SANS année → résolution « prochaine occurrence » documentée
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`BASE`) : `https://www.rouyn-noranda.ca`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : par numéro de page
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : site municipal public, robots.txt permissif
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/rouynnoranda.py : connecteur Ville de Rouyn-Noranda — événements
26 +> Source : https://www.rouyn-noranda.ca/evenements (OctoberCMS, rendu
27 +> serveur — même famille que sorel-tracy/joliette).
28 +> Extraction: page liste /evenements (pagination /evenements/N → 404 à la
29 +> fin) → cartes « event-thumb » : titre, date FR SANS année
30 +> (« 26 août » — la source n'affiche que des événements à
31 +> venir : l'année est résolue à la prochaine occurrence,
32 +> aujourd'hui ou plus tard, jamais au passé), catégorie, image,
33 +> url fiche. Enrichissement par fiche /evenement/<slug>
34 +> (heures « de 19 h 30 à 22 h 30 », lieu, description) —
35 +> CACHE INCRÉMENTAL disque, plafond RN_FICHES par sync.
36 +> Accès : site municipal public, GET directs throttlés, identification
37 +> honnête ; robots.txt permissif.
38 +> Prix : non publié par la source → jamais inventé.
39 +
40 +</details>
41 +
42 +## Champs récupérés → schéma
43 +
44 +Complétude mesurée sur les **4 événements actifs** de la source ; exemples tirés d'un événement réel (`rouynnoranda:cine-dans-lparc-dans-une-galaxie-pres-de-chez-vous-2026-08-26`).
45 +
46 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
47 +|---|---|---|---|
48 +| `title` — Titre | titre de l'événement | 100 % | Ciné dans l'parc - Dans une galaxie près de chez vous |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.rouyn-noranda.ca/evenement/cine-dans-lparc-dans-une-galaxie-pres-de-chez-vous |
51 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
52 +| `venue` — Salle (venue) | nom du lieu | 100 % | Parc Chadbourne |
53 +| `address` — Adresse | adresse civique | 0 % | ∅ |
54 +| `city` — Ville | municipalité | 100 % | Rouyn-Noranda |
55 +| `region` — Région | région administrative | 100 % | Abitibi-Témiscamingue |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 25 % | 48.2470107, -79.0343573 |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-26 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-26 |
59 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 19:30 |
60 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 75 % | 22:30 |
61 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
62 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
63 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
64 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
65 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Rouyn-Noranda |
67 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.rouyn-noranda.ca/storage/app/media/_resized/32c/Galaxie-32ce865796df5bdb71cd8f1bc7eb… |
68 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
69 +
70 +## Fréquence & budgets
71 +
72 +- **Cadence** : quotidien
73 +- **Budgets / plafonds (variables d'environnement)** :
74 +
75 +| Variable | Défaut | Rôle |
76 +|---|---|---|
77 +| `RN_PAGES` | `8` | — |
78 +| `RN_FICHES` | `40` | — |
79 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
80 +
81 +## Volumétrie & complétude live
82 +
83 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
84 +
85 +- **Événements en base** : 4 (dont **4 actifs**)
86 +- **GPS** : 1/4 actifs (25 %)
87 +- **Prix** : 0/4 actifs (0 %)
88 +- **Heure (start_time)** : 4/4 actifs (100 %)
89 +- **Heure de fin** : 3/4 actifs (75 %)
90 +- **Statut (annulé/reporté/complet)** : 0/4 actifs (0 %)
91 +- **Artistes** : 0/4 actifs (0 %)
92 +- **Image** : 4/4 actifs (100 %)
93 +- **Salle (venue)** : 4/4 actifs (100 %)
94 +- **Dernier sync** : 2026-08-25 18:19 — fetched 4, +4 ajoutés, ~0 mis à jour, -0 retirés
95 +- **Syncs enregistrés** (sync_log) : 1
96 +
97 +## Erreurs & dépannage
98 +
99 +Aucune erreur dans `sync_log` pour cette source. ✅
100 +
101 +- Rejouer un sync isolé : `python3 run.py sync --source rouynnoranda` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='rouynnoranda' ORDER BY ts DESC LIMIT 5;"`
102 +
103 +## Licence / attribution
104 +
105 +- site municipal public, robots.txt permissif
106 +
107 +## Historique
108 +
109 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
110 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
111 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/saguenay.md +1 −1
@@ -1,6 +1,6 @@
1 1 # Ville de Saguenay — Événements culturels (`saguenay`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : abandonnee** · Type : `donnees-ouvertes` · Connecteur : *aucun (source sans connecteur runtime)*
6 6
added docs/connecteurs/saguenaylacsaintjean.md +46 −0
@@ -0,0 +1,46 @@
1 +# Tourisme Saguenay–Lac-Saint-Jean (ATR) (`saguenaylacsaintjean`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : abandonnee** · Type : `tourisme-regional` · Connecteur : *aucun (source sans connecteur runtime)*
6 +
7 +## Description
8 +
9 +- **Source** : [Tourisme Saguenay–Lac-Saint-Jean (ATR)](https://saguenaylacsaintjean.ca/quoi-faire)
10 +- **Couverture** : Saguenay–Lac-Saint-Jean
11 +- **Extraction** : n/d
12 +- **Note** : ÉCARTÉE 2026-08-25 (vague ATR) : site TYPO3 = annuaire d'attraits SANS calendrier — aucune date d'événement publiée (fiches festivals = horaires d'ouverture seulement, vérifié live + sitemap). La ville de Saguenay reste couverte par saguenaysorties. À re-sonder si l'ATR publie un agenda.
13 +
14 +## Accès
15 +
16 +- **Type** : tourisme-regional
17 +- **Endpoint** : voir URL source (https://saguenaylacsaintjean.ca/quoi-faire)
18 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 +- **Accès légal** : n/d
20 +
21 +## Champs récupérés → schéma
22 +
23 +*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
24 +
25 +## Fréquence & budgets
26 +
27 +- **Cadence** : n/d
28 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
29 +
30 +## Volumétrie & complétude live
31 +
32 +*Source sans connecteur runtime : aucune donnée en base.*
33 +
34 +## Erreurs & dépannage
35 +
36 +
37 +## Licence / attribution
38 +
39 +- n/d
40 +
41 +## Historique
42 +
43 +- **Source abandonnée** — ÉCARTÉE 2026-08-25 (vague ATR) : site TYPO3 = annuaire d'attraits SANS calendrier — aucune date d'événement publiée (fiches festivals = horaires d'ouverture seulement, vérifié live + sitemap). La ville de Saguenay reste couverte par saguenaysorties. À re-sonder si l'ATR publie un agenda.
44 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
45 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
46 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/saguenaysorties.md +15 −15
@@ -1,6 +1,6 @@
1 1 # Sorties Saguenay — portail événementiel de la Ville (`saguenaysorties`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/saguenaysorties.py`
6 6
@@ -41,21 +41,21 @@
41 41
42 42 ## Champs récupérés → schéma
43 43
44 −Complétude mesurée sur les **438 événements actifs** de la source ; exemples tirés d'un événement réel (`saguenaysorties:331172-2026-11-14-20:00`).
44 +Complétude mesurée sur les **438 événements actifs** de la source ; exemples tirés d'un événement réel (`saguenaysorties:381946-2026-11-20-20:00`).
45 45
46 46 | Champ (`events.*`) | Description | Complétude | Exemple réel |
47 47 |---|---|---|---|
48 −| `title` — Titre | titre de l'événement | 100 % | Pierre Lapointe - Chansons démodées pour ceux qui ont le cœur abîmé |
49 −| `description` — Description | texte descriptif (source ou fiche détail) | 98 % | Les chansons démodées pour ceux qui ont le coeur abîmé de Pierre Lapointe sur scène, un spectacl… |
50 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://sorties.saguenay.ca/festivals?id=331172 |
48 +| `title` — Titre | titre de l'événement | 100 % | The Franklin Electric - Victory Songs |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 98 % | Un nouveau chapitre s’ouvre pour The Franklin Electric avec la sortie de l’album « Victory Songs… |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://sorties.saguenay.ca/festivals?id=381946 |
51 51 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
52 −| `venue` — Salle (venue) | nom du lieu | 86 % | Théâtre C |
53 −| `address` — Adresse | adresse civique | 100 % | 534 Rue Jacques-Cartier E |
54 −| `city` — Ville | municipalité | 86 % | Chicoutimi |
52 +| `venue` — Salle (venue) | nom du lieu | 86 % | Théâtre Palace Arvida |
53 +| `address` — Adresse | adresse civique | 100 % | 1900 Bd Mellon |
54 +| `city` — Ville | municipalité | 86 % | Jonquière |
55 55 | `region` — Région | région administrative | 100 % | Saguenay–Lac-Saint-Jean |
56 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 80 % | 48.424452, -71.053231 |
57 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-11-14 |
58 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-11-14 |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 80 % | 48.4289712, -71.1822258 |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-11-20 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-11-20 |
59 59 | `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:00 |
60 60 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
61 61 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
@@ -64,7 +64,7 @@ Complétude mesurée sur les **438 événements actifs** de la source ; exemples
64 64 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
65 65 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 66 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
67 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://lvc-medias.lavitrine.com/343c0962-3759-5b36-9776-ba53a67bbcc6.jpg |
67 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://lvc-medias.lavitrine.com/8785171e-9c10-5e19-a776-a353f2c98fa6.jpg |
68 68 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
69 69
70 70 ## Fréquence & budgets
@@ -80,7 +80,7 @@ Complétude mesurée sur les **438 événements actifs** de la source ; exemples
80 80
81 81 ## Volumétrie & complétude live
82 82
83 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
83 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
84 84
85 85 - **Événements en base** : 460 (dont **438 actifs**)
86 86 - **GPS** : 351/438 actifs (80 %)
@@ -91,8 +91,8 @@ Complétude mesurée sur les **438 événements actifs** de la source ; exemples
91 91 - **Artistes** : 0/438 actifs (0 %)
92 92 - **Image** : 438/438 actifs (100 %)
93 93 - **Salle (venue)** : 377/438 actifs (86 %)
94 −- **Dernier sync** : 2026-08-22 22:28 — fetched 438, +0 ajoutés, ~0 mis à jour, -0 retirés
95 −- **Syncs enregistrés** (sync_log) : 81
94 +- **Dernier sync** : 2026-08-25 18:06 — fetched 438, +0 ajoutés, ~0 mis à jour, -0 retirés
95 +- **Syncs enregistrés** (sync_log) : 136
96 96
97 97 ## Erreurs & dépannage
98 98
added docs/connecteurs/sainthyacinthe.md +112 −0
@@ -0,0 +1,112 @@
1 +# Ville de Saint-Hyacinthe — Calendrier des événements (`sainthyacinthe`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/sainthyacinthe.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Saint-Hyacinthe — Calendrier des événements](https://www.st-hyacinthe.ca/loisirs-et-culture/evenement/calendrier-des-evenements)
10 +- **Couverture** : Saint-Hyacinthe (Montérégie)
11 +- **Extraction** : HTML serveur (?ms=2 « tous les mois », 1 requête) + fiches modales /php/load-modal-info.php en cache incrémental (STH_FICHES=60) : lieu, clientèle, organisateur, gratuité
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`BASE`) : `https://www.st-hyacinthe.ca`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : site municipal public, GET throttlés, identification honnête
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/sainthyacinthe.py : connecteur Ville de Saint-Hyacinthe —
26 +> calendrier des événements (généré par les organismes accrédités).
27 +> Source : https://www.st-hyacinthe.ca/loisirs-et-culture/evenement/
28 +> calendrier-des-evenements (CMS municipal maison, rendu
29 +> serveur Bootstrap).
30 +> Extraction: GET ?ms=2 (« Tous » les mois, découvert dans le formulaire de
31 +> recherche) → toutes les cartes en 1 requête : id + titre
32 +> (fShowInfo), plage de dates FR, image, organisateur,
33 +> catégorie. Enrichissement par fiche modale
34 +> /php/load-modal-info.php?evenement=<id> (lieu, clientèle,
35 +> gratuit/payant, site web) — CACHE INCRÉMENTAL disque : seules
36 +> les fiches jamais vues sont requêtées (plafond STH_FICHES).
37 +> Accès : site municipal public, GET directs throttlés, identification
38 +> honnête ; robots.txt ne restreint pas ces chemins.
39 +> Prix : la fiche publie « Gratuit »/« Payant » → is_free fidèle,
40 +> jamais de montant inventé.
41 +
42 +</details>
43 +
44 +## Champs récupérés → schéma
45 +
46 +Complétude mesurée sur les **17 événements actifs** de la source ; exemples tirés d'un événement réel (`sainthyacinthe:944-2026-07-01`).
47 +
48 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
49 +|---|---|---|---|
50 +| `title` — Titre | titre de l'événement | 100 % | L’écoresponsabilité pour faire partie de la solution |
51 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
52 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.st-hyacinthe.ca/loisirs-et-culture/evenement/calendrier-des-evenements?ms=2 |
53 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
54 +| `venue` — Salle (venue) | nom du lieu | 94 % | Jardin Daniel A Séguin |
55 +| `address` — Adresse | adresse civique | 0 % | ∅ |
56 +| `city` — Ville | municipalité | 100 % | Saint-Hyacinthe |
57 +| `region` — Région | région administrative | 100 % | Montérégie |
58 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 41 % | 45.6174682, -72.9650752 |
59 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-07-01 |
60 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-30 |
61 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
62 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
63 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
64 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 100 % | 0 |
65 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
66 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 100 % | Payant |
67 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
68 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Jardin Daniel A. Séguin |
69 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.st-hyacinthe.ca/medias/organismes/uploads/bc4c2517bd34aeac8bab98194d96988c.png |
70 +| `website` — Site web | site officiel de l'événement | 76 % | https://jardindas.ca/ |
71 +
72 +## Fréquence & budgets
73 +
74 +- **Cadence** : quotidien
75 +- **Budgets / plafonds (variables d'environnement)** :
76 +
77 +| Variable | Défaut | Rôle |
78 +|---|---|---|
79 +| `STH_FICHES` | `60` | — |
80 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
81 +
82 +## Volumétrie & complétude live
83 +
84 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
85 +
86 +- **Événements en base** : 28 (dont **17 actifs**)
87 +- **GPS** : 7/17 actifs (41 %)
88 +- **Prix** : 17/17 actifs (100 %)
89 +- **Heure (start_time)** : 0/17 actifs (0 %)
90 +- **Heure de fin** : 0/17 actifs (0 %)
91 +- **Statut (annulé/reporté/complet)** : 0/17 actifs (0 %)
92 +- **Artistes** : 0/17 actifs (0 %)
93 +- **Image** : 17/17 actifs (100 %)
94 +- **Salle (venue)** : 16/17 actifs (94 %)
95 +- **Dernier sync** : 2026-08-25 18:29 — fetched 28, +28 ajoutés, ~0 mis à jour, -0 retirés
96 +- **Syncs enregistrés** (sync_log) : 1
97 +
98 +## Erreurs & dépannage
99 +
100 +Aucune erreur dans `sync_log` pour cette source. ✅
101 +
102 +- Rejouer un sync isolé : `python3 run.py sync --source sainthyacinthe` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='sainthyacinthe' ORDER BY ts DESC LIMIT 5;"`
103 +
104 +## Licence / attribution
105 +
106 +- site municipal public, GET throttlés, identification honnête
107 +
108 +## Historique
109 +
110 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
111 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
112 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/saintjerome.md +108 −0
@@ -0,0 +1,108 @@
1 +# Ville de Saint-Jérôme — Calendrier (`saintjerome`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/saintjerome.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Saint-Jérôme — Calendrier](https://www.vsj.ca/calendrier/)
10 +- **Couverture** : Saint-Jérôme (Laurentides)
11 +- **Extraction** : HTML serveur FacetWP (?fwp_calendar_dates=<aujourd'hui>&fwp_paged=N, 10 cartes/page) — date, heure, lieu+adresse, description, image
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`LIST_URL`) : `https://www.vsj.ca/calendrier/`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : site municipal public, robots.txt permissif
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/saintjerome.py : connecteur Ville de Saint-Jérôme — calendrier
26 +> Source : https://www.vsj.ca/calendrier/ (WordPress + FacetWP).
27 +> Extraction: le listing FacetWP se pagine CÔTÉ SERVEUR par paramètres
28 +> d'URL (vérifié 2026-08-25) : GET /calendrier/?fwp_calendar_
29 +> dates=<aujourd'hui>&fwp_paged=N → cartes HTML rendues
30 +> (10/page) : date FR, heure « 19 h », lieu, titre, url fiche,
31 +> extrait, image. On pagine jusqu'à épuisement (plafond
32 +> VSJ_PAGES). Le flux RSS /calendrier/feed/ est périmé (2019)
33 +> et l'API REST wp/v2/event ne publie pas les dates.
34 +> Accès : site municipal public, robots.txt permissif (wp-admin
35 +> seulement). GET directs throttlés, identification honnête.
36 +> Prix : non publié dans les cartes → jamais inventé.
37 +
38 +</details>
39 +
40 +## Champs récupérés → schéma
41 +
42 +Complétude mesurée sur les **28 événements actifs** de la source ; exemples tirés d'un événement réel (`saintjerome:seance-ordinaire-du-conseil-municipal-68-2026-08-25`).
43 +
44 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
45 +|---|---|---|---|
46 +| `title` — Titre | titre de l'événement | 100 % | Séance ordinaire du conseil municipal |
47 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Séance ordinaire du conseil municipal Les assemblées se déroulent à l’hôtel de ville, au 300, ru… |
48 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.vsj.ca/calendrier/seance-ordinaire-du-conseil-municipal-68/ |
49 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["communautaire"] |
50 +| `venue` — Salle (venue) | nom du lieu | 100 % | Hôtel de ville |
51 +| `address` — Adresse | adresse civique | 68 % | 300, rue Parent |
52 +| `city` — Ville | municipalité | 100 % | Saint-Jérôme |
53 +| `region` — Région | région administrative | 100 % | Laurentides |
54 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 71 % | 45.7764855, -73.9937696 |
55 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
56 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
57 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 86 % | 19:00 |
58 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
59 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
60 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
61 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
62 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
63 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
64 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Saint-Jérôme |
65 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.vsj.ca/wp-content/uploads/2026/01/seance_cm-V2016-01-765x495.jpg |
66 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
67 +
68 +## Fréquence & budgets
69 +
70 +- **Cadence** : quotidien
71 +- **Budgets / plafonds (variables d'environnement)** :
72 +
73 +| Variable | Défaut | Rôle |
74 +|---|---|---|
75 +| `VSJ_PAGES` | `12` | — |
76 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
77 +
78 +## Volumétrie & complétude live
79 +
80 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
81 +
82 +- **Événements en base** : 28 (dont **28 actifs**)
83 +- **GPS** : 20/28 actifs (71 %)
84 +- **Prix** : 0/28 actifs (0 %)
85 +- **Heure (start_time)** : 24/28 actifs (86 %)
86 +- **Heure de fin** : 0/28 actifs (0 %)
87 +- **Statut (annulé/reporté/complet)** : 0/28 actifs (0 %)
88 +- **Artistes** : 0/28 actifs (0 %)
89 +- **Image** : 28/28 actifs (100 %)
90 +- **Salle (venue)** : 28/28 actifs (100 %)
91 +- **Dernier sync** : 2026-08-25 18:29 — fetched 28, +28 ajoutés, ~0 mis à jour, -0 retirés
92 +- **Syncs enregistrés** (sync_log) : 1
93 +
94 +## Erreurs & dépannage
95 +
96 +Aucune erreur dans `sync_log` pour cette source. ✅
97 +
98 +- Rejouer un sync isolé : `python3 run.py sync --source saintjerome` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='saintjerome' ORDER BY ts DESC LIMIT 5;"`
99 +
100 +## Licence / attribution
101 +
102 +- site municipal public, robots.txt permissif
103 +
104 +## Historique
105 +
106 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
107 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
108 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/sallesjsonld.md +34 −34
@@ -1,6 +1,6 @@
1 1 # Salles de spectacle — moteur générique JSON-LD Event (`sallesjsonld`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `salle-de-spectacle` · Connecteur : `sortika/connectors/sallesjsonld.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Salles de spectacle — moteur générique JSON-LD Event](https://grandtheatre.qc.ca/programmation)
10 10 - **Couverture** : Longue traîne des salles de spectacle QC (amorce : Grand Théâtre de Québec, Trident, Capitole — Capitale-Nationale)
11 −- **Extraction** : par salle configurée : page(s) de programmation → liens de fiche (motif regex) → fiches incrémentales (cache data/sallesjsonld_cache.json, SALLES_FICHE_MAX/sync partagé, re-visite 7 j) → blocs JSON-LD @type Event (nom, dates horodatées, salle, adresse, offres → prix, statut, image) ; heure « T00:00:00 » traitée comme placeholder
11 +- **Extraction** : par salle configurée : page(s) de programmation → liens de fiche (motif regex) → fiches incrémentales (cache data/sallesjsonld_cache.json, SALLES_FICHE_MAX/sync partagé, re-visite 7 j) → blocs JSON-LD @type Event (nom, dates horodatées, salle, adresse, offres → prix, statut, image) ; heure « T00:00:00 » traitée comme placeholder. Vague 2 2026-08-25 : 17 salles configurées (+13 validées live : Théâtre Denise-Pelletier, MBAM, MNBAQ, Musée de la civilisation, Pointe-à-Callière, Cinémathèque québécoise, TOHU, Spect'Art Rimouski, Hector-Charland, Amphithéâtre Cogeco, Premier Acte, Maison de la littérature, Chasse-galerie Lavaltrie) ; liste du Trident réparée (page d'accueil) ; ~40 salles sondées et écartées (sans JSON-LD, heures faussées ou SSR intermittent — consignées dans le _comment de la config)
12 12
13 13 ## Accès
14 14
@@ -43,31 +43,31 @@
43 43
44 44 ## Champs récupérés → schéma
45 45
46 −Complétude mesurée sur les **308 événements actifs** de la source ; exemples tirés d'un événement réel (`sallesjsonld:odyscene-pierre-verville-en-rodage-2026-08-27-2000`).
46 +Complétude mesurée sur les **685 événements actifs** de la source ; exemples tirés d'un événement réel (`sallesjsonld:mnbaq-art-moderne-2026-10-05-2000`).
47 47
48 48 | Champ (`events.*`) | Description | Complétude | Exemple réel |
49 49 |---|---|---|---|
50 −| `title` — Titre | titre de l'événement | 100 % | Pierre Verville |
51 −| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | En rodage Plongez dans l’univers unique de Pierre Verville avec Pour une dernière voix, un spect… |
52 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://odyscene.com/programmation/pierre-verville-en-rodage/ |
50 +| `title` — Titre | titre de l'événement | 100 % | Revendiquer. Art moderne du Québec |
51 +| `description` — Description | texte descriptif (source ou fiche détail) | 94 % | ∅ |
52 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.mnbaq.org/programmation/expositions/art-moderne |
53 53 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
54 −| `venue` — Salle (venue) | nom du lieu | 100 % | Théâtre Lionel-Groulx |
55 −| `address` — Adresse | adresse civique | 96 % | ∅ |
56 −| `city` — Ville | municipalité | 100 % | Sainte-Thérèse |
57 −| `region` — Région | région administrative | 100 % | Laurentides |
58 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 45.642259, -73.842239 |
59 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-27 |
60 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-28 |
61 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:00 |
62 −| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 53 % | 22:00 |
63 −| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 1 % | ∅ |
64 −| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 97 % | 0 |
65 −| `price_min` — Prix min | tarif minimal réel ($) | 97 % | 37.0 |
66 −| `price_label` — Libellé prix | étiquette tarifaire affichable | 97 % | 37 $ à 42 $ |
54 +| `venue` — Salle (venue) | nom du lieu | 100 % | Musée national des beaux-arts du Québec |
55 +| `address` — Adresse | adresse civique | 73 % | ∅ |
56 +| `city` — Ville | municipalité | 100 % | Québec |
57 +| `region` — Région | région administrative | 100 % | Capitale-Nationale |
58 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 98 % | 46.7995475, -71.2247544 |
59 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-10-05 |
60 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-10-06 |
61 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 90 % | 20:00 |
62 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 57 % | 19:59 |
63 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
64 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 87 % | 0 |
65 +| `price_min` — Prix min | tarif minimal réel ($) | 86 % | ∅ |
66 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 87 % | Museum admission required $ |
67 67 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
68 68 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
69 −| `image` — Image | visuel (og:image ou média source) | 13 % | https://odyscene.com/wp-content/uploads/tuxedo/pierre20verville-onsp-ctxfe7h44jyexi.png |
70 −| `website` — Site web | site officiel de l'événement | 100 % | https://odyscene.com/programmation/pierre-verville-en-rodage/ |
69 +| `image` — Image | visuel (og:image ou média source) | 53 % | https://s3.ca-central-1.amazonaws.com/mnbaq-assets/production/Programmation/Expositions/Art-mode… |
70 +| `website` — Site web | site officiel de l'événement | 100 % | https://www.mnbaq.org/programmation/expositions/art-moderne |
71 71
72 72 ## Fréquence & budgets
73 73
@@ -81,19 +81,19 @@ Complétude mesurée sur les **308 événements actifs** de la source ; exemples
81 81
82 82 ## Volumétrie & complétude live
83 83
84 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
85 −
86 −- **Événements en base** : 315 (dont **308 actifs**)
87 −- **GPS** : 308/308 actifs (100 %)
88 −- **Prix** : 300/308 actifs (97 %)
89 −- **Heure (start_time)** : 308/308 actifs (100 %)
90 −- **Heure de fin** : 164/308 actifs (53 %)
91 −- **Statut (annulé/reporté/complet)** : 2/308 actifs (1 %)
92 −- **Artistes** : 0/308 actifs (0 %)
93 −- **Image** : 41/308 actifs (13 %)
94 −- **Salle (venue)** : 308/308 actifs (100 %)
95 −- **Dernier sync** : 2026-08-22 22:59 — fetched 309, +33 ajoutés, ~0 mis à jour, -0 retirés
96 −- **Syncs enregistrés** (sync_log) : 83
84 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
85 +
86 +- **Événements en base** : 775 (dont **685 actifs**)
87 +- **GPS** : 670/685 actifs (98 %)
88 +- **Prix** : 596/685 actifs (87 %)
89 +- **Heure (start_time)** : 615/685 actifs (90 %)
90 +- **Heure de fin** : 388/685 actifs (57 %)
91 +- **Statut (annulé/reporté/complet)** : 1/685 actifs (0 %)
92 +- **Artistes** : 0/685 actifs (0 %)
93 +- **Image** : 363/685 actifs (53 %)
94 +- **Salle (venue)** : 685/685 actifs (100 %)
95 +- **Dernier sync** : 2026-08-25 18:53 — fetched 768, +295 ajoutés, ~0 mis à jour, -0 retirés
96 +- **Syncs enregistrés** (sync_log) : 139
97 97
98 98 ## Erreurs & dépannage
99 99
added docs/connecteurs/shawinigan.md +109 −0
@@ -0,0 +1,109 @@
1 +# Ville de Shawinigan — Calendrier des événements (`shawinigan`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/shawinigan.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Shawinigan — Calendrier des événements](https://www.shawinigan.ca/loisirs-et-culture/evenements/calendrier/)
10 +- **Couverture** : Shawinigan (Mauricie)
11 +- **Extraction** : HTML serveur (plugin tri-evenements, cartes -single et -list, date fiable dans l'URL) + fiches (date de fin, lieu) en cache
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`LIST_URL`) : `https://www.shawinigan.ca/loisirs-et-culture/evenements/calendrier/`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : par numéro de page
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : site municipal public, robots.txt permissif
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/shawinigan.py : connecteur Ville de Shawinigan — événements
26 +> Source : https://www.shawinigan.ca/loisirs-et-culture/evenements/
27 +> calendrier/ (WordPress + plugin maison « tri-evenements »).
28 +> Extraction: archive rendue serveur (cartes « tri-evenement-single » :
29 +> date FR, titre, extrait, image, url fiche datée
30 +> /evenements/AAAA/MM/JJ/<slug>/), pagination /calendrier/
31 +> page/N. Enrichissement par fiche (date de fin « Date de
32 +> fin : ... », lieu « Lieu : ... », heure si publiée) — CACHE
33 +> INCRÉMENTAL disque, plafond SHA_FICHES par sync.
34 +> Accès : site municipal public, robots.txt permissif (wp-admin
35 +> seulement). GET directs throttlés, identification honnête.
36 +> Prix : non publié par la source → jamais inventé.
37 +
38 +</details>
39 +
40 +## Champs récupérés → schéma
41 +
42 +Complétude mesurée sur les **7 événements actifs** de la source ; exemples tirés d'un événement réel (`shawinigan:2026-09-25-maison-des-memoires`).
43 +
44 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
45 +|---|---|---|---|
46 +| `title` — Titre | titre de l'événement | 100 % | Maison des mémoires |
47 +| `description` — Description | texte descriptif (source ou fiche détail) | 14 % | ∅ |
48 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.shawinigan.ca/loisirs-et-culture/evenements/2026/09/25/maison-des-memoires/ |
49 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
50 +| `venue` — Salle (venue) | nom du lieu | 100 % | Centre des arts de Shawinigan |
51 +| `address` — Adresse | adresse civique | 0 % | ∅ |
52 +| `city` — Ville | municipalité | 100 % | Shawinigan |
53 +| `region` — Région | région administrative | 100 % | Mauricie |
54 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 14 % | 46.559367, -72.748386 |
55 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-25 |
56 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
57 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
58 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
59 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
60 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
61 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
62 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
63 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
64 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Shawinigan |
65 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.shawinigan.ca/wp-content/uploads/2026/08/Maison-de-memoire_Culture-Shawinigan-400x25… |
66 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
67 +
68 +## Fréquence & budgets
69 +
70 +- **Cadence** : quotidien
71 +- **Budgets / plafonds (variables d'environnement)** :
72 +
73 +| Variable | Défaut | Rôle |
74 +|---|---|---|
75 +| `SHA_PAGES` | `6` | — |
76 +| `SHA_FICHES` | `40` | — |
77 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
78 +
79 +## Volumétrie & complétude live
80 +
81 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
82 +
83 +- **Événements en base** : 7 (dont **7 actifs**)
84 +- **GPS** : 1/7 actifs (14 %)
85 +- **Prix** : 0/7 actifs (0 %)
86 +- **Heure (start_time)** : 0/7 actifs (0 %)
87 +- **Heure de fin** : 0/7 actifs (0 %)
88 +- **Statut (annulé/reporté/complet)** : 0/7 actifs (0 %)
89 +- **Artistes** : 0/7 actifs (0 %)
90 +- **Image** : 7/7 actifs (100 %)
91 +- **Salle (venue)** : 7/7 actifs (100 %)
92 +- **Dernier sync** : 2026-08-25 18:19 — fetched 7, +7 ajoutés, ~0 mis à jour, -0 retirés
93 +- **Syncs enregistrés** (sync_log) : 1
94 +
95 +## Erreurs & dépannage
96 +
97 +Aucune erreur dans `sync_log` pour cette source. ✅
98 +
99 +- Rejouer un sync isolé : `python3 run.py sync --source shawinigan` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='shawinigan' ORDER BY ts DESC LIMIT 5;"`
100 +
101 +## Licence / attribution
102 +
103 +- site municipal public, robots.txt permissif
104 +
105 +## Historique
106 +
107 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
108 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
109 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/sherbrooke.md +24 −24
@@ -1,6 +1,6 @@
1 1 # Ville de Sherbrooke — Calendrier des événements (`sherbrooke`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/sherbrooke.py`
6 6
@@ -36,30 +36,30 @@
36 36
37 37 ## Champs récupérés → schéma
38 38
39 −Complétude mesurée sur les **109 événements actifs** de la source ; exemples tirés d'un événement réel (`sherbrooke:1610`).
39 +Complétude mesurée sur les **141 événements actifs** de la source ; exemples tirés d'un événement réel (`sherbrooke:2395`).
40 40
41 41 | Champ (`events.*`) | Description | Complétude | Exemple réel |
42 42 |---|---|---|---|
43 −| `title` — Titre | titre de l'événement | 100 % | Festival de Danse Contemporaine de Sherbrooke |
44 −| `description` — Description | texte descriptif (source ou fiche détail) | 98 % | Le Festival de Danse Contemporaine de Sherbrooke (FDCS) est un événement récurrent annuel, dans … |
45 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.sherbrooke.ca/fr/culture-sports-et-loisirs/calendrier-des-activites/1610/festival-de… |
46 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["festival", "arts-scene"] |
43 +| `title` — Titre | titre de l'événement | 100 % | L'éveil à la lecture - Bibliothèque Bertrand-Delisle |
44 +| `description` — Description | texte descriptif (source ou fiche détail) | 99 % | Cette activité offre un premier contact avec l'univers de la littérature enfantine et vise à sti… |
45 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.sherbrooke.ca/fr/culture-sports-et-loisirs/calendrier-des-activites/2395/l-eveil-a-l… |
46 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
47 47 | `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
48 48 | `address` — Adresse | adresse civique | 0 % | ∅ |
49 49 | `city` — Ville | municipalité | 100 % | Sherbrooke |
50 50 | `region` — Région | région administrative | 100 % | Estrie |
51 51 | `lat` — GPS (lat/lng) | coordonnées géographiques | 0 % | ∅ |
52 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-20 |
53 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
54 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 85 % | 19:00 |
55 −| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 92 % | 21:00 |
52 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-30 |
53 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-10-07 |
54 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 87 % | 10:30 |
55 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 94 % | 11:15 |
56 56 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
57 57 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 100 % | 1 |
58 58 | `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
59 59 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
60 60 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
61 61 | `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Sherbrooke |
62 −| `image` — Image | visuel (og:image ou média source) | 99 % | https://www.sherbrooke.ca/Fichiers/3337a882-4a53-e611-80ea-00155d09650f/Entities/b34b6e54-1f7d-4… |
62 +| `image` — Image | visuel (og:image ou média source) | 99 % | https://www.sherbrooke.ca/Fichiers/3337a882-4a53-e611-80ea-00155d09650f/Entities/9db312c2-d24b-4… |
63 63 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
64 64
65 65 ## Fréquence & budgets
@@ -69,19 +69,19 @@ Complétude mesurée sur les **109 événements actifs** de la source ; exemples
69 69
70 70 ## Volumétrie & complétude live
71 71
72 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
73 −
74 −- **Événements en base** : 275 (dont **109 actifs**)
75 −- **GPS** : 0/109 actifs (0 %)
76 −- **Prix** : 109/109 actifs (100 %)
77 −- **Heure (start_time)** : 93/109 actifs (85 %)
78 −- **Heure de fin** : 100/109 actifs (92 %)
79 −- **Statut (annulé/reporté/complet)** : 0/109 actifs (0 %)
80 −- **Artistes** : 0/109 actifs (0 %)
81 −- **Image** : 108/109 actifs (99 %)
82 −- **Salle (venue)** : 0/109 actifs (0 %)
83 −- **Dernier sync** : 2026-08-22 22:28 — fetched 274, +0 ajoutés, ~0 mis à jour, -0 retirés
84 −- **Syncs enregistrés** (sync_log) : 116
72 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
73 +
74 +- **Événements en base** : 308 (dont **141 actifs**)
75 +- **GPS** : 0/141 actifs (0 %)
76 +- **Prix** : 141/141 actifs (100 %)
77 +- **Heure (start_time)** : 123/141 actifs (87 %)
78 +- **Heure de fin** : 132/141 actifs (94 %)
79 +- **Statut (annulé/reporté/complet)** : 0/141 actifs (0 %)
80 +- **Artistes** : 0/141 actifs (0 %)
81 +- **Image** : 140/141 actifs (99 %)
82 +- **Salle (venue)** : 0/141 actifs (0 %)
83 +- **Dernier sync** : 2026-08-25 18:06 — fetched 305, +0 ajoutés, ~0 mis à jour, -0 retirés
84 +- **Syncs enregistrés** (sync_log) : 171
85 85
86 86 ## Erreurs & dépannage
87 87
modified docs/connecteurs/showpass.md +77 −11
@@ -1,45 +1,111 @@
1 1 # Showpass — billetterie canadienne (`showpass`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 −**Statut : abandonnee** · Type : `billetterie` · Connecteur : *aucun (source sans connecteur runtime)*
5 +**Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/showpass.py`
6 6
7 7 ## Description
8 8
9 9 - **Source** : [Showpass — billetterie canadienne](https://www.showpass.com/)
10 −- **Couverture** : 13 663 événements canadiens, très majoritairement Alberta/C.-B.
11 −- **Extraction** : —
10 +- **Couverture** : ~30-40 événements QC actifs (soirées, croisières, festivals, Candlelight) — toutes régions balayées
11 +- **Extraction** : API JSON publique /api/public/events/ paginée, couverture du Québec par 10 points géo (location__point_location=lat,lng,rayon + starts_on__gte), filtre province=QC côté connecteur ; heure locale via local_starts_on ; tarifs ticket_types[].price. RÉACTIVÉE 2026-08-25 (l'écartement « aucun filtre géo » est invalidé — le paramètre fonctionne)
12 12
13 13 ## Accès
14 14
15 15 - **Type** : billetterie
16 −- **Endpoint** : voir URL source (https://www.showpass.com/)
16 +- **Endpoint** (`BASE`) : `https://www.showpass.com`
17 17 - **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 −- **Accès légal** : API publique explicitement autorisée par robots.txt (Allow: /api/)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : robots.txt Allow: /api/ (API publique explicitement autorisée), GET throttlés 0,8 s, identification honnête
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/showpass.py : connecteur Showpass (showpass.com) — billetterie
26 +> Source : billetterie canadienne (surtout AB/C.-B., mais des dizaines
27 +> d'événements QC : soirées, visites, festivals, Candlelight…).
28 +> Extraction: API JSON publique /api/public/events/ (paginée), filtrée par
29 +> points géographiques QC via location__point_location=
30 +> LAT,LNG,RAYON + starts_on__gte=aujourd'hui. Chaque événement
31 +> porte location.province → on ne garde QUE le Québec (le rayon
32 +> autour de Gatineau attrape Ottawa, vérifié live 2026-08-25).
33 +> Accès : robots.txt « Allow: /api/ » (API publique explicitement
34 +> autorisée) ; GET throttlés 0,8 s, identification honnête.
35 +> Prix : ticket_types[].price publiés par la billetterie → price_min
36 +> (plus bas tarif payant), is_free=True si TOUS les tarifs à 0 $.
37 +> Heure : local_starts_on (fuseau de l'événement) prioritaire, sinon
38 +> starts_on UTC converti heure de Montréal (utc_to_local).
39 +
40 +</details>
19 41
20 42 ## Champs récupérés → schéma
21 43
22 −*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
44 +Complétude mesurée sur les **45 événements actifs** de la source ; exemples tirés d'un événement réel (`showpass:1520335`).
45 +
46 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
47 +|---|---|---|---|
48 +| `title` — Titre | titre de l'événement | 100 % | Basia Bulat |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 64 % | Basia Bulat is a Montreal-based singer-songwriter whose work bridges gospel, soul, and classic f… |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.showpass.com/basia-bulat-3/ |
51 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
52 +| `venue` — Salle (venue) | nom du lieu | 100 % | Motel Chelsea |
53 +| `address` — Adresse | adresse civique | 100 % | 1418 Rte 105 |
54 +| `city` — Ville | municipalité | 100 % | Chelsea |
55 +| `region` — Région | région administrative | 100 % | Outaouais |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 45.574554, -75.87113 |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-10-16 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-10-16 |
59 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:00 |
60 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 89 % | 22:00 |
61 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 2 % | soldout |
62 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 98 % | 0 |
63 +| `price_min` — Prix min | tarif minimal réel ($) | 96 % | 65.0 |
64 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 98 % | General Admission 65 $ |
65 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Motel Chelsea |
67 +| `image` — Image | visuel (og:image ou média source) | 89 % | https://doavub8d2uzrx.cloudfront.net/media/images/events/motel-chelsea/images/4CYZ7E-582e1b24-ed… |
68 +| `website` — Site web | site officiel de l'événement | 62 % | https://www.motelchelsea.com/ |
23 69
24 70 ## Fréquence & budgets
25 71
26 −- **Cadence** : —
72 +- **Cadence** : quotidienne
73 +- **Budgets / plafonds (variables d'environnement)** :
74 +
75 +| Variable | Défaut | Rôle |
76 +|---|---|---|
77 +| `SHOWPASS_PAGE_SIZE` | `100` | — |
78 +| `SHOWPASS_MAX_PER_POINT` | `2000` | — |
27 79 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
28 80
29 81 ## Volumétrie & complétude live
30 82
31 −*Source sans connecteur runtime : aucune donnée en base.*
83 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
84 +
85 +- **Événements en base** : 45 (dont **45 actifs**)
86 +- **GPS** : 45/45 actifs (100 %)
87 +- **Prix** : 44/45 actifs (98 %)
88 +- **Heure (start_time)** : 45/45 actifs (100 %)
89 +- **Heure de fin** : 40/45 actifs (89 %)
90 +- **Statut (annulé/reporté/complet)** : 1/45 actifs (2 %)
91 +- **Artistes** : 0/45 actifs (0 %)
92 +- **Image** : 40/45 actifs (89 %)
93 +- **Salle (venue)** : 45/45 actifs (100 %)
94 +- **Dernier sync** : 2026-08-25 18:35 — fetched 45, +45 ajoutés, ~0 mis à jour, -0 retirés
95 +- **Syncs enregistrés** (sync_log) : 1
32 96
33 97 ## Erreurs & dépannage
34 98
99 +Aucune erreur dans `sync_log` pour cette source. ✅
100 +
101 +- Rejouer un sync isolé : `python3 run.py sync --source showpass` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='showpass' ORDER BY ts DESC LIMIT 5;"`
35 102
36 103 ## Licence / attribution
37 104
38 −- API publique explicitement autorisée par robots.txt (Allow: /api/)
105 +- robots.txt Allow: /api/ (API publique explicitement autorisée), GET throttlés 0,8 s, identification honnête
39 106
40 107 ## Historique
41 108
42 −- **Source abandonnée** — sans note
43 109 - **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
44 110 - **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
45 111 - **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/signelaval.md +114 −0
@@ -0,0 +1,114 @@
1 +# Signé Laval — calendrier culturel officiel de Laval (`signelaval`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `agregateur` · Connecteur : `sortika/connectors/signelaval.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Signé Laval — calendrier culturel officiel de Laval](https://signelaval.com/)
10 +- **Couverture** : ~290 fiches (bibliothèques, spectacles, expositions, plein air — Laval)
11 +- **Extraction** : sitemap public /sitemap/events/N.xml puis fiches rendues serveur : JSON-LD Event Footlight/artsdata (dates ISO -04:00, lieu, adresse, image IIIF, organisateur ; champs multilingues en liste → variante fr) ; incrémental cache 7 j, plafond SIGNELAVAL_MAX_FETCH
12 +
13 +## Accès
14 +
15 +- **Type** : agregateur
16 +- **Endpoint** (`BASE`) : `https://signelaval.com`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : parcours de sitemap XML
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : robots.txt sans Disallow + sitemap public, GET directs throttlés 0,8 s, identification honnête
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/signelaval.py : connecteur Signé Laval (signelaval.com)
26 +> Source : calendrier culturel officiel de Laval (Culture Laval) —
27 +> bibliothèques, spectacles, expositions, plein air (~400 fiches).
28 +> Extraction: sitemap public /sitemap/events/N.xml puis fiches rendues
29 +> serveur : JSON-LD Event complet (CMS Footlight / artsdata.ca)
30 +> avec dates ISO horodatées (fuseau -04:00), lieu, adresse,
31 +> ville, image IIIF, organisateur. Certains champs Footlight
32 +> sont des LISTES multilingues (["…", {"@language": "fr",
33 +> "@value": "…"}]) → _val privilégie la variante française
34 +> (bug corrigé à la validation live 2026-08-25).
35 +> GET directs, pas d'anti-bot.
36 +> INCRÉMENTAL avec cache disque (data/signelaval_cache.json) :
37 +> nouvelles fiches d'abord, re-visite roulante après 7 jours,
38 +> plafonné à SIGNELAVAL_MAX_FETCH fiches/sync.
39 +> Accès : robots.txt « User-agent: * / Disallow: (vide) » + sitemap
40 +> public → scraping permis, identification honnête.
41 +> Prix : offers = AggregateOffer sans montant sur les fiches vérifiées
42 +> → is_free inconnu (jamais inventé).
43 +
44 +</details>
45 +
46 +## Champs récupérés → schéma
47 +
48 +Complétude mesurée sur les **172 événements actifs** de la source ; exemples tirés d'un événement réel (`signelaval:6a43d910872b4a7e4d731ef3`).
49 +
50 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
51 +|---|---|---|---|
52 +| `title` — Titre | titre de l'événement | 100 % | Cinéma en plein air \| Souhait : Asha et la bonne étoile |
53 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Profitez du visionnement gratuit d'un film en plein air dans un parc ou une berge près de chez v… |
54 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://signelaval.com/fr/evenements/6a43d910872b4a7e4d731ef3/cinema-en-plein-air-or-souhait-ash… |
55 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["cinema"] |
56 +| `venue` — Salle (venue) | nom du lieu | 100 % | Parc René-Patenaude (Pont-Viau) |
57 +| `address` — Adresse | adresse civique | 98 % | 80, rue Ouimet Est |
58 +| `city` — Ville | municipalité | 100 % | Laval |
59 +| `region` — Région | région administrative | 100 % | Laval |
60 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 99 % | 45.5735953, -73.6909642 |
61 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
62 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
63 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 90 % | 19:45 |
64 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 54 % | 21:45 |
65 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 1 % | ∅ |
66 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
67 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
68 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
69 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
70 +| `organizer` — Organisateur | promoteur / organisme | 90 % | Ville de Laval |
71 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://cdn.footlight.io/iiif/3/7a3c4b8c-a7e6-4234-8681-f48f782b333f.png/full/1200,/0/default.pn… |
72 +| `website` — Site web | site officiel de l'événement | 70 % | https://www.laval.ca/calendrier-activites/cinema-plein-air-souhait-dehors-rene-patenaude/ |
73 +
74 +## Fréquence & budgets
75 +
76 +- **Cadence** : hebdomadaire
77 +- **Budgets / plafonds (variables d'environnement)** :
78 +
79 +| Variable | Défaut | Rôle |
80 +|---|---|---|
81 +| `SIGNELAVAL_MAX_FETCH` | `200` | — |
82 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
83 +
84 +## Volumétrie & complétude live
85 +
86 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
87 +
88 +- **Événements en base** : 172 (dont **172 actifs**)
89 +- **GPS** : 171/172 actifs (99 %)
90 +- **Prix** : 0/172 actifs (0 %)
91 +- **Heure (start_time)** : 154/172 actifs (90 %)
92 +- **Heure de fin** : 93/172 actifs (54 %)
93 +- **Statut (annulé/reporté/complet)** : 2/172 actifs (1 %)
94 +- **Artistes** : 0/172 actifs (0 %)
95 +- **Image** : 172/172 actifs (100 %)
96 +- **Salle (venue)** : 172/172 actifs (100 %)
97 +- **Dernier sync** : 2026-08-25 18:37 — fetched 172, +172 ajoutés, ~0 mis à jour, -0 retirés
98 +- **Syncs enregistrés** (sync_log) : 1
99 +
100 +## Erreurs & dépannage
101 +
102 +Aucune erreur dans `sync_log` pour cette source. ✅
103 +
104 +- Rejouer un sync isolé : `python3 run.py sync --source signelaval` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='signelaval' ORDER BY ts DESC LIMIT 5;"`
105 +
106 +## Licence / attribution
107 +
108 +- robots.txt sans Disallow + sitemap public, GET directs throttlés 0,8 s, identification honnête
109 +
110 +## Historique
111 +
112 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
113 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
114 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/sitq.md +39 −28
@@ -1,6 +1,6 @@
1 1 # SIT Québec — Ministère du Tourisme (`sitq`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `donnees-ouvertes` · Connecteur : `sortika/connectors/sitq.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [SIT Québec — Ministère du Tourisme](https://www.donneesquebec.ca/recherche/dataset/sit-quebec-evenements)
10 10 - **Couverture** : Événements touristiques, toutes les régions du Québec
11 −- **Extraction** : API JSON (Tourinsoft, syndication publique) — dates, géoloc, thèmes, contacts (TelephonePrincipals/Courriels → description « Renseignements : … ») ; le flux ne publie ni Descriptions* ni tarifs (vérifié 2026-08)
11 +- **Extraction** : API JSON (Tourinsoft, syndication publique) — dates, géoloc, thèmes, contacts ; le flux ne publie ni heure (T00:00:00 partout) ni tarif ni photo (re-vérifié 2026-08-25) → enrichissement incrémental des SITES OFFICIELS des événements : og:image + og:description (filtre anti-slogan <30 car.), cache versionné v2, plafond SITQ_IMG_MAX=200/sync (90 % des sites livrent une vraie description)
12 12
13 13 ## Accès
14 14
@@ -33,29 +33,39 @@
33 33 > Contacts : TelephonePrincipals / Courriels (moyens de communication) →
34 34 > ligne « Renseignements : … » en description (le flux syndiqué
35 35 > ne publie AUCUN groupe Descriptions* ni bloc tarifs — vérifié
36 −> sur l'union des clés des 1 598 fiches, 2026-08 ; is_free et
37 −> price_* restent donc inconnus, jamais inventés).
36 +> sur l'union des clés des 1 598 fiches, 2026-08, re-vérifié
37 +> 2026-08-25 : aucune heure non plus, tous les Datedebut/Datefin
38 +> à T00:00:00 ; is_free/price_*/start_time restent donc
39 +> inconnus, jamais inventés).
40 +> Fiches : le site officiel de chaque événement (SiteInternets) est
41 +> visité incrémentalement (cache disque, plafond SITQ_IMG_MAX)
42 +> pour og:image ET og:description (v2 — ~60 % des 40 sites
43 +> sondés la publient, 2026-08-25) ; AUCUN des 40 sites sondés
44 +> ne publie de JSON-LD Event → l'heure n'est pas récupérable à
45 +> coût raisonnable. La fiche quebecoriginal.com n'est pas
46 +> dérivable du SyndicObjectID (301 → bonjourquebec.com → 404,
47 +> vérifié 2026-08-25).
38 48 > https://www.donneesquebec.ca/recherche/dataset/sit-quebec-evenements
39 49
40 50 </details>
41 51
42 52 ## Champs récupérés → schéma
43 53
44 −Complétude mesurée sur les **1014 événements actifs** de la source ; exemples tirés d'un événement réel (`sitq:EVENMTOV503QAX`).
54 +Complétude mesurée sur les **963 événements actifs** de la source ; exemples tirés d'un événement réel (`sitq:EVENMTOV502C96`).
45 55
46 56 | Champ (`events.*`) | Description | Complétude | Exemple réel |
47 57 |---|---|---|---|
48 −| `title` — Titre | titre de l'événement | 100 % | Génération MusiquePlus – 86-96 |
49 −| `description` — Description | texte descriptif (source ou fiche détail) | 90 % | Renseignements : info@espacestdenis.com |
50 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://espacestdenis.com/evenement/generation-musique-plus/ |
51 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique", "cinema", "patrimoine"] |
52 −| `venue` — Salle (venue) | nom du lieu | 74 % | Studio-Cabaret - Théâtre St-Denis |
53 −| `address` — Adresse | adresse civique | 84 % | 1594 Rue Saint-Denis |
58 +| `title` — Titre | titre de l'événement | 100 % | Startupfest |
59 +| `description` — Description | texte descriptif (source ou fiche détail) | 90 % | Renseignements : info@startupfest.com |
60 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.startupfest.com/fr-FR/ |
61 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["festival"] |
62 +| `venue` — Salle (venue) | nom du lieu | 75 % | Grand Quai du Port de Montréal |
63 +| `address` — Adresse | adresse civique | 84 % | 200 Rue de la Commune Ouest |
54 64 | `city` — Ville | municipalité | 95 % | Montréal |
55 65 | `region` — Région | région administrative | 99 % | Montréal |
56 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 97 % | 45.514629, -73.562936 |
57 −| `start_date` — Date début | date locale AAAA-MM-JJ | 99 % | 2026-08-21 |
58 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 99 % | 2026-08-22 |
66 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 97 % | 45.502414, -73.552842 |
67 +| `start_date` — Date début | date locale AAAA-MM-JJ | 98 % | 2027-07-07 |
68 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 98 % | 2027-07-09 |
59 69 | `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
60 70 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
61 71 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
@@ -64,8 +74,8 @@ Complétude mesurée sur les **1014 événements actifs** de la source ; exemple
64 74 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
65 75 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 76 | `organizer` — Organisateur | promoteur / organisme | 100 % | Tourisme Montréal |
67 −| `image` — Image | visuel (og:image ou média source) | 63 % | https://cdn.espacestdenis.com/uploads/_1200x630_crop_center-center_82_none/GM_EnSpectacle_1000x1… |
68 −| `website` — Site web | site officiel de l'événement | 96 % | https://espacestdenis.com/evenement/generation-musique-plus/ |
77 +| `image` — Image | visuel (og:image ou média source) | 63 % | https://assets.embrase.com/assets/02487d6f-539b-466d-88ad-90d49428cf06?key=social-image-landscap… |
78 +| `website` — Site web | site officiel de l'événement | 96 % | https://www.startupfest.com/fr-FR/ |
69 79
70 80 ## Fréquence & budgets
71 81
@@ -79,19 +89,19 @@ Complétude mesurée sur les **1014 événements actifs** de la source ; exemple
79 89
80 90 ## Volumétrie & complétude live
81 91
82 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
92 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
83 93
84 −- **Événements en base** : 1814 (dont **1014 actifs**)
85 −- **GPS** : 981/1014 actifs (97 %)
86 −- **Prix** : 0/1014 actifs (0 %)
87 −- **Heure (start_time)** : 0/1014 actifs (0 %)
88 −- **Heure de fin** : 0/1014 actifs (0 %)
89 −- **Statut (annulé/reporté/complet)** : 0/1014 actifs (0 %)
90 −- **Artistes** : 0/1014 actifs (0 %)
91 −- **Image** : 639/1014 actifs (63 %)
92 −- **Salle (venue)** : 748/1014 actifs (74 %)
93 −- **Dernier sync** : 2026-08-22 22:28 — fetched 1798, +0 ajoutés, ~0 mis à jour, -0 retirés
94 −- **Syncs enregistrés** (sync_log) : 125
94 +- **Événements en base** : 1814 (dont **963 actifs**)
95 +- **GPS** : 930/963 actifs (97 %)
96 +- **Prix** : 0/963 actifs (0 %)
97 +- **Heure (start_time)** : 0/963 actifs (0 %)
98 +- **Heure de fin** : 0/963 actifs (0 %)
99 +- **Statut (annulé/reporté/complet)** : 0/963 actifs (0 %)
100 +- **Artistes** : 0/963 actifs (0 %)
101 +- **Image** : 604/963 actifs (63 %)
102 +- **Salle (venue)** : 721/963 actifs (75 %)
103 +- **Dernier sync** : 2026-08-25 18:06 — fetched 0, +0 ajoutés, ~0 mis à jour, -0 retirés, **erreur : Error binding parameter 27: type 'dict' is not supported**
104 +- **Syncs enregistrés** (sync_log) : 180
95 105
96 106 ## Erreurs & dépannage
97 107
@@ -99,6 +109,7 @@ Erreurs relevées dans `sync_log` :
99 109
100 110 | Erreur | Occurrences | Dernière fois |
101 111 |---|---|---|
112 +| Error binding parameter 27: type 'dict' is not supported | 1 | 2026-08-25 18:06 |
102 113 | database is locked | 1 | 2026-08-19 02:22 |
103 114 | 502 Server Error: Bad Gateway for url: https://api-v3.tourinsoft.com/api/syndications/mto.tourin… | 1 | 2026-08-19 01:19 |
104 115
added docs/connecteurs/soreltracy.md +111 −0
@@ -0,0 +1,111 @@
1 +# Ville de Sorel-Tracy — Événements (`soreltracy`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/soreltracy.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Sorel-Tracy — Événements](https://ville.sorel-tracy.qc.ca/evenements)
10 +- **Couverture** : Sorel-Tracy (Montérégie)
11 +- **Extraction** : HTML serveur OctoberCMS /evenements/N + fiches (heures, lieu, coût) en cache (SOR_FICHES=40)
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`BASE`) : `https://ville.sorel-tracy.qc.ca`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : par numéro de page
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : site municipal public, robots.txt permissif
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/soreltracy.py : connecteur Ville de Sorel-Tracy — événements
26 +> Source : https://ville.sorel-tracy.qc.ca/evenements (OctoberCMS,
27 +> rendu serveur — même famille que rouyn-noranda/joliette).
28 +> Extraction: pages liste /evenements puis /evenements/2, /3... (arrêt à la
29 +> première page sans carte) → cartes « thumb-event » : date FR
30 +> (« 25 août 2026 » ou « Du 29 juillet 2026 au 26 août 2026 »),
31 +> titre, catégorie, image, url fiche. Enrichissement par fiche
32 +> /evenement/<slug> (heures, lieu, coût « Gratuit » — blocs
33 +> event-info__text) — CACHE INCRÉMENTAL disque, plafond
34 +> SOR_FICHES par sync.
35 +> Accès : site municipal public, GET directs throttlés, identification
36 +> honnête ; robots.txt permissif.
37 +> Prix : la fiche publie le coût (souvent « Gratuit ») → price_label
38 +> fidèle, jamais inventé.
39 +
40 +</details>
41 +
42 +## Champs récupérés → schéma
43 +
44 +Complétude mesurée sur les **64 événements actifs** de la source ; exemples tirés d'un événement réel (`soreltracy:atelier-decouverte-les-arthropodes-2026-08-25`).
45 +
46 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
47 +|---|---|---|---|
48 +| `title` — Titre | titre de l'événement | 100 % | Atelier découverte: Les arthropodes |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://ville.sorel-tracy.qc.ca/evenement/atelier-decouverte-les-arthropodes |
51 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
52 +| `venue` — Salle (venue) | nom du lieu | 72 % | Bibliothèque « Le Survenant » |
53 +| `address` — Adresse | adresse civique | 0 % | ∅ |
54 +| `city` — Ville | municipalité | 100 % | Sorel-Tracy |
55 +| `region` — Région | région administrative | 100 % | Montérégie |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 66 % | 46.0458794, -73.1098094 |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
59 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 67 % | 10:10 |
60 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 56 % | 11:10 |
61 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
62 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 69 % | 1 |
63 +| `price_min` — Prix min | tarif minimal réel ($) | 2 % | ∅ |
64 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 69 % | Gratuit |
65 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Sorel-Tracy |
67 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://ville.sorel-tracy.qc.ca/storage/app/media/_resized/ee5/2026_atelier_decouv_arthropodes_2… |
68 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
69 +
70 +## Fréquence & budgets
71 +
72 +- **Cadence** : quotidien
73 +- **Budgets / plafonds (variables d'environnement)** :
74 +
75 +| Variable | Défaut | Rôle |
76 +|---|---|---|
77 +| `SOR_PAGES` | `8` | — |
78 +| `SOR_FICHES` | `40` | — |
79 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
80 +
81 +## Volumétrie & complétude live
82 +
83 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
84 +
85 +- **Événements en base** : 64 (dont **64 actifs**)
86 +- **GPS** : 42/64 actifs (66 %)
87 +- **Prix** : 44/64 actifs (69 %)
88 +- **Heure (start_time)** : 43/64 actifs (67 %)
89 +- **Heure de fin** : 36/64 actifs (56 %)
90 +- **Statut (annulé/reporté/complet)** : 0/64 actifs (0 %)
91 +- **Artistes** : 0/64 actifs (0 %)
92 +- **Image** : 64/64 actifs (100 %)
93 +- **Salle (venue)** : 46/64 actifs (72 %)
94 +- **Dernier sync** : 2026-08-25 18:27 — fetched 64, +64 ajoutés, ~0 mis à jour, -0 retirés
95 +- **Syncs enregistrés** (sync_log) : 1
96 +
97 +## Erreurs & dépannage
98 +
99 +Aucune erreur dans `sync_log` pour cette source. ✅
100 +
101 +- Rejouer un sync isolé : `python3 run.py sync --source soreltracy` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='soreltracy' ORDER BY ts DESC LIMIT 5;"`
102 +
103 +## Licence / attribution
104 +
105 +- site municipal public, robots.txt permissif
106 +
107 +## Historique
108 +
109 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
110 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
111 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/terrebonne.md +78 −13
@@ -1,46 +1,111 @@
1 1 # Ville de Terrebonne — Calendrier (`terrebonne`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 −**Statut : abandonnee** · Type : `municipal` · Connecteur : *aucun (source sans connecteur runtime)*
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/terrebonne.py`
6 6
7 7 ## Description
8 8
9 −- **Source** : [Ville de Terrebonne — Calendrier](https://terrebonne.ca/calendrier/)
9 +- **Source** : [Ville de Terrebonne — Calendrier](https://terrebonne.ca/calendrier-des-evenements/)
10 10 - **Couverture** : Terrebonne (Lanaudière)
11 −- **Extraction** : n/d
12 −- **Note** : Cloudflare bloque tout GET non navigateur (403) et aucun jeu « événements » sur Données Québec (vérifié 2026-08-18)
11 +- **Extraction** : API REST wp-json/wp/v2/event via Scrapfly (Cloudflare), 3-5 appels/sync. RÉACTIVÉE 2026-08-25
13 12
14 13 ## Accès
15 14
16 15 - **Type** : municipal
17 −- **Endpoint** : voir URL source (https://terrebonne.ca/calendrier/)
18 −- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 −- **Accès légal** : n/d
16 +- **Endpoint** (`API`) : `https://terrebonne.ca/wp-json/wp/v2/event?per_page=100&page={page}&_embed=wp:featuredmedia`
17 +- **Endpoint** (`TAXO`) : `https://terrebonne.ca/wp-json/wp/v2/event_category?per_page=100`
18 +- **Auth** : Aucune côté source ; `SCRAPFLY_API_KEY` requis pour le backend Scrapfly
19 +- **Pagination** : par numéro de page, per_page
20 +- **Backend** : Scrapfly (anti-bot / rendu JS) via `base.get_scrapfly()`
21 +- **Throttle** : 1.0 s entre requêtes
22 +- **Accès légal** : API publique du site municipal ; anti-bot contourné avec volumes minimes
23 +
24 +<details><summary>En-tête documentaire du connecteur</summary>
25 +
26 +> connectors/terrebonne.py : connecteur Ville de Terrebonne — calendrier
27 +> Source : https://terrebonne.ca/calendrier-des-evenements/ (WordPress).
28 +> Extraction: API REST WordPress publique /wp-json/wp/v2/event (champs ACF
29 +> start_date/end_date « YYYY-MM-DD HH:MM:SS », place.name,
30 +> isFull, hours) + taxonomie /wp-json/wp/v2/event_category.
31 +> Cloudflare bloque tout GET non navigateur (403, consigné
32 +> 2026-08-18) → chaque requête passe par Scrapfly (asp=true,
33 +> SANS rendu JS : la réponse est du JSON), ~3-5 appels/sync.
34 +> Vérifié 2026-08-25 (Scrapfly 200, JSON complet).
35 +> Accès : API publique du site municipal (aucune auth) ; robots.txt
36 +> n'interdit pas /wp-json/. Anti-bot contourné avec volumes
37 +> minimes et cadence quotidienne au plus — usage mesuré.
38 +> Prix : non publié par la source → jamais inventé. isFull=True →
39 +> status "soldout" (complet, publié par la ville).
40 +
41 +</details>
20 42
21 43 ## Champs récupérés → schéma
22 44
23 −*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
45 +Complétude mesurée sur les **31 événements actifs** de la source ; exemples tirés d'un événement réel (`terrebonne:47210`).
46 +
47 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
48 +|---|---|---|---|
49 +| `title` — Titre | titre de l'événement | 100 % | Fête des Récoltes de Terrebonne 2026 |
50 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | 🌾 Organisée par la SODAM Célébrez l’abondance de nos terres et l’arrivée de l’automne! 🎟️ Entrée… |
51 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://terrebonne.ca/evenement/fete-des-recoltes-de-terrebonne-2026/ |
52 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["festival"] |
53 +| `venue` — Salle (venue) | nom du lieu | 100 % | Centre horticole Bastien |
54 +| `address` — Adresse | adresse civique | 0 % | ∅ |
55 +| `city` — Ville | municipalité | 100 % | Terrebonne |
56 +| `region` — Région | région administrative | 100 % | Lanaudière |
57 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 39 % | 45.737651, -73.700876 |
58 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-19 |
59 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-19 |
60 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 10:00 |
61 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 100 % | 16:00 |
62 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
63 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
64 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
65 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
66 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
67 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Terrebonne |
68 +| `image` — Image | visuel (og:image ou média source) | 87 % | https://terrebonne.ca/wp-content/uploads/2026/08/SODAM_Fe╠etesDesRe╠ucoltes-19-scaled.jpg |
69 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
24 70
25 71 ## Fréquence & budgets
26 72
27 −- **Cadence** : n/d
73 +- **Cadence** : quotidien
74 +- **Budgets / plafonds (variables d'environnement)** :
75 +
76 +| Variable | Défaut | Rôle |
77 +|---|---|---|
78 +| `TER_PAGES` | `5` | — |
28 79 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
29 80
30 81 ## Volumétrie & complétude live
31 82
32 −*Source sans connecteur runtime : aucune donnée en base.*
83 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
84 +
85 +- **Événements en base** : 500 (dont **31 actifs**)
86 +- **GPS** : 12/31 actifs (39 %)
87 +- **Prix** : 0/31 actifs (0 %)
88 +- **Heure (start_time)** : 31/31 actifs (100 %)
89 +- **Heure de fin** : 31/31 actifs (100 %)
90 +- **Statut (annulé/reporté/complet)** : 0/31 actifs (0 %)
91 +- **Artistes** : 0/31 actifs (0 %)
92 +- **Image** : 27/31 actifs (87 %)
93 +- **Salle (venue)** : 31/31 actifs (100 %)
94 +- **Dernier sync** : 2026-08-25 18:34 — fetched 500, +500 ajoutés, ~0 mis à jour, -0 retirés
95 +- **Syncs enregistrés** (sync_log) : 1
33 96
34 97 ## Erreurs & dépannage
35 98
99 +Aucune erreur dans `sync_log` pour cette source. ✅
100 +
101 +- Rejouer un sync isolé : `python3 run.py sync --source terrebonne` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='terrebonne' ORDER BY ts DESC LIMIT 5;"`
36 102
37 103 ## Licence / attribution
38 104
39 −- n/d
105 +- API publique du site municipal ; anti-bot contourné avec volumes minimes
40 106
41 107 ## Historique
42 108
43 −- **Source abandonnée** — Cloudflare bloque tout GET non navigateur (403) et aucun jeu « événements » sur Données Québec (vérifié 2026-08-18)
44 109 - **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
45 110 - **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
46 111 - **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/ticketacces.md +38 −34
@@ -1,6 +1,6 @@
1 1 # TicketAcces.net — billetterie régionale québécoise (`ticketacces`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/ticketacces.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [TicketAcces.net — billetterie régionale québécoise](https://www.ticketacces.net/fr/portail/index.cfm)
10 10 - **Couverture** : 47 organisations QC — dont 14 en Abitibi-Témiscamingue (Agora des Arts, Théâtre du cuivre, culture Val-d'Or/Amos/La Sarre/Malartic, Théâtre du Rift…), Bas-Saint-Laurent (Océanic, 3L), Côte-Nord (Drakkar), Charlevoix (Domaine Forget), Nord-du-Québec (Lebel-sur-Quévillon)… ~1 300 séances (vérifié 2026-08-21)
11 −- **Extraction** : HTML ColdFusion (cp1252) : annuaire complet des organisations dans le portail (cartes data-nom/ville/région, filtre Québec — 12 orgs NB + 1 ON exclues) + calendrier AGRÉGÉ multi-organisations en 4 GET (catégories Spectacles/Cinéma/Sport/Autre) : 1 Event par représentation datée (titre, date+heure FR, salle, ville, image). Fiches incrémentales (TA_DETAIL_MAX=25/sync) : og:description, adresse, prix plancher.
11 +- **Extraction** : HTML ColdFusion (cp1252) : annuaire complet des organisations du portail (filtre Québec — 12 orgs NB + 1 ON exclues) + calendrier AGRÉGÉ en 4 GET : 1 Event par représentation datée (titre, date+heure FR, salle, ville, image). Fiches incrémentales (TA_DETAIL_MAX=47/sync = 1 par hôte, re-visite 7 j, version de parse « v ») : og:description, ADRESSE CIVIQUE de la salle (ville suffixée retirée → géocodage aval), prix plancher (les « 0,00 $ » écartés). Crawl-delay 120 s/hôte honoré
12 12
13 13 ## Accès
14 14
@@ -38,11 +38,15 @@
38 38 > calendrier par catégorie (4 GET) : titre + sous-titre, date
39 39 > et heure FR (« Samedi 22 août 2026 à 12 h 30 »), « Salle,
40 40 > Ville », image, lien fiche. Fiches détail incrémentales
41 −> (cache data/ticketacces_cache.json, TA_DETAIL_MAX/sync,
42 −> re-visite 7 j) : og:description, adresse civique, prix
43 −> plancher si les ventes sont ouvertes. 1 Event par
44 −> représentation (une même fiche multi-dates = plusieurs
45 −> séances, patron lepointdevente).
41 +> (cache data/ticketacces_cache.json, TA_DETAIL_MAX/sync —
42 +> 47 par défaut = 1 fiche par organisation/hôte, re-visite
43 +> 7 j, version de parse « v » bumpée quand le parseur
44 +> s'enrichit → re-visite progressive, patron lavitrine) :
45 +> og:description, adresse civique de la salle (bloc
46 +> « Salle | 315, rue X, Ville » des représentations, ville
47 +> finale retirée), prix plancher si les ventes sont
48 +> ouvertes. 1 Event par représentation (une même fiche
49 +> multi-dates = plusieurs séances, patron lepointdevente).
46 50 > Accès : billetterie publique. robots.txt (lu 2026-08-21, identique
47 51 > sur tous les hôtes *.ticketacces.net) : Crawl-delay: 120 +
48 52 > Disallow /achat/ et representations/prix.cfm (jamais
@@ -59,30 +63,30 @@
59 63
60 64 ## Champs récupérés → schéma
61 65
62 −Complétude mesurée sur les **1318 événements actifs** de la source ; exemples tirés d'un événement réel (`ticketacces:lachapellespectacles-11094-2026-09-20-15:00`).
66 +Complétude mesurée sur les **1371 événements actifs** de la source ; exemples tirés d'un événement réel (`ticketacces:bordee-158-2026-08-26-19:30`).
63 67
64 68 | Champ (`events.*`) | Description | Complétude | Exemple réel |
65 69 |---|---|---|---|
66 −| `title` — Titre | titre de l'événement | 100 % | Jeanick Fournier — Authentique - En supplémentaire |
67 −| `description` — Description | texte descriptif (source ou fiche détail) | 20 % | Certaines voix sont si puissantes qu'elles vous coupent le souffle dès la première note. La chan… |
68 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://lachapellespectacles.ticketacces.net/fr/organisation/representations/index.cfm?Evenement… |
70 +| `title` — Titre | titre de l'événement | 100 % | CINQ BALLES DANS LA TÊTE ( discussion ) — Prod. Théâtre DuBunker, codiff. La Bordée |
71 +| `description` — Description | texte descriptif (source ou fiche détail) | 46 % | 1h40 Récit biographique Résumé : Lorsqu'une autrice et professeure de littérature accepte de pre… |
72 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://bordee.ticketacces.net/fr/organisation/representations/index.cfm?EvenementID=158 |
69 73 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["arts-scene"] |
70 −| `venue` — Salle (venue) | nom du lieu | 100 % | La Chapelle |
71 −| `address` — Adresse | adresse civique | 0 % | ∅ |
74 +| `venue` — Salle (venue) | nom du lieu | 100 % | Théâtre La Bordée |
75 +| `address` — Adresse | adresse civique | 10 % | 315, rue Saint-Joseph Est |
72 76 | `city` — Ville | municipalité | 100 % | Québec |
73 77 | `region` — Région | région administrative | 100 % | Capitale-Nationale |
74 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 74 % | 45.5231154, -73.5771387 |
75 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-20 |
76 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-20 |
77 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 15:00 |
78 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 94 % | 46.8135798, -71.2259663 |
79 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-26 |
80 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-26 |
81 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 19:30 |
78 82 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
79 83 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
80 84 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
81 −| `price_min` — Prix min | tarif minimal réel ($) | 21 % | 47.0 |
85 +| `price_min` — Prix min | tarif minimal réel ($) | 56 % | 25.0 |
82 86 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
83 87 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
84 −| `organizer` — Organisateur | promoteur / organisme | 100 % | La Chapelle |
85 −| `image` — Image | visuel (og:image ou média source) | 99 % | https://www.ticketacces.net/organisations/lachapellespectacles/evenements/petites/nouveau-site-w… |
88 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Théâtre La Bordée |
89 +| `image` — Image | visuel (og:image ou média source) | 99 % | https://www.ticketacces.net/organisations/bordee/evenements/petites/labordee_ticket_access_1000x… |
86 90 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
87 91
88 92 ## Fréquence & budgets
@@ -92,25 +96,25 @@ Complétude mesurée sur les **1318 événements actifs** de la source ; exemple
92 96
93 97 | Variable | Défaut | Rôle |
94 98 |---|---|---|
95 −| `TA_DETAIL_MAX` | `25` | — |
99 +| `TA_DETAIL_MAX` | `47` | — |
96 100 | `TA_TTL_HOURS` | `6` | — |
97 101 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
98 102
99 103 ## Volumétrie & complétude live
100 104
101 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
102 −
103 −- **Événements en base** : 1359 (dont **1318 actifs**)
104 −- **GPS** : 974/1318 actifs (74 %)
105 −- **Prix** : 283/1318 actifs (21 %)
106 −- **Heure (start_time)** : 1318/1318 actifs (100 %)
107 −- **Heure de fin** : 0/1318 actifs (0 %)
108 −- **Statut (annulé/reporté/complet)** : 0/1318 actifs (0 %)
109 −- **Artistes** : 0/1318 actifs (0 %)
110 −- **Image** : 1300/1318 actifs (99 %)
111 −- **Salle (venue)** : 1318/1318 actifs (100 %)
112 −- **Dernier sync** : 2026-08-22 22:28 — fetched 1318, +0 ajoutés, ~0 mis à jour, -0 retirés
113 −- **Syncs enregistrés** (sync_log) : 22
105 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
106 +
107 +- **Événements en base** : 1473 (dont **1371 actifs**)
108 +- **GPS** : 1287/1371 actifs (94 %)
109 +- **Prix** : 765/1371 actifs (56 %)
110 +- **Heure (start_time)** : 1371/1371 actifs (100 %)
111 +- **Heure de fin** : 0/1371 actifs (0 %)
112 +- **Statut (annulé/reporté/complet)** : 0/1371 actifs (0 %)
113 +- **Artistes** : 0/1371 actifs (0 %)
114 +- **Image** : 1356/1371 actifs (99 %)
115 +- **Salle (venue)** : 1371/1371 actifs (100 %)
116 +- **Dernier sync** : 2026-08-25 18:06 — fetched 1371, +0 ajoutés, ~69 mis à jour, -0 retirés
117 +- **Syncs enregistrés** (sync_log) : 77
114 118
115 119 ## Erreurs & dépannage
116 120
modified docs/connecteurs/ticketmaster.md +26 −26
@@ -1,6 +1,6 @@
1 1 # Ticketmaster — Discovery API (Québec) (`ticketmaster`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/ticketmaster.py`
6 6
@@ -46,22 +46,22 @@
46 46
47 47 ## Champs récupérés → schéma
48 48
49 −Complétude mesurée sur les **359 événements actifs** de la source ; exemples tirés d'un événement réel (`ticketmaster:310064CAFB66B462`).
49 +Complétude mesurée sur les **360 événements actifs** de la source ; exemples tirés d'un événement réel (`ticketmaster:31006476DB368606`).
50 50
51 51 | Champ (`events.*`) | Description | Complétude | Exemple réel |
52 52 |---|---|---|---|
53 −| `title` — Titre | titre de l'événement | 100 % | BINGO BRUNCH - Hip Hop & RnB Bingo avec Tom Swoope |
53 +| `title` — Titre | titre de l'événement | 100 % | ANNULÉ - Rock This Town |
54 54 | `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
55 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.ticketmaster.ca/bingo-brunch-hip-hop-rnb-bingo-montreal-quebec-08-22-2026/event/3100… |
55 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.ticketmaster.ca/annule-rock-this-town-quebec-quebec-09-13-2026/event/31006476DB36860… |
56 56 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
57 −| `venue` — Salle (venue) | nom du lieu | 100 % | Le Balcon |
58 −| `address` — Adresse | adresse civique | 100 % | 463 Sainte-Catherine Ouest |
59 −| `city` — Ville | municipalité | 100 % | Montréal |
60 −| `region` — Région | région administrative | 100 % | Montréal |
61 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 45.506011, -73.567007 |
62 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
63 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
64 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 96 % | 11:00 |
57 +| `venue` — Salle (venue) | nom du lieu | 100 % | Théâtre Capitole |
58 +| `address` — Adresse | adresse civique | 100 % | 972 Rue Saint-Jean |
59 +| `city` — Ville | municipalité | 100 % | Québec |
60 +| `region` — Région | région administrative | 100 % | Capitale-Nationale |
61 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 46.813254, -71.214215 |
62 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-13 |
63 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-13 |
64 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 97 % | 20:00 |
65 65 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
66 66 | `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 4 % | cancelled |
67 67 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
@@ -69,7 +69,7 @@ Complétude mesurée sur les **359 événements actifs** de la source ; exemples
69 69 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
70 70 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
71 71 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
72 −| `image` — Image | visuel (og:image ou média source) | 89 % | https://mapsapi.tmol.io/maps/geometry/3/event/310064CAFB66B462/staticImage?type=png&systemId=HOS… |
72 +| `image` — Image | visuel (og:image ou média source) | 88 % | https://mapsapi.tmol.io/maps/geometry/3/event/31006476DB368606/staticImage?type=png&systemId=HOS… |
73 73 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
74 74
75 75 ## Fréquence & budgets
@@ -87,19 +87,19 @@ Complétude mesurée sur les **359 événements actifs** de la source ; exemples
87 87
88 88 ## Volumétrie & complétude live
89 89
90 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
91 −
92 −- **Événements en base** : 404 (dont **359 actifs**)
93 −- **GPS** : 359/359 actifs (100 %)
94 −- **Prix** : 0/359 actifs (0 %)
95 −- **Heure (start_time)** : 346/359 actifs (96 %)
96 −- **Heure de fin** : 0/359 actifs (0 %)
97 −- **Statut (annulé/reporté/complet)** : 15/359 actifs (4 %)
98 −- **Artistes** : 0/359 actifs (0 %)
99 −- **Image** : 318/359 actifs (89 %)
100 −- **Salle (venue)** : 359/359 actifs (100 %)
101 −- **Dernier sync** : 2026-08-22 22:29 — fetched 360, +0 ajoutés, ~0 mis à jour, -0 retirés
102 −- **Syncs enregistrés** (sync_log) : 105
90 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
91 +
92 +- **Événements en base** : 437 (dont **360 actifs**)
93 +- **GPS** : 360/360 actifs (100 %)
94 +- **Prix** : 0/360 actifs (0 %)
95 +- **Heure (start_time)** : 349/360 actifs (97 %)
96 +- **Heure de fin** : 0/360 actifs (0 %)
97 +- **Statut (annulé/reporté/complet)** : 15/360 actifs (4 %)
98 +- **Artistes** : 0/360 actifs (0 %)
99 +- **Image** : 315/360 actifs (88 %)
100 +- **Salle (venue)** : 360/360 actifs (100 %)
101 +- **Dernier sync** : 2026-08-25 18:07 — fetched 360, +0 ajoutés, ~0 mis à jour, -0 retirés
102 +- **Syncs enregistrés** (sync_log) : 160
103 103
104 104 ## Erreurs & dépannage
105 105
modified docs/connecteurs/ticketpro.md +45 −29
@@ -1,6 +1,6 @@
1 1 # Ticketpro — billetterie canadienne (tenants Québec) (`ticketpro`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/ticketpro.py`
6 6
@@ -8,7 +8,7 @@
8 8
9 9 - **Source** : [Ticketpro — billetterie canadienne (tenants Québec)](https://www.ticketpro.ca/)
10 10 - **Couverture** : Théâtre St-Denis + Studio St-Denis (Montréal), Fun Nation (Salle Pierre-Mercure, Monument-National, Club Soda), ExpoCité (Québec), Salle André-Gagnon (La Pocatière), Palais des congrès de Montréal, La Belle Église de Chertsey, Théâtre Rialto, Huntingdon… ~200 spectacles / ~1 100 séances datées (vérifié 2026-08-22)
11 −- **Extraction** : API JSON publique de la nouvelle plateforme SPA par tenant (<org>.ticketpro.ca/api/v1/pages, aucun anti-bot ni rendu JS) : 1 fiche par spectacle avec titre, artiste, description, image, lieu complet (adresse, ville, code postal, lat/lng), toutes les dates horodatées, tags et drapeaux cancelled/soldout → 1 Event par (page, date à venir). 6 tenants QC dans data/ticketpro_tenants.json (tproqc agrège la province mais pas tprotsd), dédup inter-tenants par id de page. Lieux de restauration (soupers-spectacles St-Denis) et musée à créneaux exclus par config. startingPrice toujours 0 → aucun prix émis.
11 +- **Extraction** : API JSON publique de la nouvelle plateforme SPA par tenant (<org>.ticketpro.ca/api/v1/pages, aucun anti-bot ni rendu JS) : 1 fiche par spectacle avec titre, artiste, description, image, lieu complet (adresse, ville, code postal, lat/lng), toutes les dates horodatées, tags et drapeaux cancelled/soldout → 1 Event par (page, date à venir). 6 tenants QC dans data/ticketpro_tenants.json (tproqc agrège la province mais pas tprotsd), dédup inter-tenants par id de page. Lieux de restauration (soupers-spectacles St-Denis) et musée à créneaux exclus par config. startingPrice toujours 0 → aucun prix émis. MAJ 2026-08-25 : startingPrice vide dans la liste MAIS tarifs réels publics en 2 GET/spectacle (détail /api/v1/pages/<id> → offre ?options=prices) : priceLevels.price_min en cents (taxes+frais inclus) → prix plancher (92,8 % des séances) + statut PAR séance (saleState, séances datées sans heure → repli au jour) ; cache data/ticketpro_cache.json, TP_PRICE_MAX=40/sync, re-visite 3 j ; 8 tenants QC (+arsenalcontemporarysale, +lefifa)
12 12
13 13 ## Accès
14 14
@@ -39,8 +39,19 @@
39 39 > dédup inter-tenants par id de page (ids globaux). Les lieux
40 40 > de restauration (soupers-spectacles) et le musée à créneaux
41 41 > sont exclus par config (exclure_lieux). Filtre province:
42 −> provinceState == QC. startingPrice est toujours 0 sur ces
43 −> tenants → aucun prix émis (jamais inventé).
42 +> provinceState == QC. startingPrice et prices[] sont toujours
43 +> vides dans la LISTE, mais les tarifs réels sont publics en
44 +> 2 GET par spectacle (vérifié 2026-08-25) :
45 +> /api/v1/pages/<id> (détail → itemsDates.items[].dates[] =
46 +> séances avec saleState + URL relative de l'offre) puis
47 +> /api/v1<url de la 1re séance à venir> (?options=prices) →
48 +> itemHours[].priceLevels[].price_min en CENTS (taxes et
49 +> frais inclus : taxIncluded/serviceChargesIncluded=true).
50 +> Prix plancher de la page appliqué à ses séances + statut
51 +> PAR séance (saleState SOLDOUT/CANCELLED…). Enrichissement
52 +> incrémental : cache data/ticketpro_cache.json,
53 +> TP_PRICE_MAX pages/sync, re-visite 3 j, version de parse
54 +> « v » (patron lavitrine).
44 55 > Accès : billetterie publique, API du site officiel, GET throttlés
45 56 > 1 s, identification honnête, lien d'achat vers la fiche
46 57 > officielle /fr/pages/<id> du tenant.
@@ -49,52 +60,57 @@
49 60
50 61 ## Champs récupérés → schéma
51 62
52 −Complétude mesurée sur les **361 événements actifs** de la source ; exemples tirés d'un événement réel (`ticketpro:1678581012-2026-09-25T2000`).
63 +Complétude mesurée sur les **395 événements actifs** de la source ; exemples tirés d'un événement réel (`ticketpro:1673194652-2026-12-13T1400`).
53 64
54 65 | Champ (`events.*`) | Description | Complétude | Exemple réel |
55 66 |---|---|---|---|
56 −| `title` — Titre | titre de l'événement | 100 % | Fun Nation présente Jérémy Demay: Naturel |
57 −| `description` — Description | texte descriptif (source ou fiche détail) | 98 % | Jérémy Demay, le plus québécois des humoristes français, résidant au Québec depuis plus de 17 an… |
58 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://tproqc.ticketpro.ca/fr/pages/1678581012 |
67 +| `title` — Titre | titre de l'événement | 100 % | Décembre |
68 +| `description` — Description | texte descriptif (source ou fiche détail) | 98 % | Québec Issime fête ses 30 ans et est de retour à la Salle Pierre-Mercure avec son spectacle embl… |
69 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://tproqc.ticketpro.ca/fr/pages/Decembre2026 |
59 70 | `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["arts-scene"] |
60 71 | `venue` — Salle (venue) | nom du lieu | 100 % | Salle Pierre-Mercure du centre Pierre-Péladeau |
61 72 | `address` — Adresse | adresse civique | 99 % | 300, boulevard De Maisonneuve Est |
62 73 | `city` — Ville | municipalité | 100 % | Montréal |
63 74 | `region` — Région | région administrative | 100 % | Montréal |
64 75 | `lat` — GPS (lat/lng) | coordonnées géographiques | 99 % | 45.5134852, -73.5622721 |
65 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-25 |
66 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-25 |
67 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:00 |
76 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-12-13 |
77 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-12-13 |
78 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 14:00 |
68 79 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
69 −| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
70 −| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
71 −| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
80 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 2 % | soldout |
81 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 85 % | 0 |
82 +| `price_min` — Prix min | tarif minimal réel ($) | 85 % | 39.5 |
72 83 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
73 −| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 78 % | ["Jérémy Demay"] |
84 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 70 % | ["Décembre"] |
74 85 | `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
75 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://tproqc.ticketpro.ca/api/v1/resources/1678588362.jpeg?version=2 |
86 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://tproqc.ticketpro.ca/api/v1/resources/1673194602.jpeg?version=1 |
76 87 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
77 88
78 89 ## Fréquence & budgets
79 90
80 91 - **Cadence** : horaire (watcher PM2)
92 +- **Budgets / plafonds (variables d'environnement)** :
93 +
94 +| Variable | Défaut | Rôle |
95 +|---|---|---|
96 +| `TP_PRICE_MAX` | `40` | — |
81 97 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
82 98
83 99 ## Volumétrie & complétude live
84 100
85 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
86 −
87 −- **Événements en base** : 361 (dont **361 actifs**)
88 −- **GPS** : 357/361 actifs (99 %)
89 −- **Prix** : 0/361 actifs (0 %)
90 −- **Heure (start_time)** : 361/361 actifs (100 %)
91 −- **Heure de fin** : 0/361 actifs (0 %)
92 −- **Statut (annulé/reporté/complet)** : 0/361 actifs (0 %)
93 −- **Artistes** : 280/361 actifs (78 %)
94 −- **Image** : 361/361 actifs (100 %)
95 −- **Salle (venue)** : 361/361 actifs (100 %)
96 −- **Dernier sync** : 2026-08-22 22:57 — fetched 364, +361 ajoutés, ~0 mis à jour, -0 retirés
97 −- **Syncs enregistrés** (sync_log) : 1
101 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
102 +
103 +- **Événements en base** : 403 (dont **395 actifs**)
104 +- **GPS** : 390/395 actifs (99 %)
105 +- **Prix** : 334/395 actifs (85 %)
106 +- **Heure (start_time)** : 395/395 actifs (100 %)
107 +- **Heure de fin** : 0/395 actifs (0 %)
108 +- **Statut (annulé/reporté/complet)** : 7/395 actifs (2 %)
109 +- **Artistes** : 275/395 actifs (70 %)
110 +- **Image** : 394/395 actifs (100 %)
111 +- **Salle (venue)** : 395/395 actifs (100 %)
112 +- **Dernier sync** : 2026-08-25 18:54 — fetched 398, +0 ajoutés, ~334 mis à jour, -0 retirés
113 +- **Syncs enregistrés** (sync_log) : 57
98 114
99 115 ## Erreurs & dépannage
100 116
added docs/connecteurs/tourismelaval.md +113 −0
@@ -0,0 +1,113 @@
1 +# Tourisme Laval — événements (ATR) (`tourismelaval`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `tourisme-regional` · Connecteur : `sortika/connectors/tourismelaval.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Tourisme Laval — événements (ATR)](https://www.tourismelaval.com/explorer-possibilites/evenements/)
10 +- **Couverture** : Laval
11 +- **Extraction** : Cartes SSR FacetWP (CPT event, 2 pages : titre, plage de dates FR, catégorie, extrait, image) — REST wp/v2/event verrouillé (401 Kadence) et calendrier tribe mort à la source (déc. 2025)
12 +
13 +## Accès
14 +
15 +- **Type** : tourisme-regional
16 +- **Endpoint** (`BASE`) : `https://www.tourismelaval.com`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : par numéro de page
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : Pages publiques de l'ATR — GET throttlés, UA honnête, lien vers la fiche originale
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/tourismelaval.py : Tourisme Laval (ATR, vague ATR 2026-08)
26 +> Source : tourismelaval.com/explorer-possibilites/evenements/ — calendrier
27 +> officiel de l'ATR (WordPress + FacetWP, CPT « event », cartes
28 +> RENDUES SERVEUR ; vérifié 2026-08-25 : 21 événements sur
29 +> 2 pages). NB : le calendrier « tribe » du même site
30 +> (/calendrier/) est mort à la source (derniers événements
31 +> décembre 2025) ; le CPT event est le vivant. L'API REST
32 +> wp/v2/event est verrouillée (401 Kadence Security) → parse des
33 +> cartes SSR.
34 +> Extraction: GET /explorer-possibilites/evenements/ (+ /page/N/) →
35 +> <article class="grid__item item--event post-<id>"> : lien,
36 +> image, catégorie, titre, extrait, plage de dates FR
37 +> (« 18 août 2026 - 25 août 2026 »). Pas de fiche détail
38 +> (adresse/heure non structurées sur les fiches, vérifié).
39 +> Accès : pages publiques — GET throttlés, identification honnête, lien
40 +> vers la fiche originale. Ni prix ni heure structurés → jamais
41 +> inventés. Ville = Laval (périmètre de l'ATR, île de Laval).
42 +
43 +</details>
44 +
45 +## Champs récupérés → schéma
46 +
47 +Complétude mesurée sur les **21 événements actifs** de la source ; exemples tirés d'un événement réel (`tourismelaval:29863`).
48 +
49 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
50 +|---|---|---|---|
51 +| `title` — Titre | titre de l'événement | 100 % | Quiz nature |
52 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Partez à l’aventure en famille et découvrez les fascinants animaux du bois Papineau, tels que le… |
53 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.tourismelaval.com/explorer-possibilites/evenements/quiz-nature/ |
54 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["famille", "plein-air"] |
55 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
56 +| `address` — Adresse | adresse civique | 0 % | ∅ |
57 +| `city` — Ville | municipalité | 100 % | Laval |
58 +| `region` — Région | région administrative | 100 % | Laval |
59 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 0 % | ∅ |
60 +| `start_date` — Date début | date locale AAAA-MM-JJ | 95 % | 2026-08-18 |
61 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 95 % | 2026-08-25 |
62 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
63 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
64 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
65 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
66 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
67 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
68 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
69 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
70 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.tourismelaval.com/wp-content/uploads/2026/08/185076434-10165041971070277-76571668107… |
71 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
72 +
73 +## Fréquence & budgets
74 +
75 +- **Cadence** : hebdomadaire
76 +- **Budgets / plafonds (variables d'environnement)** :
77 +
78 +| Variable | Défaut | Rôle |
79 +|---|---|---|
80 +| `LVT_PAGE_MAX` | `5` | — |
81 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
82 +
83 +## Volumétrie & complétude live
84 +
85 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
86 +
87 +- **Événements en base** : 21 (dont **21 actifs**)
88 +- **GPS** : 0/21 actifs (0 %)
89 +- **Prix** : 0/21 actifs (0 %)
90 +- **Heure (start_time)** : 0/21 actifs (0 %)
91 +- **Heure de fin** : 0/21 actifs (0 %)
92 +- **Statut (annulé/reporté/complet)** : 0/21 actifs (0 %)
93 +- **Artistes** : 0/21 actifs (0 %)
94 +- **Image** : 21/21 actifs (100 %)
95 +- **Salle (venue)** : 0/21 actifs (0 %)
96 +- **Dernier sync** : 2026-08-25 18:19 — fetched 21, +21 ajoutés, ~0 mis à jour, -0 retirés
97 +- **Syncs enregistrés** (sync_log) : 1
98 +
99 +## Erreurs & dépannage
100 +
101 +Aucune erreur dans `sync_log` pour cette source. ✅
102 +
103 +- Rejouer un sync isolé : `python3 run.py sync --source tourismelaval` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='tourismelaval' ORDER BY ts DESC LIMIT 5;"`
104 +
105 +## Licence / attribution
106 +
107 +- Pages publiques de l'ATR — GET throttlés, UA honnête, lien vers la fiche originale
108 +
109 +## Historique
110 +
111 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
112 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
113 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added docs/connecteurs/troisrivieres.md +106 −0
@@ -0,0 +1,106 @@
1 +# Ville de Trois-Rivières — Calendrier des activités (`troisrivieres`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/troisrivieres.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Trois-Rivières — Calendrier des activités](https://www.v3r.net/activites-et-loisirs/calendrier-des-activites)
10 +- **Couverture** : Trois-Rivières (Mauricie)
11 +- **Extraction** : endpoint JSON public /ajax/wp-queries/archive-activites.php (POST, 1 requête — celui du composant AngularJS ; wp-json 401 contourné proprement). Remplace la source « trois-rivieres » abandonnée (API WP verrouillée)
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`API`) : `https://www.v3r.net/ajax/wp-queries/archive-activites.php`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : site municipal public, robots.txt permissif, identification honnête
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/troisrivieres.py : connecteur Ville de Trois-Rivières — activités
26 +> Source : https://www.v3r.net/activites-et-loisirs/calendrier-des-activites
27 +> (l'API REST WP /wp-json est verrouillée en 401 — consigné
28 +> 2026-08-18 — mais le calendrier AngularJS interroge un
29 +> endpoint AJAX public du thème).
30 +> Extraction: POST JSON /ajax/wp-queries/archive-activites.php (même appel
31 +> que le frontend, découvert dans ActivitesController.js) —
32 +> 1 requête : title, startDate/endDate « dd/mm/yyyy »,
33 +> description (= NOM DU LIEU, vérifié sur les 17 fiches live
34 +> 2026-08-25), imageUrl, url de la fiche. Heure non publiée
35 +> par l'endpoint → jamais inventée.
36 +> Accès : endpoint public du site municipal (aucune auth) ; robots.txt
37 +> n'interdit pas /ajax/. GET/POST directs, identification
38 +> honnête, 1 requête par sync.
39 +> Prix : non publié par l'endpoint → jamais inventé.
40 +
41 +</details>
42 +
43 +## Champs récupérés → schéma
44 +
45 +Complétude mesurée sur les **17 événements actifs** de la source ; exemples tirés d'un événement réel (`troisrivieres:exposition-princesse-fatiguee-patricia-b-lavoie-2026-07-02`).
46 +
47 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
48 +|---|---|---|---|
49 +| `title` — Titre | titre de l'événement | 100 % | Exposition - Princesse fatiguée \| Patricia B.-Lavoie |
50 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
51 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.v3r.net/activites-et-loisirs/calendrier-des-activites/exposition-princesse-fatiguee-… |
52 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["exposition-musee"] |
53 +| `venue` — Salle (venue) | nom du lieu | 100 % | Bibliothèque Aline-Piché |
54 +| `address` — Adresse | adresse civique | 0 % | ∅ |
55 +| `city` — Ville | municipalité | 100 % | Trois-Rivières |
56 +| `region` — Région | région administrative | 100 % | Mauricie |
57 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 59 % | 46.3342514, -72.5904588 |
58 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-07-02 |
59 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-30 |
60 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
61 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
62 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
63 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
64 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
65 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
66 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
67 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Trois-Rivières |
68 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.v3r.net/wp-content/uploads/2026/06/brulee-840x554.jpg |
69 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
70 +
71 +## Fréquence & budgets
72 +
73 +- **Cadence** : quotidien
74 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
75 +
76 +## Volumétrie & complétude live
77 +
78 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
79 +
80 +- **Événements en base** : 17 (dont **17 actifs**)
81 +- **GPS** : 10/17 actifs (59 %)
82 +- **Prix** : 0/17 actifs (0 %)
83 +- **Heure (start_time)** : 0/17 actifs (0 %)
84 +- **Heure de fin** : 0/17 actifs (0 %)
85 +- **Statut (annulé/reporté/complet)** : 0/17 actifs (0 %)
86 +- **Artistes** : 0/17 actifs (0 %)
87 +- **Image** : 17/17 actifs (100 %)
88 +- **Salle (venue)** : 17/17 actifs (100 %)
89 +- **Dernier sync** : 2026-08-25 18:29 — fetched 17, +17 ajoutés, ~0 mis à jour, -0 retirés
90 +- **Syncs enregistrés** (sync_log) : 1
91 +
92 +## Erreurs & dépannage
93 +
94 +Aucune erreur dans `sync_log` pour cette source. ✅
95 +
96 +- Rejouer un sync isolé : `python3 run.py sync --source troisrivieres` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='troisrivieres' ORDER BY ts DESC LIMIT 5;"`
97 +
98 +## Licence / attribution
99 +
100 +- site municipal public, robots.txt permissif, identification honnête
101 +
102 +## Historique
103 +
104 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
105 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
106 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/tuxedobillet.md +53 −36
@@ -1,6 +1,6 @@
1 1 # Tuxedo Billet — billetterie québécoise multi-tenants (`tuxedobillet`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/tuxedobillet.py`
6 6
@@ -8,16 +8,16 @@
8 8
9 9 - **Source** : [Tuxedo Billet — billetterie québécoise multi-tenants](https://www.tuxedobillet.com/)
10 10 - **Couverture** : 29 salles/diffuseurs QC : Théâtre Outremont, Le Gesù, Centre Pierre-Péladeau, FTA, FIMAV, Espace Théâtre (Mont-Laurier), L'Anglicane (Lévis), Chandler, Lac-Mégantic, Îles-de-la-Madeleine…
11 −- **Extraction** : Acteur Apify maison gorgeous_thistle/ka-tuxedo (Playwright, proxy résidentiel CA, code versionné dans apify/ka-tuxedo/) : les tenants <org>.tuxedobillet.com sont des SPA Angular sur Firebase (RTDB verrouillée, 401 même avec jeton anonyme) → rendu navigateur obligatoire. 1 run/TUX_TTL_HOURS (20 h), 29 tenants QC configurés dans data/tuxedo_tenants.json (ville vérifiée). Liste sans heure ni prix → jamais inventés.
11 +- **Extraction** : Acteur Apify maison gorgeous_thistle/ka-tuxedo (Playwright, proxy résidentiel CA, code versionné dans apify/ka-tuxedo/) : les tenants <org>.tuxedobillet.com sont des SPA Angular sur Firebase (RTDB verrouillée, 401 même avec jeton anonyme) → rendu navigateur obligatoire. 1 run/TUX_TTL_HOURS (20 h), 29 tenants QC configurés dans data/tuxedo_tenants.json (ville vérifiée). Liste sans heure ni prix → jamais inventés. MAJ 2026-08-25 : fiches /main/<slug> rendues Scrapfly render_js (TUX_FICHE_MAX=20/sync, re-visite 7 j, version v2 = mois janv./févr. compris, +71 événements) : 1 Event par séance datée avec HEURE, statut COMPLET→soldout, catégorie, description. Prix et adresse non publiés (choix des sièges seulement) → jamais inventés. 35 tenants QC (+6 : chateauscenes, azimutdiffusion, culture-umontreal, mcgillathletics, osjm, lesaintjean) ; 10 tenants à liens /Web/ incompatibles consignés dans le _comment du fichier tenants
12 12 - **Note** : RÉACTIVÉE 2026-08-21 (Phase 4) : l'apex reste vide mais les tenants vivent. Première source consommée via un acteur Apify (billetterie dure à crawler). Énumération des tenants non exhaustive — compléter via Serper/config.
13 13
14 14 ## Accès
15 15
16 16 - **Type** : billetterie
17 17 - **Endpoint** : voir URL source (https://www.tuxedobillet.com/)
18 −- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Auth** : Aucune côté source ; `SCRAPFLY_API_KEY` requis pour le backend Scrapfly
19 19 - **Pagination** : aucune (flux unique)
20 −- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Backend** : Scrapfly (anti-bot / rendu JS) via `base.get_scrapfly()`
21 21 - **Throttle** : 1.0 s entre requêtes
22 22 - **Accès légal** : Billetteries publiques ; rendu navigateur modéré (1 page/tenant/run, ~1 run par jour), lien d'achat vers la fiche officielle du tenant
23 23
@@ -38,42 +38,56 @@
38 38 > salles bougent lentement). Résultat mis en cache disque
39 39 > (data/tuxedobillet_cache.json) et réémis entre deux runs.
40 40 > Tenants configurés dans data/tuxedo_tenants.json (Québec
41 −> seulement, ville vérifiée). La liste ne publie ni heure ni
42 −> prix → jamais inventés (heure « non confirmée » côté front).
41 +> seulement, ville vérifiée).
42 +> FICHES /main/<slug> rendues via Scrapfly render_js (le GET
43 +> direct ne renvoie que la coquille Angular, vérifié
44 +> 2026-08-25) : chaque fiche publie ses SÉANCES (jour + date
45 +> + HEURE « 20H00 » + étiquette « COMPLET »), la catégorie et
46 +> la description → 1 Event par séance datée, heure et statut
47 +> soldout captés. Enrichissement incrémental (clé « fiches »
48 +> du cache, TUX_FICHE_MAX/sync, re-visite 7 j, version de
49 +> parse « v » — patron lavitrine ; v2 : abréviations FR
50 +> « janv./févr. » comprises, tuiles et séances de janvier-
51 +> février récupérées). Les fiches ne publient NI
52 +> prix NI adresse de salle (vérifié : le tarif n'apparaît
53 +> qu'au choix des sièges) → jamais inventés.
43 54 > Accès : billetteries publiques ; rendu navigateur modéré (1 page par
44 −> tenant par run, ~1 run/jour), UA/robots gérés par Apify,
45 −> lien d'achat = la fiche officielle du tenant.
55 +> tenant par run Apify ~1/jour + TUX_FICHE_MAX fiches
56 +> Scrapfly/sync throttlées), lien d'achat = la fiche
57 +> officielle du tenant.
46 58 > Secrets : APIFY_TOKEN requis (dans .env) — sans lui, le connecteur
47 59 > réémet le cache et n'échoue que s'il n'y a aucun cache.
60 +> SCRAPFLY_API_KEY requis pour l'enrichissement des fiches —
61 +> sans lui, la liste seule est émise (comportement d'avant).
48 62
49 63 </details>
50 64
51 65 ## Champs récupérés → schéma
52 66
53 −Complétude mesurée sur les **442 événements actifs** de la source ; exemples tirés d'un événement réel (`tuxedobillet:centrepierrepeladeau-le-comte-de-monte-cristo`).
67 +Complétude mesurée sur les **382 événements actifs** de la source ; exemples tirés d'un événement réel (`tuxedobillet:centreculturelbeloeil-stephanie-boulay-2026-09-25`).
54 68
55 69 | Champ (`events.*`) | Description | Complétude | Exemple réel |
56 70 |---|---|---|---|
57 −| `title` — Titre | titre de l'événement | 100 % | Le Comte de Monte-Cristo — Présenté par Les Agents Doubles Productions |
58 −| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
59 −| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://centrepierrepeladeau.tuxedobillet.com/main/le-comte-de-monte-cristo |
60 −| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
61 −| `venue` — Salle (venue) | nom du lieu | 100 % | Centre Pierre-Péladeau |
71 +| `title` — Titre | titre de l'événement | 100 % | Stéphanie Boulay — Est-ce que quelqu'un me voit? |
72 +| `description` — Description | texte descriptif (source ou fiche détail) | 10 % | Est-ce que quelqu’un me voit ? est un spectacle intime et sans détour où Stéphanie Boulay se liv… |
73 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://centreculturelbeloeil.tuxedobillet.com/main/stephanie-boulay |
74 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
75 +| `venue` — Salle (venue) | nom du lieu | 100 % | Centre culturel de Belœil |
62 76 | `address` — Adresse | adresse civique | 0 % | ∅ |
63 −| `city` — Ville | municipalité | 100 % | Montréal |
64 −| `region` — Région | région administrative | 96 % | Montréal |
65 −| `lat` — GPS (lat/lng) | coordonnées géographiques | 44 % | 45.5134852, -73.5622721 |
66 −| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-23 |
67 −| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-23 |
68 −| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
77 +| `city` — Ville | municipalité | 100 % | Belœil |
78 +| `region` — Région | région administrative | 89 % | ∅ |
79 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 80 % | ∅ |
80 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-25 |
81 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-25 |
82 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 9 % | 20:00 |
69 83 | `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
70 −| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
84 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 2 % | soldout |
71 85 | `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
72 86 | `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
73 87 | `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
74 88 | `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
75 −| `organizer` — Organisateur | promoteur / organisme | 100 % | Centre Pierre-Péladeau |
76 −| `image` — Image | visuel (og:image ou média source) | 100 % | https://tuxedov1.blob.core.windows.net/tuxedo-centrepierrepeladeau/show/Le%20Comte%20de%20Monte-… |
89 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Centre culturel de Belœil |
90 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://tuxedov1.blob.core.windows.net/tuxedo-centreculturelbeloeil/show/St%C3%A9phanie%20Boulay… |
77 91 | `website` — Site web | site officiel de l'événement | 0 % | ∅ |
78 92
79 93 ## Fréquence & budgets
@@ -85,24 +99,27 @@ Complétude mesurée sur les **442 événements actifs** de la source ; exemples
85 99 |---|---|---|
86 100 | `TUX_TTL_HOURS` | `20` | — |
87 101 | `TUX_POLL_TIMEOUT` | `1500` | s |
102 +| `TUX_FICHE_MAX` | `20` | — |
88 103 | `APIFY_TOKEN` | `—` | — |
104 +| `SCRAPFLY_API_KEY` | `—` | — |
105 +| `SCRAPFLY_API_KEY` | `—` | — |
89 106 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
90 107
91 108 ## Volumétrie & complétude live
92 109
93 −*(mesuré le 2026-08-22 23:05 EDT dans `data/sortika.db`)*
94 −
95 −- **Événements en base** : 580 (dont **442 actifs**)
96 −- **GPS** : 195/442 actifs (44 %)
97 −- **Prix** : 0/442 actifs (0 %)
98 −- **Heure (start_time)** : 0/442 actifs (0 %)
99 −- **Heure de fin** : 0/442 actifs (0 %)
100 −- **Statut (annulé/reporté/complet)** : 0/442 actifs (0 %)
101 −- **Artistes** : 0/442 actifs (0 %)
102 −- **Image** : 442/442 actifs (100 %)
103 −- **Salle (venue)** : 442/442 actifs (100 %)
104 −- **Dernier sync** : 2026-08-22 22:29 — fetched 442, +0 ajoutés, ~0 mis à jour, -0 retirés
105 −- **Syncs enregistrés** (sync_log) : 22
110 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
111 +
112 +- **Événements en base** : 700 (dont **382 actifs**)
113 +- **GPS** : 305/382 actifs (80 %)
114 +- **Prix** : 0/382 actifs (0 %)
115 +- **Heure (start_time)** : 33/382 actifs (9 %)
116 +- **Heure de fin** : 0/382 actifs (0 %)
117 +- **Statut (annulé/reporté/complet)** : 7/382 actifs (2 %)
118 +- **Artistes** : 0/382 actifs (0 %)
119 +- **Image** : 382/382 actifs (100 %)
120 +- **Salle (venue)** : 382/382 actifs (100 %)
121 +- **Dernier sync** : 2026-08-25 19:00 — fetched 351, +104 ajoutés, ~0 mis à jour, -0 retirés
122 +- **Syncs enregistrés** (sync_log) : 78
106 123
107 124 ## Erreurs & dépannage
108 125
added docs/connecteurs/victoriaville.md +110 −0
@@ -0,0 +1,110 @@
1 +# Ville de Victoriaville — Quoi faire à Victo (`victoriaville`)
2 +
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/victoriaville.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Victoriaville — Quoi faire à Victo](https://victoriaville.ca/calendriers-cours-et-ateliers/quoi-faire-a-victo)
10 +- **Couverture** : Victoriaville (Centre-du-Québec)
11 +- **Extraction** : HTML serveur Craft, navigation ?dateDebut par semaine (VIC_WEEKS=4) ; « 00h00 » = placeholder toute-la-journée → heure None
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`LIST_URL`) : `https://victoriaville.ca/calendriers-cours-et-ateliers/quoi-faire-a-victo`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 1.0 s entre requêtes
21 +- **Accès légal** : page municipale publique, robots.txt permissif
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/victoriaville.py : connecteur Ville de Victoriaville — « Quoi
26 +> faire à Victo » (calendrier municipal officiel).
27 +> Source : https://victoriaville.ca/calendriers-cours-et-ateliers/
28 +> quoi-faire-a-victo (Craft CMS, rendu serveur).
29 +> Extraction: la page liste UNE SEMAINE de séances, jour par jour
30 +> (sections id="day-YYYY-MM-DD", ~115 <article> par semaine :
31 +> titre, lieu, heures « De 9 h à 17 h », image CloudFront,
32 +> lien fiche ?date=YYYY-MM-DD). Navigation par
33 +> ?dateDebut=YYYY-MM-DD → on couvre VIC_WEEKS semaines
34 +> (défaut 4) = 1 GET/semaine. Vérifié 2026-08-25.
35 +> Accès : page municipale publique, robots.txt permissif (Craft :
36 +> /cpresources/ et /vendor/ seulement). GET directs throttlés,
37 +> identification honnête.
38 +> Prix : non publié dans la liste → jamais inventé.
39 +
40 +</details>
41 +
42 +## Champs récupérés → schéma
43 +
44 +Complétude mesurée sur les **290 événements actifs** de la source ; exemples tirés d'un événement réel (`victoriaville:promo-estivale-tennis-2026-08-25`).
45 +
46 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
47 +|---|---|---|---|
48 +| `title` — Titre | titre de l'événement | 100 % | Tennis intérieur |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://victoriaville.ca/calendriers-cours-et-ateliers/quoi-faire-a-victo/evenements/promo-estiv… |
51 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
52 +| `venue` — Salle (venue) | nom du lieu | 99 % | Complexe sportif Sani Marc |
53 +| `address` — Adresse | adresse civique | 0 % | ∅ |
54 +| `city` — Ville | municipalité | 100 % | Victoriaville |
55 +| `region` — Région | région administrative | 100 % | Centre-du-Québec |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 58 % | 46.0730704, -71.9460673 |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-25 |
59 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 78 % | 08:00 |
60 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
61 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
62 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
63 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
64 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
65 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
66 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Victoriaville |
67 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://d16k40le270owi.cloudfront.net/_430x240_crop_center-center_100_none/527239/Affiche-Promo-… |
68 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
69 +
70 +## Fréquence & budgets
71 +
72 +- **Cadence** : quotidien
73 +- **Budgets / plafonds (variables d'environnement)** :
74 +
75 +| Variable | Défaut | Rôle |
76 +|---|---|---|
77 +| `VIC_WEEKS` | `4` | — |
78 +- **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
79 +
80 +## Volumétrie & complétude live
81 +
82 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
83 +
84 +- **Événements en base** : 290 (dont **290 actifs**)
85 +- **GPS** : 168/290 actifs (58 %)
86 +- **Prix** : 0/290 actifs (0 %)
87 +- **Heure (start_time)** : 226/290 actifs (78 %)
88 +- **Heure de fin** : 0/290 actifs (0 %)
89 +- **Statut (annulé/reporté/complet)** : 0/290 actifs (0 %)
90 +- **Artistes** : 0/290 actifs (0 %)
91 +- **Image** : 290/290 actifs (100 %)
92 +- **Salle (venue)** : 287/290 actifs (99 %)
93 +- **Dernier sync** : 2026-08-25 18:29 — fetched 290, +290 ajoutés, ~0 mis à jour, -0 retirés
94 +- **Syncs enregistrés** (sync_log) : 1
95 +
96 +## Erreurs & dépannage
97 +
98 +Aucune erreur dans `sync_log` pour cette source. ✅
99 +
100 +- Rejouer un sync isolé : `python3 run.py sync --source victoriaville` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='victoriaville' ORDER BY ts DESC LIMIT 5;"`
101 +
102 +## Licence / attribution
103 +
104 +- page municipale publique, robots.txt permissif
105 +
106 +## Historique
107 +
108 +- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
109 +- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
110 +- **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/weezevent.md +6 −5
@@ -1,21 +1,22 @@
1 1 # Weezevent — billetterie libre-service (`weezevent`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 5 **Statut : abandonnee** · Type : `billetterie` · Connecteur : *aucun (source sans connecteur runtime)*
6 6
7 7 ## Description
8 8
9 9 - **Source** : [Weezevent — billetterie libre-service](https://www.weezevent.com/)
10 −- **Couverture** : Festivals/événements FR + QC
10 +- **Couverture** : minisites my.weezevent.com avec JSON-LD Event complet, mais non énumérables (sitemap = 28 URLs tournantes France, pas de recherche publique, API par organisateur)
11 11 - **Extraction** : —
12 +- **Note** : ÉCART CONFIRMÉ 2026-08-25 : aucune surface d'énumération publique — tout connecteur dépendrait de l'index Google. Les gros clients QC sont déjà captés par les ATR/agrégateurs.
12 13
13 14 ## Accès
14 15
15 16 - **Type** : billetterie
16 17 - **Endpoint** : voir URL source (https://www.weezevent.com/)
17 18 - **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 −- **Accès légal** : robots.txt permissif
19 +- **Accès légal** : robots.txt permissif mais aucune surface de découverte publique
19 20
20 21 ## Champs récupérés → schéma
21 22
@@ -35,11 +36,11 @@
35 36
36 37 ## Licence / attribution
37 38
38 −- robots.txt permissif
39 +- robots.txt permissif mais aucune surface de découverte publique
39 40
40 41 ## Historique
41 42
42 −- **Source abandonnée** — sans note
43 +- **Source abandonnée** — ÉCART CONFIRMÉ 2026-08-25 : aucune surface d'énumération publique — tout connecteur dépendrait de l'index Google. Les gros clients QC sont déjà captés par les ATR/agrégateurs.
43 44 - **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
44 45 - **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
45 46 - **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
modified docs/connecteurs/zeffy.md +94 −13
@@ -1,45 +1,126 @@
1 −# Zeffy — billetterie OSBL (Montréal) (`zeffy`)
1 +# Zeffy — billetterie OSBL (`zeffy`)
2 2
3 −> Fiche générée le 2026-08-22 23:05 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
3 +> Fiche générée le 2026-08-25 19:01 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 4
5 −**Statut : abandonnee** · Type : `billetterie` · Connecteur : *aucun (source sans connecteur runtime)*
5 +**Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/zeffy.py`
6 6
7 7 ## Description
8 8
9 −- **Source** : [Zeffy — billetterie OSBL (Montréal)](https://www.zeffy.com/)
10 −- **Couverture** : Événements communautaires/OSBL partout au Canada et aux É.-U.
11 −- **Extraction** : —
9 +- **Source** : [Zeffy — billetterie OSBL](https://www.zeffy.com/)
10 +- **Couverture** : ~100-200 événements OSBL QC actifs (galas, spectacles-bénéfice, ateliers communautaires), toutes régions
11 +- **Extraction** : sitemap des organisations (api.zeffy.com/sitemap/organizations/sitemap-0.xml, ~4 400 pages fr-CA/en-CA) → pages org rendues serveur (__NEXT_DATA__ organizationPage.upcomingEvents : dates UTC, adresse, image, URL) filtrées « , QC » et formCategory=Event, puis fiches billetterie JSON-LD offers[].price pour les tarifs réels ; incrémental cache 7 j, plafonds ZEFFY_ORG_MAX=400 / ZEFFY_DETAIL_MAX=80. RÉACTIVÉE 2026-08-25 (le sitemap billets était infaisable ; les pages d'organisations sont la bonne surface)
12 12
13 13 ## Accès
14 14
15 15 - **Type** : billetterie
16 −- **Endpoint** : voir URL source (https://www.zeffy.com/)
16 +- **Endpoint** (`ORG_SITEMAP`) : `https://api.zeffy.com/sitemap/organizations/sitemap-0.xml`
17 17 - **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 −- **Accès légal** : robots.txt permissif, sitemaps publics
18 +- **Pagination** : par numéro de page, parcours de sitemap XML
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.8 s entre requêtes
21 +- **Accès légal** : robots.txt Disallow: /*=$ uniquement (aucune URL visée) + sitemaps publiés ; GET throttlés, identification honnête, pas d'anti-bot constaté
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/zeffy.py : connecteur Zeffy (zeffy.com) — billetterie OSBL
26 +> Source : billetterie gratuite très utilisée par les OSBL québécois
27 +> (galas, spectacles-bénéfice, marches, ateliers communautaires).
28 +> Extraction: 1) sitemap des organisations (api.zeffy.com/sitemap/
29 +> organizations/sitemap-0.xml) → pages d'organisation fr-CA/en-CA
30 +> (~4 400, dont ~1 300 fr-CA) ; 2) chaque page d'organisation est
31 +> rendue serveur (Next.js __NEXT_DATA__ → organizationPage.
32 +> upcomingEvents : titre, description, adresse postale complète,
33 +> occurrences startUtc/endUtc, bannière, URL de billetterie) —
34 +> AUCUN rendu JS requis ; 3) fiches billetterie des événements QC
35 +> retenus : JSON-LD (offers[].price) → tarifs réels publiés.
36 +> Périmètre QUÉBEC : on ne garde que les événements datés dont
37 +> l'adresse porte « , QC » (les organisations fr-CA/en-CA
38 +> couvrent tout le Canada). Le sitemap des BILLETS (55 000+ URLs
39 +> fr-CA mêlant dons/adhésions/archives, sans dates) est ignoré :
40 +> la page d'organisation est la seule surface « à venir » fiable.
41 +> INCRÉMENTAL avec cache disque (data/zeffy_cache.json) :
42 +> nouvelles organisations d'abord, re-visite après 7 jours,
43 +> plafonds ZEFFY_ORG_MAX pages org et ZEFFY_DETAIL_MAX fiches
44 +> prix par sync.
45 +> Accès : robots.txt « User-agent: * / Disallow: /*=$ » (seules les URLs
46 +> finissant par « = » sont exclues — aucune de celles qu'on
47 +> visite) + sitemaps publiés ; GET throttlés, identification
48 +> honnête, pas d'anti-bot constaté (sondé 2026-08-25).
49 +> Prix : offers[].price du JSON-LD de la fiche (tarifs affichés par
50 +> l'organisme ; Zeffy suggère un pourboire plateforme au paiement,
51 +> non inclus). is_free=True si TOUS les tarifs publiés sont à 0 $.
52 +> Jamais inventé (événement sans fiche lisible → prix inconnu).
53 +
54 +</details>
19 55
20 56 ## Champs récupérés → schéma
21 57
22 −*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
58 +Complétude mesurée sur les **15 événements actifs** de la source ; exemples tirés d'un événement réel (`zeffy:e77cf52c-dd96-4290-b747-50ab8564771d:60c945f7-c2e8-44f8-82dc-c840f808e2ed`).
59 +
60 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
61 +|---|---|---|---|
62 +| `title` — Titre | titre de l'événement | 100 % | Dîner Spaghetti |
63 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Partagez un bon repas de spaghetti 🍝 et un moment convivial en l’honneur des 175 ans de Sainte‑H… |
64 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.zeffy.com/en-CA/ticketing/diner-spaghetti-6 |
65 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
66 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
67 +| `address` — Adresse | adresse civique | 100 % | 1299 Rte Sainte-Thérèse |
68 +| `city` — Ville | municipalité | 100 % | Sainte-Hénédine |
69 +| `region` — Région | région administrative | 100 % | Chaudière-Appalaches |
70 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 80 % | 46.5585152, -70.9836412 |
71 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-23 |
72 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-23 |
73 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 11:00 |
74 +| `end_time` — Heure de fin (end_time) | heure locale HH:MM (Québec) | 100 % | 14:00 |
75 +| `status` — Statut | cancelled / postponed / soldout (NULL = prévu) | 0 % | ∅ |
76 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 100 % | 0 |
77 +| `price_min` — Prix min | tarif minimal réel ($) | 100 % | 5.0 |
78 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 100 % | Adulte 25 $ · Enfant (5 à 12 ans) 15 $ · Enfant 0-5 ans 5 $ |
79 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
80 +| `organizer` — Organisateur | promoteur / organisme | 100 % | 175e de Sainte-Hénédine |
81 +| `image` — Image | visuel (og:image ou média source) | 87 % | https://res.cloudinary.com/hxn9dbuhd/image/upload/v1785879985/organizations/a/2/5/b/a25bf52d-2cf… |
82 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
23 83
24 84 ## Fréquence & budgets
25 85
26 −- **Cadence** : —
86 +- **Cadence** : quotidienne
87 +- **Budgets / plafonds (variables d'environnement)** :
88 +
89 +| Variable | Défaut | Rôle |
90 +|---|---|---|
91 +| `ZEFFY_LOCALES` | `fr-CA,en-CA` | — |
92 +| `ZEFFY_ORG_MAX` | `400` | — |
93 +| `ZEFFY_DETAIL_MAX` | `80` | — |
27 94 - **Budget global** : géocodage Nominatim plafonné à `SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, toutes sources confondues (`sortika/geocode.py`, cache disque).
28 95
29 96 ## Volumétrie & complétude live
30 97
31 −*Source sans connecteur runtime : aucune donnée en base.*
98 +*(mesuré le 2026-08-25 19:01 EDT dans `data/sortika.db`)*
99 +
100 +- **Événements en base** : 15 (dont **15 actifs**)
101 +- **GPS** : 12/15 actifs (80 %)
102 +- **Prix** : 15/15 actifs (100 %)
103 +- **Heure (start_time)** : 15/15 actifs (100 %)
104 +- **Heure de fin** : 15/15 actifs (100 %)
105 +- **Statut (annulé/reporté/complet)** : 0/15 actifs (0 %)
106 +- **Artistes** : 0/15 actifs (0 %)
107 +- **Image** : 13/15 actifs (87 %)
108 +- **Salle (venue)** : 0/15 actifs (0 %)
109 +- **Dernier sync** : 2026-08-25 18:49 — fetched 15, +15 ajoutés, ~0 mis à jour, -0 retirés
110 +- **Syncs enregistrés** (sync_log) : 1
32 111
33 112 ## Erreurs & dépannage
34 113
114 +Aucune erreur dans `sync_log` pour cette source. ✅
115 +
116 +- Rejouer un sync isolé : `python3 run.py sync --source zeffy` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='zeffy' ORDER BY ts DESC LIMIT 5;"`
35 117
36 118 ## Licence / attribution
37 119
38 −- robots.txt permissif, sitemaps publics
120 +- robots.txt Disallow: /*=$ uniquement (aucune URL visée) + sitemaps publiés ; GET throttlés, identification honnête, pas d'anti-bot constaté
39 121
40 122 ## Historique
41 123
42 −- **Source abandonnée** — sans note
43 124 - **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente (description, heure, tarifs réels `/plugins/rates/<id>`), contacts Tourinsoft (SITQ), heure locale Montréal via JSON-LD.
44 125 - **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : colonnes additives `events.start_time` (HH:MM locale QC) et `events.artists` (JSON) + migration auto ; heure captée partout où la source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs (brossard, ticketmaster, bandsintown).
45 126 - **2026-08-19 — Phase 2 (travail de moine)** : colonnes additives `end_time`, `status` (annulé/reporté/complet) et `quarantine` (règles anti-aberrations, non publié mais réintégrable) ; règle d'ARCHIVAGE des événements terminés à chaque cycle (jamais supprimés) ; dedup_key inclut désormais l'heure (les séances multiples d'un même jour ne sont plus écrasées à l'affichage) ; alerte « 0 événement futur / chute > 80 % » par source (data/future_counts.json + /api/sources) ; heure et description La Vitrine ; end_time/organisateur/statut Eventbrite ; og:description Atuvu ; premières parties + portes + tournée evenko ; offres billets Bandsintown ; taxonomie type-event + description intégrale Brossard ; image Longueuil ; géocodage par ADRESSE CIVIQUE prioritaire ; villes normalisées au Répertoire MAMH (data/villes_canon.json) ; laval consignée « morte-a-la-source ».
added sortika/connectors/abitibi.py +98 −0
@@ -0,0 +1,98 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/abitibi.py : Tourisme Abitibi-Témiscamingue (ATR, vague ATR 2026-08)
5 +# Source : abitibi-temiscamingue.org/festivals-et-evenements/ (domaine
6 +# tourisme-abitibi-temiscamingue.org → redirection) — répertoire
7 +# officiel des festivals et événements de l'ATR (WordPress,
8 +# gisement Tourinsoft : images mto.media.tourinsoft.eu).
9 +# Extraction: pages liste RENDUES SERVEUR /festivals-et-evenements/[page/N/]
10 +# (~3 pages × 12 cartes vérifié 2026-08-25) : lien
11 +# /evenements/<slug>/, image, ville, titre, plage de dates FR
12 +# (« Du 26 au 30 août 2026 »). Le site est derrière un challenge
13 +# Cloudflare (403 + « Just a moment ») → chaque GET passe par
14 +# l'AUTO-ESCALADE anti-bot de BaseConnector.get() (Oxylabs →
15 +# Scrapfly ASP → Bright Data) ; budget borné : ABT_PAGE_MAX
16 +# pages/sync, AUCUNE fiche détail (la carte porte tout ce que la
17 +# source publie de structuré ; wp-json est aussi derrière le
18 +# challenge et n'expose pas de CPT événement).
19 +# Accès : pages publiques de l'ATR — requêtes throttlées et plafonnées
20 +# (coût par requête), identification honnête, lien vers la fiche
21 +# originale. Ni prix, ni heure, ni description structurés sur la
22 +# carte → jamais inventés.
23 +# -----------------------------------------------------------------------------
24 +from __future__ import annotations
25 +
26 +import os
27 +import re
28 +
29 +from ..normalize import clean_text, parse_date_range_fr
30 +from ..schema import Event
31 +from .base import BaseConnector
32 +
33 +BASE = "https://abitibi-temiscamingue.org"
34 +LIST_URL = BASE + "/festivals-et-evenements/"
35 +ABT_PAGE_MAX = int(os.environ.get("ABT_PAGE_MAX", "4"))
36 +
37 +# une carte du répertoire : post-id, image de fond, lien, ville, titre, date
38 +_CARD_RE = re.compile(
39 + r'data-post-id="(\d+)">\s*'
40 + r'<div class="quanto-blog-thumb">\s*'
41 + r'<a class="aspect-ratio" href="(https://abitibi-temiscamingue\.org/'
42 + r'evenements/[^"]+)"\s*style="background-image: url\(\'([^\']*)\'\);">'
43 + r'</a>.*?<li><span>([^<]*?)\s*<svg.*?'
44 + r'<p class="h6"><a href="\2">(.*?)</a></p>\s*'
45 + r'(?:<p class="h6 repertoire-preview-date"[^>]*>([^<]*)</p>)?', re.S)
46 +
47 +
48 +class AbitibiConnector(BaseConnector):
49 + source_id = "abitibi"
50 + request_delay = 1.0 # chaque requête peut coûter un crédit Scrapfly
51 + timeout = 120
52 +
53 + def _cards(self, html: str) -> list[dict]:
54 + out = []
55 + for m in _CARD_RE.finditer(html):
56 + pid, url, img, city, title, date_raw = m.groups()
57 + start, end = parse_date_range_fr(date_raw or "")
58 + out.append({
59 + "external_id": pid,
60 + "url": url,
61 + "title": clean_text(title),
62 + "city": clean_text(city),
63 + "start_date": start, "end_date": end,
64 + "image": (img or "").strip(),
65 + })
66 + return out
67 +
68 + def fetch(self) -> list[Event]:
69 + events: list[Event] = []
70 + seen: set[str] = set()
71 + for page in range(1, ABT_PAGE_MAX + 1):
72 + url = LIST_URL if page == 1 else f"{LIST_URL}page/{page}/"
73 + try:
74 + cards = self._cards(self.get(url).text)
75 + except Exception:
76 + break
77 + new = [c for c in cards if c["external_id"] not in seen]
78 + if not new:
79 + break
80 + for c in new:
81 + seen.add(c["external_id"])
82 + if not c["title"]:
83 + continue
84 + events.append(Event(
85 + source=self.source_id,
86 + external_id=c["external_id"],
87 + url=c["url"],
88 + title=c["title"],
89 + raw_categories=["Festivals et événements", c["title"]],
90 + city=c["city"],
91 + tourist_region="Abitibi-Témiscamingue",
92 + start_date=c["start_date"],
93 + end_date=c["end_date"],
94 + image=c["image"],
95 + ))
96 + if len(cards) < 12: # page incomplète (12/page) = dernière
97 + break
98 + return events
modified sortika/connectors/atuvu.py +71 −12
@@ -3,15 +3,23 @@
3 3 # Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 4 # connectors/atuvu.py : connecteur Atuvu.ca — calendrier culturel (Grand Montréal +)
5 5 # Source : atuvu.ca (théâtre, musique, humour, danse, expositions…)
6 −# Extraction: SCRAPFLY obligatoire (Cloudflare) — sitemaps XML publics
6 +# Extraction: GET direct d'abord (vérifié 200 depuis le nœud de prod,
7 +# 2026-08-25 ; escalade anti-bot auto du BaseConnector), repli
8 +# SCRAPFLY si défi Cloudflare — sitemaps XML publics
7 9 # (evenements_N_2000.xml, lastmod par fiche) puis pages fiche
8 −# (h1, blocs .event-play : date FR, salle, ville, prix).
9 −# INCRÉMENTAL avec cache disque (data/atuvu_cache.json) : seules
10 −# les fiches nouvelles ou modifiées sont re-scrapées, plafonné à
11 −# ATUVU_MAX_FETCH pages/sync pour contenir le coût en crédits.
10 +# (h1, blocs .event-play : date FR + HEURE, salle, ville, prix).
11 +# INCRÉMENTAL avec cache disque (data/atuvu_cache.json), version
12 +# de parse « v » (patron lavitrine) : fiches nouvelles/modifiées
13 +# d'abord, backfill v<2 (rows figés sans heure/prix par un
14 +# parseur antérieur — constaté 2026-08-25), puis re-visite
15 +# roulante 14 j des fiches à venir (les tarifs apparaissent
16 +# souvent APRÈS la mise en vente, sans bump de lastmod),
17 +# plafonné à ATUVU_MAX_FETCH pages/sync.
12 18 # Accès : robots.txt « User-Agent: * / Allow: / » + sitemap public.
13 19 # Prix : bloc .price-reg de la fiche (prix régulier) → price_min ;
14 20 # « Invitation gratuite » = offre membre, PAS gratuité → ignorée.
21 +# Séances : 2 représentations le même jour = 2 événements (suffixe -HHMM
22 +# dès la 2ᵉ séance du jour ; la 1ʳᵉ garde l'id historique).
15 23 # -----------------------------------------------------------------------------
16 24 from __future__ import annotations
17 25
@@ -28,6 +36,12 @@ SITEMAP_INDEX = "https://atuvu.ca/atuvu_sitemap_index.xml"
28 36 ATUVU_MAX_FETCH = int(__import__("os").environ.get("ATUVU_MAX_FETCH", "120")) # fiches/sync (coût Scrapfly)
29 37 LASTMOD_WINDOW_DAYS = 90 # fiches modifiées dans cette fenêtre seulement
30 38 SHARDS_TO_READ = 3 # derniers shards du sitemap (ids les plus récents)
39 +PARSE_V = 2 # version du parseur (bump → backfill des rows figés)
40 +REFRESH_AFTER = 14 * 86400 # re-visite roulante d'une fiche à venir (tarifs)
41 +
42 +# défi Cloudflare renvoyé en 200 (non escaladé par get()) → repli Scrapfly
43 +_BLOCK_HINTS = ("just a moment", "attention required", "cf-challenge",
44 + "challenge-platform")
31 45
32 46 _LOC_RE = re.compile(r"<loc>([^<]+)</loc>\s*(?:<lastmod>([^<]+)</lastmod>)?")
33 47 _URL_BLOCK_RE = re.compile(r"<url>(.*?)</url>", re.S)
@@ -45,15 +59,29 @@ class AtuvuConnector(BaseConnector):
45 59 source_id = "atuvu"
46 60 request_delay = 1.0
47 61
62 + # -- extraction -------------------------------------------------------
63 + def _fetch_html(self, url: str) -> str:
64 + """GET direct d'abord (fonctionne depuis le nœud de prod, escalade
65 + anti-bot automatique du BaseConnector sur 403/429), repli Scrapfly si
66 + la page renvoyée est un défi Cloudflare servi en 200."""
67 + try:
68 + html = self.get(url).text
69 + low = html[:2000].lower()
70 + if not any(h in low for h in _BLOCK_HINTS):
71 + return html
72 + except Exception:
73 + pass
74 + return self.get_rendered(url, render_js=False)
75 +
48 76 # -- sitemap ----------------------------------------------------------
49 77 def _recent_fiches(self) -> dict[str, str]:
50 78 """URL de fiche → lastmod, pour les fiches modifiées récemment."""
51 − idx = self.get_rendered(SITEMAP_INDEX, render_js=False)
79 + idx = self._fetch_html(SITEMAP_INDEX)
52 80 shards = [u for u, _ in _LOC_RE.findall(idx) if "/evenements_" in u]
53 81 cutoff = (date.today() - timedelta(days=LASTMOD_WINDOW_DAYS)).isoformat()
54 82 fiches: dict[str, str] = {}
55 83 for shard in shards[-SHARDS_TO_READ:]:
56 − xml = self.get_rendered(shard, render_js=False)
84 + xml = self._fetch_html(shard)
57 85 for block in _URL_BLOCK_RE.findall(xml):
58 86 m = _LOC_RE.search(block)
59 87 if not m:
@@ -85,13 +113,22 @@ class AtuvuConnector(BaseConnector):
85 113 price = float(price_m.group(1).replace(",", ".")) if price_m else None
86 114 slug = url.rstrip("/").rsplit("/", 1)[-1]
87 115 rows = []
116 + seen_days: dict[str, int] = {}
88 117 for date_txt, venue, city in _PLAY_RE.findall(html)[:10]:
89 118 day = parse_date_fr(date_txt)
90 119 if not day:
91 120 continue
121 + hhmm = parse_time(date_txt) # « … - 20h00 » de la fiche
122 + # séances multiples le même jour : la 1ʳᵉ garde l'id historique
123 + # (zéro churn en base), les suivantes sont suffixées par l'heure
124 + # — sinon elles s'écrasaient mutuellement à l'ingestion.
125 + n = seen_days.get(day, 0)
126 + seen_days[day] = n + 1
127 + ext = (f"{slug}-{day}" if n == 0
128 + else f"{slug}-{day}-{(hhmm or f'x{n}').replace(':', '')}")
92 129 rows.append({
93 − "external_id": f"{slug}-{day}",
94 − "start_time": parse_time(date_txt), # « … - 20h00 » de la fiche
130 + "external_id": ext,
131 + "start_time": hhmm,
95 132 "url": url, "title": title, "image": image,
96 133 "description": description,
97 134 "raw_categories": cats, "venue": clean_text(venue),
@@ -109,13 +146,35 @@ class AtuvuConnector(BaseConnector):
109 146 # retirer du cache les fiches disparues du sitemap récent
110 147 cache = {u: c for u, c in cache.items() if u in fiches}
111 148
149 + now = time.time()
150 + today = date.today().isoformat()
112 151 to_fetch = [u for u, mod in fiches.items()
113 − if cache.get(u, {}).get("lastmod") != mod][:ATUVU_MAX_FETCH]
152 + if cache.get(u, {}).get("lastmod") != mod]
153 +
154 + def _stale_rank(u: str) -> int | None:
155 + """0 = rows figés par un parseur < v2 (heure/prix manquants) ;
156 + 1 = fiche à venir non re-visitée depuis REFRESH_AFTER (les tarifs
157 + apparaissent souvent après la mise en vente, sans bump de lastmod
158 + — constaté 2026-08-25) ; None = rien à faire."""
159 + entry = cache.get(u) or {}
160 + if entry.get("v", 1) < PARSE_V:
161 + return 0
162 + if (now - entry.get("fetched_at", 0) > REFRESH_AFTER
163 + and any((r.get("start_date") or "") >= today
164 + for r in entry.get("rows") or [])):
165 + return 1
166 + return None
167 +
168 + backfill = sorted(
169 + (u for u in fiches
170 + if u not in set(to_fetch) and _stale_rank(u) is not None),
171 + key=lambda u: (_stale_rank(u), cache.get(u, {}).get("fetched_at", 0)))
172 + to_fetch = (to_fetch + backfill)[:ATUVU_MAX_FETCH]
114 173 for url in to_fetch:
115 174 try:
116 − html = self.get_rendered(url, render_js=False)
175 + html = self._fetch_html(url)
117 176 cache[url] = {"lastmod": fiches[url], "fetched_at": time.time(),
118 − "rows": self._parse_fiche(url, html)}
177 + "v": PARSE_V, "rows": self._parse_fiche(url, html)}
119 178 except Exception as exc: # une fiche cassée ne bloque pas la source
120 179 print(f"[sorti-ka] atuvu : fiche ignorée {url} : {exc}")
121 180 self.save_cache(cache)
modified sortika/connectors/bandsintown.py +26 −1
@@ -16,7 +16,12 @@
16 16 # cache entre deux visites (re-visite après 3 jours).
17 17 # Champs : datetime local (date + heure), salle géolocalisée (lat/lng),
18 18 # lineup → artistes, `free` → is_free, offres/billets → url,
19 −# image de l'artiste.
19 +# image de l'artiste. L'objet `artist` n'est renseigné que sur
20 +# CERTAINS événements de la réponse (dict vide sur d'autres —
21 +# constaté 2026-08-25, cause de l'image à 8 %) : l'image est
22 +# reprise de n'importe quel événement de la réponse, sinon du
23 +# profil /artists/{artiste} (1 GET de plus, seulement si
24 +# l'artiste a des dates QC sans image).
20 25 # -----------------------------------------------------------------------------
21 26 from __future__ import annotations
22 27
@@ -69,6 +74,11 @@ class BandsintownConnector(BaseConnector):
69 74 data = self.get_json(url, params={"app_id": APP_ID, "date": "upcoming"})
70 75 if not isinstance(data, list): # {"Message": …} = refus/erreur
71 76 raise RuntimeError(f"réponse inattendue pour {artist!r} : {data}")
77 + # l'objet `artist` (image_url) n'est renseigné que sur certains
78 + # événements de la réponse — on prend la 1ʳᵉ image trouvée n'importe où
79 + artist_img = next(
80 + (((e.get("artist") or {}).get("image_url") or "") for e in data
81 + if (e.get("artist") or {}).get("image_url")), "")
72 82 rows = []
73 83 for e in data:
74 84 venue = e.get("venue") or {}
@@ -105,6 +115,21 @@ class BandsintownConnector(BaseConnector):
105 115 "free": e.get("free"),
106 116 "image": art.get("image_url") or "",
107 117 })
118 + # aucune image dans toute la réponse mais des dates QC à émettre :
119 + # 1 GET de repli sur le profil artiste (image_url canonique)
120 + if rows and not artist_img and not any(r["image"] for r in rows):
121 + try:
122 + prof = self.get_json(
123 + "https://rest.bandsintown.com/artists/"
124 + + urllib.parse.quote(artist, safe=""),
125 + params={"app_id": APP_ID})
126 + if isinstance(prof, dict):
127 + artist_img = prof.get("image_url") or ""
128 + except Exception: # profil indisponible : on n'invente rien
129 + artist_img = ""
130 + for r in rows:
131 + if not r["image"]:
132 + r["image"] = artist_img
108 133 return rows
109 134
110 135 def fetch(self) -> list[Event]:
added sortika/connectors/banq.py +163 −0
@@ -0,0 +1,163 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/banq.py : connecteur BAnQ (banq.qc.ca) — activités & conférences
5 +# Source : calendrier des activités de Bibliothèque et Archives nationales
6 +# du Québec (Grande Bibliothèque + centres régionaux) — ateliers,
7 +# conférences, heure du conte, expositions (~140 fiches à venir).
8 +# Extraction: SCRAPFLY obligatoire (anti-bot Radware : GET direct → 302
9 +# challenge, /jsonapi bloqué — sondé 2026-08-25).
10 +# Liste /calendrier/a-venir rendue AVEC JavaScript (les cartes
11 +# sont injectées côté client) → liens /calendrier/a-venir/<id>.
12 +# Fiches : Scrapfly SANS rendu JS (HTML serveur), JSON-LD Event
13 +# complet (nom, dates ISO avec fuseau -04:00, lieu, adresse,
14 +# ville, image, organisateur, offre 0,00 $ → gratuité PUBLIÉE).
15 +# INCRÉMENTAL avec cache disque (data/banq_cache.json) :
16 +# nouvelles fiches d'abord, re-visite après 14 jours, plafond
17 +# BANQ_DETAIL_MAX fiches/sync pour contenir le coût en crédits.
18 +# Accès : robots.txt Drupal standard (le calendrier n'est pas exclu) ;
19 +# 1 rendu JS + quelques fiches par sync, cadence hebdomadaire.
20 +# Prix : offers.price du JSON-LD (« 0.00 » = gratuit affiché par la
21 +# source) → is_free/price_label. Jamais inventé.
22 +# -----------------------------------------------------------------------------
23 +from __future__ import annotations
24 +
25 +import json
26 +import os
27 +import re
28 +import time
29 +
30 +from ..normalize import clean_text, parse_time
31 +from ..schema import Event
32 +from .base import BaseConnector
33 +
34 +BASE = "https://www.banq.qc.ca"
35 +CALENDAR = f"{BASE}/calendrier/a-venir"
36 +
37 +DETAIL_MAX = int(os.environ.get("BANQ_DETAIL_MAX", "50"))
38 +REFRESH_AFTER = 14 * 86400
39 +
40 +_FICHE_RE = re.compile(r'href="(/calendrier/a-venir/(\d+))"')
41 +_LD_RE = re.compile(r'<script type="application/ld\+json"[^>]*>(.*?)</script>',
42 + re.S)
43 +
44 +
45 +class BanqConnector(BaseConnector):
46 + source_id = "banq"
47 + request_delay = 1.0
48 +
49 + # -- découverte (liste rendue JS) -------------------------------------------
50 + def _fiches(self) -> list[str]:
51 + html = self.get_rendered(CALENDAR, render_js=True)
52 + return sorted({BASE + path for path, _ in _FICHE_RE.findall(html)})
53 +
54 + # -- fiche ---------------------------------------------------------------------
55 + def _parse_fiche(self, url: str, html: str) -> dict | None:
56 + data = None
57 + for blob in _LD_RE.findall(html):
58 + try:
59 + d = json.loads(blob.strip())
60 + except ValueError:
61 + continue
62 + if d.get("@type") == "Event":
63 + data = d
64 + break
65 + if not data:
66 + return None
67 + title = clean_text(data.get("name") or "")
68 + if not title:
69 + return None
70 + loc = data.get("location") or {}
71 + addr = loc.get("address") or {}
72 + org = data.get("organizer") or {}
73 + start = str(data.get("startDate") or "") # "2026-08-27T10:30:00-0400"
74 + end = str(data.get("endDate") or "")
75 + offers = data.get("offers") or {}
76 + is_free, price_label = None, ""
77 + price = offers.get("price")
78 + if price is not None:
79 + try:
80 + amount = float(price)
81 + is_free = amount == 0
82 + price_label = ("Gratuit (0,00 $ affiché)" if amount == 0
83 + else f"{amount:g} $")
84 + except (TypeError, ValueError):
85 + pass
86 + return {
87 + "title": title,
88 + "description": clean_text(data.get("description") or "")[:2000],
89 + "raw_categories": [title],
90 + "venue": clean_text(loc.get("name") or ""),
91 + "address": clean_text(addr.get("streetAddress") or ""),
92 + "city": clean_text(addr.get("addressLocality") or ""),
93 + "postal_code": addr.get("postalCode") or "",
94 + "start_date": start[:10] or None,
95 + "start_time": parse_time(start),
96 + "end_date": end[:10] or None,
97 + "end_time": parse_time(end),
98 + "status": ("cancelled"
99 + if "cancel" in str(data.get("eventStatus", "")).lower()
100 + else ""),
101 + "is_free": is_free, "price_label": price_label,
102 + "organizer": clean_text(org.get("name") or ""),
103 + "image": data.get("image") or "",
104 + }
105 +
106 + # -- pipeline ---------------------------------------------------------------------
107 + def fetch(self) -> list[Event]:
108 + cache = self.load_cache()
109 + urls = self._fiches()
110 + if urls: # liste indisponible → on garde le cache
111 + current = set(urls)
112 + cache = {u: c for u, c in cache.items() if u in current}
113 + else:
114 + urls = list(cache)
115 +
116 + now = time.time()
117 + candidates = sorted(
118 + (u for u in urls
119 + if u not in cache
120 + or now - cache[u].get("fetched_at", 0) > REFRESH_AFTER),
121 + key=lambda u: cache.get(u, {}).get("fetched_at", 0))[:DETAIL_MAX]
122 +
123 + for url in candidates: # séquentiel : coût Scrapfly maîtrisé
124 + try:
125 + row = self._parse_fiche(url, self.get_rendered(url,
126 + render_js=False))
127 + except Exception as exc: # fiche cassée : on ne bloque pas la source
128 + print(f"[sorti-ka] banq : fiche ignorée {url} : {exc}")
129 + row = cache.get(url, {}).get("row")
130 + cache[url] = {"fetched_at": now, "row": row}
131 + self.save_cache(cache)
132 +
133 + events: list[Event] = []
134 + seen: set[str] = set()
135 + for url, entry in cache.items():
136 + row = entry.get("row")
137 + if not row:
138 + continue
139 + ext = url.rstrip("/").rsplit("/", 1)[-1]
140 + if ext in seen:
141 + continue
142 + seen.add(ext)
143 + events.append(Event(
144 + source=self.source_id,
145 + external_id=ext,
146 + url=url, title=row["title"],
147 + description=row.get("description", ""),
148 + raw_categories=row.get("raw_categories") or [],
149 + venue=row.get("venue", ""),
150 + address=row.get("address", ""),
151 + city=row.get("city", ""),
152 + postal_code=row.get("postal_code", ""),
153 + start_date=row.get("start_date"),
154 + start_time=row.get("start_time"),
155 + end_date=row.get("end_date"),
156 + end_time=row.get("end_time"),
157 + status=row.get("status", ""),
158 + is_free=row.get("is_free"),
159 + price_label=row.get("price_label", ""),
160 + organizer=row.get("organizer", ""),
161 + image=row.get("image", ""),
162 + ))
163 + return events
modified sortika/connectors/bassaintlaurent.py +58 −10
@@ -4,12 +4,17 @@
4 4 # connectors/bassaintlaurent.py : Tourisme Bas-Saint-Laurent — événements (ATR)
5 5 # Source : https://www.bassaintlaurent.ca/fr/quoi-faire/evenements/ —
6 6 # WordPress (Bedrock), liste rendue serveur (~27 fiches à venir,
7 −# vérifié 2026-08-19).
7 +# revérifié 2026-08-25 ; le marqueur ajax-load-more__end-page
8 +# confirme que TOUTE la liste tient sur la page 1 — aucun
9 +# événement caché derrière l'AJAX get_grid).
8 10 # Extraction: cartes .card--type-event (lien, plage de dates FR, titre,
9 11 # municipalité, image) + fiches détail incrémentales (cache
10 12 # data/bassaintlaurent_cache.json, BSL_FICHE_MAX/sync, re-visite
11 −# 7 j) : og:description + GPS exact (coordonnées de la carte
12 −# embarquées dans la fiche).
13 +# 7 j, version v=2). ENRICHI (2026-08-25) : bloc window.locations
14 +# de la fiche (JSON structuré) → adresse civique (street), ville,
15 +# code postal, GPS exact, site web officiel ; description longue
16 +# = paragraphes « À propos » (fallback og:description). Pas
17 +# d'heure ni de tarif publiés → jamais inventés.
13 18 # Accès : pages publiques de l'ATR, rendu serveur — GET throttlés 0,8 s,
14 19 # identification honnête, lien vers la fiche originale.
15 20 # Décision : le Bas-Saint-Laurent n'avait que 168 fiches au baseline —
@@ -17,6 +22,7 @@
17 22 # -----------------------------------------------------------------------------
18 23 from __future__ import annotations
19 24
25 +import json
20 26 import os
21 27 import re
22 28 import time
@@ -29,6 +35,8 @@ BASE = "https://www.bassaintlaurent.ca"
29 35 LIST_URL = BASE + "/fr/quoi-faire/evenements/"
30 36 FICHE_MAX = int(os.environ.get("BSL_FICHE_MAX", "15"))
31 37 REFRESH_AFTER = 7 * 86400
38 +CACHE_V = 2 # v2 : + adresse/ville/CP/site web (window.locations)
39 +# + description « À propos » (2026-08-25)
32 40
33 41 _CARD_RE = re.compile(
34 42 r'<a href="(https://www\.bassaintlaurent\.ca/fr/quoi-faire/evenements/'
@@ -41,6 +49,12 @@ _OG_DESC_RE = re.compile(r'property="og:description"\s+content="([^"]*)"')
41 49 _GPS_RE = re.compile(
42 50 r'"lat(?:itude)?"\s*[:=]\s*"?(-?\d{2}\.\d+)"?\s*,\s*'
43 51 r'"(?:lng|lon|longitude)"\s*[:=]\s*"?(-?\d{2}\.\d+)"?')
52 +# fiche : annuaire JSON embarqué (adresse structurée + site web)
53 +_LOCATIONS_RE = re.compile(r"window\.locations\s*=\s*(\[.*?\]);\s*</script>",
54 + re.S)
55 +# fiche : paragraphes « À propos » (description longue)
56 +_ABOUT_RE = re.compile(r'js-truncated text-content">(.*?)</div>', re.S)
57 +_END_PAGE_MARK = "ajax-load-more__end-page"
44 58
45 59
46 60 class BasSaintLaurentConnector(BaseConnector):
@@ -48,37 +62,68 @@ class BasSaintLaurentConnector(BaseConnector):
48 62 request_delay = 0.8
49 63
50 64 def _parse_fiche(self, html: str) -> dict:
51 − m = _OG_DESC_RE.search(html)
52 65 gps = _GPS_RE.search(html)
53 − return {
54 − "desc": clean_text(m.group(1)) if m else "",
55 − "lat": float(gps.group(1)) if gps else None,
66 + det: dict = {
67 + "desc": "", "lat": float(gps.group(1)) if gps else None,
56 68 "lng": float(gps.group(2)) if gps else None,
69 + "address": "", "city": "", "postal_code": "", "website": "",
57 70 }
71 + about = _ABOUT_RE.search(html)
72 + if about:
73 + det["desc"] = clean_text(about.group(1))[:2000]
74 + if not det["desc"]:
75 + m = _OG_DESC_RE.search(html)
76 + det["desc"] = clean_text(m.group(1)) if m else ""
77 + loc_m = _LOCATIONS_RE.search(html)
78 + if loc_m:
79 + try:
80 + loc = json.loads(loc_m.group(1))[0]
81 + except Exception:
82 + loc = {}
83 + det["address"] = clean_text(loc.get("street") or "")
84 + det["city"] = clean_text(loc.get("city") or "")
85 + det["postal_code"] = (loc.get("postal_code") or "").strip()
86 + site = (loc.get("url") or "").strip()
87 + if site and not site.startswith("http"):
88 + site = "https://" + site
89 + det["website"] = site
90 + return det
58 91
59 92 def _enrich_fiches(self, rows: list[dict]) -> None:
60 93 cache = self.load_cache()
61 94 now = time.time()
62 95 current = {r["url"] for r in rows}
63 96 cache = {u: c for u, c in cache.items() if u in current}
97 + # nouvelles fiches puis celles parsées avant v2, puis re-visite roulante
64 98 to_fetch = sorted(
65 99 (u for u in current
66 − if u not in cache or now - cache[u].get("ts", 0) > REFRESH_AFTER),
67 − key=lambda u: cache.get(u, {}).get("ts", 0))[:FICHE_MAX]
100 + if u not in cache or cache[u].get("v", 1) < CACHE_V
101 + or now - cache[u].get("ts", 0) > REFRESH_AFTER),
102 + key=lambda u: (cache.get(u, {}).get("v", 1) if u in cache else 0,
103 + cache.get(u, {}).get("ts", 0)))[:FICHE_MAX]
68 104
69 105 def worker(url, session):
70 106 return self._parse_fiche(session.get(url, timeout=25).text)
71 107
72 108 for url, det in self.fetch_many(to_fetch, worker, max_workers=3).items():
73 − cache[url] = {"ts": now, **(det or {"desc": "", "lat": None, "lng": None})}
109 + cache[url] = {"ts": now, "v": CACHE_V,
110 + **(det or {"desc": "", "lat": None, "lng": None})}
74 111 self.save_cache(cache)
75 112 for r in rows:
76 113 det = cache.get(r["url"]) or {}
77 114 r["description"] = det.get("desc", "")
78 115 r["lat"], r["lng"] = det.get("lat"), det.get("lng")
116 + r["address"] = det.get("address", "")
117 + r["postal_code"] = det.get("postal_code", "")
118 + r["website"] = det.get("website", "")
119 + if det.get("city"): # ville structurée de la fiche
120 + r["city"] = det["city"]
79 121
80 122 def fetch(self) -> list[Event]:
81 123 html = self.get(LIST_URL).text
124 + if _END_PAGE_MARK not in html: # garde-fou : pagination apparue ?
125 + print("[sorti-ka] bassaintlaurent : liste possiblement paginée "
126 + "(marqueur end-page absent) — vérifier get_grid")
82 127 rows: list[dict] = []
83 128 for m in _CARD_RE.finditer(html):
84 129 url, card = m.group(1), m.group(2)
@@ -113,11 +158,14 @@ class BasSaintLaurentConnector(BaseConnector):
113 158 title=r["title"],
114 159 description=r.get("description", ""),
115 160 raw_categories=[r["title"]],
161 + address=r.get("address", ""),
116 162 city=r["city"],
163 + postal_code=r.get("postal_code", ""),
117 164 tourist_region="Bas-Saint-Laurent",
118 165 lat=r.get("lat"), lng=r.get("lng"),
119 166 start_date=r["start_date"],
120 167 end_date=r["end_date"],
168 + website=r.get("website", ""),
121 169 image=r["image"],
122 170 ))
123 171 return events
modified sortika/connectors/cantonsdelest.py +113 −26
@@ -5,13 +5,17 @@
5 5 # Source : cantonsdelest.com/evenements — calendrier officiel de
6 6 # l'association touristique régionale (données Tourinsoft, la
7 7 # même plateforme que le SIT Québec : médias mto.tourinsoft.eu).
8 −# Extraction: page liste rendue serveur (~25 fiches à venir, liens
9 −# /evenements/<id>/<slug>[/<idDate>]) puis fiches détail
8 +# Extraction: la page liste ne rend que ~13 cartes mais publie ~596
9 +# occurrences (data-found) derrière un bouton « Voir plus » →
10 +# PAGINATION AJAX : POST /event/more {ids, offset} avec le
11 +# jeton CSRF Yii de la page (vérifié 2026-08-25). Chaque carte
12 +# donne lien /evenements/<id>/<slug>[/<idDate>], catégorie,
13 +# titre, image et « Ville | date FR ». Fiches détail
10 14 # incrémentales (cache disque data/cantonsdelest_cache.json,
11 15 # plafond CDE_FICHE_MAX/sync, re-visite 7 j) : JSON-LD Event
12 −# complet (nom, dates, description riche, adresse civique,
13 −# images) + GPS exact (attributs data-lat/data-lng de la carte).
14 −# Accès : pages publiques de l'ATR, rendu serveur — GET directs
16 +# complet (description riche, adresse civique, images) + GPS
17 +# exact (attributs data-lat/data-lng de la carte).
18 +# Accès : pages publiques de l'ATR, rendu serveur — GET/POST directs
15 19 # throttlés, identification honnête, lien vers la fiche
16 20 # originale. Recoupement avec le flux SITQ géré par dedup_key.
17 21 # Prix : non publié de façon structurée → jamais inventé.
@@ -23,18 +27,64 @@ import os
23 27 import re
24 28 import time
25 29
26 −from ..normalize import clean_text
30 +from ..normalize import clean_text, parse_date_range_fr
27 31 from ..schema import Event
28 32 from .base import BaseConnector
29 33
30 34 BASE = "https://www.cantonsdelest.com"
31 35 LIST_URL = BASE + "/evenements"
36 +MORE_URL = BASE + "/event/more"
32 37 CDE_FICHE_MAX = int(os.environ.get("CDE_FICHE_MAX", "40")) # fiches/sync
38 +CDE_PAGE_MAX = int(os.environ.get("CDE_PAGE_MAX", "120")) # pages AJAX/sync
33 39 REFRESH_AFTER = 7 * 86400
34 40
35 −_LINK_RE = re.compile(r"href=\"(/evenements/\d+/[^\"#?]+)\"")
36 41 _LD_JSON_RE = re.compile(r"application/ld\+json[^>]*>")
37 42 _GPS_RE = re.compile(r"data-lat=\"(-?\d+\.?\d*)\" data-lng=\"(-?\d+\.?\d*)\"")
43 +_CSRF_PARAM_RE = re.compile(r'<meta name="csrf-param" content="([^"]+)"')
44 +_CSRF_TOKEN_RE = re.compile(r'<meta name="csrf-token" content="([^"]+)"')
45 +_IDS_RE = re.compile(r'data-ids="([^"]+)"')
46 +# une carte de la liste (rendue serveur ou renvoyée par /event/more) :
47 +# lien, image, catégorie, titre, « Ville | 28 août 2026 »
48 +_CARD_RE = re.compile(
49 + r'<a href="(/evenements/\d+/[^"#?]+)">\s*'
50 + r'(?:<img src="([^"]*)"[^>]*>)?\s*</a>.*?'
51 + r'container-article-category">\s*<b>([^<]*)</b>.*?'
52 + r'container-article-title[^"]*">(.*?)</a>.*?'
53 + r'listing-city""?>([^<]*)</p>', re.S)
54 +# mois abrégés du gabarit (« 21 janv. 2027 », « 5 févr. 2027 ») que le parseur
55 +# central ne connaît pas — constaté live 2026-08-25 (45/596 cartes)
56 +_MONTH_ABBR_RE = re.compile(r"\b(janv|f[ée]vr)\.?", re.I)
57 +_MONTH_ABBR = {"janv": "janvier", "fevr": "février", "févr": "février"}
58 +
59 +
60 +def _dates_fr(raw: str) -> tuple[str | None, str | None]:
61 + """parse_date_range_fr avec les abréviations propres au site déployées."""
62 + raw = _MONTH_ABBR_RE.sub(
63 + lambda m: _MONTH_ABBR[m.group(1).lower()], raw)
64 + return parse_date_range_fr(raw)
65 +
66 +
67 +def _cards(html: str) -> list[dict]:
68 + """Cartes d'une page liste : path, image, catégorie, titre, ville, dates."""
69 + out = []
70 + for m in _CARD_RE.finditer(html):
71 + path, img, cat, title, city_date = m.groups()
72 + city, date_raw = "", city_date or ""
73 + if "|" in city_date:
74 + city, date_raw = city_date.split("|", 1)
75 + start, end = _dates_fr(date_raw)
76 + parts = path.strip("/").split("/") # evenements/<id>/<slug>[/<idDate>]
77 + out.append({
78 + "path": path,
79 + "external_id": parts[1] + (f"-{parts[3]}" if len(parts) > 3 else ""),
80 + "url": BASE + path,
81 + "title": clean_text(title),
82 + "raw_categories": [clean_text(cat), clean_text(title)],
83 + "city": clean_text(city),
84 + "start_date": start, "end_date": end,
85 + "image": (img or "").strip(),
86 + })
87 + return out
38 88
39 89
40 90 def _ld_event(html: str) -> dict | None:
@@ -54,6 +104,39 @@ class CantonsDeLEstConnector(BaseConnector):
54 104 source_id = "cantonsdelest"
55 105 request_delay = 0.8
56 106
107 + def _post_more(self, data: dict) -> str:
108 + """POST throttlé vers /event/more (pagination AJAX Yii)."""
109 + self._throttle()
110 + resp = self.session.post(MORE_URL, data=data, timeout=self.timeout)
111 + self._last_request = time.time()
112 + resp.raise_for_status()
113 + return resp.text
114 +
115 + def _list_cards(self) -> list[dict]:
116 + """Toutes les cartes du calendrier : page 1 rendue serveur puis
117 + pagination AJAX (POST {ids, offset, _csrf}) jusqu'à épuisement."""
118 + first = self.get(LIST_URL).text
119 + cards = _cards(first)
120 + seen = {c["path"] for c in cards}
121 + param = _CSRF_PARAM_RE.search(first)
122 + token = _CSRF_TOKEN_RE.search(first)
123 + ids = _IDS_RE.search(first)
124 + if not (param and token and ids): # structure changée : page 1 seule
125 + print("[sorti-ka] cantonsdelest : pagination AJAX introuvable")
126 + return cards
127 + base = {param.group(1): token.group(1), "ids": ids.group(1)}
128 + for offset in range(2, CDE_PAGE_MAX + 1):
129 + try:
130 + more = _cards(self._post_more({**base, "offset": offset}))
131 + except Exception:
132 + break
133 + new = [c for c in more if c["path"] not in seen]
134 + if not new: # page vide ou répétée → fin
135 + break
136 + cards.extend(new)
137 + seen.update(c["path"] for c in new)
138 + return cards
139 +
57 140 def _parse_fiche(self, path: str, html: str) -> dict | None:
58 141 ld = _ld_event(html)
59 142 if not ld or not ld.get("name"):
@@ -83,8 +166,8 @@ class CantonsDeLEstConnector(BaseConnector):
83 166 }
84 167
85 168 def fetch(self) -> list[Event]:
86 − html = self.get(LIST_URL).text
87 − paths = list(dict.fromkeys(_LINK_RE.findall(html)))
169 + cards = self._list_cards()
170 + paths = [c["path"] for c in cards]
88 171 cache = self.load_cache()
89 172 now = time.time()
90 173 current = set(paths)
@@ -108,28 +191,32 @@ class CantonsDeLEstConnector(BaseConnector):
108 191 cache[path] = {"fetched_at": now, "row": row}
109 192 self.save_cache(cache)
110 193
194 + # émission : la carte (titre, catégorie, ville, date, image) porte
195 + # chaque occurrence ; la fiche (cache) enrichit description, adresse,
196 + # code postal, GPS et précise les dates (JSON-LD de l'occurrence)
111 197 events: list[Event] = []
112 198 seen: set[str] = set()
113 − for entry in cache.values():
114 − row = entry.get("row")
115 − if not row or row["external_id"] in seen:
199 + for card in cards:
200 + if card["external_id"] in seen:
116 201 continue
117 − seen.add(row["external_id"])
202 + seen.add(card["external_id"])
203 + fiche = (cache.get(card["path"]) or {}).get("row") or {}
118 204 events.append(Event(
119 205 source=self.source_id,
120 − external_id=row["external_id"],
121 − url=row["url"],
122 − title=row["title"],
123 − description=row["description"],
124 − raw_categories=row["raw_categories"],
125 − address=row["address"],
126 − city=row["city"],
127 − postal_code=row["postal_code"],
206 + external_id=card["external_id"],
207 + url=card["url"],
208 + title=card["title"] or fiche.get("title", ""),
209 + description=fiche.get("description", ""),
210 + raw_categories=[c for c in card["raw_categories"] if c]
211 + or fiche.get("raw_categories") or [],
212 + address=fiche.get("address", ""),
213 + city=card["city"] or fiche.get("city", ""),
214 + postal_code=fiche.get("postal_code", ""),
128 215 tourist_region="Cantons-de-l'Est",
129 − lat=row["lat"],
130 − lng=row["lng"],
131 − start_date=row["start_date"],
132 − end_date=row["end_date"],
133 − image=row["image"],
216 + lat=fiche.get("lat"),
217 + lng=fiche.get("lng"),
218 + start_date=fiche.get("start_date") or card["start_date"],
219 + end_date=fiche.get("end_date") or card["end_date"],
220 + image=card["image"] or fiche.get("image", ""),
134 221 ))
135 222 return events
modified sortika/connectors/centreduquebec.py +46 −17
@@ -5,14 +5,19 @@
5 5 # (ATR, Phase 4). Première source de la région Centre-du-Québec (0 fiche
6 6 # avant connexion).
7 7 # Source : https://tourismecentreduquebec.com/evenements/ — WordPress,
8 −# liste rendue serveur (~10 fiches à venir, vérifié 2026-08-21) :
9 −# chaque bloc events_listing_results_event = plage de dates FR
8 +# liste rendue serveur (~9 fiches à venir, revérifié 2026-08-25,
9 +# aucune pagination — la liste est complète) : chaque bloc
10 +# events_listing_results_event = plage de dates FR
10 11 # (« 1 septembre 2026 au 6 septembre 2026 ») + titre + lien.
11 12 # Extraction: GET liste (1 requête) → fiches détail incrémentales (cache
12 13 # data/centreduquebec_cache.json, CDQ_FICHE_MAX/sync, re-visite
13 −# 7 j) : bloc « Adresse » (adresse civique), og:image,
14 −# og:description. La fiche ne publie ni ville structurée, ni
15 −# heure, ni tarif → jamais inventés (région dérivée de l'ATR).
14 +# 7 j, version v=2) : champs étiquetés <small> (Adresse, VILLE,
15 +# Entreprise → organisateur), description du bloc
16 +# event_details_row_description, « Programme » (liste d'activités
17 +# → catégories brutes), lien « Visiter le site web » (href vide
18 +# sur toutes les fiches en 2026-08 — bogue du gabarit à la
19 +# source, champ émis dès qu'il sera rempli), og:image.
20 +# Ni heure ni tarif ni GPS publiés → jamais inventés.
16 21 # Accès : site public, robots.txt sans interdiction sur /evenements/ —
17 22 # GET throttlés, UA honnête.
18 23 # -----------------------------------------------------------------------------
@@ -30,13 +35,22 @@ BASE = "https://tourismecentreduquebec.com"
30 35 LIST_URL = BASE + "/evenements/"
31 36 FICHE_MAX = int(os.environ.get("CDQ_FICHE_MAX", "10"))
32 37 REFRESH_AFTER = 7 * 86400
38 +CACHE_V = 2 # v2 : + ville, organisateur, site web, programme,
39 +# description du corps de fiche (2026-08-25)
33 40
34 41 _BLOCK_RE = re.compile(
35 42 r'events_listing_results_event_date">\s*<h4>\s*(?P<date>[^<]+?)\s*</h4>.*?'
36 43 r'events_listing_results_event_name">\s*<h4>(?P<title>[^<]+)</h4>.*?'
37 44 r'href="(?P<url>https://tourismecentreduquebec\.com/evenements/[^"]+)"',
38 45 re.S)
39 −_ADDR_RE = re.compile(r'Adresse\s*</[^>]+>\s*<[^>]+>\s*([^<]{4,120})', re.S)
46 +# champs étiquetés de la fiche : <small>Adresse</small> 1910, chemin Dublin
47 +_FIELD_RE = re.compile(
48 + r"<small>\s*(Adresse|Ville|Entreprise)\s*</small>\s*([^<]+)")
49 +_DESC_RE = re.compile(
50 + r'event_details_row_description">\s*(.*?)\s*</div>', re.S)
51 +_PROG_RE = re.compile(r"<h5>Programme</h5>\s*<p>(.*?)</p>", re.S)
52 +_WEBSITE_RE = re.compile(
53 + r'href="(https?://[^"]+)"[^>]*>\s*Visiter le site web')
40 54 _OG_IMG_RE = re.compile(r'property="og:image"\s+content="([^"]*)"')
41 55 _OG_DESC_RE = re.compile(r'property="og:description"\s+content="([^"]*)"')
42 56
@@ -46,18 +60,25 @@ class CentreDuQuebecConnector(BaseConnector):
46 60 request_delay = 0.8
47 61
48 62 def _parse_fiche(self, html: str) -> dict:
49 − # la fiche présente « Adresse » comme libellé suivi de l'adresse civique
50 − plain = re.sub(r"<[^>]+>", "|", html)
51 − addr = ""
52 − m = re.search(r"\|Adresse\|+\s*([^|]{4,120})", plain)
53 − if m:
54 − addr = clean_text(m.group(1))
63 + fields = {label: clean_text(val)
64 + for label, val in _FIELD_RE.findall(html)}
65 + desc_m = _DESC_RE.search(html)
66 + og_desc = _OG_DESC_RE.search(html)
67 + desc = clean_text(desc_m.group(1))[:2000] if desc_m else (
68 + clean_text(og_desc.group(1)) if og_desc else "")
69 + prog = _PROG_RE.search(html)
70 + programme = [clean_text(x) for x in
71 + re.split(r"<br\s*/?>", prog.group(1))] if prog else []
72 + site = _WEBSITE_RE.search(html)
55 73 img = _OG_IMG_RE.search(html)
56 − desc = _OG_DESC_RE.search(html)
57 74 return {
58 − "address": addr,
75 + "address": fields.get("Adresse", ""),
76 + "city": fields.get("Ville", ""),
77 + "organizer": fields.get("Entreprise", ""),
78 + "website": site.group(1) if site else "",
79 + "raw_categories": [p for p in programme if p],
59 80 "image": clean_text(img.group(1)) if img else "",
60 − "description": clean_text(desc.group(1)) if desc else "",
81 + "description": desc,
61 82 }
62 83
63 84 def fetch(self) -> list[Event]:
@@ -80,14 +101,18 @@ class CentreDuQuebecConnector(BaseConnector):
80 101 to_fetch = sorted(
81 102 (r for r in rows
82 103 if r["external_id"] not in cache
104 + or cache[r["external_id"]].get("v", 1) < CACHE_V
83 105 or now - cache[r["external_id"]].get("ts", 0) > REFRESH_AFTER),
84 − key=lambda r: cache.get(r["external_id"], {}).get("ts", 0))[:FICHE_MAX]
106 + key=lambda r: (cache.get(r["external_id"], {}).get("v", 1)
107 + if r["external_id"] in cache else 0,
108 + cache.get(r["external_id"], {}).get("ts", 0)))[:FICHE_MAX]
85 109 for r in to_fetch:
86 110 try:
87 111 detail = self._parse_fiche(self.get(r["url"]).text)
88 112 except Exception:
89 113 detail = {}
90 − cache[r["external_id"]] = {"ts": now, "detail": detail}
114 + cache[r["external_id"]] = {"ts": now, "detail": detail,
115 + "v": CACHE_V}
91 116 self.save_cache(cache)
92 117
93 118 events: list[Event] = []
@@ -99,10 +124,14 @@ class CentreDuQuebecConnector(BaseConnector):
99 124 url=r["url"],
100 125 title=r["title"],
101 126 description=detail.get("description", ""),
127 + raw_categories=(detail.get("raw_categories") or []) + [r["title"]],
102 128 address=detail.get("address", ""),
129 + city=detail.get("city", ""),
103 130 tourist_region="Centre-du-Québec",
104 131 start_date=r["start_date"],
105 132 end_date=r["end_date"],
133 + organizer=detail.get("organizer", ""),
134 + website=detail.get("website", ""),
106 135 image=detail.get("image", ""),
107 136 ))
108 137 return events
added sortika/connectors/charlevoix.py +171 −0
@@ -0,0 +1,171 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/charlevoix.py : Tourisme Charlevoix (ATR, vague ATR 2026-08)
5 +# Source : tourisme-charlevoix.com/fr/evenements — calendrier officiel de
6 +# l'ATR (frontend SvelteKit + Craft CMS, listing servi par le
7 +# même index Algolia public que le site).
8 +# Extraction: index Algolia « prod_event_fr » (appId/clé de recherche
9 +# publiques, embarquées dans le JS du site — vérifié 2026-08-25 :
10 +# ~700 occurrences avec titre, description, adresse complète,
11 +# GPS, catégories, secteur (ville), start/end en timestamps
12 +# Unix, allDay). 1 requête/sync (hitsPerPage=1000, filtre
13 +# end >= hier). Enrichissement incrémental par l'API du site
14 +# POST /api/get-event {slug} (cache data/charlevoix_cache.json,
15 +# plafond CHX_FICHE_MAX/sync, re-visite 7 j) : image CDN,
16 +# site web, tarif (price), gratuité (free).
17 +# Accès : clé Algolia « search-only » publiée par le site lui-même et
18 +# API publique du frontend — requêtes throttlées, identification
19 +# honnête, lien vers la fiche originale. Prix : texte source
20 +# (price_label) ; is_free seulement si free=true (jamais inventé).
21 +# -----------------------------------------------------------------------------
22 +from __future__ import annotations
23 +
24 +import json
25 +import os
26 +import time
27 +from datetime import datetime, timezone
28 +from zoneinfo import ZoneInfo
29 +
30 +from ..normalize import clean_text
31 +from ..schema import Event
32 +from .base import BaseConnector
33 +
34 +BASE = "https://www.tourisme-charlevoix.com"
35 +ALGOLIA_URL = ("https://2GIBX867UB-dsn.algolia.net/1/indexes/"
36 + "prod_event_fr/query")
37 +ALGOLIA_HEADERS = {
38 + "X-Algolia-Application-Id": "2GIBX867UB",
39 + "X-Algolia-API-Key": "96e46e72a21cf33ca610df780875c7ae",
40 + "Content-Type": "application/json",
41 +}
42 +GET_EVENT_API = BASE + "/api/get-event"
43 +CHX_FICHE_MAX = int(os.environ.get("CHX_FICHE_MAX", "20"))
44 +CHX_PAGE_MAX = int(os.environ.get("CHX_PAGE_MAX", "5"))
45 +REFRESH_AFTER = 7 * 86400
46 +TZ = ZoneInfo("America/Montreal")
47 +
48 +
49 +def _ts_local(ts) -> tuple[str | None, str | None]:
50 + """Timestamp Unix → (date ISO, heure HH:MM locale QC, None si minuit)."""
51 + try:
52 + dt = datetime.fromtimestamp(int(ts), tz=timezone.utc).astimezone(TZ)
53 + except (TypeError, ValueError, OSError):
54 + return None, None
55 + hm = dt.strftime("%H:%M")
56 + return dt.strftime("%Y-%m-%d"), (None if hm == "00:00" else hm)
57 +
58 +
59 +def _split_address(raw: str) -> tuple[str, str, str]:
60 + """« 306, chemin Principal, L'Isle-aux-Coudres, G0A 3J0 » →
61 + (rue, ville, code postal). Le n° civique est parfois isolé par une virgule
62 + (style FR) : la ville est le DERNIER segment (hors code postal)."""
63 + parts = [clean_text(p) for p in (raw or "").split(",") if clean_text(p)]
64 + postal = ""
65 + if parts and len(parts[-1]) <= 8 and any(ch.isdigit() for ch in parts[-1]):
66 + postal = parts.pop()
67 + city = parts.pop() if len(parts) > 1 else ""
68 + return ", ".join(parts), city, postal
69 +
70 +
71 +class CharlevoixConnector(BaseConnector):
72 + source_id = "charlevoix"
73 + request_delay = 0.8
74 +
75 + def _post_json(self, url: str, payload: dict, headers: dict | None = None):
76 + self._throttle()
77 + resp = self.session.post(url, json=payload, headers=headers,
78 + timeout=self.timeout)
79 + self._last_request = time.time()
80 + resp.raise_for_status()
81 + return resp.json()
82 +
83 + def _hits(self) -> list[dict]:
84 + """Toutes les occurrences à venir de l'index événements du site."""
85 + since = int(time.time()) - 86400
86 + hits: list[dict] = []
87 + for page in range(CHX_PAGE_MAX):
88 + data = self._post_json(ALGOLIA_URL, {
89 + "params": f"hitsPerPage=1000&page={page}"
90 + f"&numericFilters=end>={since}"},
91 + headers=ALGOLIA_HEADERS)
92 + hits.extend(data.get("hits") or [])
93 + if page + 1 >= int(data.get("nbPages") or 1):
94 + break
95 + return hits
96 +
97 + def _fiche(self, slug: str) -> dict:
98 + """Détail Craft de l'événement : image, site web, tarif, gratuité."""
99 + entry = (self._post_json(GET_EVENT_API, {
100 + "slug": slug, "language": "fr", "path": "/fr/evenements",
101 + }) or {}).get("entry") or {}
102 + images = entry.get("image") or []
103 + image = ""
104 + if images and isinstance(images[0], dict):
105 + image = images[0].get("medium") or images[0].get("small") or ""
106 + return {
107 + "image": image,
108 + "website": entry.get("website") or "",
109 + "price": clean_text(str(entry.get("price") or "")),
110 + "free": entry.get("free") is True,
111 + }
112 +
113 + def fetch(self) -> list[Event]:
114 + hits = self._hits()
115 + cache = self.load_cache()
116 + now = time.time()
117 + slugs = {h.get("slug", "") for h in hits}
118 + cache = {k: v for k, v in cache.items() if k in slugs}
119 +
120 + to_fetch = sorted(
121 + (s for s in slugs
122 + if s and (s not in cache
123 + or now - cache[s].get("ts", 0) > REFRESH_AFTER)),
124 + key=lambda s: cache.get(s, {}).get("ts", 0))[:CHX_FICHE_MAX]
125 + for slug in to_fetch:
126 + detail: dict = {}
127 + try:
128 + detail = self._fiche(slug)
129 + except Exception:
130 + detail = {}
131 + cache[slug] = {"ts": now, "detail": detail}
132 + self.save_cache(cache)
133 +
134 + events: list[Event] = []
135 + seen: set[str] = set()
136 + for h in hits:
137 + eid = str(h.get("objectID") or "")
138 + title = clean_text(h.get("title") or "")
139 + if not eid or eid in seen or not title:
140 + continue
141 + seen.add(eid)
142 + start_date, start_time = _ts_local(h.get("start"))
143 + end_date, end_time = _ts_local(h.get("end"))
144 + if h.get("allDay"):
145 + start_time = end_time = None
146 + street, city, postal = _split_address(h.get("address") or "")
147 + sectors = h.get("sectors") or []
148 + geo = h.get("_geoloc") or {}
149 + detail = cache.get(h.get("slug", ""), {}).get("detail") or {}
150 + events.append(Event(
151 + source=self.source_id,
152 + external_id=eid,
153 + url=BASE + (h.get("url") or ""),
154 + title=title,
155 + description=clean_text(h.get("description") or ""),
156 + raw_categories=[clean_text(c) for c in
157 + (h.get("event_categories") or [])
158 + + (h.get("themes") or [])] + [title],
159 + address=street,
160 + city=city or (clean_text(sectors[0]) if sectors else ""),
161 + postal_code=postal,
162 + tourist_region="Charlevoix",
163 + lat=geo.get("lat"), lng=geo.get("lng"),
164 + start_date=start_date, start_time=start_time,
165 + end_date=end_date, end_time=end_time,
166 + is_free=True if detail.get("free") else None,
167 + price_label=detail.get("price", ""),
168 + website=detail.get("website", ""),
169 + image=detail.get("image", ""),
170 + ))
171 + return events
modified sortika/connectors/chaudiereappalaches.py +69 −10
@@ -6,20 +6,27 @@
6 6 # avant connexion (essentiellement SITQ).
7 7 # Source : https://chaudiereappalaches.com/planifier-votre-sejour/
8 8 # ete-automne/festivals-et-evenements/ — CMS Woody, liste rendue
9 −# serveur paginée (?listpage=1..3, ~12 fiches/page, vérifié
10 −# 2026-08-21). Chaque fiche embarque un bloc JSON-LD Event
11 −# COMPLET : dates ISO, description, adresse civique, ville,
12 −# code postal, GPS, images.
9 +# serveur paginée (?listpage=1..3, ~12 fiches/page, revérifié
10 +# 2026-08-25 — liste saisonnière unique, pas d'équivalent hiver).
11 +# Chaque fiche embarque un bloc JSON-LD Event COMPLET (dates ISO,
12 +# description, adresse civique, ville, code postal, GPS, images)
13 +# ET un payload JS « const HwSheet = {...} » (fiche touristique
14 +# Raccourci/Hubo).
13 15 # Extraction: pages liste (1 GET/page) → fiches détail incrémentales (cache
14 16 # data/chaudiereappalaches_cache.json, CHA_FICHE_MAX/sync,
15 −# re-visite 7 j) → JSON-LD Event (parseur générique ld_events
16 −# de sallesjsonld). Pas d'heure ni de tarif structurés fiables
17 −# → jamais inventés.
17 +# re-visite 7 j, version v=2) → JSON-LD Event (parseur générique
18 +# ld_events de sallesjsonld). ENRICHI (2026-08-25) : payload
19 +# HwSheet → site web officiel (websites/bookingUrl) et tarif
20 +# textuel publié (tariffFree/tariffComplement → price_label
21 +# brut, ex. « Accès gratuit au site… »). Pas d'heure structurée
22 +# (openingPeriods.hasHours=false partout, schedules vides) →
23 +# jamais inventée.
18 24 # Accès : site public de l'ATR, robots.txt sans interdiction sur
19 25 # /planifier-votre-sejour/ — GET throttlés, UA honnête.
20 26 # -----------------------------------------------------------------------------
21 27 from __future__ import annotations
22 28
29 +import json
23 30 import os
24 31 import re
25 32 import time
@@ -34,11 +41,43 @@ LIST_URL = BASE + "/planifier-votre-sejour/ete-automne/festivals-et-evenements/"
34 41 PAGE_MAX = 6 # 3 pages constatées en 2026-08
35 42 FICHE_MAX = int(os.environ.get("CHA_FICHE_MAX", "12"))
36 43 REFRESH_AFTER = 7 * 86400
44 +CACHE_V = 2 # v2 : + site web + tarif textuel (HwSheet),
45 +# 2026-08-25
37 46
38 47 _FICHE_RE = re.compile(
39 48 r'href="(https://chaudiereappalaches\.com/planifier-votre-sejour/[^"]*'
40 49 r'/festivals-et-evenements/[a-z0-9-]+-fr-(\d+)/)"')
41 50 _LISTPAGE_RE = re.compile(r"listpage=(\d+)")
51 +_HWSHEET_RE = re.compile(r"const\s+HwSheet\s*=")
52 +
53 +
54 +def _hwsheet(html: str) -> dict:
55 + """Payload « const HwSheet = {...} » d'une fiche ({} si absent/cassé)."""
56 + m = _HWSHEET_RE.search(html)
57 + if not m:
58 + return {}
59 + try:
60 + d, _ = json.JSONDecoder().raw_decode(html[m.end():].lstrip())
61 + return d if isinstance(d, dict) else {}
62 + except Exception:
63 + return {}
64 +
65 +
66 +def _dig(obj, key):
67 + """Première valeur non-nulle de `key` dans un arbre dict/list (prudent)."""
68 + if isinstance(obj, dict):
69 + if obj.get(key) is not None:
70 + return obj[key]
71 + for v in obj.values():
72 + r = _dig(v, key)
73 + if r is not None:
74 + return r
75 + elif isinstance(obj, list):
76 + for v in obj:
77 + r = _dig(v, key)
78 + if r is not None:
79 + return r
80 + return None
42 81
43 82
44 83 class ChaudiereAppalachesConnector(BaseConnector):
@@ -81,6 +120,17 @@ class ChaudiereAppalachesConnector(BaseConnector):
81 120 venue = clean_text(str(loc.get("name") or ""))
82 121 if venue.lower() in ("adresse", title.lower()):
83 122 venue = "" # libellé générique du CMS
123 + # payload HwSheet : site web officiel + tarif textuel publié
124 + sheet = _hwsheet(html)
125 + websites = _dig(sheet, "websites") or []
126 + website = ""
127 + if isinstance(websites, list) and websites:
128 + website = str(websites[0]).strip()
129 + if not website:
130 + website = str(_dig(sheet, "bookingUrl") or "").strip()
131 + price_label = clean_text(str(
132 + _dig(sheet, "tariffFree")
133 + or _dig(sheet, "tariffComplement") or ""))[:300]
84 134 return {
85 135 "external_id": fid,
86 136 "url": url,
@@ -93,6 +143,8 @@ class ChaudiereAppalachesConnector(BaseConnector):
93 143 "lat": geo.get("latitude"), "lng": geo.get("longitude"),
94 144 "start_date": start,
95 145 "end_date": parse_date_iso(str(ld.get("endDate") or "")) or start,
146 + "website": website if website.startswith("http") else "",
147 + "price_label": price_label,
96 148 "image": image if isinstance(image, str) else "",
97 149 }
98 150 return None
@@ -103,10 +155,15 @@ class ChaudiereAppalachesConnector(BaseConnector):
103 155 now = time.time()
104 156 current = set(fiches.values())
105 157 cache = {k: v for k, v in cache.items() if k in current}
158 + # nouvelles fiches puis celles parsées avant v2 (sans site web/tarif),
159 + # puis re-visite roulante
106 160 to_fetch = sorted(
107 161 (u for u, fid in fiches.items()
108 − if fid not in cache or now - cache[fid].get("ts", 0) > REFRESH_AFTER),
109 − key=lambda u: cache.get(fiches[u], {}).get("ts", 0))[:FICHE_MAX]
162 + if fid not in cache or cache[fid].get("v", 1) < CACHE_V
163 + or now - cache[fid].get("ts", 0) > REFRESH_AFTER),
164 + key=lambda u: (cache.get(fiches[u], {}).get("v", 1)
165 + if fiches[u] in cache else 0,
166 + cache.get(fiches[u], {}).get("ts", 0)))[:FICHE_MAX]
110 167 for url in to_fetch:
111 168 fid = fiches[url]
112 169 try:
@@ -115,7 +172,7 @@ class ChaudiereAppalachesConnector(BaseConnector):
115 172 row = None
116 173 if row is None:
117 174 print(f"[sorti-ka] chaudiereappalaches : fiche ignorée {url}")
118 − cache[fid] = {"ts": now, "row": row}
175 + cache[fid] = {"ts": now, "row": row, "v": CACHE_V}
119 176 self.save_cache(cache)
120 177
121 178 events: list[Event] = []
@@ -138,6 +195,8 @@ class ChaudiereAppalachesConnector(BaseConnector):
138 195 lat=r["lat"], lng=r["lng"],
139 196 start_date=r["start_date"],
140 197 end_date=r["end_date"],
198 + price_label=r.get("price_label", ""),
199 + website=r.get("website", ""),
141 200 image=r["image"],
142 201 ))
143 202 return events
added sortika/connectors/cotenord.py +154 −0
@@ -0,0 +1,154 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/cotenord.py : connecteur Tourisme Côte-Nord (ATR, vague ATR 2026-08)
5 +# Source : tourismecote-nord.com/fr/activites/culture-et-decouverte/
6 +# evenements-et-spectacles/ — calendrier officiel de l'ATR.
7 +# Extraction: la page liste (rendue serveur) embarque TOUTES les fiches dans
8 +# un bloc JavaScript « var fiches = [...] » (JSON complet vérifié
9 +# 2026-08-25 : ~37 fiches avec titre, description, dates ISO
10 +# dateDebutFiche/dateFinFiche, ville, adresse civique, GPS,
11 +# tarif entreeFicheLangue, site web). 1 GET/sync pour tout le
12 +# gisement. Fiches détail incrémentales (cache disque
13 +# data/cotenord_cache.json, plafond CN_FICHE_MAX/sync,
14 +# re-visite 7 j) UNIQUEMENT pour résoudre l'URL d'image
15 +# optimisée (fichiersUploadOpt/<hash>/<fichier>, hash non
16 +# dérivable du JSON).
17 +# Accès : pages publiques de l'ATR, robots.txt « Allow: / » — GET directs
18 +# throttlés, identification honnête, lien vers la fiche
19 +# originale. Prix : texte brut de la source (price_label),
20 +# jamais inventé.
21 +# -----------------------------------------------------------------------------
22 +from __future__ import annotations
23 +
24 +import json
25 +import os
26 +import re
27 +import time
28 +
29 +from ..normalize import clean_text
30 +from ..schema import Event
31 +from .base import BaseConnector
32 +
33 +BASE = "https://www.tourismecote-nord.com"
34 +LIST_URL = BASE + "/fr/activites/culture-et-decouverte/evenements-et-spectacles/"
35 +CN_FICHE_MAX = int(os.environ.get("CN_FICHE_MAX", "10")) # fiches image/sync
36 +REFRESH_AFTER = 7 * 86400
37 +
38 +# « opt-<x>-<y>-<w>-<h>-<a>-<b>-<fichier> » → nom de fichier original
39 +_IMG_NAME_RE = re.compile(r"^opt(?:-\d+){6}-")
40 +
41 +
42 +def _fiches_json(html: str) -> list[dict]:
43 + """Le bloc « var fiches = [...] » de la page liste (JSON brut)."""
44 + i = html.find("var fiches = ")
45 + if i < 0:
46 + return []
47 + try:
48 + data, _ = json.JSONDecoder().raw_decode(html, html.index("[", i))
49 + except (ValueError, IndexError):
50 + return []
51 + return [f for f in data if isinstance(f, dict)]
52 +
53 +
54 +def _date_or_none(raw: str | None) -> str | None:
55 + """Date ISO de la source, en écartant les placeholders « 0000-00-00 »."""
56 + raw = (raw or "").strip()
57 + return raw if re.match(r"^(?!0000)\d{4}-\d{2}-\d{2}$", raw) else None
58 +
59 +
60 +def _latlng(fiche: dict) -> tuple[float | None, float | None]:
61 + """GPS le plus précis publié : adresse > fiche > ville (0 = absent)."""
62 + for lat_k, lng_k in (("latitudeAdresse", "longitudeAdresse"),
63 + ("latitudeFiche", "longitudeFiche"),
64 + ("latitudeVille", "longitudeVille")):
65 + try:
66 + lat, lng = float(fiche.get(lat_k) or 0), float(fiche.get(lng_k) or 0)
67 + except (TypeError, ValueError):
68 + continue
69 + if lat and lng:
70 + return lat, lng
71 + return None, None
72 +
73 +
74 +class CoteNordConnector(BaseConnector):
75 + source_id = "cotenord"
76 + request_delay = 0.8
77 +
78 + def _image_from_fiche(self, html: str, image_name: str) -> str:
79 + """URL optimisée de l'image de la fiche (hash présent sur la page)."""
80 + fname = _IMG_NAME_RE.sub("", image_name or "").strip()
81 + if not fname:
82 + return ""
83 + m = re.search(r"fichiersUploadOpt/[^\"'\\ )]*/" + re.escape(fname), html)
84 + return BASE + "/" + m.group(0) if m else ""
85 +
86 + def fetch(self) -> list[Event]:
87 + fiches = _fiches_json(self.get(LIST_URL).text)
88 + cache = self.load_cache()
89 + now = time.time()
90 + current = {str(f.get("noFiche")) for f in fiches}
91 + cache = {k: v for k, v in cache.items() if k in current}
92 +
93 + # fiches détail (image seulement) : incrémental + plafond
94 + to_fetch = sorted(
95 + (f for f in fiches
96 + if f.get("repertoireMembreLangue") and f.get("imageFiche")
97 + and (str(f.get("noFiche")) not in cache
98 + or now - cache[str(f.get("noFiche"))].get("ts", 0) > REFRESH_AFTER)),
99 + key=lambda f: cache.get(str(f.get("noFiche")), {}).get("ts", 0)
100 + )[:CN_FICHE_MAX]
101 + for fiche in to_fetch:
102 + eid = str(fiche.get("noFiche"))
103 + url = f"{BASE}/fr/{fiche['repertoireMembreLangue']}/evenements-et-spectacles/"
104 + image = ""
105 + try:
106 + image = self._image_from_fiche(self.get(url).text,
107 + fiche.get("imageFiche", ""))
108 + except Exception:
109 + pass
110 + cache[eid] = {"ts": now, "image": image}
111 + self.save_cache(cache)
112 +
113 + events: list[Event] = []
114 + seen: set[str] = set()
115 + for f in fiches:
116 + eid = str(f.get("noFiche") or "")
117 + title = clean_text(f.get("overrideTitreFicheLangue")
118 + or f.get("titreFicheLangue")
119 + or f.get("titreMembreLangue") or "")
120 + slug = (f.get("repertoireMembreLangue") or "").strip()
121 + if not eid or eid in seen or not title or not slug:
122 + continue
123 + seen.add(eid)
124 + lat, lng = _latlng(f)
125 + address = clean_text(" ".join(
126 + p for p in (f.get("noCiviqueAdresse"), f.get("rueAdresse"))
127 + if p) or f.get("adresseFiche") or "")
128 + price_label = clean_text(" — ".join(
129 + p for p in (f.get("entreeFicheLangue"), f.get("detailPrixLangue"))
130 + if p and str(p).strip()))
131 + website = (f.get("siteInternetFicheLangue")
132 + or f.get("siteInternetFiche") or "").strip()
133 + if website and not website.startswith("http"):
134 + website = "https://" + website
135 + events.append(Event(
136 + source=self.source_id,
137 + external_id=eid,
138 + url=f"{BASE}/fr/{slug}/evenements-et-spectacles/",
139 + title=title,
140 + description=clean_text(f.get("descriptionFicheLangue") or ""),
141 + raw_categories=["Événements et spectacles", title],
142 + address=address,
143 + city=clean_text(f.get("titreVille") or f.get("villeFiche") or ""),
144 + postal_code=(f.get("codePostalAdresse") or f.get("codePostalFiche")
145 + or f.get("cpFiche") or "").strip(),
146 + tourist_region="Côte-Nord",
147 + lat=lat, lng=lng,
148 + start_date=_date_or_none(f.get("dateDebutFiche")),
149 + end_date=_date_or_none(f.get("dateFinFiche")),
150 + price_label=price_label,
151 + website=website,
152 + image=cache.get(eid, {}).get("image", ""),
153 + ))
154 + return events
added sortika/connectors/drummondville.py +160 −0
@@ -0,0 +1,160 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/drummondville.py : connecteur Ville de Drummondville — calendrier
5 +# Source : https://www.drummondville.ca/culture-loisirs-et-sports/
6 +# calendrier-des-evenements/ (WordPress + Toolset Views).
7 +# Extraction: la vue Toolset se pagine CÔTÉ SERVEUR par ?wpv_paged=N
8 +# (vérifié 2026-08-25) → cartes HTML (10/page) : plage de dates
9 +# FR, heure texte (« 19 h » / « Horaire variable »), catégorie,
10 +# titre, url fiche, image. Enrichissement par fiche
11 +# /evenement/<slug>/ (lieu « event-place », coût « Gratuit »)
12 +# — CACHE INCRÉMENTAL disque, plafond DRU_FICHES par sync.
13 +# Accès : site municipal public, robots.txt permissif (wp-admin
14 +# seulement). GET directs throttlés, identification honnête.
15 +# Prix : la fiche publie le coût (souvent « Gratuit ») → price_label
16 +# fidèle, jamais inventé.
17 +# -----------------------------------------------------------------------------
18 +from __future__ import annotations
19 +
20 +import html as _html
21 +import os
22 +import re
23 +
24 +from ..normalize import clean_text, parse_date_range_fr, parse_time
25 +from ..schema import Event
26 +from .base import BaseConnector
27 +
28 +LIST_URL = ("https://www.drummondville.ca/culture-loisirs-et-sports/"
29 + "calendrier-des-evenements/")
30 +PAGES_MAX = int(os.environ.get("DRU_PAGES", "12"))
31 +FICHES_MAX = int(os.environ.get("DRU_FICHES", "40"))
32 +
33 +_CARD_RE = re.compile(
34 + r'<div class="events-list">(.*?)(?=<div class="events-list">|<!-- FIN|$)',
35 + re.S)
36 +_TITLE_RE = re.compile(
37 + r'class="events-titre"><a href="([^"]+)"[^>]*>(.*?)</a>', re.S)
38 +_DATE_RE = re.compile(r'class="events-jours">\s*(.*?)\s*</span>', re.S)
39 +_HEURE_RE = re.compile(r'class="events-heure">(.*?)</p>', re.S)
40 +_CAT_RE = re.compile(r'class="events-cat[^"]*">.*?</i>\s*(.*?)</span>', re.S)
41 +_IMG_RE = re.compile(r'<img[^>]+src="([^"]+)"')
42 +# fiche
43 +_F_PLACE_RE = re.compile(
44 + r'class="event-place">\s*<a[^>]*>\s*<p>(.*?)</p>', re.S)
45 +_F_COUT_RE = re.compile(r'class="events-cout">(.*?)</span>', re.S)
46 +# id de la vue Toolset du calendrier (requis pour wpv_paged, sinon la
47 +# pagination ressert la page 1) — extrait du FORMULAIRE DE FILTRE de la page
48 +# (la vue « alerte » 1209 publie aussi un wpv_view_count : ne pas la prendre)
49 +_VIEW_RE = re.compile(r'name="wpv-filter-(\d+)"')
50 +VIEW_FALLBACK = "898"
51 +
52 +
53 +def _range(txt: str) -> tuple[str | None, str | None]:
54 + """« 27 juin au 29 août 2026 » → plage ISO (préfixe « du » requis par le
55 + parseur commun pour résoudre le début partiel)."""
56 + txt = re.sub(r"^\s*du\s+", "", txt, flags=re.I)
57 + return parse_date_range_fr("du " + txt)
58 +
59 +
60 +class DrummondvilleConnector(BaseConnector):
61 + source_id = "drummondville"
62 + request_delay = 0.8
63 +
64 + def _parse_page(self, html: str) -> list[dict]:
65 + rows: list[dict] = []
66 + for block in _CARD_RE.findall(html):
67 + t = _TITLE_RE.search(block)
68 + d = _DATE_RE.search(block)
69 + if not t or not d:
70 + continue
71 + start, end = _range(clean_text(d.group(1)))
72 + if not start:
73 + continue
74 + h = _HEURE_RE.search(block)
75 + heure = clean_text(re.sub(r"<[^>]+>", " ", h.group(1))) if h else ""
76 + cat = _CAT_RE.search(block)
77 + img = _IMG_RE.search(block)
78 + rows.append({
79 + "url": t.group(1),
80 + "title": clean_text(_html.unescape(
81 + re.sub(r"<[^>]+>", " ", t.group(2)))),
82 + "start": start,
83 + "end": end or start,
84 + "start_time": parse_time(heure),
85 + "category": clean_text(cat.group(1)) if cat else "",
86 + "image": img.group(1) if img else "",
87 + })
88 + return rows
89 +
90 + def _fiche(self, url: str) -> dict:
91 + html = self.get(url).text
92 + out: dict = {}
93 + m = _F_PLACE_RE.search(html)
94 + if m:
95 + out["venue"] = clean_text(_html.unescape(
96 + re.sub(r"<[^>]+>", " ", m.group(1))))
97 + m = _F_COUT_RE.search(html)
98 + if m:
99 + out["price_label"] = clean_text(re.sub(r"<[^>]+>", " ", m.group(1)))
100 + return out
101 +
102 + def fetch(self) -> list[Event]:
103 + rows: list[dict] = []
104 + seen_urls: set[str] = set()
105 + view_id = VIEW_FALLBACK
106 + for page in range(1, PAGES_MAX + 1):
107 + params = ({"wpv_view_count": view_id, "wpv_paged": str(page)}
108 + if page > 1 else None)
109 + html = self.get(LIST_URL, params=params).text
110 + if page == 1:
111 + m = _VIEW_RE.search(html)
112 + if m:
113 + view_id = m.group(1)
114 + batch = self._parse_page(html)
115 + new = [r for r in batch if r["url"] not in seen_urls]
116 + if not new: # Toolset ressert la dernière page → stop
117 + break
118 + seen_urls.update(r["url"] for r in new)
119 + rows += new
120 + if len(batch) < 10:
121 + break
122 + cache = self.load_cache()
123 + fetched = 0
124 + for r in rows:
125 + slug = r["url"].rstrip("/").rsplit("/", 1)[-1]
126 + r["slug"] = slug
127 + if slug not in cache and fetched < FICHES_MAX:
128 + try:
129 + cache[slug] = self._fiche(r["url"])
130 + fetched += 1
131 + except Exception as exc: # fiche cassée ≠ source cassée
132 + print(f"[sorti-ka] drummondville : fiche {slug} "
133 + f"ignorée : {exc}")
134 + if fetched:
135 + self.save_cache(cache)
136 + events: list[Event] = []
137 + seen: set[str] = set()
138 + for r in rows:
139 + ext = f"{r['slug']}-{r['start']}"
140 + if ext in seen:
141 + continue
142 + seen.add(ext)
143 + extra = cache.get(r["slug"]) or {}
144 + events.append(Event(
145 + source=self.source_id,
146 + external_id=ext,
147 + url=r["url"],
148 + title=r["title"],
149 + raw_categories=[r["category"]] if r["category"] else [r["title"]],
150 + venue=extra.get("venue", ""),
151 + city="Drummondville",
152 + region="Centre-du-Québec",
153 + start_date=r["start"],
154 + start_time=r["start_time"],
155 + end_date=r["end"],
156 + price_label=extra.get("price_label", ""),
157 + organizer="Ville de Drummondville",
158 + image=r["image"],
159 + ))
160 + return events
added sortika/connectors/eeyouistchee.py +153 −0
@@ -0,0 +1,153 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/eeyouistchee.py : Eeyou Istchee Baie-James (ATR, vague ATR 2026-08)
5 +# Source : eeyouistcheebaiejames.com/fr/quoi-faire/evenements/ (domaine
6 +# decrochezcommejamais.com → redirection) — calendrier officiel
7 +# de l'ATR (plateforme membres rendue serveur, vérifié
8 +# 2026-08-25 : ~28 cartes événements avec titre, ville, date FR,
9 +# catégorie, image).
10 +# Extraction: 1 GET liste → cartes « update-secondary » (lien fiche membre
11 +# /fr/membres/<ville>/<noFiche>/evenements-et-festivals/, image
12 +# fichiersUploadOpt, titre, « Région : <ville> », date
13 +# « update-meta » : « 30 juillet au 2 août 2026 » ou « Date à
14 +# venir »). Fiches détail incrémentales (cache disque
15 +# data/eeyouistchee_cache.json, plafond EIB_FICHE_MAX/sync,
16 +# re-visite 7 j) : description (paragraphe principal), code
17 +# postal et site web de l'organisme.
18 +# Accès : pages publiques de l'ATR — GET throttlés, identification
19 +# honnête, lien vers la fiche originale. Ni prix ni heure
20 +# structurés publiés → jamais inventés.
21 +# -----------------------------------------------------------------------------
22 +from __future__ import annotations
23 +
24 +import os
25 +import re
26 +import time
27 +
28 +from ..normalize import clean_text, parse_date_range_fr
29 +from ..schema import Event
30 +from .base import BaseConnector
31 +
32 +BASE = "https://www.eeyouistcheebaiejames.com"
33 +LIST_URL = BASE + "/fr/quoi-faire/evenements/"
34 +EIB_FICHE_MAX = int(os.environ.get("EIB_FICHE_MAX", "10"))
35 +REFRESH_AFTER = 7 * 86400
36 +
37 +# une carte événement : image de fond, lien fiche, titre, ville, date
38 +_CARD_RE = re.compile(
39 + r'update-image" style="background-image: url\(([^)]*)\)[^>]*>\s*'
40 + r'<a href="(/fr/membres/[^"]+/evenements-et-festivals/)"></a>.*?'
41 + r'<a href="\2">([^<]*)</a>.*?'
42 + r'<p>R[ée]gion\s*:\s*([^<]*)</p>.*?'
43 + r'(?:<span class="update-meta">([^<]*)</span>.*?)?</div>\s*\n?\s*'
44 + r'<div class="update-actions">', re.S)
45 +
46 +_POSTAL_RE = re.compile(r'\b([A-Z]\d[A-Z]\s?\d[A-Z]\d)\b')
47 +_P_RE = re.compile(r"<p[^>]*>(.*?)</p>", re.S)
48 +_WEBSITE_RE = re.compile(
49 + r'href="(https?://(?!www\.eeyouistcheebaiejames)[^"]+)"[^>]*>\s*'
50 + r'(?:Site\s|www\.|[a-z0-9.-]+\.(?:com|ca|qc\.ca|org|net))', re.I)
51 +
52 +
53 +def _parse_range(raw: str) -> tuple[str | None, str | None]:
54 + """« 8 au 9 août 2026 » / « 30 juillet au 2 août 2026 » : la source omet
55 + le « du » initial → on le préfixe pour la plage partielle."""
56 + raw = clean_text(raw or "")
57 + if re.match(r"^\d", raw) and " au " in raw:
58 + raw = "du " + raw
59 + return parse_date_range_fr(raw)
60 +
61 +
62 +class EeyouIstcheeConnector(BaseConnector):
63 + source_id = "eeyouistchee"
64 + request_delay = 0.8
65 +
66 + def _cards(self, html: str) -> list[dict]:
67 + out = []
68 + for m in _CARD_RE.finditer(html):
69 + img, path, title, city, date_raw = m.groups()
70 + start, end = _parse_range(date_raw or "")
71 + # fr/membres/<slug-membre>/<noFiche>/evenements-et-festivals
72 + parts = path.strip("/").split("/")
73 + out.append({
74 + "path": path,
75 + "external_id": (parts[3] if len(parts) > 4 and
76 + parts[3].isdigit() else parts[2]),
77 + "title": clean_text(title),
78 + "city": clean_text(city),
79 + "start_date": start, "end_date": end,
80 + "image": BASE + img.strip("'\" ") if img else "",
81 + })
82 + return out
83 +
84 + def _parse_fiche(self, html: str) -> dict:
85 + """Description (paragraphe principal), code postal, site web."""
86 + i = html.find("<h1")
87 + seg = html[i:i + 15000] if i >= 0 else html[:15000]
88 + out: dict = {}
89 + phone_re = re.compile(r"\d{3}[ .-]\d{3}[ .-]\d{4}")
90 + paras = [clean_text(p) for p in _P_RE.findall(seg)]
91 + # écarte les blocs coordonnées (adresse/téléphone/code postal)
92 + paras = [p for p in paras if len(p) > 80
93 + and not phone_re.search(p) and not _POSTAL_RE.search(p)
94 + # scripts anti-pourriel (codes décimaux) recrachés en texte
95 + and not re.search(r"(?:\d{2,3},){5,}", p)]
96 + if paras:
97 + out["description"] = max(paras, key=len)
98 + m = _POSTAL_RE.search(re.sub(r"<[^>]+>", " ", seg))
99 + if m:
100 + out["postal_code"] = m.group(1)
101 + w = _WEBSITE_RE.search(seg)
102 + if w:
103 + out["website"] = w.group(1)
104 + return out
105 +
106 + def fetch(self) -> list[Event]:
107 + cards = self._cards(self.get(LIST_URL).text)
108 + cache = self.load_cache()
109 + now = time.time()
110 + current = {c["path"] for c in cards}
111 + cache = {k: v for k, v in cache.items() if k in current}
112 +
113 + to_fetch = sorted(
114 + (c["path"] for c in cards
115 + if c["path"] not in cache
116 + or now - cache[c["path"]].get("ts", 0) > REFRESH_AFTER),
117 + key=lambda p: cache.get(p, {}).get("ts", 0))[:EIB_FICHE_MAX]
118 + for path in to_fetch:
119 + detail: dict = {}
120 + try:
121 + detail = self._parse_fiche(self.get(BASE + path).text)
122 + except Exception:
123 + detail = {}
124 + cache[path] = {"ts": now, "detail": detail}
125 + self.save_cache(cache)
126 +
127 + events: list[Event] = []
128 + seen: set[str] = set()
129 + for c in cards:
130 + key = f'{c["external_id"]}:{c["title"].lower()}'
131 + if not c["title"] or key in seen:
132 + continue
133 + seen.add(key)
134 + detail = cache.get(c["path"], {}).get("detail") or {}
135 + events.append(Event(
136 + source=self.source_id,
137 + # une même fiche membre peut porter plusieurs événements :
138 + # l'identifiant inclut le titre pour rester stable et unique
139 + external_id=f'{c["external_id"]}-'
140 + + re.sub(r"[^a-z0-9]+", "-", c["title"].lower())[:60],
141 + url=BASE + c["path"],
142 + title=c["title"],
143 + description=detail.get("description", ""),
144 + raw_categories=["Événements et festivals", c["title"]],
145 + city=c["city"],
146 + postal_code=detail.get("postal_code", ""),
147 + tourist_region="Eeyou Istchee Baie-James",
148 + start_date=c["start_date"],
149 + end_date=c["end_date"],
150 + website=detail.get("website", ""),
151 + image=c["image"],
152 + ))
153 + return events
added sortika/connectors/evenementsattractions.py +173 −0
@@ -0,0 +1,173 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/evenementsattractions.py : connecteur Événements Attractions Québec
5 +# Source : répertoire consommateur des festivals/événements membres d'EAQ,
6 +# publié sur quebecvacances.com (~350 fiches fr, toute la province).
7 +# (Le site corporatif evenementsattractions.quebec ne liste que
8 +# des formations aux membres — sondé 2026-08-25, sans intérêt.)
9 +# Extraction: sitemap paginé (/sitemap.xml?page=1..N) → fiches
10 +# /fr/evenements/<slug>, chacune avec JSON-LD Event complet
11 +# (nom, description, dates ISO de la prochaine édition, lieu,
12 +# adresse, image Cloudinary). Le site est derrière un anti-bot
13 +# (403 en GET direct) → chaque GET passe par la chaîne de secours
14 +# du BaseConnector (Oxylabs → Scrapfly ASP → Bright Data),
15 +# d'où un cache INCRÉMENTAL agressif (data/
16 +# evenementsattractions_cache.json) : nouvelles fiches d'abord,
17 +# re-visite après 14 jours (festivals annuels stables), plafond
18 +# EAQ_DETAIL_MAX fiches/sync.
19 +# Accès : quebecvacances.com/robots.txt « User-agent: * / Allow: / » →
20 +# crawl permis ; rate-limiting poli, identification honnête.
21 +# Prix : non publié dans le JSON-LD → is_free inconnu (jamais inventé).
22 +# -----------------------------------------------------------------------------
23 +from __future__ import annotations
24 +
25 +import json
26 +import os
27 +import re
28 +import time
29 +
30 +from ..normalize import clean_text
31 +from ..schema import Event
32 +from .base import BaseConnector
33 +
34 +BASE = "https://www.quebecvacances.com"
35 +SITEMAP_PAGES = 4 # /sitemap.xml?page=1..4 (sondé 2026-08-25)
36 +
37 +DETAIL_MAX = int(os.environ.get("EAQ_DETAIL_MAX", "80"))
38 +REFRESH_AFTER = 14 * 86400 # festivals annuels : fiches stables
39 +
40 +_LOC_RE = re.compile(r"<loc>([^<]+)</loc>")
41 +_LD_RE = re.compile(r'<script type="application/ld\+json"[^>]*>(.*?)</script>',
42 + re.S)
43 +# « Montréal (ville de) » / « Québec (ville de) » → nom de ville nu
44 +_CITY_SUFFIX_RE = re.compile(r"\s*\((?:ville|municipalité|village|paroisse|"
45 + r"canton|mrc)[^)]*\)\s*$", re.I)
46 +
47 +
48 +def _utf8_text(resp) -> str:
49 + """Texte de la réponse décodé en UTF-8 réel.
50 +
51 + Le site sert son HTML UTF-8 sans charset dans Content-Type ; certains
52 + backends de la chaîne de secours anti-bot décodent alors en latin-1 →
53 + mojibake (« siège » au lieu de « siège », constaté live 2026-08-25).
54 + On répare de façon déterministe : latin-1 est bijectif octet↔caractère,
55 + donc ré-encoder puis décoder en UTF-8 restaure le texte d'origine.
56 + """
57 + text = resp.text
58 + if "Ã" in text: # marqueur sûr de mojibake UTF-8/latin-1
59 + try:
60 + return text.encode("latin-1").decode("utf-8")
61 + except (UnicodeEncodeError, UnicodeDecodeError):
62 + pass
63 + return text
64 +
65 +
66 +class EvenementsAttractionsConnector(BaseConnector):
67 + source_id = "evenementsattractions"
68 + request_delay = 1.0
69 +
70 + # -- découverte (sitemap paginé) -------------------------------------------
71 + def _fiches(self) -> list[str]:
72 + urls: list[str] = []
73 + for page in range(1, SITEMAP_PAGES + 1):
74 + try:
75 + xml = self.get(f"{BASE}/sitemap.xml", params={"page": page}).text
76 + except Exception as exc: # une page cassée ne bloque pas le reste
77 + print(f"[sorti-ka] evenementsattractions : sitemap p{page} "
78 + f"ignorée : {exc}")
79 + continue
80 + urls += [u for u in _LOC_RE.findall(xml)
81 + if "/fr/evenements/" in u]
82 + return sorted(set(urls))
83 +
84 + # -- fiche --------------------------------------------------------------------
85 + def _parse_fiche(self, url: str, html: str) -> dict | None:
86 + data = None
87 + for blob in _LD_RE.findall(html):
88 + try:
89 + d = json.loads(blob.strip())
90 + except ValueError:
91 + continue
92 + if d.get("@type") == "Event" and d.get("startDate"):
93 + data = d
94 + break
95 + if not data:
96 + return None
97 + title = clean_text(data.get("name") or "")
98 + if not title:
99 + return None
100 + loc = data.get("location") or {}
101 + addr = loc.get("address") or {}
102 + city = _CITY_SUFFIX_RE.sub("", clean_text(addr.get("addressLocality")
103 + or ""))
104 + venue = clean_text(loc.get("name") or "")
105 + if venue == title: # la source répète le nom de l'événement
106 + venue = ""
107 + return {
108 + "title": title,
109 + "description": clean_text(data.get("description") or "")[:2000],
110 + "raw_categories": [title],
111 + "venue": venue,
112 + "address": clean_text(addr.get("streetAddress") or ""),
113 + "city": city,
114 + "postal_code": addr.get("postalCode") or "",
115 + "start_date": str(data.get("startDate") or "")[:10] or None,
116 + "end_date": str(data.get("endDate") or "")[:10] or None,
117 + "website": data.get("url") or "",
118 + "image": data.get("image") or "",
119 + }
120 +
121 + # -- pipeline -------------------------------------------------------------------
122 + def fetch(self) -> list[Event]:
123 + cache = self.load_cache()
124 + urls = self._fiches()
125 + if urls: # sitemap indisponible → on garde le cache
126 + current = set(urls)
127 + cache = {u: c for u, c in cache.items() if u in current}
128 + else:
129 + urls = list(cache)
130 +
131 + now = time.time()
132 + candidates = sorted(
133 + (u for u in urls
134 + if u not in cache
135 + or now - cache[u].get("fetched_at", 0) > REFRESH_AFTER),
136 + key=lambda u: cache.get(u, {}).get("fetched_at", 0))[:DETAIL_MAX]
137 +
138 + for url in candidates: # séquentiel : chaque GET peut escalader
139 + try:
140 + row = self._parse_fiche(url, _utf8_text(self.get(url)))
141 + except Exception as exc: # fiche cassée : on ne bloque pas la source
142 + print(f"[sorti-ka] evenementsattractions : fiche ignorée "
143 + f"{url} : {exc}")
144 + row = cache.get(url, {}).get("row")
145 + cache[url] = {"fetched_at": now, "row": row}
146 + self.save_cache(cache)
147 +
148 + events: list[Event] = []
149 + seen: set[str] = set()
150 + for url, entry in cache.items():
151 + row = entry.get("row")
152 + if not row:
153 + continue
154 + slug = url.rstrip("/").rsplit("/", 1)[-1]
155 + if slug in seen:
156 + continue
157 + seen.add(slug)
158 + events.append(Event(
159 + source=self.source_id,
160 + external_id=slug,
161 + url=url, title=row["title"],
162 + description=row.get("description", ""),
163 + raw_categories=row.get("raw_categories") or [],
164 + venue=row.get("venue", ""),
165 + address=row.get("address", ""),
166 + city=row.get("city", ""),
167 + postal_code=row.get("postal_code", ""),
168 + start_date=row.get("start_date"),
169 + end_date=row.get("end_date"),
170 + website=row.get("website", ""),
171 + image=row.get("image", ""),
172 + ))
173 + return events
modified sortika/connectors/evenko.py +15 −6
@@ -9,12 +9,21 @@
9 9 # options], lang:"fr-CA"}). ~14 requêtes/sync (100 hits/page).
10 10 # Accès : robots.txt Allow /api/search* ; GET throttlés, identification
11 11 # honnête ; lien billets = URL Ticketmaster officielle du hit.
12 −# Prix : champ `free` de la source → is_free, MAIS sondé null sur
13 −# 100 % des hits (462/462, vérifié 2026-08) et l'index ne porte
14 −# aucun montant (additional_information.regular_ticket = libellés
15 −# + URLs Ticketmaster seulement, filtre free:true → 0 hit).
16 −# Complément honnête : étiquettes explicites « gratuit » dans
17 −# event_tag / promotional_tag → is_free=True. On n'invente rien.
12 +# Prix : NON PUBLIÉS par la source. Champ `free` → is_free, MAIS sondé
13 +# null sur 100 % des hits (462/462 vérifié 2026-08 ; re-vérifié
14 +# 660/660 le 2026-08-25) et l'index
15 +# ne porte aucun montant (additional_information.regular_ticket
16 +# = libellés + URLs Ticketmaster seulement, filtre free:true →
17 +# 0 hit ; event_discount = {percentage, amount, minimum} d'un
18 +# rabais, pas un tarif). Re-sondé live 2026-08-25 sur l'index
19 +# COMPLET (660 hits, tous attributs) ET sur les pages événement
20 +# /fr/evenements/<salle>/<slug> de 6 salles : le JSON-LD Event
21 +# y porte offers SANS price/priceCurrency (URL Ticketmaster +
22 +# availability seulement) — les montants ne vivent que chez
23 +# Ticketmaster (couvert par le connecteur ticketmaster, API
24 +# officielle priceRanges). Complément honnête : étiquettes
25 +# explicites « gratuit » dans event_tag / promotional_tag →
26 +# is_free=True. On n'invente rien.
18 27 # -----------------------------------------------------------------------------
19 28 from __future__ import annotations
20 29
modified sortika/connectors/gaspesie.py +56 −11
@@ -8,19 +8,24 @@
8 8 # (CMS Umbraco). Le site est derrière un WAF : 403 sur GET
9 9 # identifié (revérifié 2026-08-21) → extraction via SCRAPFLY
10 10 # (asp, country=ca, sans render_js — le HTML est rendu serveur),
11 −# usage consigné et budgeté (GASP_FICHE_MAX/sync).
11 +# usage consigné et budgeté (GASP_FICHE_MAX/sync). La liste
12 +# publie les 27 fiches d'un coup (pagination purement côté
13 +# client, 12/page — revérifié 2026-08-25) : rien de caché.
12 14 # Extraction: liste (1 crédit) → fiches détail incrémentales (cache
13 15 # data/gaspesie_cache.json, GASP_FICHE_MAX/sync, re-visite 14 j
14 −# — festivals annuels stables) : titre <h1>, plage de dates FR
15 −# (« 18 au 21 juin 2026 »), ville (« Carleton-sur-Mer, Québec »),
16 −# og:description, og:image. Pas d'heure ni de tarif structurés
17 −# → jamais inventés.
16 +# — festivals annuels stables, version v=2) : titre <h1>, plage
17 +# de dates FR (« 18 au 21 juin 2026 »), og:description, og:image.
18 +# ENRICHI (2026-08-25) : JSON-LD Organization de la fiche → GPS
19 +# exact (GeoCoordinates), adresse civique/ville/code postal
20 +# structurés, site web officiel (sameAs non-réseau-social).
21 +# Pas d'heure ni de tarif structurés → jamais inventés.
18 22 # Accès : contenu public de l'ATR ; anti-bot contourné avec modération
19 23 # (≤ 1 + GASP_FICHE_MAX crédits Scrapfly/sync, re-visite 14 j),
20 24 # attribution et lien vers la fiche officielle conservés.
21 25 # -----------------------------------------------------------------------------
22 26 from __future__ import annotations
23 27
28 +import json
24 29 import os
25 30 import re
26 31 import time
@@ -33,6 +38,8 @@ BASE = "https://www.tourisme-gaspesie.com"
33 38 LIST_URL = BASE + "/fr/festival-et-evenements/"
34 39 FICHE_MAX = int(os.environ.get("GASP_FICHE_MAX", "8"))
35 40 REFRESH_AFTER = 14 * 86400 # festivals annuels : 2 semaines
41 +CACHE_V = 2 # v2 : + GPS/adresse structurée/site web (JSON-LD
42 +# Organization) — 2026-08-25
36 43
37 44 _FICHE_RE = re.compile(r'href="(/fr/festival-et-evenements/[a-z0-9-]+/)"')
38 45 _TITLE_RE = re.compile(r"<h1[^>]*>\s*([^<]+?)\s*</h1>")
@@ -47,6 +54,23 @@ _ADDR_CITY_RE = re.compile(
47 54 r"(?:\s*(?P<pc>[A-Z]\d[A-Z]\s?\d[A-Z]\d))?", re.S)
48 55 _OG_IMG_RE = re.compile(r'property="og:image"\s+content="([^"]*)"')
49 56 _OG_DESC_RE = re.compile(r'property="og:description"\s+content="([^"]*)"')
57 +_LDJSON_RE = re.compile(
58 + r'<script type="application/ld\+json"[^>]*>(.*?)</script>', re.S)
59 +_SOCIAL = ("facebook.", "instagram.", "youtube.", "twitter.", "x.com",
60 + "tiktok.", "linkedin.")
61 +
62 +
63 +def _ld_organization(html: str) -> dict:
64 + """Bloc JSON-LD Organization d'une fiche (GPS, adresse, sameAs)."""
65 + for m in _LDJSON_RE.finditer(html):
66 + try:
67 + d = json.loads(m.group(1))
68 + except ValueError:
69 + continue
70 + for doc in (d if isinstance(d, list) else [d]):
71 + if isinstance(doc, dict) and doc.get("@type") == "Organization":
72 + return doc
73 + return {}
50 74
51 75
52 76 class GaspesieConnector(BaseConnector):
@@ -71,14 +95,29 @@ class GaspesieConnector(BaseConnector):
71 95 coord = _ADDR_CITY_RE.search(html)
72 96 img = _OG_IMG_RE.search(html)
73 97 desc = _OG_DESC_RE.search(html)
98 + # JSON-LD Organization : GPS + adresse structurée + site web officiel
99 + org = _ld_organization(html)
100 + geo = org.get("geo") or {}
101 + addr = org.get("address") or {}
102 + website = ""
103 + for same in (org.get("sameAs") or []):
104 + s = str(same).strip()
105 + if s.startswith("http") and not any(k in s for k in _SOCIAL):
106 + website = s
107 + break
74 108 return {
75 109 "external_id": url.rstrip("/").split("/")[-1],
76 110 "url": BASE + url,
77 111 "title": clean_text(title.group(1)),
78 112 "description": clean_text(desc.group(1)) if desc else "",
79 − "address": clean_text(coord.group("addr") or "") if coord else "",
80 − "city": clean_text(coord.group("city")) if coord else "",
81 − "postal_code": clean_text(coord.group("pc") or "") if coord else "",
113 + "address": clean_text(addr.get("streetAddress") or "")
114 + or (clean_text(coord.group("addr") or "") if coord else ""),
115 + "city": clean_text(addr.get("addressLocality") or "")
116 + or (clean_text(coord.group("city")) if coord else ""),
117 + "postal_code": (addr.get("postalCode") or "").strip()
118 + or (clean_text(coord.group("pc") or "") if coord else ""),
119 + "lat": geo.get("latitude"), "lng": geo.get("longitude"),
120 + "website": website,
82 121 "start_date": start, "end_date": end,
83 122 "image": clean_text(img.group(1)) if img else "",
84 123 }
@@ -91,10 +130,14 @@ class GaspesieConnector(BaseConnector):
91 130 now = time.time()
92 131 current = set(urls)
93 132 cache = {u: c for u, c in cache.items() if u in current}
133 + # nouvelles fiches puis celles parsées avant v2 (sans GPS/site web),
134 + # puis re-visite roulante — budget Scrapfly plafonné
94 135 to_fetch = sorted(
95 136 (u for u in urls
96 − if u not in cache or now - cache[u].get("ts", 0) > REFRESH_AFTER),
97 − key=lambda u: cache.get(u, {}).get("ts", 0))[:FICHE_MAX]
137 + if u not in cache or cache[u].get("v", 1) < CACHE_V
138 + or now - cache[u].get("ts", 0) > REFRESH_AFTER),
139 + key=lambda u: (cache.get(u, {}).get("v", 1) if u in cache else 0,
140 + cache.get(u, {}).get("ts", 0)))[:FICHE_MAX]
98 141 for url in to_fetch: # séquentiel : budget crédits
99 142 try:
100 143 row = self._parse_fiche(url, self._get_waf(BASE + url))
@@ -102,7 +145,7 @@ class GaspesieConnector(BaseConnector):
102 145 row = None
103 146 if row is None:
104 147 print(f"[sorti-ka] gaspesie : fiche ignorée {url}")
105 − cache[url] = {"ts": now, "row": row}
148 + cache[url] = {"ts": now, "row": row, "v": CACHE_V}
106 149 self.save_cache(cache)
107 150
108 151 events: list[Event] = []
@@ -123,8 +166,10 @@ class GaspesieConnector(BaseConnector):
123 166 city=r["city"],
124 167 postal_code=r.get("postal_code", ""),
125 168 tourist_region="Gaspésie",
169 + lat=r.get("lat"), lng=r.get("lng"),
126 170 start_date=r["start_date"],
127 171 end_date=r["end_date"],
172 + website=r.get("website", ""),
128 173 image=r["image"],
129 174 ))
130 175 return events
added sortika/connectors/gatineau.py +102 −0
@@ -0,0 +1,102 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/gatineau.py : connecteur Ville de Gatineau — calendrier officiel
5 +# Source : https://calendrier.gatineau.cloud — application calendrier
6 +# dédiée de la Ville (gatineau.ca/calendrier y redirige).
7 +# Extraction: API JSON interne POST /calendrier/json/evenements.aspx
8 +# (même appel que le frontend, découvert dans scripts.js) —
9 +# ~160 événements / ~250 occurrences en 1 requête : titre,
10 +# description, image, lien officiel, occurrences horodatées
11 +# (UTC → heure locale via utc_to_local), emplacements
12 +# (nom, adresse, secteur). Vérifié 2026-08-25.
13 +# Accès : application municipale publique, robots.txt « robots: all »
14 +# sur la page, GET/POST directs, identification honnête.
15 +# Prix : non publié par l'API → jamais inventé (les liens « Acheter
16 +# des billets » sont conservés dans website).
17 +# -----------------------------------------------------------------------------
18 +from __future__ import annotations
19 +
20 +import os
21 +import time
22 +
23 +from ..normalize import clean_text, utc_to_local
24 +from ..schema import Event
25 +from .base import BaseConnector
26 +
27 +BASE = "https://calendrier.gatineau.cloud"
28 +API = BASE + "/calendrier/json/evenements.aspx"
29 +FICHE = BASE + "/calendrier/evenement.aspx?langue=fr-ca&id_evenement={id}"
30 +LIGNES = int(os.environ.get("GATINEAU_LIGNES", "500")) # plafond de lignes
31 +
32 +
33 +class GatineauConnector(BaseConnector):
34 + source_id = "gatineau"
35 + request_delay = 0.8
36 +
37 + def _post_json(self, data: dict):
38 + self._throttle()
39 + resp = self.session.post(API, data=data, timeout=self.timeout)
40 + self._last_request = time.time()
41 + resp.raise_for_status()
42 + return resp.json()
43 +
44 + def fetch(self) -> list[Event]:
45 + records = self._post_json({"langue": "fr-ca", "lignes": str(LIGNES)})
46 + events: list[Event] = []
47 + seen: set[str] = set()
48 + for rec in records:
49 + ev_id = str(rec.get("id") or "")
50 + title = clean_text(rec.get("evenement") or "")
51 + if not ev_id or not title:
52 + continue
53 + description = clean_text(rec.get("description") or "")
54 + image = rec.get("image") or ""
55 + website = rec.get("url") or ""
56 + # billets éventuels (« Acheter des billets ») → site officiel
57 + if not website:
58 + for act in rec.get("actions") or []:
59 + if act.get("url"):
60 + website = act["url"]
61 + break
62 + cats = [clean_text(e.get("etiquette") or str(e))
63 + for e in rec.get("etiquettes") or [] if e]
64 + for occ in rec.get("occurrences") or []:
65 + ext = f"{ev_id}-{occ.get('id')}"
66 + if ext in seen:
67 + continue
68 + seen.add(ext)
69 + # horodatages publiés en UTC (« ...Z ») → date + heure locales
70 + start, start_time = utc_to_local(occ.get("date_heure_debut"))
71 + end, end_time = utc_to_local(occ.get("date_heure_fin"))
72 + if not start:
73 + continue
74 + venue = address = ""
75 + emp = (occ.get("emplacements") or [{}])[0]
76 + venue = clean_text(emp.get("emplacement_primaire")
77 + or emp.get("emplacement") or "")
78 + address = clean_text(emp.get("adresse") or "")
79 + desc = description or clean_text(occ.get("description") or "")
80 + mention = clean_text(occ.get("mention_speciale") or "")
81 + if mention:
82 + desc = f"{desc}\n{mention}".strip()
83 + events.append(Event(
84 + source=self.source_id,
85 + external_id=ext,
86 + url=FICHE.format(id=ev_id),
87 + title=title,
88 + description=desc,
89 + raw_categories=[c for c in cats if c] or [title],
90 + venue=venue,
91 + address=address,
92 + city="Gatineau",
93 + region="Outaouais",
94 + start_date=start,
95 + start_time=start_time,
96 + end_date=end or start,
97 + end_time=end_time,
98 + organizer="Ville de Gatineau",
99 + website=website,
100 + image=image,
101 + ))
102 + return events
added sortika/connectors/granby.py +106 −0
@@ -0,0 +1,106 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/granby.py : connecteur Ville de Granby — calendrier des événements
5 +# Source : https://www.granby.ca/fr/calendrier-evenements (Drupal).
6 +# Extraction: la Ville ne publie PAS de calendrier structuré (jsonapi
7 +# désactivé, page « actualités/événements » vide côté serveur,
8 +# vérifié 2026-08-25) — la page calendrier est une liste
9 +# ÉDITORIALE rendue serveur : blocs riches <h5><strong>Titre
10 +# </strong></h5> suivi d'un <p> de dates FR (« Les 22 et 23 mai
11 +# 2026 », « Du 28 au 30 mai 2026 ») et d'un lien « Découvrez
12 +# l'événement » (souvent externe, vers l'organisateur).
13 +# Parsing tolérant : un bloc sans date parseable est ignoré.
14 +# Volume faible (~15-25 événements saisonniers), 1 requête.
15 +# Accès : site municipal public, GET direct throttlé, identification
16 +# honnête ; robots.txt permissif.
17 +# Prix : non publié par la source → jamais inventé. Lieu/heure non
18 +# publiés dans la liste → laissés vides.
19 +# -----------------------------------------------------------------------------
20 +from __future__ import annotations
21 +
22 +import html as _html
23 +import re
24 +
25 +from ..normalize import _FR_MONTHS, clean_text, strip_accents # type: ignore
26 +from ..schema import Event
27 +from .base import BaseConnector
28 +
29 +LIST_URL = "https://www.granby.ca/fr/calendrier-evenements"
30 +
31 +_BLOCK_RE = re.compile(
32 + r"<h5><strong>(.*?)</strong></h5>(.*?)(?=<h5><strong>|</section>|$)", re.S)
33 +_LINK_RE = re.compile(r'<a href="([^"]+)"[^>]*>\s*D&eacute;couvrez|'
34 + r'<a href="([^"]+)"[^>]*>\s*Découvrez')
35 +# « les 22 et 23 mai 2026 », « du 28 au 30 mai 2026 », « le 24 juin 2026 »,
36 +# « les 4, 5 et 6 juin 2026 » → jours multiples, mois+année en fin
37 +_DAYS_RE = re.compile(
38 + r"((?:\d{1,2}(?:er)?[,\s]*(?:et\s+|au\s+)?)+)\s*([a-z]+)\s+(\d{4})")
39 +
40 +
41 +def _range(txt: str) -> tuple[str | None, str | None]:
42 + """Dates éditoriales FR (jours multiples, un mois+année) → (début, fin)."""
43 + k = strip_accents(clean_text(txt)).lower()
44 + dates: list[str] = []
45 + for m in _DAYS_RE.finditer(k):
46 + mo, y = _FR_MONTHS.get(m.group(2)), int(m.group(3))
47 + if not mo or not (1990 <= y <= 2100):
48 + continue
49 + for dm in re.finditer(r"\d{1,2}", m.group(1)):
50 + d = int(dm.group(0))
51 + if 1 <= d <= 31:
52 + dates.append(f"{y:04d}-{mo:02d}-{d:02d}")
53 + if not dates:
54 + return None, None
55 + return min(dates), max(dates)
56 +
57 +
58 +class GranbyConnector(BaseConnector):
59 + source_id = "granby"
60 +
61 + def _parse_page(self, html: str) -> list[dict]:
62 + rows: list[dict] = []
63 + for title_html, body in _BLOCK_RE.findall(html):
64 + title = clean_text(_html.unescape(
65 + re.sub(r"<[^>]+>", " ", title_html)))
66 + if not title:
67 + continue
68 + # 1er <p> avec une date parseable = la plage de l'événement
69 + start = end = None
70 + for p in re.findall(r"<p[^>]*>(.*?)</p>", body, re.S):
71 + start, end = _range(_html.unescape(re.sub(r"<[^>]+>", " ", p)))
72 + if start:
73 + break
74 + if not start:
75 + continue
76 + link = _LINK_RE.search(body)
77 + website = (link.group(1) or link.group(2)).strip() if link else ""
78 + rows.append({"title": title, "start": start, "end": end,
79 + "website": website})
80 + return rows
81 +
82 + def fetch(self) -> list[Event]:
83 + rows = self._parse_page(self.get(LIST_URL).text)
84 + events: list[Event] = []
85 + seen: set[str] = set()
86 + for r in rows:
87 + slug = re.sub(r"[^a-z0-9]+", "-",
88 + strip_accents(r["title"]).lower()).strip("-")
89 + ext = f"{slug}-{r['start']}"
90 + if ext in seen:
91 + continue
92 + seen.add(ext)
93 + events.append(Event(
94 + source=self.source_id,
95 + external_id=ext,
96 + url=r["website"] or LIST_URL,
97 + title=r["title"],
98 + raw_categories=[r["title"]],
99 + city="Granby",
100 + region="Estrie",
101 + start_date=r["start"],
102 + end_date=r["end"] or r["start"],
103 + organizer="Ville de Granby",
104 + website=r["website"],
105 + ))
106 + return events
added sortika/connectors/ilesdelamadeleine.py +139 −0
@@ -0,0 +1,139 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/ilesdelamadeleine.py : Tourisme Îles-de-la-Madeleine (ATR)
5 +# Source : tourismeilesdelamadeleine.com/fr/decouvrir-les-iles/experiences/
6 +# culture/festivals-et-evenements/ — calendrier officiel de l'ATR
7 +# (plateforme PHP maison, rendue serveur ; vérifié 2026-08-25 :
8 +# ~10 cartes avec titre, plage de dates FR « Du 22 février au
9 +# 1 mars 2026 », catégorie, image).
10 +# Extraction: 1 GET liste → cartes (lien /fr/<slug>/festivals-et-evenements/,
11 +# image listesite_, titre, dates FR, catégorie). Fiches détail
12 +# incrémentales (cache data/ilesdelamadeleine_cache.json,
13 +# plafond IDM_FICHE_MAX/sync, re-visite 7 j) : microdonnées
14 +# schema.org (streetAddress, addressLocality, postalCode,
15 +# telephone) + description du bloc principal (itemprop) +
16 +# plage de dates de la fiche (souvent plus précise).
17 +# Accès : robots.txt « Allow: / » (hors utilitaires) — GET throttlés,
18 +# identification honnête, lien vers la fiche originale.
19 +# Ni prix ni heure publiés de façon structurée → jamais inventés.
20 +# -----------------------------------------------------------------------------
21 +from __future__ import annotations
22 +
23 +import os
24 +import re
25 +import time
26 +
27 +from ..normalize import clean_text, parse_date_range_fr
28 +from ..schema import Event
29 +from .base import BaseConnector
30 +
31 +BASE = "https://www.tourismeilesdelamadeleine.com"
32 +LIST_URL = (BASE + "/fr/decouvrir-les-iles/experiences/culture/"
33 + "festivals-et-evenements/")
34 +IDM_FICHE_MAX = int(os.environ.get("IDM_FICHE_MAX", "10"))
35 +REFRESH_AFTER = 7 * 86400
36 +
37 +# une carte du listing : lien fiche, image, titre + « aside » (dates, catégorie)
38 +_CARD_RE = re.compile(
39 + r'<a href="(/fr/[^"]+/festivals-et-evenements/)">.*?'
40 + r'(?:<img src="([^"]*)" class="imageFiche"[^>]*>)?.*?'
41 + r'<h3>\s*(.*?)<aside>(.*?)</aside>', re.S)
42 +_ASIDE_DATE_RE = re.compile(r'<span[^>]*>([^<]*)</span>')
43 +_CAT_RE = re.compile(r'titreCategorie">([^<]*)')
44 +
45 +_STREET_RE = re.compile(r'itemprop="streetAddress"[^>]*>([^<]*)')
46 +_CITY_RE = re.compile(r'itemprop="addressLocality"[^>]*>([^<]*)')
47 +_POSTAL_RE = re.compile(r'itemprop="postalCode"[^>]*>([^<]*)')
48 +_DESC_RE = re.compile(r'itemprop="description"[^>]*>(.*?)</div>', re.S)
49 +_DATE_TXT_RE = re.compile(
50 + r'[Dd]u \d{1,2}(?:er)?[^<]{0,60}?\d{4}|\d{1,2}(?:er)?\s+\w+\s+\d{4}')
51 +
52 +
53 +class IlesDeLaMadeleineConnector(BaseConnector):
54 + source_id = "ilesdelamadeleine"
55 + request_delay = 0.8
56 +
57 + def _cards(self, html: str) -> list[dict]:
58 + out = []
59 + for m in _CARD_RE.finditer(html):
60 + path, img, title, aside = m.groups()
61 + spans = _ASIDE_DATE_RE.findall(aside or "")
62 + date_raw = clean_text(spans[0]) if spans else ""
63 + cat = _CAT_RE.search(aside or "")
64 + start, end = parse_date_range_fr(date_raw)
65 + out.append({
66 + "path": path,
67 + "external_id": path.strip("/").split("/")[1],
68 + "title": clean_text(title),
69 + "raw_categories": [clean_text(cat.group(1)) if cat else "",
70 + clean_text(title)],
71 + "start_date": start, "end_date": end,
72 + "image": BASE + img if img else "",
73 + })
74 + return out
75 +
76 + def _parse_fiche(self, html: str) -> dict:
77 + """Adresse/ville/description du bloc principal de la fiche membre."""
78 + i = html.find("<h1")
79 + seg = html[i:i + 12000] if i >= 0 else html[:12000]
80 + out: dict = {}
81 + for key, rx in (("address", _STREET_RE), ("city", _CITY_RE),
82 + ("postal_code", _POSTAL_RE)):
83 + m = rx.search(seg) or rx.search(html)
84 + if m:
85 + out[key] = clean_text(m.group(1))
86 + m = _DESC_RE.search(seg)
87 + if m:
88 + out["description"] = clean_text(m.group(1))
89 + d = _DATE_TXT_RE.search(m.group(1))
90 + if d:
91 + start, end = parse_date_range_fr(d.group(0))
92 + if start or end:
93 + out["start_date"], out["end_date"] = start, end
94 + return out
95 +
96 + def fetch(self) -> list[Event]:
97 + cards = self._cards(self.get(LIST_URL).text)
98 + cache = self.load_cache()
99 + now = time.time()
100 + current = {c["path"] for c in cards}
101 + cache = {k: v for k, v in cache.items() if k in current}
102 +
103 + to_fetch = sorted(
104 + (c["path"] for c in cards
105 + if c["path"] not in cache
106 + or now - cache[c["path"]].get("ts", 0) > REFRESH_AFTER),
107 + key=lambda p: cache.get(p, {}).get("ts", 0))[:IDM_FICHE_MAX]
108 + for path in to_fetch:
109 + detail: dict = {}
110 + try:
111 + detail = self._parse_fiche(self.get(BASE + path).text)
112 + except Exception:
113 + detail = {}
114 + cache[path] = {"ts": now, "detail": detail}
115 + self.save_cache(cache)
116 +
117 + events: list[Event] = []
118 + seen: set[str] = set()
119 + for c in cards:
120 + if not c["title"] or c["external_id"] in seen:
121 + continue
122 + seen.add(c["external_id"])
123 + detail = cache.get(c["path"], {}).get("detail") or {}
124 + events.append(Event(
125 + source=self.source_id,
126 + external_id=c["external_id"],
127 + url=BASE + c["path"],
128 + title=c["title"],
129 + description=detail.get("description", ""),
130 + raw_categories=[x for x in c["raw_categories"] if x],
131 + address=detail.get("address", ""),
132 + city=detail.get("city", ""),
133 + postal_code=detail.get("postal_code", ""),
134 + tourist_region="Îles-de-la-Madeleine",
135 + start_date=c["start_date"] or detail.get("start_date"),
136 + end_date=c["end_date"] or detail.get("end_date"),
137 + image=c["image"],
138 + ))
139 + return events
added sortika/connectors/joliette.py +147 −0
@@ -0,0 +1,147 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/joliette.py : connecteur Ville de Joliette — événements
5 +# Source : https://www.joliette.ca/evenements (OctoberCMS/Blanko,
6 +# rendu serveur).
7 +# Extraction: pages liste /evenements puis /evenements/2, /3... (arrêt à la
8 +# première page sans carte) → cartes « c-event-card » :
9 +# catégorie, titre, date FR (simple ou plage « Tous les
10 +# dimanches du 26 juillet au 30 août 2026 »), lieu, url fiche,
11 +# image. Enrichissement par fiche /evenement/<cat>/<slug>
12 +# (heures « 19 h à 21 h », coût « Gratuit ») — CACHE
13 +# INCRÉMENTAL disque, plafond JOL_FICHES par sync.
14 +# Accès : site municipal public, GET directs throttlés, identification
15 +# honnête ; robots.txt permissif.
16 +# Prix : la fiche publie le coût (souvent « Gratuit ») → price_label
17 +# fidèle, jamais inventé.
18 +# -----------------------------------------------------------------------------
19 +from __future__ import annotations
20 +
21 +import html as _html
22 +import os
23 +import re
24 +
25 +from ..normalize import clean_text, parse_date_range_fr, parse_time
26 +from ..schema import Event
27 +from .base import BaseConnector
28 +
29 +LIST_URL = "https://www.joliette.ca/evenements"
30 +PAGES_MAX = int(os.environ.get("JOL_PAGES", "8"))
31 +FICHES_MAX = int(os.environ.get("JOL_FICHES", "40"))
32 +
33 +_CARD_RE = re.compile(
34 + r'<a href="(https://www\.joliette\.ca/evenement/[^"]+)" class="c-event-card[^"]*"[^>]*>(.*?)</a>',
35 + re.S)
36 +_CAT_RE = re.compile(r'c-event-card__category">([^<]+)<')
37 +_TITLE_RE = re.compile(r'c-event-card__title">([^<]+)<')
38 +_DATE_RE = re.compile(r'c-event-card__date">([^<]+)<')
39 +_VENUE_RE = re.compile(r'c-location__text">([^<]+)<')
40 +_IMG_RE = re.compile(r'data-src="([^"]+)"')
41 +# fiche
42 +_F_ITEM_RE = re.compile(r'c-event-infos__text"><span>([^<]+)</span>')
43 +
44 +
45 +def _range(txt: str) -> tuple[str | None, str | None]:
46 + txt = re.sub(r"^\s*(tous les \w+\s+)?du\s+", "", txt, flags=re.I)
47 + return parse_date_range_fr("du " + txt)
48 +
49 +
50 +class JolietteConnector(BaseConnector):
51 + source_id = "joliette"
52 + request_delay = 0.8
53 +
54 + def _parse_page(self, html: str) -> list[dict]:
55 + rows: list[dict] = []
56 + for url, block in _CARD_RE.findall(html):
57 + t = _TITLE_RE.search(block)
58 + d = _DATE_RE.search(block)
59 + if not t or not d:
60 + continue
61 + raw_date = clean_text(_html.unescape(d.group(1)))
62 + start, end = _range(raw_date)
63 + if not start:
64 + continue
65 + cat = _CAT_RE.search(block)
66 + venue = _VENUE_RE.search(block)
67 + img = _IMG_RE.search(block)
68 + rows.append({
69 + "url": url,
70 + "title": clean_text(_html.unescape(t.group(1))),
71 + "start": start,
72 + "end": end or start,
73 + "category": clean_text(cat.group(1)) if cat else "",
74 + "venue": clean_text(_html.unescape(
75 + venue.group(1))) if venue else "",
76 + "image": img.group(1) if img else "",
77 + })
78 + return rows
79 +
80 + def _fiche(self, url: str) -> dict:
81 + """Heures et coût depuis les items d'info de la fiche (icônes non
82 + discriminantes → classement par contenu : heure si « N h », coût si
83 + gratuit/$, le reste est déjà connu de la carte)."""
84 + out: dict = {}
85 + for txt in _F_ITEM_RE.findall(self.get(url).text):
86 + txt = clean_text(_html.unescape(txt))
87 + low = txt.lower()
88 + if "hours" not in out and re.search(r"\d{1,2}\s*h", low):
89 + out["hours"] = txt
90 + elif "price_label" not in out and (
91 + "gratuit" in low or "$" in txt or "payant" in low):
92 + out["price_label"] = txt
93 + return out
94 +
95 + def fetch(self) -> list[Event]:
96 + rows: list[dict] = []
97 + for page in range(1, PAGES_MAX + 1):
98 + url = LIST_URL if page == 1 else f"{LIST_URL}/{page}"
99 + batch = self._parse_page(self.get(url).text)
100 + if not batch:
101 + break
102 + rows += batch
103 + cache = self.load_cache()
104 + fetched = 0
105 + for r in rows:
106 + slug = "/".join(r["url"].rstrip("/").rsplit("/", 2)[-2:])
107 + r["slug"] = slug
108 + if slug not in cache and fetched < FICHES_MAX:
109 + try:
110 + cache[slug] = self._fiche(r["url"])
111 + fetched += 1
112 + except Exception as exc: # fiche cassée ≠ source cassée
113 + print(f"[sorti-ka] joliette : fiche {slug} ignorée : {exc}")
114 + if fetched:
115 + self.save_cache(cache)
116 + events: list[Event] = []
117 + seen: set[str] = set()
118 + for r in rows:
119 + ext = f"{r['slug'].replace('/', '-')}-{r['start']}"
120 + if ext in seen:
121 + continue
122 + seen.add(ext)
123 + extra = cache.get(r["slug"]) or {}
124 + hours = extra.get("hours", "")
125 + start_time = parse_time(hours)
126 + end_time = None
127 + m = re.search(r"\bà\s+(\d{1,2}\s*h(?:\s*\d{2})?)", hours)
128 + if m:
129 + end_time = parse_time(m.group(1))
130 + events.append(Event(
131 + source=self.source_id,
132 + external_id=ext,
133 + url=r["url"],
134 + title=r["title"],
135 + raw_categories=[r["category"]] if r["category"] else [r["title"]],
136 + venue=r["venue"],
137 + city="Joliette",
138 + region="Lanaudière",
139 + start_date=r["start"],
140 + start_time=start_time,
141 + end_date=r["end"],
142 + end_time=end_time,
143 + price_label=extra.get("price_label", ""),
144 + organizer="Ville de Joliette",
145 + image=r["image"],
146 + ))
147 + return events
modified sortika/connectors/lanaudiere.py +66 −12
@@ -7,13 +7,18 @@
7 7 # le SIT Québec, recoupement géré par dedup_key). La liste rendue
8 8 # serveur ne montre que ~10 cartes (le reste charge en JS) → on
9 9 # passe par le SITEMAP officiel (robots.txt), qui publie TOUTES
10 −# les fiches /fr/evenements-lanaudiere/<slug>/ (~44 à venir,
11 −# vérifié 2026-08-19).
10 +# les fiches /fr/evenements-lanaudiere/<slug>/ (~85 URLs,
11 +# revérifié 2026-08-25).
12 12 # Extraction: sitemap.xml (1 GET) → fiches détail incrémentales (cache
13 −# data/lanaudiere_cache.json, LAN_FICHE_MAX/sync, re-visite 7 j) :
14 −# en-tête structuré member-title-1-1 (titre, plage de dates FR
15 −# « Du 17 au 26 juillet 2026 », municipalité) + og:image +
16 −# og:description. Pas d'heure ni tarif structurés → jamais inventés.
13 +# data/lanaudiere_cache.json, LAN_FICHE_MAX/sync, re-visite 7 j,
14 +# version v=2) : en-tête structuré member-title-1-1 (titre,
15 +# plage de dates FR « Du 17 au 26 juillet 2026 », municipalité)
16 +# + og:image. ENRICHI (2026-08-25) : description longue
17 +# (expandable-intro, fallback og:description), adresse civique +
18 +# code postal (member-coords-1-1__address), organisateur
19 +# (member-coords-1-1__name), site web (member-website-1-1) et
20 +# audience (« Clientèle admise »). Pas d'heure ni tarif
21 +# structurés → jamais inventés.
17 22 # Accès : robots.txt : « Disallow: /*? » (aucune URL à paramètres
18 23 # requêtée) + « Crawl-delay: 2 » → GET séquentiels 2,1 s.
19 24 # -----------------------------------------------------------------------------
@@ -31,6 +36,8 @@ BASE = "https://lanaudiere.ca"
31 36 SITEMAP_URL = BASE + "/sitemap.xml"
32 37 FICHE_MAX = int(os.environ.get("LAN_FICHE_MAX", "10"))
33 38 REFRESH_AFTER = 7 * 86400
39 +CACHE_V = 2 # v2 : + adresse/CP, organisateur, site web,
40 +# audience, description longue (2026-08-25)
34 41
35 42 _SITEMAP_RE = re.compile(
36 43 r"<loc>\s*(https://lanaudiere\.ca/fr/evenements-lanaudiere/"
@@ -40,6 +47,23 @@ _DATE_RE = re.compile(r'member-title-1-1__subtitle">\s*([^<]+?)\s*<')
40 47 _CITY_RE = re.compile(r'member-title-1-1__location"><span>([^<]+)<')
41 48 _OG_IMG_RE = re.compile(r'property="og:image"\s+content="([^"]*)"')
42 49 _OG_DESC_RE = re.compile(r'property="og:description"\s+content="([^"]*)"')
50 +# description longue de la fiche (bloc « expandable-intro »)
51 +_INTRO_RE = re.compile(
52 + r'expandable-intro-1-1__text[^>]*>(.*?)</div>', re.S)
53 +# coordonnées du membre : nom (organisateur), adresse « 1655, boul.
54 +# Base-de-Roc<br />Joliette, QC J6E 0L1 »
55 +_COORD_NAME_RE = re.compile(
56 + r'member-coords-1-1__name">\s*(.*?)\s*</div>', re.S)
57 +_COORD_ADDR_RE = re.compile(
58 + r'member-coords-1-1__address">\s*(.*?)\s*</div>', re.S)
59 +_ADDR_SPLIT_RE = re.compile(
60 + r"^(?P<addr>.*?)<br\s*/?>\s*(?P<city>[^,<]+),\s*QC"
61 + r"(?:\s*(?P<pc>[A-Z]\d[A-Z]\s?\d[A-Z]\d))?", re.S)
62 +_WEBSITE_RE = re.compile(r'member-website-1-1__btn"\s+href="([^"]+)"')
63 +# « Clientèle admise » : liste de publics (Adultes, Adolescents, Familles…)
64 +_AUDIENCE_RE = re.compile(
65 + r"Client\S*le admise.*?<ul>(.*?)</ul>", re.S)
66 +_LI_RE = re.compile(r"<li[^>]*>\s*([^<]+?)\s*</li>")
43 67
44 68
45 69 class LanaudiereConnector(BaseConnector):
@@ -55,13 +79,34 @@ class LanaudiereConnector(BaseConnector):
55 79 date_raw.group(1) if date_raw else "")
56 80 city = _CITY_RE.search(html)
57 81 img = _OG_IMG_RE.search(html)
58 − desc = _OG_DESC_RE.search(html)
82 + intro = _INTRO_RE.search(html)
83 + og_desc = _OG_DESC_RE.search(html)
84 + desc = clean_text(intro.group(1))[:2000] if intro else (
85 + clean_text(og_desc.group(1)) if og_desc else "")
86 + address, postal = "", ""
87 + coord = _COORD_ADDR_RE.search(html)
88 + if coord:
89 + m = _ADDR_SPLIT_RE.match(coord.group(1).strip())
90 + if m:
91 + address = clean_text(m.group("addr"))
92 + postal = (m.group("pc") or "").strip()
93 + else: # adresse sur une seule ligne
94 + address = clean_text(coord.group(1))
95 + name = _COORD_NAME_RE.search(html)
96 + website = _WEBSITE_RE.search(html)
97 + aud = _AUDIENCE_RE.search(html)
98 + audience = ", ".join(clean_text(x) for x in _LI_RE.findall(
99 + aud.group(1))) if aud else ""
59 100 return {
60 101 "external_id": url.rstrip("/").split("/")[-1],
61 102 "url": url,
62 103 "title": clean_text(title.group(1)),
63 − "description": clean_text(desc.group(1)) if desc else "",
104 + "description": desc,
64 105 "city": clean_text(city.group(1)) if city else "",
106 + "address": address, "postal_code": postal,
107 + "organizer": clean_text(name.group(1)) if name else "",
108 + "website": website.group(1).strip() if website else "",
109 + "audience": audience,
65 110 "start_date": start, "end_date": end,
66 111 "image": clean_text(img.group(1)) if img else "",
67 112 }
@@ -74,10 +119,14 @@ class LanaudiereConnector(BaseConnector):
74 119 now = time.time()
75 120 current = set(urls)
76 121 cache = {u: c for u, c in cache.items() if u in current}
122 + # nouvelles fiches d'abord, puis celles parsées avant v2 (sans
123 + # adresse/site/audience), puis re-visite roulante
77 124 to_fetch = sorted(
78 125 (u for u in urls
79 − if u not in cache or now - cache[u].get("ts", 0) > REFRESH_AFTER),
80 − key=lambda u: cache.get(u, {}).get("ts", 0))[:FICHE_MAX]
126 + if u not in cache or cache[u].get("v", 1) < CACHE_V
127 + or now - cache[u].get("ts", 0) > REFRESH_AFTER),
128 + key=lambda u: (cache.get(u, {}).get("v", 1) if u in cache else 0,
129 + cache.get(u, {}).get("ts", 0)))[:FICHE_MAX]
81 130 for url in to_fetch: # séquentiel : Crawl-delay respecté
82 131 try:
83 132 row = self._parse_fiche(url, self.get(url).text)
@@ -85,9 +134,9 @@ class LanaudiereConnector(BaseConnector):
85 134 row = None
86 135 if row is None:
87 136 print(f"[sorti-ka] lanaudiere : fiche ignorée {url}")
88 − cache[url] = {"ts": now, "row": None}
137 + cache[url] = {"ts": now, "row": None, "v": CACHE_V}
89 138 continue
90 − cache[url] = {"ts": now, "row": row}
139 + cache[url] = {"ts": now, "row": row, "v": CACHE_V}
91 140 self.save_cache(cache)
92 141
93 142 events: list[Event] = []
@@ -104,10 +153,15 @@ class LanaudiereConnector(BaseConnector):
104 153 title=r["title"],
105 154 description=r.get("description", ""),
106 155 raw_categories=[r["title"]],
156 + audience=r.get("audience", ""),
157 + address=r.get("address", ""),
107 158 city=r["city"],
159 + postal_code=r.get("postal_code", ""),
108 160 tourist_region="Lanaudière",
109 161 start_date=r["start_date"],
110 162 end_date=r["end_date"],
163 + organizer=r.get("organizer", ""),
164 + website=r.get("website", ""),
111 165 image=r["image"],
112 166 ))
113 167 return events
added sortika/connectors/laurentides.py +173 −0
@@ -0,0 +1,173 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/laurentides.py : Tourisme Laurentides (ATR) — RÉHABILITÉE (vague
5 +# ATR 2026-08 ; écartée Phase 3 pour WAF 403 : la chaîne anti-bot passe)
6 +# Source : laurentides.com/evenements — calendrier officiel de l'ATR
7 +# (WordPress + Modern Events Calendar « mec-events »).
8 +# Extraction: API REST WordPress /wp-json/wp/v2/mec-events (paginée
9 +# per_page=100, ~228 fiches, X-WP-TotalPages vérifié 2026-08-25)
10 +# via BaseConnector.get() dont l'AUTO-ESCALADE anti-bot
11 +# (Oxylabs → Scrapfly ASP → Bright Data) perce le WAF qui bloque
12 +# le GET direct (403). La liste donne titre, description
13 +# (content), image (_embedded featuredmedia), lien, modified.
14 +# MEC n'expose PAS les dates en REST → fiches détail
15 +# incrémentales (cache data/laurentides_cache.json, plafond
16 +# LAU_FICHE_MAX/sync, re-visite si `modified` change) : dates
17 +# (mec-start/end-date-label « Déc 05 2026 »), tarif
18 +# (mec-events-event-cost), lieu + adresse
19 +# (mec-single-event-location).
20 +# Accès : API REST publique (aucune auth) ; le WAF de l'hébergeur
21 +# bloque les UA non-navigateur → escalade proxy/Scrapfly,
22 +# requêtes throttlées et plafonnées (coût par requête), lien
23 +# vers la fiche originale. Prix : texte source, jamais inventé.
24 +# -----------------------------------------------------------------------------
25 +from __future__ import annotations
26 +
27 +import os
28 +import re
29 +import time
30 +
31 +from ..normalize import clean_text, parse_date_fr, utc_to_local
32 +from ..schema import Event
33 +from .base import BaseConnector
34 +
35 +BASE = "https://www.laurentides.com"
36 +API = BASE + "/wp-json/wp/v2/mec-events"
37 +PER_PAGE = 100
38 +LAU_PAGE_MAX = int(os.environ.get("LAU_PAGE_MAX", "6"))
39 +LAU_FICHE_MAX = int(os.environ.get("LAU_FICHE_MAX", "10"))
40 +
41 +# « Déc 05 2026 » (format MEC) → date ISO via parse_date_fr (« 05 dec 2026 »)
42 +_MEC_DATE_RE = re.compile(r"([A-Za-zÀ-ÿ]{3,9})\s+(\d{1,2})\s+(\d{4})")
43 +_START_RE = re.compile(r'mec-start-date-label[^>]*>([^<]*)')
44 +_END_RE = re.compile(r'mec-end-date-label[^>]*>([^<]*)')
45 +_COST_RE = re.compile(r'mec-events-event-cost[^>]*>([^<]*)')
46 +_LOC_VENUE_RE = re.compile(
47 + r'mec-single-event-location.*?<dd class="author[^"]*">([^<]*)', re.S)
48 +_LOC_ADDR_RE = re.compile(
49 + r'mec-single-event-location.*?<span class="mec-address">([^<]*)', re.S)
50 +_ADDR_CITY_RE = re.compile(r",\s*([^,]+),\s*QC", re.I)
51 +# lien « Ajouter à mon agenda Google » : datetimes UTC réels de la séance
52 +_GCAL_RE = re.compile(r"dates=(\d{8}T\d{6})Z(?:%2F|/)(\d{8}T\d{6})Z")
53 +
54 +
55 +def _mec_date(raw: str) -> str | None:
56 + m = _MEC_DATE_RE.search(clean_text(raw or ""))
57 + if not m:
58 + return None
59 + return parse_date_fr(f"{m.group(2)} {m.group(1)} {m.group(3)}")
60 +
61 +
62 +class LaurentidesConnector(BaseConnector):
63 + source_id = "laurentides"
64 + request_delay = 1.0 # chaque requête peut coûter un crédit Scrapfly
65 +
66 + def _list_events(self) -> list[dict]:
67 + rows: list[dict] = []
68 + for page in range(1, LAU_PAGE_MAX + 1):
69 + try:
70 + batch = self.get_json(API, params={
71 + "per_page": PER_PAGE, "page": page, "_embed": "1"})
72 + except Exception:
73 + break
74 + if not isinstance(batch, list) or not batch:
75 + break
76 + rows.extend(batch)
77 + if len(batch) < PER_PAGE:
78 + break
79 + return rows
80 +
81 + def _parse_fiche(self, html: str) -> dict:
82 + out: dict = {}
83 + # datetimes UTC réels (lien Google Agenda généré par MEC), sinon
84 + # libellés de dates du bandeau (« Déc 05 2026 », sans heure)
85 + m = _GCAL_RE.search(html)
86 + if m:
87 + def _iso(c: str) -> str: # « 20261205T150000 » → ISO UTC
88 + return (f"{c[:4]}-{c[4:6]}-{c[6:8]}T"
89 + f"{c[9:11]}:{c[11:13]}:{c[13:15]}Z")
90 + out["start_date"], out["start_time"] = utc_to_local(_iso(m.group(1)))
91 + out["end_date"], out["end_time"] = utc_to_local(_iso(m.group(2)))
92 + if not out.get("start_date"):
93 + d = _START_RE.search(html)
94 + if d:
95 + out["start_date"] = _mec_date(d.group(1))
96 + d = _END_RE.search(html)
97 + if d:
98 + out["end_date"] = _mec_date(d.group(1))
99 + m = _COST_RE.search(html)
100 + if m:
101 + out["price_label"] = clean_text(m.group(1))
102 + m = _LOC_VENUE_RE.search(html)
103 + if m:
104 + out["venue"] = clean_text(m.group(1))
105 + m = _LOC_ADDR_RE.search(html)
106 + if m:
107 + out["address"] = clean_text(m.group(1))
108 + c = _ADDR_CITY_RE.search(out["address"])
109 + if c:
110 + out["city"] = clean_text(c.group(1))
111 + return out
112 +
113 + def fetch(self) -> list[Event]:
114 + rows = self._list_events()
115 + cache = self.load_cache()
116 + now = time.time()
117 + current = {str(r.get("id")) for r in rows}
118 + cache = {k: v for k, v in cache.items() if k in current}
119 +
120 + # incrémental : nouvelles fiches d'abord, puis celles dont le contenu
121 + # a changé chez la source (champ `modified` du REST)
122 + def _stale(r: dict) -> bool:
123 + entry = cache.get(str(r.get("id")))
124 + return not entry or entry.get("modified") != r.get("modified")
125 +
126 + to_fetch = sorted(
127 + (r for r in rows if r.get("link") and _stale(r)),
128 + key=lambda r: cache.get(str(r.get("id")), {}).get("ts", 0)
129 + )[:LAU_FICHE_MAX]
130 + for row in to_fetch:
131 + eid = str(row.get("id"))
132 + detail: dict = {}
133 + try:
134 + detail = self._parse_fiche(self.get(row["link"]).text)
135 + except Exception:
136 + detail = {}
137 + cache[eid] = {"ts": now, "modified": row.get("modified"),
138 + "detail": detail}
139 + self.save_cache(cache)
140 +
141 + events: list[Event] = []
142 + for r in rows:
143 + eid = str(r.get("id") or "")
144 + title = clean_text((r.get("title") or {}).get("rendered", ""))
145 + if not eid or not title:
146 + continue
147 + detail = cache.get(eid, {}).get("detail") or {}
148 + image = ""
149 + media = ((r.get("_embedded") or {}).get("wp:featuredmedia") or [])
150 + if media and isinstance(media[0], dict):
151 + image = media[0].get("source_url", "") or ""
152 + description = clean_text(
153 + (r.get("content") or {}).get("rendered", "")) or clean_text(
154 + (r.get("excerpt") or {}).get("rendered", ""))
155 + events.append(Event(
156 + source=self.source_id,
157 + external_id=eid,
158 + url=r.get("link", ""),
159 + title=title,
160 + description=description,
161 + raw_categories=[title],
162 + venue=detail.get("venue", ""),
163 + address=detail.get("address", ""),
164 + city=detail.get("city", ""),
165 + tourist_region="Laurentides",
166 + start_date=detail.get("start_date"),
167 + start_time=detail.get("start_time"),
168 + end_date=detail.get("end_date") or detail.get("start_date"),
169 + end_time=detail.get("end_time"),
170 + price_label=detail.get("price_label", ""),
171 + image=image,
172 + ))
173 + return events
modified sortika/connectors/lavitrine.py +156 −31
@@ -16,7 +16,19 @@
16 16 # Accès : robots.txt « User-Agent: * / Allow: / » + Content-Signal
17 17 # « search=yes, use=reference » → usage index + lien vers la
18 18 # fiche originale, conforme aux signaux publiés (CLAUDE.md §15).
19 −# Prix : non exposés sur la fiche → is_free inconnu (jamais inventé).
19 +# Prix/GPS : les pages FICHE ne publient ni tarif, ni JSON-LD, ni adresse.
20 +# MAIS le sitemap contient aussi ~1 000 pages « représentation »
21 +# (/fr/evenement/<slug>/<ficheId>/<dateId>) qui publient un
22 +# JSON-LD Event complet (vérifié live 2026-08-25, 14/14 pages :
23 +# Place name + PostalAddress + GeoCoordinates 100 %, offers.price
24 +# ~80 %) → parseur v3 : adresse civique, code postal, lat/lng et
25 +# prix billetterie attachés à LA représentation correspondante
26 +# (identifier = dateId de la slide), sans AUCUN GET de plus (ces
27 +# pages étaient déjà téléchargées et ignorées). Les autres
28 +# représentations restent au géocodage aval salle+ville
29 +# (sortika/venues.py + geocode.py). isAccessibleForFree vaut
30 +# false par défaut chez la source → jamais utilisé seul ;
31 +# is_free=False seulement si un prix > 0 est publié.
20 32 # -----------------------------------------------------------------------------
21 33 from __future__ import annotations
22 34
@@ -30,6 +42,8 @@ from .base import BaseConnector
30 42 SITEMAP = "https://www.lavitrine.com/sitemap.xml"
31 43 LV_MAX_FETCH = int(__import__("os").environ.get("LV_MAX_FETCH", "400")) # fiches/sync (GET directs polis)
32 44 REFRESH_AFTER = 7 * 86400 # re-visite roulante d'une fiche (secondes)
45 +PARSE_V = 3 # v2 : heure + description ; v3 : JSON-LD des pages représentation
46 + # (adresse/GPS/prix) → backfill prioritaire des entrées < v3
33 47
34 48 _LOC_RE = re.compile(r"<loc>([^<]+)</loc>\s*(?:<lastmod>([^<]+)</lastmod>)?")
35 49 _URL_BLOCK_RE = re.compile(r"<url>(.*?)</url>", re.S)
@@ -57,16 +71,81 @@ _DESC_LONG_RE = re.compile(r'id="about-description">(.*?)</div>', re.S)
57 71 _SLIDE_A_RE = re.compile(
58 72 r'<a[^>]*href="([^"]*)"[^>]*class="event_calendar_slide-content(.*?)</a>', re.S)
59 73
74 +_LD_SCRIPT_RE = re.compile(
75 + r'<script type="application/ld\+json">(.*?)</script>', re.S)
60 76
61 −def _slides(html: str) -> list[tuple[str, str, str, str, str, str, str, str | None]]:
62 − """Slides du calendrier : (id de fiche de la slide, url canonique, jour,
63 − mois, année, salle, ville).
77 +
78 +def _ld_representations(html: str) -> dict[str, dict]:
79 + """JSON-LD Event des pages « représentation » du sitemap
80 + (…/<ficheId>/<dateId>) : {dateId → salle, adresse civique, code postal,
81 + lat/lng, prix billetterie}. `identifier` = le dateId de la slide
82 + correspondante du carrousel. Les pages fiche n'en publient pas (vérifié
83 + live 2026-08-25) → dict vide, aucun impact."""
84 + import json
85 + out: dict[str, dict] = {}
86 + for m in _LD_SCRIPT_RE.finditer(html):
87 + raw = m.group(1)
88 + try:
89 + data = json.loads(raw)
90 + except Exception:
91 + try: # caractères de contrôle bruts dans les descriptions
92 + data = json.loads(re.sub(r"[\x00-\x1f]", " ", raw))
93 + except Exception:
94 + continue
95 + for d in (data if isinstance(data, list) else [data]):
96 + if not isinstance(d, dict) or d.get("@type") != "Event":
97 + continue
98 + ident = str(d.get("identifier") or "")
99 + if not ident:
100 + continue
101 + loc = d.get("location") or {}
102 + adr = loc.get("address") or {}
103 + geo = loc.get("geo") or {}
104 + off = d.get("offers") or {}
105 + if isinstance(off, list):
106 + off = off[0] if off else {}
107 + try:
108 + price = float(off.get("price"))
109 + except (TypeError, ValueError):
110 + price = None
111 + out[ident] = {
112 + "venue": clean_text(loc.get("name") or ""),
113 + "address": clean_text(adr.get("streetAddress") or ""),
114 + "city": clean_text(adr.get("addressLocality") or ""),
115 + "postal_code": clean_text(adr.get("postalCode") or ""),
116 + "lat": geo.get("latitude"), "lng": geo.get("longitude"),
117 + # prix publié par la billetterie elle-même (souvent null tant
118 + # que la vente n'est pas ouverte : jamais inventé)
119 + "price": price if price and price > 0 else None,
120 + }
121 + return out
122 +
123 +
124 +# date + heure LOCALES de la représentation courante, publiées dans le fil
125 +# d'Ariane JSON-LD (« 17 sept 2026 | 20:00 HAE ») — le startDate de l'Event
126 +# est en UTC et peut donc changer de jour, on ne l'utilise JAMAIS.
127 +_CRUMB_DATE_RE = re.compile(
128 + r'"name":\s*"(\d{1,2})\s+([a-zA-Zûéà]+)\.?\s+(\d{4})'
129 + r'(?:\s*\|\s*(\d{1,2}:\d{2}))?[^"]*"')
130 +
131 +
132 +def _month_num(month_txt: str) -> int | None:
133 + t = clean_text(month_txt).lower()
134 + return _MONTHS.get(t[:4].rstrip(".")) or _MONTHS.get(t[:3])
135 +
136 +
137 +def _slides(html: str) -> list[tuple]:
138 + """Slides du calendrier : (id de fiche de la slide, id de représentation
139 + « dateId », url canonique, jour, mois, année, salle, ville, heure).
64 140
65 141 Le carrousel d'une fiche de tournée liste TOUTES les dates de la tournée,
66 142 chacune avec l'id de SA fiche (/fr/evenement/<slug>/<ficheId>/<dateId>).
67 143 On garde tout mais on identifie chaque représentation par l'id de sa
68 144 slide : la même date vue depuis plusieurs fiches → même identifiant →
69 − aucun doublon à l'ingestion, aucune date perdue.
145 + aucun doublon à l'ingestion, aucune date perdue. Le dateId (dernier
146 + segment) sert de clé de jointure vers le JSON-LD Event des pages
147 + représentation (identifier = dateId — v3, jamais utilisé dans
148 + external_id : zéro churn).
70 149 """
71 150 out = []
72 151 for m in _SLIDE_A_RE.finditer(html):
@@ -74,13 +153,14 @@ def _slides(html: str) -> list[tuple[str, str, str, str, str, str, str, str | No
74 153 parts = href.strip("/").split("/")
75 154 # …/evenement/<slug>/<ficheId>/<dateId>
76 155 slide_id = parts[-2] if len(parts) >= 2 and parts[-2].isdigit() else ""
156 + date_id = parts[-1] if slide_id and parts[-1].isdigit() else ""
77 157 canon = "https://www.lavitrine.com/" + "/".join(parts[:-1]) if slide_id else ""
78 158 d = _DATE_RE.search(block)
79 159 if not d:
80 160 continue
81 161 p = _PLACE_RE.search(block)
82 162 t = _TIME_RE.search(block)
83 − out.append((slide_id, canon, d.group(1), d.group(2), d.group(3),
163 + out.append((slide_id, date_id, canon, d.group(1), d.group(2), d.group(3),
84 164 p.group(1) if p else "", p.group(2) if p else "",
85 165 t.group(1).replace("h", ":") if t else None))
86 166 return out
@@ -117,11 +197,14 @@ class LaVitrineConnector(BaseConnector):
117 197 clean_text(d_short.group(1)) if d_short else "",
118 198 clean_text(d_long.group(1)) if d_long else "") if p)[:2000]
119 199 ext = url.rstrip("/").rsplit("/", 1)[-1] # id numérique stable
120 − slug = url.rstrip("/").split("/")[-2]
200 + parts = url.rstrip("/").split("/")
201 + # dernier segment non numérique = slug lisible (les URLs représentation
202 + # …/<slug>/<ficheId>/<dateId> donnaient « 22040 » en catégorie brute)
203 + slug = next((p for p in reversed(parts) if not p.isdigit()), "")
121 204 rows = []
122 − for slide_id, canon, day, month_txt, year, venue, city, hhmm in _slides(html)[:60]:
123 − mo = _MONTHS.get(clean_text(month_txt).lower()[:4].rstrip(".")) \
124 − or _MONTHS.get(clean_text(month_txt).lower()[:3])
205 + for slide_id, date_id, canon, day, month_txt, year, venue, city, hhmm \
206 + in _slides(html)[:60]:
207 + mo = _month_num(month_txt)
125 208 if not mo:
126 209 continue
127 210 iso = f"{int(year):04d}-{mo:02d}-{int(day):02d}"
@@ -136,6 +219,36 @@ class LaVitrineConnector(BaseConnector):
136 219 "start_date": iso, "end_date": iso,
137 220 "start_time": hhmm, # heure de la slide (« 15:00 HNE »)
138 221 })
222 + # page « représentation » du sitemap (…/<ficheId>/<dateId>) : son
223 + # JSON-LD Event décrit la représentation COURANTE — jamais listée
224 + # dans son propre carrousel (vérifié live 2026-08-25) → on émet son
225 + # occurrence à part : adresse/GPS/prix du JSON-LD, date+heure LOCALES
226 + # du fil d'Ariane. À l'émission, cette version enrichie remplace la
227 + # slide nue vue depuis les autres fiches (même external_id).
228 + info = (_ld_representations(html).get(parts[-1])
229 + if len(parts) >= 2 and parts[-1].isdigit()
230 + and parts[-2].isdigit() else None)
231 + crumb = _CRUMB_DATE_RE.search(html) if info else None
232 + mo = _month_num(crumb.group(2)) if crumb else None
233 + if info and crumb and mo:
234 + iso = f"{int(crumb.group(3)):04d}-{mo:02d}-{int(crumb.group(1)):02d}"
235 + price = info.get("price")
236 + rows.append({
237 + "external_id": f"{parts[-2]}-{iso}", # même schéma que les slides
238 + "url": "/".join(parts[:-1]), # fiche canonique
239 + "title": title, "image": image,
240 + "description": description,
241 + "raw_categories": [title, slug.replace("-", " ")],
242 + "venue": info.get("venue", ""), "city": info.get("city", ""),
243 + "address": info.get("address", ""),
244 + "postal_code": info.get("postal_code", ""),
245 + "lat": info.get("lat"), "lng": info.get("lng"),
246 + "start_date": iso, "end_date": iso,
247 + "start_time": crumb.group(4),
248 + "price_min": price,
249 + "price_label": (f"{price:.2f} $ (billetterie lavitrine.com)"
250 + if price else ""),
251 + })
139 252 return rows
140 253
141 254 def fetch(self) -> list[Event]:
@@ -145,14 +258,17 @@ class LaVitrineConnector(BaseConnector):
145 258 cache = {u: c for u, c in cache.items() if u in current}
146 259
147 260 now = time.time()
148 − # nouvelles fiches d'abord, puis celles parsées avant la Phase 2
149 − # (v < 2 : sans heure ni description — montée progressive), puis les
261 + # nouvelles fiches d'abord, puis celles parsées avant PARSE_V
262 + # (v2 sans adresse/GPS/prix — montée progressive), puis les
150 263 # plus anciennes visites (roulant)
151 264 candidates = sorted(
152 265 (u for u in urls
153 − if u not in cache or cache[u].get("v", 1) < 2
266 + if u not in cache or cache[u].get("v", 1) < PARSE_V
154 267 or now - cache[u].get("fetched_at", 0) > REFRESH_AFTER),
155 268 key=lambda u: (cache.get(u, {}).get("v", 1) if u in cache else 0,
269 + # pages représentation d'abord : seules à livrer
270 + # adresse/GPS/prix (JSON-LD v3)
271 + 0 if re.search(r"/\d+/\d+/?$", u) else 1,
156 272 cache.get(u, {}).get("fetched_at", 0)))[:LV_MAX_FETCH]
157 273
158 274 def worker(url, session):
@@ -162,26 +278,35 @@ class LaVitrineConnector(BaseConnector):
162 278 if rows is None: # fiche cassée : on ne bloque pas la source
163 279 print(f"[sorti-ka] lavitrine : fiche ignorée {url}")
164 280 continue
165 − cache[url] = {"fetched_at": now, "rows": rows, "v": 2}
281 + cache[url] = {"fetched_at": now, "rows": rows, "v": PARSE_V}
166 282 self.save_cache(cache)
167 283
168 − events: list[Event] = []
169 − seen: set[str] = set()
284 + # même slide vue depuis plusieurs pages (fiche de tournée + page
285 + # représentation) : la version enrichie JSON-LD (adresse) l'emporte
286 + best: dict[str, dict] = {}
170 287 for entry in cache.values():
171 288 for row in entry.get("rows", []):
172 − if row["external_id"] in seen: # même slide vue de 2 fiches
173 − continue
174 − seen.add(row["external_id"])
175 − events.append(Event(
176 − source=self.source_id,
177 − external_id=row["external_id"],
178 − url=row["url"], title=row["title"],
179 − description=row.get("description", ""),
180 − raw_categories=row.get("raw_categories") or [],
181 − venue=row.get("venue", ""), city=row.get("city", ""),
182 − start_date=row.get("start_date"),
183 − start_time=row.get("start_time"),
184 − end_date=row.get("end_date"),
185 − image=row.get("image", ""),
186 − ))
289 + cur = best.get(row["external_id"])
290 + if cur is None or (not cur.get("address") and row.get("address")):
291 + best[row["external_id"]] = row
292 + events: list[Event] = []
293 + for row in best.values():
294 + events.append(Event(
295 + source=self.source_id,
296 + external_id=row["external_id"],
297 + url=row["url"], title=row["title"],
298 + description=row.get("description", ""),
299 + raw_categories=row.get("raw_categories") or [],
300 + venue=row.get("venue", ""), city=row.get("city", ""),
301 + address=row.get("address", ""),
302 + postal_code=row.get("postal_code", ""),
303 + lat=row.get("lat"), lng=row.get("lng"),
304 + start_date=row.get("start_date"),
305 + start_time=row.get("start_time"),
306 + end_date=row.get("end_date"),
307 + price_min=row.get("price_min"),
308 + price_label=row.get("price_label", ""),
309 + is_free=False if row.get("price_min") else None,
310 + image=row.get("image", ""),
311 + ))
187 312 return events
modified sortika/connectors/lepointdevente.py +35 −11
@@ -10,14 +10,18 @@
10 10 # Accès : robots.txt = « User-Agent: * / Allow: * » (scraping permis) ;
11 11 # GET throttlés 0,6 s, identification honnête, ~65 requêtes/sync.
12 12 # Détail : enrichissement incrémental par fiches /billets/<id> (modèle
13 −# lavitrine) — cache disque data/lepointdevente_cache.json,
14 −# plafond LPDV_DETAIL_MAX fiches/sync (défaut 150), re-visite
15 −# après 7 jours, throttle poli 0,8 s. Chaque fiche donne :
16 −# description (og:description), heure précise (data-event-name)
17 −# et l'id interne du widget billetterie (data-reference) →
18 −# tarifs JSON réels via /plugins/rates/<id> (prix TTC affichés
19 −# par la billetterie elle-même : price_min / price_label /
20 −# is_free si tous les tarifs sont à 0 $). Jamais de prix inventé.
13 +# lavitrine, version de parse « v ») — cache disque
14 +# data/lepointdevente_cache.json, plafond LPDV_DETAIL_MAX
15 +# fiches/sync (défaut 150), re-visite après 7 jours, throttle
16 +# poli 0,8 s. Chaque fiche donne : description (og:description),
17 +# heure précise (data-event-name), ADRESSE CIVIQUE du lieu
18 +# (microdonnées schema.org PostalAddress : streetAddress +
19 +# addressLocality, vérifié 2026-08-25 → géocodage aval, la
20 +# liste ne donnait ni adresse ni GPS) et l'id interne du widget
21 +# billetterie (data-reference) → tarifs JSON réels via
22 +# /plugins/rates/<id> (prix TTC affichés par la billetterie
23 +# elle-même : price_min / price_label / is_free si tous les
24 +# tarifs sont à 0 $). Jamais de prix inventé.
21 25 # -----------------------------------------------------------------------------
22 26 from __future__ import annotations
23 27
@@ -33,9 +37,15 @@ BASE = "https://lepointdevente.com"
33 37
34 38 DETAIL_MAX = int(os.environ.get("LPDV_DETAIL_MAX", "150")) # fiches/sync
35 39 REFRESH_AFTER = 7 * 86400 # re-visite d'une fiche (les tarifs évoluent)
40 +PARSE_V = 2 # bump → re-visite prioritaire des fiches < v2
36 41
37 42 _OG_DESC_RE = re.compile(r'property="og:description" content="([^"]*)"')
38 43 _META_DESC_RE = re.compile(r'<meta name="description" content="([^"]*)"')
44 +# adresse civique du lieu (microdonnées schema.org Place/PostalAddress de la
45 +# fiche, bloc .event-header-venue — vérifié 2026-08-25) : le géocodage aval
46 +# (sortika/geocode.py) privilégie l'adresse civique.
47 +_MD_STREET_RE = re.compile(r'itemprop="streetAddress">([^<]*)<')
48 +_MD_CITY_RE = re.compile(r'itemprop="addressLocality">([^<]*)<')
39 49 _REFERENCE_RE = re.compile(r'data-model="event" data-reference="(\d+)"')
40 50 _REFERENCE_ALT_RE = re.compile(r'data-event="(\d+)"')
41 51 _EVENT_TIME_RE = re.compile(r'data-event-name="[^"]*\((\d{1,2}\s+\S+\s+\d{4})'
@@ -106,6 +116,12 @@ class LePointDeVenteConnector(BaseConnector):
106 116 m = _EVENT_TIME_RE.search(html)
107 117 if m: # ex. « (29 août 2026 19h30) » → "19h30"
108 118 info["time"] = m.group(2)
119 + m = _MD_STREET_RE.search(html)
120 + if m and clean_text(m.group(1)):
121 + info["address"] = clean_text(m.group(1))
122 + m = _MD_CITY_RE.search(html)
123 + if m and clean_text(m.group(1)):
124 + info["city"] = clean_text(m.group(1))
109 125 m = _REFERENCE_RE.search(html) or _REFERENCE_ALT_RE.search(html)
110 126 if m:
111 127 info["reference"] = m.group(1)
@@ -133,8 +149,9 @@ class LePointDeVenteConnector(BaseConnector):
133 149
134 150 def _enrich_details(self, events: list[Event]) -> None:
135 151 """Fiches détail — incrémental (modèle lavitrine) : nouvelles fiches
136 − d'abord puis re-visite roulante après 7 jours, plafonné à
137 − LPDV_DETAIL_MAX fiches/sync, séquentiel throttlé 0,8 s (2 GET par
152 + d'abord, puis fiches parsées avant la v2 (sans adresse civique —
153 + backfill progressif), puis re-visite roulante après 7 jours, plafonné
154 + à LPDV_DETAIL_MAX fiches/sync, séquentiel throttlé 0,8 s (2 GET par
138 155 fiche : HTML + tarifs JSON). Événements à venir seulement."""
139 156 from datetime import date
140 157 today = date.today().isoformat()
@@ -147,8 +164,10 @@ class LePointDeVenteConnector(BaseConnector):
147 164 (ev.url for ev in events
148 165 if (ev.start_date or today) >= today
149 166 and (ev.url not in cache
167 + or cache[ev.url].get("v", 1) < PARSE_V
150 168 or now - cache[ev.url].get("fetched_at", 0) > REFRESH_AFTER)),
151 − key=lambda u: cache.get(u, {}).get("fetched_at", 0))[:DETAIL_MAX]
169 + key=lambda u: (cache.get(u, {}).get("v", 1) if u in cache else 0,
170 + cache.get(u, {}).get("fetched_at", 0)))[:DETAIL_MAX]
152 171
153 172 for url in candidates:
154 173 try:
@@ -162,6 +181,7 @@ class LePointDeVenteConnector(BaseConnector):
162 181 print(f"[sorti-ka] lepointdevente : fiche ignorée {url}")
163 182 info = {}
164 183 info["fetched_at"] = now
184 + info["v"] = PARSE_V
165 185 cache[url] = info
166 186 self.save_cache(cache)
167 187
@@ -171,6 +191,10 @@ class LePointDeVenteConnector(BaseConnector):
171 191 ev.description = info["description"]
172 192 if info.get("time"): # heure précise de la fiche ("19h30")
173 193 ev.start_time = info["time"]
194 + if info.get("address"): # adresse civique du lieu (microdonnées)
195 + ev.address = info["address"]
196 + if info.get("city") and not ev.city:
197 + ev.city = info["city"]
174 198 if "is_free" in info: # tarifs réels de la billetterie
175 199 ev.is_free = info["is_free"]
176 200 ev.price_min = info.get("price_min")
added sortika/connectors/levis.py +148 −0
@@ -0,0 +1,148 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/levis.py : connecteur Ville de Lévis — calendrier des événements
5 +# Source : https://levis.ca/fr/calendrier-des-evenements (nouveau site
6 +# Nuxt/Craft CMS lancé fin 2025 ; l'ancien ville.levis.qc.ca et
7 +# son WAF 403 — cause de l'abandon 2026-08-18 — y redirige).
8 +# Extraction: API GraphQL publique https://levis.ca/graphql/ avec le
9 +# queryToken PUBLIC exposé dans window.__NUXT__.config de la
10 +# page (extrait à chaque run, constante de repli) — requête
11 +# calquée sur getListingEvenements du bundle : occurrences
12 +# datées (startDateLocalized), fiche (titre, url, accroche,
13 +# image, lieu, types, secteur). ~160 événements à venir,
14 +# vérifié 2026-08-25.
15 +# Accès : API du site municipal public, jeton de LECTURE publié dans
16 +# le HTML à tous les visiteurs ; robots.txt n'interdit pas
17 +# /graphql/. POST throttlés, identification honnête.
18 +# Prix : non publié par la source → jamais inventé.
19 +# -----------------------------------------------------------------------------
20 +from __future__ import annotations
21 +
22 +import re
23 +import time
24 +
25 +from ..normalize import clean_text, parse_date_iso, parse_time
26 +from ..schema import Event
27 +from .base import BaseConnector
28 +
29 +PAGE = "https://levis.ca/fr/calendrier-des-evenements"
30 +GRAPHQL = "https://levis.ca/graphql/"
31 +# jeton de lecture publié dans la page (repli si l'extraction échoue)
32 +FALLBACK_TOKEN = "dfPT62ua1B5YtAebfrOgqXodvS235bob"
33 +_TOKEN_RE = re.compile(r'queryToken:"([^"]+)"')
34 +BATCH = 100
35 +
36 +QUERY = """query($limit:Int,$offset:Int,$rangeStart:String){
37 + pagesEntries(slug:["calendrier-des-evenements"], type:"listingEvenementsTemplate"){
38 + ... on listingEvenementsTemplate_Entry {
39 + calendarElement {
40 + eventCount(rangeStart:$rangeStart)
41 + events(offset:$offset, limit:$limit, rangeStart:$rangeStart,
42 + orderBy:"startDate ASC"){
43 + ... on evenements_Event {
44 + startDate: startDateLocalized endDate: endDateLocalized
45 + hideStartHourToggle hideEndHourToggle
46 + selectEventEntry {
47 + ... on evenementStructure_Entry {
48 + id: uid url title slug wysiwygSimpleElement
49 + thumbnailImage: thubmnailImageElement { url }
50 + selectLieuElement { ... on lieuStructure_Entry { title } }
51 + selectEventTypeElement: selectEventTypesElement { title }
52 + selectSecteurElement { title }
53 + }
54 + }
55 + }
56 + }
57 + }
58 + }
59 + }
60 +}"""
61 +
62 +
63 +class LevisConnector(BaseConnector):
64 + source_id = "levis"
65 + request_delay = 0.8
66 +
67 + def _token(self) -> str:
68 + try:
69 + m = _TOKEN_RE.search(self.get(PAGE).text)
70 + if m:
71 + return m.group(1)
72 + except Exception:
73 + pass
74 + return FALLBACK_TOKEN
75 +
76 + def _gql(self, token: str, variables: dict) -> dict:
77 + self._throttle()
78 + resp = self.session.post(GRAPHQL, json={
79 + "query": QUERY, "variables": variables,
80 + }, headers={"Authorization": f"Bearer {token}"}, timeout=self.timeout)
81 + self._last_request = time.time()
82 + resp.raise_for_status()
83 + return resp.json()
84 +
85 + def _calendar(self, payload: dict) -> dict:
86 + pages = (payload.get("data") or {}).get("pagesEntries") or [{}]
87 + return pages[0].get("calendarElement") or {}
88 +
89 + def fetch(self) -> list[Event]:
90 + token = self._token()
91 + events: list[Event] = []
92 + seen: set[str] = set()
93 + offset, total = 0, None
94 + while True:
95 + cal = self._calendar(self._gql(token, {
96 + "limit": BATCH, "offset": offset, "rangeStart": "today"}))
97 + if total is None:
98 + total = int(cal.get("eventCount") or 0)
99 + batch = cal.get("events") or []
100 + for occ in batch:
101 + sel = (occ.get("selectEventEntry") or [{}])[0]
102 + title = clean_text(sel.get("title") or "")
103 + url = sel.get("url") or ""
104 + start = parse_date_iso(occ.get("startDate"))
105 + if not title or not url or not start:
106 + continue
107 + ext = f"{sel.get('id') or sel.get('slug')}-{start}"
108 + if ext in seen:
109 + continue
110 + seen.add(ext)
111 + start_time = (None if occ.get("hideStartHourToggle")
112 + else parse_time(occ.get("startDate")))
113 + if start_time == "00:00":
114 + start_time = None
115 + end = parse_date_iso(occ.get("endDate"))
116 + end_time = (None if occ.get("hideEndHourToggle")
117 + else parse_time(occ.get("endDate")))
118 + if end_time == "00:00":
119 + end_time = None
120 + venue = clean_text(((sel.get("selectLieuElement")
121 + or [{}])[0]).get("title") or "")
122 + cats = [clean_text(t.get("title") or "")
123 + for t in sel.get("selectEventTypeElement") or []]
124 + image = ""
125 + thumb = sel.get("thumbnailImage") or []
126 + if thumb and thumb[0].get("url"):
127 + image = thumb[0]["url"]
128 + events.append(Event(
129 + source=self.source_id,
130 + external_id=ext,
131 + url=url,
132 + title=title,
133 + description=clean_text(sel.get("wysiwygSimpleElement") or ""),
134 + raw_categories=[c for c in cats if c] or [title],
135 + venue=venue,
136 + city="Lévis",
137 + region="Chaudière-Appalaches",
138 + start_date=start,
139 + start_time=start_time,
140 + end_date=end or start,
141 + end_time=end_time,
142 + organizer="Ville de Lévis",
143 + image=image,
144 + ))
145 + offset += BATCH
146 + if len(batch) < BATCH or (total and offset >= total):
147 + break
148 + return events
added sortika/connectors/magog.py +208 −0
@@ -0,0 +1,208 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/magog.py : connecteur Ville de Magog — événements et activités
5 +# Source : https://www.ville.magog.qc.ca/calendrier/evenements-et-activites/
6 +# — la page municipale embarque un calendrier Google public
7 +# (plugin « Simple Google Calendar », id découvert dans le HTML).
8 +# Extraction: flux iCal PUBLIC du calendrier Google
9 +# fr7c02sjcaibul422352d811oc@group.calendar.google.com
10 +# (~1 230 VEVENT dont l'historique depuis 2017) : SUMMARY,
11 +# DTSTART/DTEND UTC → date+heure locales (utc_to_local),
12 +# LOCATION (« Salle, adresse, ville... »), DESCRIPTION (contient
13 +# l'URL de la fiche ville.magog.qc.ca), STATUS. Récurrences
14 +# Google observées (2026-08-25) : FREQ=DAILY/WEEKLY avec COUNT/
15 +# UNTIL/BYDAY → mini-expanseur déterministe (aucune invention),
16 +# occurrences plafonnées à HORIZON_DAYS. 1 requête par sync.
17 +# Accès : flux iCal publié publiquement par la Ville (bouton « + Google
18 +# Agenda » de sa page) ; conditions Google : accès lecture aux
19 +# calendriers publics. GET direct, identification honnête.
20 +# Prix : non publié par le flux → jamais inventé.
21 +# -----------------------------------------------------------------------------
22 +from __future__ import annotations
23 +
24 +import re
25 +from datetime import date, timedelta
26 +
27 +from ..normalize import clean_text, utc_to_local
28 +from ..schema import Event
29 +from .base import BaseConnector
30 +
31 +CAL_ID = "fr7c02sjcaibul422352d811oc@group.calendar.google.com"
32 +FEED = ("https://calendar.google.com/calendar/ical/"
33 + + CAL_ID.replace("@", "%40") + "/public/basic.ics")
34 +PAGE = "https://www.ville.magog.qc.ca/calendrier/evenements-et-activites/"
35 +HORIZON_DAYS = 365 # occurrences futures émises au plus à 1 an
36 +GRACE_DAYS = 7 # on garde les événements finis depuis < 7 jours
37 +
38 +# URL de fiche dans DESCRIPTION — parfois enrobée d'un redirect Google
39 +# (« ...&sa=D&source=... ») : on coupe au premier « & »
40 +_URL_RE = re.compile(r"https?://(?:www\.)?ville\.magog\.qc\.ca[^\s\"'<>&]*")
41 +_BYDAY = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, "FR": 4, "SA": 5, "SU": 6}
42 +_DATE_ONLY_RE = re.compile(r"^(\d{4})(\d{2})(\d{2})$")
43 +
44 +
45 +def _unfold(ics: str) -> str:
46 + """Déplie les lignes continuées du format iCalendar (RFC 5545)."""
47 + return re.sub(r"\r?\n[ \t]", "", ics.replace("\r\n", "\n"))
48 +
49 +
50 +def _unescape(v: str) -> str:
51 + return (v.replace("\\n", "\n").replace("\\,", ",")
52 + .replace("\\;", ";").replace("\\\\", "\\"))
53 +
54 +
55 +def _prop(block: str, name: str) -> str:
56 + m = re.search(rf"^{name}(?:;[^:\n]*)?:(.*)$", block, re.M)
57 + return _unescape(m.group(1).strip()) if m else ""
58 +
59 +
60 +def _dt(block: str, name: str) -> tuple[str | None, str | None]:
61 + """DTSTART/DTEND iCal → (date, heure) locales QC. Trois formes Google :
62 + « ...Z » (UTC → conversion), « ;TZID=America/Toronto:... » (déjà locale),
63 + « ;VALUE=DATE:AAAAMMJJ » (journée entière, heure None)."""
64 + m = re.search(rf"^{name}(;[^:\n]*)?:(.*)$", block, re.M)
65 + if not m:
66 + return None, None
67 + raw = m.group(2).strip()
68 + if _DATE_ONLY_RE.match(raw):
69 + return f"{raw[:4]}-{raw[4:6]}-{raw[6:8]}", None
70 + if raw.endswith("Z"):
71 + return utc_to_local(raw)
72 + dm = re.match(r"(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})", raw)
73 + if not dm:
74 + return None, None
75 + t = f"{dm.group(4)}:{dm.group(5)}" # TZID/flottant = heure locale
76 + return (f"{dm.group(1)}-{dm.group(2)}-{dm.group(3)}",
77 + None if t == "00:00" else t)
78 +
79 +
80 +def _expand(start_iso: str, rrule: str, horizon: "date") -> list["date"]:
81 + """Occurrences d'une RRULE Google simple (DAILY/WEEKLY, COUNT/UNTIL/BYDAY/
82 + INTERVAL) — expansion déterministe, plafonnée à `horizon`. Les fréquences
83 + non gérées retournent la seule date de départ (jamais d'invention)."""
84 + try:
85 + d0 = date.fromisoformat(start_iso)
86 + except ValueError:
87 + return []
88 + parts = dict(p.split("=", 1) for p in rrule.split(";") if "=" in p)
89 + freq = parts.get("FREQ", "")
90 + if freq not in ("DAILY", "WEEKLY"):
91 + return [d0]
92 + interval = max(int(parts.get("INTERVAL", "1") or 1), 1)
93 + count = int(parts["COUNT"]) if parts.get("COUNT", "").isdigit() else None
94 + until = None
95 + m = re.match(r"(\d{4})(\d{2})(\d{2})", parts.get("UNTIL", ""))
96 + if m:
97 + until = date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
98 + out: list[date] = []
99 + if freq == "DAILY":
100 + d, n = d0, 0
101 + while d <= horizon and (count is None or n < count) \
102 + and (until is None or d <= until):
103 + out.append(d)
104 + n += 1
105 + d += timedelta(days=interval)
106 + return out
107 + # WEEKLY : jours de la semaine de BYDAY (défaut : jour de départ)
108 + days = sorted({_BYDAY[t] for t in parts.get("BYDAY", "").split(",")
109 + if t in _BYDAY}) or [d0.weekday()]
110 + week0 = d0 - timedelta(days=d0.weekday()) # lundi de la 1re semaine
111 + n, w = 0, 0
112 + while True:
113 + base = week0 + timedelta(weeks=w * interval)
114 + if base > horizon:
115 + break
116 + for wd in days:
117 + d = base + timedelta(days=wd)
118 + if d < d0 or d > horizon or (until and d > until):
119 + continue
120 + if count is not None and n >= count:
121 + return out
122 + out.append(d)
123 + n += 1
124 + w += 1
125 + if count is not None and n >= count:
126 + break
127 + return out
128 +
129 +
130 +class MagogConnector(BaseConnector):
131 + source_id = "magog"
132 +
133 + def _parse_feed(self, ics: str, today: str | None = None) -> list[dict]:
134 + today = today or date.today().isoformat()
135 + floor = (date.fromisoformat(today)
136 + - timedelta(days=GRACE_DAYS)).isoformat()
137 + horizon = date.fromisoformat(today) + timedelta(days=HORIZON_DAYS)
138 + rows: list[dict] = []
139 + for block in re.findall(r"BEGIN:VEVENT\n(.*?)\nEND:VEVENT",
140 + _unfold(ics), re.S):
141 + title = clean_text(_prop(block, "SUMMARY"))
142 + if not title:
143 + continue
144 + start, start_time = _dt(block, "DTSTART")
145 + end, end_time = _dt(block, "DTEND")
146 + if not start:
147 + continue
148 + # DTEND iCal exclusif sur les événements « journée entière »
149 + if end and end_time is None and start_time is None and end > start:
150 + end = (date.fromisoformat(end) - timedelta(days=1)).isoformat()
151 + desc = _prop(block, "DESCRIPTION")
152 + m = _URL_RE.search(desc)
153 + url = m.group(0).rstrip(").,&") if m else PAGE
154 + base = {
155 + "uid": _prop(block, "UID").split("@")[0],
156 + "title": title,
157 + "url": url,
158 + "location": clean_text(_prop(block, "LOCATION")),
159 + "status": _prop(block, "STATUS"),
160 + "start_time": start_time,
161 + "end_time": end_time,
162 + }
163 + duration = 0
164 + if end and end >= start:
165 + duration = (date.fromisoformat(end)
166 + - date.fromisoformat(start)).days
167 + rrule = _prop(block, "RRULE")
168 + starts = (_expand(start, rrule, horizon) if rrule
169 + else [date.fromisoformat(start)])
170 + for d in starts:
171 + e = (d + timedelta(days=duration)).isoformat()
172 + if e < floor or d.isoformat() > horizon.isoformat():
173 + continue
174 + rows.append(dict(base, start=d.isoformat(), end=e))
175 + return rows
176 +
177 + def fetch(self) -> list[Event]:
178 + rows = self._parse_feed(self.get(FEED).text)
179 + events: list[Event] = []
180 + seen: set[str] = set()
181 + for r in rows:
182 + ext = f"{r['uid']}-{r['start']}"
183 + if ext in seen:
184 + continue
185 + seen.add(ext)
186 + # LOCATION Google : « Lieu, 100 Rue X, Magog, QC J1X..., Canada »
187 + venue, address = r["location"], ""
188 + if "," in r["location"]:
189 + venue, address = [p.strip() for p in
190 + r["location"].split(",", 1)]
191 + events.append(Event(
192 + source=self.source_id,
193 + external_id=ext,
194 + url=r["url"],
195 + title=r["title"],
196 + raw_categories=[r["title"]],
197 + venue=venue,
198 + address=address,
199 + city="Magog",
200 + region="Estrie",
201 + start_date=r["start"],
202 + start_time=r["start_time"],
203 + end_date=r["end"],
204 + end_time=r["end_time"],
205 + status="" if r["status"] == "CONFIRMED" else r["status"],
206 + organizer="Ville de Magog",
207 + ))
208 + return events
added sortika/connectors/mauricie.py +151 −0
@@ -0,0 +1,151 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/mauricie.py : Tourisme Mauricie (ATR) — RÉHABILITÉE (vague ATR
5 +# 2026-08 ; écartée Phase 3 « liste rendue JS », l'endpoint XHR est percé)
6 +# Source : tourismemauricie.com/evenements — calendrier officiel de l'ATR
7 +# (thème WordPress « Acolyte », listing Vue.js).
8 +# Extraction: API JSON interne du thème (celle que le composant Vue appelle,
9 +# vérifiée 2026-08-25) :
10 +# GET /wp-json/acolyte/v1/get-events-data?lang=fr&page=N
11 +# → ~174 événements paginés 9/page : id, titre, url, images CDN,
12 +# ville, salle (venue_name), catégories, date FR, GPS. Fiches
13 +# détail incrémentales (cache data/mauricie_cache.json, plafond
14 +# MAU_FICHE_MAX/sync, re-visite 7 j) : JSON-LD Event → date de
15 +# début ISO, adresse civique, code postal, description.
16 +# Accès : API REST WordPress publique du thème (aucune auth), robots.txt
17 +# sans interdiction — GET throttlés, UA honnête. Ni prix ni heure
18 +# structurés publiés → jamais inventés. Région administrative
19 +# résolue par la VILLE (l'ATR couvre aussi des villes du
20 +# Centre-du-Québec : Bécancour, Nicolet…), repli « Mauricie ».
21 +# -----------------------------------------------------------------------------
22 +from __future__ import annotations
23 +
24 +import json
25 +import os
26 +import re
27 +import time
28 +
29 +from ..normalize import clean_text, parse_date_range_fr
30 +from ..regions import region_from_city
31 +from ..schema import Event
32 +from .base import BaseConnector
33 +
34 +BASE = "https://tourismemauricie.com"
35 +API = BASE + "/wp-json/acolyte/v1/get-events-data"
36 +MAU_PAGE_MAX = int(os.environ.get("MAU_PAGE_MAX", "40"))
37 +MAU_FICHE_MAX = int(os.environ.get("MAU_FICHE_MAX", "12"))
38 +REFRESH_AFTER = 7 * 86400
39 +
40 +_LDJSON_RE = re.compile(
41 + r'<script type="application/ld\+json"[^>]*>(.*?)</script>', re.S)
42 +
43 +
44 +def _ld_event(html: str) -> dict | None:
45 + for m in _LDJSON_RE.findall(html):
46 + try:
47 + data = json.loads(m)
48 + except ValueError:
49 + continue
50 + for item in (data if isinstance(data, list) else [data]):
51 + if isinstance(item, dict) and item.get("@type") == "Event":
52 + return item
53 + return None
54 +
55 +
56 +class MauricieConnector(BaseConnector):
57 + source_id = "mauricie"
58 + request_delay = 0.8
59 +
60 + def _list_events(self) -> list[dict]:
61 + rows: list[dict] = []
62 + page, total_pages = 1, 1
63 + while page <= min(total_pages, MAU_PAGE_MAX):
64 + data = self.get_json(API, params={"lang": "fr", "page": page})
65 + batch = data.get("events") or []
66 + if not batch:
67 + break
68 + rows.extend(batch)
69 + total_pages = int(data.get("total_pages") or 1)
70 + page += 1
71 + return rows
72 +
73 + def _parse_fiche(self, html: str) -> dict:
74 + """JSON-LD Event de la fiche : date ISO, adresse, description."""
75 + ld = _ld_event(html)
76 + if not ld:
77 + return {}
78 + addr = ((ld.get("location") or {}).get("address") or {})
79 + return {
80 + "start_date": (ld.get("startDate") or "")[:10],
81 + "end_date": (ld.get("endDate") or "")[:10],
82 + "description": clean_text(ld.get("description") or ""),
83 + "address": clean_text(addr.get("streetAddress") or ""),
84 + "city": clean_text(addr.get("addressLocality") or ""),
85 + "postal_code": (addr.get("postalCode") or "").strip(),
86 + }
87 +
88 + def fetch(self) -> list[Event]:
89 + rows = self._list_events()
90 + cache = self.load_cache()
91 + now = time.time()
92 + current = {str(r.get("id")) for r in rows}
93 + cache = {k: v for k, v in cache.items() if k in current}
94 +
95 + to_fetch = sorted(
96 + (r for r in rows
97 + if r.get("url")
98 + and (str(r.get("id")) not in cache
99 + or now - cache[str(r.get("id"))].get("ts", 0) > REFRESH_AFTER)),
100 + key=lambda r: cache.get(str(r.get("id")), {}).get("ts", 0)
101 + )[:MAU_FICHE_MAX]
102 + for row in to_fetch:
103 + eid = str(row.get("id"))
104 + detail: dict = {}
105 + try:
106 + detail = self._parse_fiche(self.get(row["url"]).text)
107 + except Exception:
108 + detail = {}
109 + cache[eid] = {"ts": now, "detail": detail}
110 + self.save_cache(cache)
111 +
112 + events: list[Event] = []
113 + seen: set[str] = set()
114 + for r in rows:
115 + eid = str(r.get("id") or "")
116 + title = clean_text(r.get("title") or "")
117 + if not eid or eid in seen or not title:
118 + continue
119 + seen.add(eid)
120 + detail = cache.get(eid, {}).get("detail") or {}
121 + start, end = parse_date_range_fr(r.get("date") or "")
122 + pos = r.get("position") or {}
123 + city = clean_text(r.get("city") or "") or detail.get("city", "")
124 + cats = [clean_text(c.get("title", "")) for c in
125 + (r.get("categories") or []) if isinstance(c, dict)]
126 + # la source recopie parfois le nom de l'événement comme nom de
127 + # salle — on ne garde le lieu que s'il diffère du titre
128 + venue = clean_text(r.get("venue_name") or "")
129 + if venue.lower() == title.lower():
130 + venue = ""
131 + events.append(Event(
132 + source=self.source_id,
133 + external_id=eid,
134 + url=r.get("url", ""),
135 + title=title,
136 + description=detail.get("description", ""),
137 + raw_categories=[c for c in cats if c] + [title],
138 + venue=venue,
139 + address=detail.get("address", ""),
140 + city=city,
141 + # l'ATR couvre des villes du Centre-du-Québec : la ville prime
142 + region=region_from_city(city),
143 + postal_code=detail.get("postal_code", ""),
144 + tourist_region="Mauricie",
145 + lat=pos.get("lat"), lng=pos.get("lng"),
146 + start_date=detail.get("start_date") or start,
147 + end_date=detail.get("end_date") or end,
148 + image=r.get("image_large") or r.get("image_medium")
149 + or r.get("image_small") or "",
150 + ))
151 + return events
modified sortika/connectors/monteregie.py +33 −12
@@ -3,18 +3,22 @@
3 3 # Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 4 # connectors/monteregie.py : Tourisme Montérégie — événements (ATR, Phase 4)
5 5 # Source : https://www.tourisme-monteregie.qc.ca — WordPress dont l'API
6 −# REST publique expose le CPT `cpt_event` (~78 fiches à venir,
7 −# vérifié 2026-08-21). Gisement Tourinsoft (acf.syndic_object_id
8 −# EVENMTOV… — même famille que le SIT Québec ; recoupement géré
9 −# par dedup_key, mais l'ATR ajoute GPS, image, site web et
10 −# description que le flux SITQ ne publie pas).
6 +# REST publique expose le CPT `cpt_event` (78 fiches publiées,
7 +# X-WP-TotalPages=1, revérifié 2026-08-25). Gisement Tourinsoft
8 +# (acf.syndic_object_id EVENMTOV… — même famille que le SIT
9 +# Québec ; recoupement géré par dedup_key, mais l'ATR ajoute
10 +# GPS, image, site web et description que le flux SITQ ne
11 +# publie pas).
11 12 # Extraction: API JSON /wp-json/wp/v2/cpt_event (paginée per_page=100) →
12 −# titre, description, plage de dates FR (« Du 18 septembre au
13 −# 20 septembre 2026 »), lat/long, site web, image. Fiches détail
13 +# titre, description (content, fallback excerpt), plage de
14 +# dates FR (« Du 18 septembre au 20 septembre 2026 »), lat/long,
15 +# site web, image. ENRICHI (2026-08-25) : taxonomie
16 +# event-categorie (1 GET/sync) → catégories brutes (« Marchés
17 +# de Noël », « Festival et autres événements »). Fiches détail
14 18 # incrémentales (cache data/monteregie_cache.json,
15 19 # MTG_FICHE_MAX/sync, re-visite 7 j) : bloc JSON-LD Event →
16 −# adresse civique, ville, code postal. Pas d'heure ni de tarif
17 −# structurés → jamais inventés.
20 +# adresse civique, ville, code postal. Ni heure ni tarif
21 +# structurés publiés (vérifié sur fiches) → jamais inventés.
18 22 # Accès : API REST WordPress publique (aucune auth), robots.txt sans
19 23 # interdiction sur /wp-json/ — GET throttlés, UA honnête.
20 24 # -----------------------------------------------------------------------------
@@ -31,6 +35,7 @@ from .base import BaseConnector
31 35
32 36 BASE = "https://www.tourisme-monteregie.qc.ca"
33 37 API = BASE + "/wp-json/wp/v2/cpt_event"
38 +TAX_API = BASE + "/wp-json/wp/v2/event-categorie"
34 39 PER_PAGE = 100
35 40 PAGE_MAX = 5 # garde-fou (78 fiches en 2026-08)
36 41 FICHE_MAX = int(os.environ.get("MTG_FICHE_MAX", "15"))
@@ -56,6 +61,15 @@ class MonteregieConnector(BaseConnector):
56 61 break
57 62 return rows
58 63
64 + def _categories(self) -> dict[int, str]:
65 + """Taxonomie event-categorie : {id: libellé} (1 GET, jamais bloquant)."""
66 + try:
67 + terms = self.get_json(TAX_API, params={"per_page": 100})
68 + return {t["id"]: clean_text(t.get("name", ""))
69 + for t in terms if isinstance(t, dict) and t.get("id")}
70 + except Exception:
71 + return {}
72 +
59 73 def _parse_fiche(self, html: str) -> dict:
60 74 """Adresse/ville/salle du bloc JSON-LD Event de la fiche (thème ATR)."""
61 75 for m in _LDJSON_RE.findall(html):
@@ -85,6 +99,7 @@ class MonteregieConnector(BaseConnector):
85 99
86 100 def fetch(self) -> list[Event]:
87 101 rows = self._list_events()
102 + categories = self._categories()
88 103 cache = self.load_cache()
89 104 now = time.time()
90 105 current = {str(r.get("id")) for r in rows}
@@ -117,13 +132,19 @@ class MonteregieConnector(BaseConnector):
117 132 if isinstance(images, list) and images and isinstance(images[0], dict):
118 133 image = images[0].get("url", "")
119 134 detail = cache.get(eid, {}).get("detail") or {}
135 + title = clean_text((row.get("title") or {}).get("rendered", ""))
136 + description = clean_text(
137 + (row.get("content") or {}).get("rendered", "")) or clean_text(
138 + (row.get("excerpt") or {}).get("rendered", ""))
139 + raw_cats = [categories[t] for t in (row.get("event-categorie") or [])
140 + if t in categories]
120 141 events.append(Event(
121 142 source=self.source_id,
122 143 external_id=eid,
123 144 url=row.get("link", ""),
124 − title=clean_text((row.get("title") or {}).get("rendered", "")),
125 − description=clean_text(
126 − (row.get("content") or {}).get("rendered", "")),
145 + title=title,
146 + description=description,
147 + raw_categories=raw_cats + [title],
127 148 venue=detail.get("venue", ""),
128 149 address=detail.get("address", ""),
129 150 city=detail.get("city", ""),
modified sortika/connectors/montreal.py +12 −7
@@ -9,7 +9,10 @@
9 9 # sport, marchés) avec dates, coût, arrondissement, lat/lng.
10 10 # Images : absentes du jeu de données → enrichissement incrémental par
11 11 # og:image des fiches montreal.ca (cache disque, plafonné par
12 −# sync via MTL_IMG_MAX, défaut 300).
12 +# sync via MTL_IMG_MAX, défaut 600 — doublé 2026-08-25 pour un
13 +# rattrapage plus rapide ; candidats collectés en entier PUIS
14 +# triés — jamais visités d'abord, ensuite par date de début la
15 +# plus proche — avant d'appliquer le plafond).
13 16 # https://donnees.montreal.ca/dataset/evenements-publics
14 17 # -----------------------------------------------------------------------------
15 18 from __future__ import annotations
@@ -23,7 +26,7 @@ from .base import BaseConnector
23 26 API = "https://donnees.montreal.ca/api/3/action/datastore_search"
24 27 RESOURCE_ID = "6decf611-6f11-4f34-bb36-324d804c9bad"
25 28 PAGE = 1000
26 −IMG_MAX = int(os.environ.get("MTL_IMG_MAX", "300"))
29 +IMG_MAX = int(os.environ.get("MTL_IMG_MAX", "600"))
27 30
28 31 _OG_IMG_RE = re.compile(r'property="og:image" content="([^"]*)"')
29 32 # heures locales de la fiche montreal.ca (JSON-LD Event, offset Québec inclus) :
@@ -68,11 +71,13 @@ class MontrealConnector(BaseConnector):
68 71 if isinstance(entry, dict) and "end_time" in entry:
69 72 continue # déjà au format Phase 2
70 73 last_day = (ev.end_date or ev.start_date or "")[:10]
71 − if last_day >= today and len(to_fetch) < IMG_MAX:
72 − # None = jamais visité (prioritaire) ; str/dict vague 2 =
73 − # hérité (backfill de l'heure de fin, priorité basse)
74 − to_fetch.append((entry is not None, ev.url))
75 − to_fetch = [u for _, u in sorted(to_fetch, key=lambda t: t[0])][:IMG_MAX]
74 + if last_day >= today:
75 + # collecte COMPLÈTE puis tri (avant 2026-08-25 le plafond
76 + # s'appliquait à la collecte : la priorisation ne jouait pas) :
77 + # jamais visités d'abord, puis les événements les plus proches
78 + to_fetch.append((entry is not None,
79 + ev.start_date or "9999", ev.url))
80 + to_fetch = [u for *_, u in sorted(to_fetch)][:IMG_MAX]
76 81
77 82 def worker(url, session):
78 83 html = session.get(url, timeout=15).text
added sortika/connectors/mtlorg.py +122 −0
@@ -0,0 +1,122 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/mtlorg.py : Tourisme Montréal — mtl.org (ATR, vague ATR 2026-08)
5 +# Source : mtl.org/fr/quoi-faire/festivals-et-evenements — répertoire
6 +# officiel de Tourisme Montréal (frontend SvelteKit + Drupal,
7 +# listing servi par le même index Algolia que le site).
8 +# Extraction: index Algolia « prod_fr » (appId/clé de recherche publiques,
9 +# embarquées dans le JS du site — vérifié 2026-08-25), filtre
10 +# facettes _sections:« Festivals et événements » +
11 +# content_type:product → ~650 fiches : titre, surtitre,
12 +# description, plages de dates STRUCTURÉES _open_dates
13 +# [{start,end}], adresse complète (_address), GPS (_geoloc),
14 +# sous-catégories, image CDN, URL. 1 requête/sync
15 +# (hitsPerPage=1000). Les fiches SANS _open_dates (salles /
16 +# attraits permanents listés dans la section) sont écartées :
17 +# un événement sans date publiée n'est pas émis (jamais inventé).
18 +# Accès : clé Algolia « search-only » publiée par le site lui-même —
19 +# requêtes throttlées, identification honnête, lien vers la
20 +# fiche originale. Ni prix ni heure publiés → jamais inventés.
21 +# -----------------------------------------------------------------------------
22 +from __future__ import annotations
23 +
24 +import os
25 +import time
26 +from datetime import date
27 +
28 +from ..normalize import clean_text
29 +from ..schema import Event
30 +from .base import BaseConnector
31 +
32 +BASE = "https://www.mtl.org"
33 +ALGOLIA_URL = "https://XGNQMFEPVY-dsn.algolia.net/1/indexes/prod_fr/query"
34 +ALGOLIA_HEADERS = {
35 + "X-Algolia-Application-Id": "XGNQMFEPVY",
36 + "X-Algolia-API-Key": "ca0b7ec6d7560ae093455e13208291b6",
37 + "Content-Type": "application/json",
38 +}
39 +FACETS = ('[["_sections:Festivals et événements"],'
40 + '["content_type:product"]]')
41 +MTL_PAGE_MAX = int(os.environ.get("MTL_PAGE_MAX", "5"))
42 +
43 +
44 +def _pick_range(open_dates: list, today: str) -> tuple[str | None, str | None]:
45 + """Première plage encore active/à venir, sinon la dernière publiée."""
46 + ranges = [(d.get("start"), d.get("end")) for d in open_dates
47 + if isinstance(d, dict) and d.get("start")]
48 + if not ranges:
49 + return None, None
50 + ranges.sort()
51 + for start, end in ranges:
52 + if (end or start) >= today:
53 + return start, end or start
54 + start, end = ranges[-1]
55 + return start, end or start
56 +
57 +
58 +def _image_url(hit: dict) -> str:
59 + img = ((hit.get("_image") or {}).get("image") or {})
60 + for size in ("medium", "large", "small"):
61 + entries = img.get(size) or []
62 + if entries and isinstance(entries[0], dict) and entries[0].get("url"):
63 + return entries[0]["url"]
64 + return ""
65 +
66 +
67 +class MtlOrgConnector(BaseConnector):
68 + source_id = "mtlorg"
69 + request_delay = 0.8
70 +
71 + def _hits(self) -> list[dict]:
72 + import json as _json
73 + from urllib.parse import quote
74 + hits: list[dict] = []
75 + for page in range(MTL_PAGE_MAX):
76 + self._throttle()
77 + resp = self.session.post(ALGOLIA_URL, json={
78 + "params": f"hitsPerPage=1000&page={page}"
79 + f"&facetFilters={quote(FACETS)}"},
80 + headers=ALGOLIA_HEADERS, timeout=self.timeout)
81 + self._last_request = time.time()
82 + resp.raise_for_status()
83 + data = resp.json()
84 + hits.extend(data.get("hits") or [])
85 + if page + 1 >= int(data.get("nbPages") or 1):
86 + break
87 + return hits
88 +
89 + def fetch(self) -> list[Event]:
90 + today = date.today().isoformat()
91 + events: list[Event] = []
92 + seen: set[str] = set()
93 + for h in self._hits():
94 + eid = str(h.get("nid") or h.get("uuid") or "")
95 + title = clean_text(h.get("title") or "")
96 + start, end = _pick_range(h.get("_open_dates") or [], today)
97 + if not eid or eid in seen or not title or not start:
98 + continue # salle/attrait permanent sans date → pas un événement
99 + seen.add(eid)
100 + addr = h.get("_address") or {}
101 + geo = h.get("_geoloc") or {}
102 + cats = ((h.get("_filter_events_sub_categories") or [])
103 + + (h.get("_filter_global") or []))
104 + events.append(Event(
105 + source=self.source_id,
106 + external_id=eid,
107 + url=BASE + (h.get("_url") or ""),
108 + title=title,
109 + description=clean_text(h.get("description") or "")
110 + or clean_text(h.get("surtitle") or ""),
111 + raw_categories=[clean_text(c) for c in cats if c] + [title],
112 + venue=clean_text(addr.get("name") or ""),
113 + address=clean_text(addr.get("address") or ""),
114 + city=clean_text(addr.get("city") or ""),
115 + postal_code=(addr.get("postalCode") or "").strip(),
116 + tourist_region="Montréal",
117 + lat=geo.get("lat"), lng=geo.get("lng"),
118 + start_date=start,
119 + end_date=end,
120 + image=_image_url(h),
121 + ))
122 + return events
modified sortika/connectors/outaouais.py +91 −13
@@ -5,13 +5,19 @@
5 5 # Source : https://tourismeoutaouais.com/calendrier-des-evenements — site
6 6 # Astro : la page embarque TOUT le calendrier dans les props
7 7 # sérialisées de l'îlot <astro-island CalendrierDesEvenements>
8 −# (162 événements dont 25 à venir, vérifié 2026-08-19) →
9 −# UNE SEULE requête par sync.
8 +# (159 événements dont ~22 à venir, revérifié 2026-08-25) →
9 +# UNE SEULE requête pour la liste.
10 10 # Extraction: props JSON (format Svelte [type, valeur] déballé) : id, titre,
11 11 # slug/url (interne /attraits/<slug> ou site officiel externe),
12 12 # municipalité, territoire, photo (CDN admin.tourismeoutaouais.com),
13 −# startDate/endDate ISO. Pas d'heure ni de tarif publiés → jamais
14 −# inventés. GPS via annuaire de salles/géocodeur central.
13 +# startDate/endDate ISO. ENRICHISSEMENT (2026-08-25) : les fiches
14 +# internes /attraits/<slug> des événements à venir sont visitées
15 +# incrémentalement (cache data/outaouais_cache.json,
16 +# OUT_FICHE_MAX/sync, re-visite 7 j) : description (meta
17 +# description / div.description), adresse civique + ville
18 +# (div.address « 988, rue Saint-louis, Gatineau (Québec) »),
19 +# site web officiel (« Visitez le site web »). Pas d'heure ni de
20 +# tarif publiés → jamais inventés. GPS via annuaire/géocodeur.
15 21 # Accès : robots.txt « Allow: / » — GET direct, identification honnête.
16 22 # Décision : comble l'échec Gatineau (site 100 % JS, consigné 2026-08-18) —
17 23 # l'Outaouais n'avait que 337 fiches au baseline Phase 3.
@@ -20,7 +26,10 @@ from __future__ import annotations
20 26
21 27 import html as _html
22 28 import json
29 +import os
23 30 import re
31 +import time
32 +from datetime import date
24 33
25 34 from ..normalize import clean_text
26 35 from ..schema import Event
@@ -29,6 +38,15 @@ from .base import BaseConnector
29 38 BASE = "https://tourismeoutaouais.com"
30 39 ADMIN = "https://admin.tourismeoutaouais.com"
31 40 LIST_URL = BASE + "/calendrier-des-evenements"
41 +FICHE_MAX = int(os.environ.get("OUT_FICHE_MAX", "20"))
42 +REFRESH_AFTER = 7 * 86400
43 +
44 +_META_DESC_RE = re.compile(r'<meta name="description" content="([^"]*)"')
45 +_DIV_DESC_RE = re.compile(r'class="my-4 description"[^>]*>(.*?)</div>', re.S)
46 +_ADDR_RE = re.compile(r'class="address"[^>]*>\s*([^<]+?)\s*</div>')
47 +_WEBSITE_RE = re.compile(
48 + r'href="(https?://[^"]+)"[^>]*>\s*Visitez le site web')
49 +
32 50
33 51 def _unwrap(v):
34 52 """Déballe le format de sérialisation Astro/Svelte : [0, valeur] scalaire,
@@ -62,9 +80,55 @@ class OutaouaisConnector(BaseConnector):
62 80 source_id = "outaouais"
63 81 request_delay = 0.8
64 82
83 + def _parse_fiche(self, html: str) -> dict:
84 + """Fiche interne /attraits/<slug> : description, adresse, ville, site."""
85 + desc = ""
86 + m = _DIV_DESC_RE.search(html) or _META_DESC_RE.search(html)
87 + if m:
88 + desc = clean_text(m.group(1))[:2000]
89 + address, city = "", ""
90 + a = _ADDR_RE.search(html)
91 + if a:
92 + raw = clean_text(a.group(1)).replace("(Québec)", "").strip(" ,")
93 + if "," in raw: # « 988, rue Saint-louis, Gatineau »
94 + address, city = (s.strip() for s in raw.rsplit(",", 1))
95 + else:
96 + address = raw
97 + w = _WEBSITE_RE.search(html)
98 + return {"desc": desc, "address": address, "city": city,
99 + "website": w.group(1) if w else ""}
100 +
101 + def _enrich_fiches(self, rows: list[dict]) -> None:
102 + """Visite incrémentale des fiches internes des événements À VENIR
103 + (budget : les fiches passées ne sont pas re-crawlées)."""
104 + today = date.today().isoformat()
105 + urls = sorted({r["url"] for r in rows
106 + if r["url"].startswith(BASE + "/attraits/")
107 + and (r.get("end") or r.get("start") or "") >= today})
108 + cache = self.load_cache()
109 + now = time.time()
110 + cache = {u: c for u, c in cache.items() if u in {r["url"] for r in rows}}
111 + to_fetch = sorted(
112 + (u for u in urls
113 + if u not in cache or now - cache[u].get("ts", 0) > REFRESH_AFTER),
114 + key=lambda u: cache.get(u, {}).get("ts", 0))[:FICHE_MAX]
115 +
116 + def worker(url, session):
117 + return self._parse_fiche(session.get(url, timeout=25).text)
118 +
119 + for url, det in self.fetch_many(to_fetch, worker, max_workers=3).items():
120 + cache[url] = {"ts": now, **(det or {})}
121 + self.save_cache(cache)
122 + for r in rows:
123 + det = cache.get(r["url"]) or {}
124 + r["description"] = det.get("desc", "")
125 + r["address"] = det.get("address", "")
126 + r["fiche_city"] = det.get("city", "")
127 + r["website"] = det.get("website", "")
128 +
65 129 def fetch(self) -> list[Event]:
66 130 html = self.get(LIST_URL).text
67 − events: list[Event] = []
131 + rows: list[dict] = []
68 132 seen: set[str] = set()
69 133 for e in _extract_events(html):
70 134 ext_id = str(e.get("id") or "")
@@ -84,16 +148,30 @@ class OutaouaisConnector(BaseConnector):
84 148 if muni_slug.startswith("municipalite-exterieure"):
85 149 muni_slug = "" # « extérieur de la région » ≠ ville
86 150 muni = clean_text(muni_slug.replace("-", " ")).title()
151 + rows.append({
152 + "ext_id": ext_id, "title": title, "url": url,
153 + "territory": clean_text(e.get("territory") or ""),
154 + "city": muni, "photo": photo,
155 + "start": e.get("startDate"),
156 + "end": e.get("endDate") or e.get("startDate"),
157 + })
158 + self._enrich_fiches(rows)
159 +
160 + events: list[Event] = []
161 + for r in rows:
87 162 events.append(Event(
88 163 source=self.source_id,
89 − external_id=ext_id,
90 − url=url,
91 − title=title,
92 − raw_categories=[title, clean_text(e.get("territory") or "")],
93 − city=muni,
164 + external_id=r["ext_id"],
165 + url=r["url"],
166 + title=r["title"],
167 + description=r.get("description", ""),
168 + raw_categories=[r["title"], r["territory"]],
169 + address=r.get("address", ""),
170 + city=r["city"] or r.get("fiche_city", ""),
94 171 tourist_region="Outaouais",
95 − start_date=e.get("startDate"),
96 − end_date=e.get("endDate") or e.get("startDate"),
97 − image=photo,
172 + start_date=r["start"],
173 + end_date=r["end"],
174 + website=r.get("website", ""),
175 + image=r["photo"],
98 176 ))
99 177 return events
modified sortika/connectors/ovation.py +10 −2
@@ -17,8 +17,16 @@
17 17 # OVA_FICHE_MAX/sync, re-visite 7 j) : date pleine FR
18 18 # (« mercredi 26 août 2026, 20h00 »), catégories, artistes
19 19 # (EventsByPerformer), description, salle + adresse + ville +
20 −# code postal. Prix non exposé par le sous-site mobile →
21 −# jamais inventé.
20 +# code postal.
21 +# Prix : NON PUBLIÉS par la source sur ses surfaces crawlables —
22 +# re-sondé live 2026-08-25 : la page Event complète mobile
23 +# (77 Ko) et la page desktop www.ovation.ca (ForceWebsite-
24 +# Version=Desktop, 72 Ko) ne contiennent AUCUN montant ni
25 +# JSON-LD offers (les seules mentions « prix » = politique de
26 +# cookies) ; aucun endpoint JSON public. Les tarifs
27 +# n'apparaissent qu'au choix des sièges (/event/tickets),
28 +# chemin Disallow par robots.txt (mobile ET desktop, avec
29 +# /api/) → jamais requêté, prix jamais inventé.
22 30 # Accès : billetterie publique, HTML public sans auth. robots.txt
23 31 # (lu 2026-08-21) : User-agent * → Crawl-delay: 30 +
24 32 # Disallow /event/tickets (jamais requêté) → GET espacés de
modified sortika/connectors/quebecanimee.py +46 −9
@@ -11,8 +11,12 @@
11 11 # data-date-debut / data-date-fin ISO, data-arrondissement,
12 12 # data-categorie, data-prix (« Gratuit ») + titre/image de la
13 13 # carte ; fiches détail incrémentales (cache
14 −# data/quebecanimee_cache.json, QA_FICHE_MAX/sync, re-visite 7 j) :
15 −# description, lieu (nom de salle/parc), heures si publiées.
14 +# data/quebecanimee_cache.json, QA_FICHE_MAX/sync, re-visite 7 j,
15 +# version de parse « v » — patron lavitrine) : description,
16 +# lieu réel (.lieu-nom) + adresse civique (.lieu-adresse) pour
17 +# le géocodage aval, et HEURES par représentation (attributs
18 +# data-date / data-heure des boutons « Ajouter à mon horaire »,
19 +# vérifié 2026-08-25) → start_time par occurrence datée.
16 20 # Accès : site municipal public rendu serveur — GET throttlés 0,8 s,
17 21 # identification honnête, lien vers la fiche officielle.
18 22 # -----------------------------------------------------------------------------
@@ -30,6 +34,7 @@ BASE = "https://animee.ville.quebec.qc.ca"
30 34 LIST_URL = BASE + "/activites/"
31 35 FICHE_MAX = int(os.environ.get("QA_FICHE_MAX", "20"))
32 36 REFRESH_AFTER = 7 * 86400
37 +PARSE_V = 2 # bump → re-visite des fiches parsées avant v2
33 38
34 39 _CARD_RE = re.compile(
35 40 r'<a[^>]+href="(/activites/[a-z0-9-]+/)"[^>]*'
@@ -39,8 +44,15 @@ _CARD_RE = re.compile(
39 44 _TITLE_RE = re.compile(r'<h3[^>]*>\s*([^<]+?)\s*</h3>|<h2[^>]*>\s*([^<]+?)\s*</h2>')
40 45 _IMG_RE = re.compile(r'<img[^>]+src="([^"]+)"')
41 46 _OG_DESC_RE = re.compile(r'property="og:description"\s+content="([^"]*)"')
42 −_LIEU_RE = re.compile(r'class="[^"]*lieu[^"]*"[^>]*>\s*(?:<[^>]+>\s*)*([^<]{3,120})')
43 −_HEURE_RE = re.compile(r'(\d{1,2}\s*h(?:\s*\d{2})?)')
47 +# le lieu réel de la fiche : <p class="lieu-nom">…</p> + <p class="lieu-adresse">…</p>
48 +# (l'ancien motif générique « class=*lieu* » attrapait le menu « Lieux » du site
49 +# — venue était 100 % du bruit, corrigé 2026-08-25)
50 +_LIEU_NOM_RE = re.compile(r'class="lieu-nom">\s*([^<]{2,120}?)\s*</p>')
51 +_LIEU_ADR_RE = re.compile(r'class="lieu-adresse">\s*([^<]{2,160}?)\s*</p>')
52 +# une représentation datée = un bouton « Ajouter à mon horaire » :
53 +# data-date="2026-08-30" … data-heure="13:30" (vérifié sur fiche réelle)
54 +_REP_RE = re.compile(
55 + r'data-date="(\d{4}-\d{2}-\d{2})"\s+data-heure="(\d{1,2}:\d{2})"')
44 56
45 57
46 58 class QuebecAnimeeConnector(BaseConnector):
@@ -49,10 +61,17 @@ class QuebecAnimeeConnector(BaseConnector):
49 61
50 62 def _parse_fiche(self, html: str) -> dict:
51 63 m = _OG_DESC_RE.search(html)
52 − lieu = _LIEU_RE.search(html)
64 + nom = _LIEU_NOM_RE.search(html)
65 + adr = _LIEU_ADR_RE.search(html)
66 + times: dict[str, str] = {}
67 + for d, h in _REP_RE.findall(html):
68 + times.setdefault(d, h) # 1ʳᵉ heure publiée pour ce jour
53 69 return {
54 70 "desc": clean_text(m.group(1)) if m else "",
55 − "venue": clean_text(lieu.group(1)) if lieu else "",
71 + "venue": clean_text(nom.group(1)) if nom else "",
72 + "address": clean_text(adr.group(1)) if adr else "",
73 + "times": times,
74 + "v": PARSE_V,
56 75 }
57 76
58 77 def _enrich_fiches(self, rows: list[dict]) -> None:
@@ -62,19 +81,35 @@ class QuebecAnimeeConnector(BaseConnector):
62 81 cache = {u: c for u, c in cache.items() if u in current}
63 82 to_fetch = sorted(
64 83 (u for u in current
65 − if u not in cache or now - cache[u].get("ts", 0) > REFRESH_AFTER),
66 − key=lambda u: cache.get(u, {}).get("ts", 0))[:FICHE_MAX]
84 + if u not in cache or cache[u].get("v", 1) < PARSE_V
85 + or now - cache[u].get("ts", 0) > REFRESH_AFTER),
86 + key=lambda u: (cache.get(u, {}).get("v", 1) if u in cache else 0,
87 + cache.get(u, {}).get("ts", 0)))[:FICHE_MAX]
67 88
68 89 def worker(url, session):
69 90 return self._parse_fiche(session.get(url, timeout=20).text)
70 91
71 92 for url, det in self.fetch_many(to_fetch, worker, max_workers=4).items():
72 − cache[url] = {"ts": now, **(det or {"desc": "", "venue": ""})}
93 + if det is None: # fiche cassée : garder l'entrée existante
94 + det = {k: v for k, v in (cache.get(url) or {}).items() if k != "ts"}
95 + det.setdefault("desc", "")
96 + det.setdefault("venue", "")
97 + cache[url] = {"ts": now, **det}
73 98 self.save_cache(cache)
74 99 for r in rows:
75 100 det = cache.get(r["url"]) or {}
76 101 r["description"] = det.get("desc", "")
102 + if det.get("v", 1) < PARSE_V:
103 + # entrée héritée v1 : son venue vient du motif générique qui
104 + # attrapait le menu « Lieux » du site (100 % bruit, constaté
105 + # en live 2026-08-25) — on n'en publie que la description en
106 + # attendant le backfill v2 (plafond QA_FICHE_MAX/sync).
107 + det = {}
77 108 r["venue"] = det.get("venue", "")
109 + r["address"] = det.get("address", "")
110 + # heure de LA représentation de cette occurrence datée (jamais
111 + # inventée : seulement si la fiche publie ce jour précis)
112 + r["start_time"] = (det.get("times") or {}).get(r["start_date"] or "")
78 113
79 114 def fetch(self) -> list[Event]:
80 115 html = self.get(LIST_URL).text
@@ -123,9 +158,11 @@ class QuebecAnimeeConnector(BaseConnector):
123 158 description=r.get("description", ""),
124 159 raw_categories=r["cats"] or [r["title"]],
125 160 venue=r.get("venue", ""),
161 + address=r.get("address", ""),
126 162 city="Québec",
127 163 region="Capitale-Nationale",
128 164 start_date=r["start_date"],
165 + start_time=r.get("start_time") or None,
129 166 end_date=r["end_date"],
130 167 price_label=r["prix"],
131 168 image=img,
added sortika/connectors/quebeccite.py +120 −0
@@ -0,0 +1,120 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/quebeccite.py : Québec cité / Destination Québec cité (ATR,
5 +# vague ATR 2026-08)
6 +# Source : quebec-cite.com/fr/quoi-faire-quebec/evenements — calendrier
7 +# officiel de l'office de tourisme de la région de Québec
8 +# (Drupal, listing servi par le même index Algolia que le site).
9 +# Extraction: index Algolia « default_fr_event_date_start_asc » (appId/clé
10 +# de recherche publiques, embarquées dans le JS du thème —
11 +# vérifié 2026-08-25 : ~840 fiches avec titre, résumé, lieu
12 +# (event_location), GPS, prix texte (« À partir de 98,73 $ »),
13 +# gratuité event_free, dates start/end en timestamps Unix,
14 +# type/sujets/thèmes, image, lien externe officiel). 1-2
15 +# requêtes/sync (hitsPerPage=1000, filtre event_date_end >=
16 +# hier). Aucune fiche détail nécessaire.
17 +# Accès : clé Algolia « search-only » publiée par le site lui-même —
18 +# requêtes throttlées, identification honnête, lien vers la
19 +# fiche originale. is_free seulement si event_free=true ;
20 +# price_label = texte source (jamais inventé).
21 +# -----------------------------------------------------------------------------
22 +from __future__ import annotations
23 +
24 +import os
25 +import time
26 +from datetime import datetime, timezone
27 +from ..normalize import clean_text
28 +from ..schema import Event
29 +from .base import BaseConnector
30 +
31 +BASE = "https://www.quebec-cite.com"
32 +ALGOLIA_URL = ("https://WOZPDCK6IL-dsn.algolia.net/1/indexes/"
33 + "default_fr_event_date_start_asc/query")
34 +ALGOLIA_HEADERS = {
35 + "X-Algolia-Application-Id": "WOZPDCK6IL",
36 + "X-Algolia-API-Key": "c1c4abd63a65bcf4efc646449d5c52ed",
37 + "Content-Type": "application/json",
38 +}
39 +QBC_PAGE_MAX = int(os.environ.get("QBC_PAGE_MAX", "5"))
40 +
41 +
42 +def _txt(v) -> str:
43 + """Champ Algolia texte OU liste de textes → chaîne nettoyée."""
44 + if isinstance(v, (list, tuple)):
45 + return clean_text(", ".join(str(x) for x in v if x))
46 + return clean_text(str(v)) if v else ""
47 +
48 +
49 +def _ts_date(ts) -> str | None:
50 + """Timestamp Unix → date ISO. La source encode ses dates à 12:00 UTC
51 + (placeholder « jour entier », vérifié sur tout l'index 2026-08-25) :
52 + AUCUNE heure réelle n'est publiée → on n'émet jamais d'heure."""
53 + try:
54 + ts = int(ts)
55 + if ts <= 0:
56 + return None
57 + return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d")
58 + except (TypeError, ValueError, OSError):
59 + return None
60 +
61 +
62 +class QuebecCiteConnector(BaseConnector):
63 + source_id = "quebeccite"
64 + request_delay = 0.8
65 +
66 + def _post_json(self, payload: dict):
67 + self._throttle()
68 + resp = self.session.post(ALGOLIA_URL, json=payload,
69 + headers=ALGOLIA_HEADERS, timeout=self.timeout)
70 + self._last_request = time.time()
71 + resp.raise_for_status()
72 + return resp.json()
73 +
74 + def _hits(self) -> list[dict]:
75 + since = int(time.time()) - 86400
76 + hits: list[dict] = []
77 + for page in range(QBC_PAGE_MAX):
78 + data = self._post_json({
79 + "params": f"hitsPerPage=1000&page={page}"
80 + f"&numericFilters=event_date_end>={since}"})
81 + hits.extend(data.get("hits") or [])
82 + if page + 1 >= int(data.get("nbPages") or 1):
83 + break
84 + return hits
85 +
86 + def fetch(self) -> list[Event]:
87 + events: list[Event] = []
88 + seen: set[str] = set()
89 + for h in self._hits():
90 + eid = str(h.get("nid") or "")
91 + title = clean_text(h.get("title") or h.get("index_title") or "")
92 + if not eid or eid in seen or not title:
93 + continue
94 + seen.add(eid)
95 + start_date = _ts_date(h.get("event_date_start"))
96 + end_date = _ts_date(h.get("event_date_end"))
97 + image = h.get("teaser_image") or ""
98 + if image.startswith("/"):
99 + image = BASE + image
100 + cats = [_txt(h.get("event_type")), _txt(h.get("subjects")),
101 + _txt(h.get("themes"))]
102 + events.append(Event(
103 + source=self.source_id,
104 + external_id=eid,
105 + url=BASE + (h.get("url") or ""),
106 + title=title,
107 + description=_txt(h.get("summary")),
108 + raw_categories=[c for c in cats if c] + [title],
109 + venue=_txt(h.get("event_location")),
110 + city="", # non publié (lieu = salle)
111 + tourist_region="Région de Québec",
112 + lat=h.get("lat"), lng=h.get("lng"),
113 + start_date=start_date,
114 + end_date=end_date,
115 + is_free=True if h.get("event_free") is True else None,
116 + price_label=_txt(h.get("price")),
117 + website=(h.get("external_link") or "").strip(),
118 + image=image,
119 + ))
120 + return events
added sortika/connectors/quoifaire.py +192 −0
@@ -0,0 +1,192 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/quoifaire.py : connecteur Quoi faire (quoifaire.com) — agrégateur
5 +# Source : agrégateur de sorties québécois (sections villes : Québec,
6 +# Montréal, Saguenay, Outaouais, Mauricie — les autres sections
7 +# renvoyaient 502 à la sonde du 2026-08-25, ignorées proprement).
8 +# Extraction: listes HTML rendues serveur /<ville>/evenements (+ ?page=N)
9 +# → URLs de fiches À VENIR seulement (le sitemap remonte 15 ans
10 +# d'archives, on ne s'en sert pas). Fiches rendues serveur :
11 +# h1, résumé, plage de dates FR (next-date__date), prix publié
12 +# (next-date__price « min. X$ | max. Y$ »), heure (data-meta-time,
13 +# « À confirmer » → None), Région touristique / Lieu / adresse,
14 +# lat/lng du lien Google Maps, auditoire, catégorie, og:image.
15 +# INCRÉMENTAL avec cache disque (data/quoifaire_cache.json) :
16 +# nouvelles fiches d'abord, re-visite après 7 jours, plafonné à
17 +# QUOIFAIRE_DETAIL_MAX fiches/sync.
18 +# Accès : robots.txt « Allow: / » (seuls /search et l'admin sont exclus,
19 +# on n'y touche pas) ; GET throttlés 0,8 s, identification honnête.
20 +# Prix : price_label = texte publié (« min. 40.00$ | max. 150.00$ ») →
21 +# parse_price en aval (price_min = plus bas montant). Jamais inventé.
22 +# -----------------------------------------------------------------------------
23 +from __future__ import annotations
24 +
25 +import os
26 +import re
27 +import time
28 +
29 +from ..normalize import clean_text, parse_date_range_fr, parse_time
30 +from ..schema import Event
31 +from .base import BaseConnector
32 +
33 +BASE = "https://quoifaire.com"
34 +CITIES = [c.strip() for c in os.environ.get(
35 + "QUOIFAIRE_CITIES", "quebec,montreal,saguenay,outaouais,mauricie"
36 +).split(",") if c.strip()]
37 +
38 +DETAIL_MAX = int(os.environ.get("QUOIFAIRE_DETAIL_MAX", "150"))
39 +REFRESH_AFTER = 7 * 86400
40 +MAX_PAGES = 40 # garde-fou pagination par ville
41 +
42 +_LINK_RE = re.compile(r'href="(https://quoifaire\.com/[a-z-]+/evenements/'
43 + r'(?!categories)[^"#?]+)"')
44 +_PAGE_RE = re.compile(r'\?page=(\d+)')
45 +_H1_RE = re.compile(r"<h1[^>]*>(.*?)</h1>", re.S)
46 +_SUMMARY_RE = re.compile(r'class="summary">\s*<p>(.*?)</p>', re.S)
47 +_DATE_RE = re.compile(r'class="next-date__date">\s*([^<]+?)\s*</p>')
48 +_PRICE_RE = re.compile(r'class="next-date__price">([^<]*)<')
49 +_TIME_RE = re.compile(r'data-meta-time>([^<]*)<')
50 +_OG_IMG_RE = re.compile(r'property="og:image" content="([^"]*)"')
51 +_MAPS_RE = re.compile(r'maps\.google\.[a-z.]+/\?q=(-?[\d.]+),(-?[\d.]+)')
52 +# blocs « info-item » : Région / Lieu (nom en <strong>, adresse en <p>)
53 +_REGION_RE = re.compile(r'info-item__label">R&eacute;gion</div>\s*'
54 + r'<div class="info-item__value">([^<]*)<', re.S)
55 +_REGION2_RE = re.compile(r'info-item__label">Région</div>\s*'
56 + r'<div class="info-item__value">([^<]*)<', re.S)
57 +_LIEU_RE = re.compile(r'info-item__label">Lieu</div>\s*'
58 + r'<div class="info-item__value">\s*'
59 + r'<strong>([^<]*)</strong>\s*(?:<p>([^<]*)</p>)?', re.S)
60 +_META_ITEM_RE = re.compile(r'<span class="label">([^<]+)</span>\s*'
61 + r'<span class="value">\s*(?:<a[^>]*>)?([^<]+)', re.S)
62 +
63 +
64 +class QuoiFaireConnector(BaseConnector):
65 + source_id = "quoifaire"
66 + request_delay = 0.8
67 +
68 + # -- listes (événements à venir seulement) --------------------------------
69 + def _city_links(self, city: str) -> list[str]:
70 + links: list[str] = []
71 + last = 1
72 + page = 1
73 + while page <= min(last, MAX_PAGES):
74 + url = f"{BASE}/{city}/evenements" + (f"?page={page}" if page > 1 else "")
75 + html = self.get(url).text
76 + batch = [u for u in _LINK_RE.findall(html) if u not in links]
77 + if page > 1 and not batch:
78 + break # fin réelle de la pagination
79 + links += batch
80 + pages = [int(p) for p in _PAGE_RE.findall(html)]
81 + if pages:
82 + last = max(last, max(pages))
83 + page += 1
84 + return links
85 +
86 + # -- fiche -----------------------------------------------------------------
87 + def _parse_fiche(self, url: str, html: str) -> dict | None:
88 + h1 = _H1_RE.search(html)
89 + title = clean_text(h1.group(1)) if h1 else ""
90 + if not title:
91 + return None
92 + date_m = _DATE_RE.search(html)
93 + start, end = parse_date_range_fr(date_m.group(1) if date_m else None)
94 + if not start:
95 + return None # fiche sans date sûre : inutilisable
96 + price_m = _PRICE_RE.search(html)
97 + time_m = _TIME_RE.search(html)
98 + region_m = _REGION_RE.search(html) or _REGION2_RE.search(html)
99 + lieu_m = _LIEU_RE.search(html)
100 + img_m = _OG_IMG_RE.search(html)
101 + maps_m = _MAPS_RE.search(html)
102 + summary_m = _SUMMARY_RE.search(html)
103 +
104 + venue, address, city = "", "", ""
105 + if lieu_m:
106 + venue = clean_text(lieu_m.group(1))
107 + address = clean_text(lieu_m.group(2) or "")
108 + # « 250 Boul. …, Lairet, La Cité-Limoilou, Québec, Capitale-
109 + # Nationale, G1L 5A7, Canada » → la ville précède la région adm.
110 + parts = [p.strip() for p in address.split(",")]
111 + if len(parts) >= 4:
112 + city = parts[-4]
113 +
114 + audience, category = "", ""
115 + for label, value in _META_ITEM_RE.findall(html):
116 + k, v = clean_text(label), clean_text(value)
117 + if k == "Auditoire" and v.lower() != "non spécifié":
118 + audience = v
119 + elif k == "Catégorie":
120 + category = v
121 +
122 + return {
123 + "title": title,
124 + "description": clean_text(summary_m.group(1)) if summary_m else "",
125 + "raw_categories": [c for c in (category, title) if c],
126 + "venue": venue, "address": address, "city": city,
127 + "tourist_region": clean_text(region_m.group(1)) if region_m else "",
128 + "lat": maps_m.group(1) if maps_m else None,
129 + "lng": maps_m.group(2) if maps_m else None,
130 + "start_date": start, "end_date": end,
131 + "start_time": parse_time(time_m.group(1)) if time_m else None,
132 + "price_label": clean_text(price_m.group(1)) if price_m else "",
133 + "audience": audience,
134 + "image": img_m.group(1) if img_m else "",
135 + }
136 +
137 + # -- pipeline ---------------------------------------------------------------
138 + def fetch(self) -> list[Event]:
139 + urls: list[str] = []
140 + for city in CITIES:
141 + try:
142 + urls += [u for u in self._city_links(city) if u not in urls]
143 + except Exception as exc: # section 502 : on ne bloque pas la source
144 + print(f"[sorti-ka] quoifaire : section '{city}' ignorée : {exc}")
145 +
146 + cache = self.load_cache()
147 + current = set(urls)
148 + cache = {u: c for u, c in cache.items() if u in current}
149 +
150 + now = time.time()
151 + candidates = sorted(
152 + (u for u in urls
153 + if u not in cache
154 + or now - cache[u].get("fetched_at", 0) > REFRESH_AFTER),
155 + key=lambda u: cache.get(u, {}).get("fetched_at", 0))[:DETAIL_MAX]
156 +
157 + def worker(url, session):
158 + return self._parse_fiche(url, session.get(url, timeout=20).text)
159 +
160 + for url, row in self.fetch_many(candidates, worker, max_workers=4).items():
161 + cache[url] = {"fetched_at": now, "row": row}
162 + self.save_cache(cache)
163 +
164 + events: list[Event] = []
165 + seen: set[str] = set()
166 + for url, entry in cache.items():
167 + row = entry.get("row")
168 + if not row:
169 + continue
170 + slug = url.rstrip("/").rsplit("/", 1)[-1]
171 + if slug in seen: # même fiche listée dans deux sections villes
172 + continue
173 + seen.add(slug)
174 + events.append(Event(
175 + source=self.source_id,
176 + external_id=slug,
177 + url=url, title=row["title"],
178 + description=row.get("description", ""),
179 + raw_categories=row.get("raw_categories") or [],
180 + audience=row.get("audience", ""),
181 + venue=row.get("venue", ""),
182 + address=row.get("address", ""),
183 + city=row.get("city", ""),
184 + tourist_region=row.get("tourist_region", ""),
185 + lat=row.get("lat"), lng=row.get("lng"),
186 + start_date=row.get("start_date"),
187 + start_time=row.get("start_time"),
188 + end_date=row.get("end_date"),
189 + price_label=row.get("price_label", ""),
190 + image=row.get("image", ""),
191 + ))
192 + return events
added sortika/connectors/rimouski.py +95 −0
@@ -0,0 +1,95 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/rimouski.py : connecteur Ville de Rimouski — événements officiels
5 +# Source : https://rimouski.ca/loisirs-culture/activites/evenements
6 +# Extraction: flux RSS ÉTENDU du calendrier (découvert sur la page) :
7 +# /rss/loisirs-culture/activites/evenements/ — ~240 items avec
8 +# balises maison <startDate> (ISO), <endDate>, <eventTime>
9 +# (« 13 h 30 »), <category>, <organizer>, <location>
10 +# (« Salle (adresse civique) »), <image>, <description>.
11 +# 1 seule requête. Vérifié 2026-08-25.
12 +# Accès : flux RSS public du site municipal, robots.txt permissif
13 +# (sitemap seulement). GET direct, identification honnête.
14 +# Prix : non publié par le flux → jamais inventé.
15 +# -----------------------------------------------------------------------------
16 +from __future__ import annotations
17 +
18 +import html as _html
19 +import re
20 +
21 +from ..normalize import clean_text, parse_date_iso, parse_time
22 +from ..schema import Event
23 +from .base import BaseConnector
24 +
25 +FEED = "https://rimouski.ca/rss/loisirs-culture/activites/evenements/"
26 +
27 +_ITEM_RE = re.compile(r"<item>(.*?)</item>", re.S)
28 +# « Bibliothèque Lisette-Morin (110, rue de l'Évêché Est) » → salle + adresse
29 +_LOC_RE = re.compile(r"^(.*?)\s*(?:\(([^)]+)\))?\s*$", re.S)
30 +
31 +
32 +def _tag(block: str, name: str) -> str:
33 + m = re.search(rf"<{name}>(.*?)</{name}>", block, re.S)
34 + return _html.unescape(m.group(1)).strip() if m else ""
35 +
36 +
37 +class RimouskiConnector(BaseConnector):
38 + source_id = "rimouski"
39 +
40 + def _parse_feed(self, xml: str) -> list[dict]:
41 + rows = []
42 + for block in _ITEM_RE.findall(xml):
43 + link = _tag(block, "link")
44 + title = clean_text(_tag(block, "title"))
45 + start = parse_date_iso(_tag(block, "startDate"))
46 + if not link or not title or not start:
47 + continue
48 + venue = address = ""
49 + m = _LOC_RE.match(_tag(block, "location"))
50 + if m:
51 + venue = clean_text(m.group(1))
52 + address = clean_text(m.group(2) or "")
53 + rows.append({
54 + "link": link,
55 + "title": title,
56 + "start": start,
57 + "end": parse_date_iso(_tag(block, "endDate")),
58 + "time": parse_time(_tag(block, "eventTime")),
59 + "description": clean_text(_tag(block, "description")),
60 + "category": clean_text(_tag(block, "category")),
61 + "organizer": clean_text(_tag(block, "organizer")),
62 + "venue": venue,
63 + "address": address,
64 + "image": _tag(block, "image"),
65 + })
66 + return rows
67 +
68 + def fetch(self) -> list[Event]:
69 + rows = self._parse_feed(self.get(FEED).text)
70 + events: list[Event] = []
71 + seen: set[str] = set()
72 + for r in rows:
73 + slug = r["link"].rstrip("/").rsplit("/", 1)[-1]
74 + ext = f"{slug}-{r['start']}"
75 + if ext in seen:
76 + continue
77 + seen.add(ext)
78 + events.append(Event(
79 + source=self.source_id,
80 + external_id=ext,
81 + url=r["link"],
82 + title=r["title"],
83 + description=r["description"],
84 + raw_categories=[r["category"]] if r["category"] else [r["title"]],
85 + venue=r["venue"],
86 + address=r["address"],
87 + city="Rimouski",
88 + region="Bas-Saint-Laurent",
89 + start_date=r["start"],
90 + start_time=r["time"],
91 + end_date=r["end"] or r["start"],
92 + organizer=r["organizer"],
93 + image=r["image"],
94 + ))
95 + return events
added sortika/connectors/rouynnoranda.py +182 −0
@@ -0,0 +1,182 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/rouynnoranda.py : connecteur Ville de Rouyn-Noranda — événements
5 +# Source : https://www.rouyn-noranda.ca/evenements (OctoberCMS, rendu
6 +# serveur — même famille que sorel-tracy/joliette).
7 +# Extraction: page liste /evenements (pagination /evenements/N → 404 à la
8 +# fin) → cartes « event-thumb » : titre, date FR SANS année
9 +# (« 26 août » — la source n'affiche que des événements à
10 +# venir : l'année est résolue à la prochaine occurrence,
11 +# aujourd'hui ou plus tard, jamais au passé), catégorie, image,
12 +# url fiche. Enrichissement par fiche /evenement/<slug>
13 +# (heures « de 19 h 30 à 22 h 30 », lieu, description) —
14 +# CACHE INCRÉMENTAL disque, plafond RN_FICHES par sync.
15 +# Accès : site municipal public, GET directs throttlés, identification
16 +# honnête ; robots.txt permissif.
17 +# Prix : non publié par la source → jamais inventé.
18 +# -----------------------------------------------------------------------------
19 +from __future__ import annotations
20 +
21 +import html as _html
22 +import os
23 +import re
24 +from datetime import date
25 +
26 +from ..normalize import _FR_MONTHS, clean_text, parse_time # type: ignore
27 +from ..schema import Event
28 +from .base import BaseConnector
29 +
30 +BASE = "https://www.rouyn-noranda.ca"
31 +LIST_URL = BASE + "/evenements"
32 +PAGES_MAX = int(os.environ.get("RN_PAGES", "8"))
33 +FICHES_MAX = int(os.environ.get("RN_FICHES", "40"))
34 +
35 +_CARD_RE = re.compile(
36 + r'<a href="(https://www\.rouyn-noranda\.ca/evenement/[^"]+)"[^>]*class="events-page__list-item event-thumb[^"]*"[^>]*>(.*?)</a>',
37 + re.S)
38 +_TAG_RE = re.compile(r'taglist__tag(?:\s+taglist__tag--inverse)?">(.*?)</li>',
39 + re.S)
40 +_TITLE_RE = re.compile(r'event-thumb__title">\s*(.*?)\s*</span>', re.S)
41 +_IMG_RE = re.compile(r'data-src="([^"]+)"')
42 +_PARTIAL_RE = re.compile(r"(\d{1,2})(?:er)?\s+([a-zû]+)", re.I)
43 +
44 +
45 +def _resolve(txt: str, today: "date") -> tuple[str | None, str | None]:
46 + """« 26 août » / « du 26 août au 2 septembre » → plage ISO. La source
47 + n'affiche que l'à-venir : chaque jour/mois est daté de sa PROCHAINE
48 + occurrence (dérivation déterministe, pas d'invention d'année)."""
49 + from ..normalize import strip_accents
50 + dates: list[str] = []
51 + for m in _PARTIAL_RE.finditer(strip_accents(txt).lower()):
52 + d, mo = int(m.group(1)), _FR_MONTHS.get(m.group(2))
53 + if not mo or not (1 <= d <= 31):
54 + continue
55 + y = today.year
56 + if (mo, d) < (today.month, today.day):
57 + y += 1
58 + try:
59 + dates.append(date(y, mo, d).isoformat())
60 + except ValueError:
61 + continue
62 + if not dates:
63 + return None, None
64 + return min(dates), max(dates)
65 +
66 +
67 +class RouynNorandaConnector(BaseConnector):
68 + source_id = "rouynnoranda"
69 + request_delay = 0.8
70 +
71 + def _parse_page(self, html: str, today: "date | None" = None) -> list[dict]:
72 + today = today or date.today()
73 + rows: list[dict] = []
74 + for url, block in _CARD_RE.findall(html):
75 + t = _TITLE_RE.search(block)
76 + if not t:
77 + continue
78 + tags = [clean_text(_html.unescape(re.sub(r"<[^>]+>", " ", x)))
79 + for x in _TAG_RE.findall(block)]
80 + start = end = None
81 + category = ""
82 + for tag in tags:
83 + if start is None:
84 + start, end = _resolve(tag, today)
85 + if start:
86 + continue
87 + if not category and not any(c.isdigit() for c in tag):
88 + category = tag
89 + if not start:
90 + continue
91 + img = _IMG_RE.search(block)
92 + image = img.group(1) if img else ""
93 + if image.startswith("/"):
94 + image = BASE + image
95 + rows.append({
96 + "url": url,
97 + "title": clean_text(_html.unescape(
98 + re.sub(r"<[^>]+>", " ", t.group(1)))),
99 + "start": start,
100 + "end": end or start,
101 + "category": category,
102 + "image": image,
103 + })
104 + return rows
105 +
106 + def _fiche(self, url: str) -> dict:
107 + """Heures et lieu depuis l'entête de la fiche (blocs event-info)."""
108 + html = self.get(url).text
109 + out: dict = {}
110 + for m in re.finditer(
111 + r'__event-info-text">([^<]+)<|__event-info-link[^"]*"[^>]*>([^<]+)</a>',
112 + html):
113 + txt = clean_text(_html.unescape(m.group(1) or m.group(2) or ""))
114 + if not txt:
115 + continue
116 + low = txt.lower()
117 + if "hours" not in out and re.search(r"\d{1,2}\s*h(\s|$|\d)", low):
118 + out["hours"] = txt
119 + elif "venue" not in out and m.group(2):
120 + out["venue"] = txt
121 + elif "venue" not in out and not _PARTIAL_RE.search(low):
122 + out["venue"] = txt
123 + return out
124 +
125 + def fetch(self) -> list[Event]:
126 + rows: list[dict] = []
127 + for page in range(1, PAGES_MAX + 1):
128 + url = LIST_URL if page == 1 else f"{LIST_URL}/{page}"
129 + try:
130 + batch = self._parse_page(self.get(url).text)
131 + except Exception:
132 + if page > 1: # fin de pagination (404)
133 + break
134 + raise
135 + if not batch:
136 + break
137 + rows += batch
138 + cache = self.load_cache()
139 + fetched = 0
140 + for r in rows:
141 + slug = r["url"].rstrip("/").rsplit("/", 1)[-1]
142 + r["slug"] = slug
143 + if slug not in cache and fetched < FICHES_MAX:
144 + try:
145 + cache[slug] = self._fiche(r["url"])
146 + fetched += 1
147 + except Exception as exc: # fiche cassée ≠ source cassée
148 + print(f"[sorti-ka] rouynnoranda : fiche {slug} "
149 + f"ignorée : {exc}")
150 + if fetched:
151 + self.save_cache(cache)
152 + events: list[Event] = []
153 + seen: set[str] = set()
154 + for r in rows:
155 + ext = f"{r['slug']}-{r['start']}"
156 + if ext in seen:
157 + continue
158 + seen.add(ext)
159 + extra = cache.get(r["slug"]) or {}
160 + hours = extra.get("hours", "")
161 + start_time = parse_time(hours)
162 + end_time = None
163 + m = re.search(r"\bà\s+(\d{1,2}\s*h(?:\s*\d{2})?)", hours)
164 + if m:
165 + end_time = parse_time(m.group(1))
166 + events.append(Event(
167 + source=self.source_id,
168 + external_id=ext,
169 + url=r["url"],
170 + title=r["title"],
171 + raw_categories=[r["category"]] if r["category"] else [r["title"]],
172 + venue=extra.get("venue", ""),
173 + city="Rouyn-Noranda",
174 + region="Abitibi-Témiscamingue",
175 + start_date=r["start"],
176 + start_time=start_time,
177 + end_date=r["end"],
178 + end_time=end_time,
179 + organizer="Ville de Rouyn-Noranda",
180 + image=r["image"],
181 + ))
182 + return events
added sortika/connectors/sainthyacinthe.py +144 −0
@@ -0,0 +1,144 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/sainthyacinthe.py : connecteur Ville de Saint-Hyacinthe —
5 +# calendrier des événements (généré par les organismes accrédités).
6 +# Source : https://www.st-hyacinthe.ca/loisirs-et-culture/evenement/
7 +# calendrier-des-evenements (CMS municipal maison, rendu
8 +# serveur Bootstrap).
9 +# Extraction: GET ?ms=2 (« Tous » les mois, découvert dans le formulaire de
10 +# recherche) → toutes les cartes en 1 requête : id + titre
11 +# (fShowInfo), plage de dates FR, image, organisateur,
12 +# catégorie. Enrichissement par fiche modale
13 +# /php/load-modal-info.php?evenement=<id> (lieu, clientèle,
14 +# gratuit/payant, site web) — CACHE INCRÉMENTAL disque : seules
15 +# les fiches jamais vues sont requêtées (plafond STH_FICHES).
16 +# Accès : site municipal public, GET directs throttlés, identification
17 +# honnête ; robots.txt ne restreint pas ces chemins.
18 +# Prix : la fiche publie « Gratuit »/« Payant » → is_free fidèle,
19 +# jamais de montant inventé.
20 +# -----------------------------------------------------------------------------
21 +from __future__ import annotations
22 +
23 +import html as _html
24 +import os
25 +import re
26 +
27 +from ..normalize import clean_text, parse_date_range_fr
28 +from ..schema import Event
29 +from .base import BaseConnector
30 +
31 +BASE = "https://www.st-hyacinthe.ca"
32 +LIST_URL = BASE + "/loisirs-et-culture/evenement/calendrier-des-evenements"
33 +MODAL_URL = BASE + "/php/load-modal-info.php?evenement={id}"
34 +FICHES_MAX = int(os.environ.get("STH_FICHES", "60"))
35 +
36 +_CARD_RE = re.compile(r'<div class="card h-100">(.*?)</div>\s*</div>\s*</div>',
37 + re.S)
38 +_INFO_RE = re.compile(r"fShowInfo\('(\d+)','(.*?)'\)")
39 +_DATES_RE = re.compile(r"<strong>(.*?)</strong>", re.S)
40 +_IMG_RE = re.compile(r'background:url\(([^)]+)\)')
41 +_ORG_RE = re.compile(r"Organisateur de l'événement\"></i>\s*([^<]+)</p>")
42 +_CAT_RE = re.compile(r"Type\(s\) de l'événement\"></i>\s*([^<]+)</p>")
43 +# fiche modale
44 +_M_VENUE_RE = re.compile(r"Lieu de l'événement\"></i>\s*([^<]+)</p>")
45 +_M_AUD_RE = re.compile(r"Clientèle\(s\) de l'événement\"></i>\s*([^<]+)</p>")
46 +_M_PRICE_RE = re.compile(
47 + r'data-content="Cette activité (?:est gratuite|n\'est pas gratuite)"></i>\s*([^<]+)</p>')
48 +_M_SITE_RE = re.compile(r'<a href="([^"]+)"[^>]*>Consulter le site internet')
49 +_M_HOURS_RE = re.compile(r'fa-clock[^>]*"></i>\s*([^<]+)</p>')
50 +
51 +
52 +class SaintHyacintheConnector(BaseConnector):
53 + source_id = "sainthyacinthe"
54 + request_delay = 0.8
55 +
56 + def _parse_list(self, html: str) -> list[dict]:
57 + rows: list[dict] = []
58 + for block in _CARD_RE.findall(html):
59 + m = _INFO_RE.search(block)
60 + d = _DATES_RE.search(block)
61 + if not m or not d:
62 + continue
63 + start, end = parse_date_range_fr(clean_text(d.group(1)))
64 + if not start and not end:
65 + continue
66 + img = _IMG_RE.search(block)
67 + org = _ORG_RE.search(block)
68 + cat = _CAT_RE.search(block)
69 + rows.append({
70 + "id": m.group(1),
71 + "title": clean_text(_html.unescape(m.group(2))),
72 + "start": start or end,
73 + "end": end or start,
74 + "image": (BASE + img.group(1)) if img
75 + and img.group(1).startswith("/") else
76 + (img.group(1) if img else ""),
77 + "organizer": clean_text(org.group(1)) if org else "",
78 + "category": clean_text(cat.group(1)) if cat else "",
79 + })
80 + return rows
81 +
82 + def _parse_modal(self, html: str) -> dict:
83 + out: dict = {}
84 + m = _M_VENUE_RE.search(html)
85 + if m:
86 + out["venue"] = clean_text(m.group(1))
87 + m = _M_AUD_RE.search(html)
88 + if m:
89 + out["audience"] = clean_text(m.group(1))
90 + m = _M_PRICE_RE.search(html)
91 + if m:
92 + out["price_label"] = clean_text(m.group(1)) # Gratuit | Payant
93 + m = _M_SITE_RE.search(html)
94 + if m:
95 + out["website"] = m.group(1).strip()
96 + m = _M_HOURS_RE.search(html)
97 + if m:
98 + out["hours"] = clean_text(m.group(1))
99 + return out
100 +
101 + def fetch(self) -> list[Event]:
102 + rows = self._parse_list(self.get(LIST_URL, params={"ms": "2"}).text)
103 + cache = self.load_cache()
104 + fetched = 0
105 + for r in rows:
106 + if r["id"] not in cache and fetched < FICHES_MAX:
107 + try:
108 + cache[r["id"]] = self._parse_modal(
109 + self.get(MODAL_URL.format(id=r["id"])).text)
110 + fetched += 1
111 + except Exception as exc: # fiche cassée ≠ source cassée
112 + print(f"[sorti-ka] sainthyacinthe : fiche {r['id']} "
113 + f"ignorée : {exc}")
114 + if fetched:
115 + self.save_cache(cache)
116 + events: list[Event] = []
117 + seen: set[str] = set()
118 + for r in rows:
119 + ext = f"{r['id']}-{r['start']}"
120 + if ext in seen:
121 + continue
122 + seen.add(ext)
123 + extra = cache.get(r["id"]) or {}
124 + label = extra.get("price_label", "")
125 + events.append(Event(
126 + source=self.source_id,
127 + external_id=ext,
128 + url=LIST_URL + "?ms=2", # pas de fiche pérenne (modale)
129 + title=r["title"],
130 + raw_categories=[r["category"]] if r["category"] else [r["title"]],
131 + audience=extra.get("audience", ""),
132 + venue=extra.get("venue", ""),
133 + city="Saint-Hyacinthe",
134 + region="Montérégie",
135 + start_date=r["start"],
136 + end_date=r["end"],
137 + is_free=(True if label == "Gratuit"
138 + else False if label == "Payant" else None),
139 + price_label=label,
140 + organizer=r["organizer"],
141 + website=extra.get("website", ""),
142 + image=r["image"],
143 + ))
144 + return events
added sortika/connectors/saintjerome.py +123 −0
@@ -0,0 +1,123 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/saintjerome.py : connecteur Ville de Saint-Jérôme — calendrier
5 +# Source : https://www.vsj.ca/calendrier/ (WordPress + FacetWP).
6 +# Extraction: le listing FacetWP se pagine CÔTÉ SERVEUR par paramètres
7 +# d'URL (vérifié 2026-08-25) : GET /calendrier/?fwp_calendar_
8 +# dates=<aujourd'hui>&fwp_paged=N → cartes HTML rendues
9 +# (10/page) : date FR, heure « 19 h », lieu, titre, url fiche,
10 +# extrait, image. On pagine jusqu'à épuisement (plafond
11 +# VSJ_PAGES). Le flux RSS /calendrier/feed/ est périmé (2019)
12 +# et l'API REST wp/v2/event ne publie pas les dates.
13 +# Accès : site municipal public, robots.txt permissif (wp-admin
14 +# seulement). GET directs throttlés, identification honnête.
15 +# Prix : non publié dans les cartes → jamais inventé.
16 +# -----------------------------------------------------------------------------
17 +from __future__ import annotations
18 +
19 +import html as _html
20 +import os
21 +import re
22 +from datetime import date
23 +
24 +from ..normalize import clean_text, parse_date_fr, parse_time
25 +from ..schema import Event
26 +from .base import BaseConnector
27 +
28 +LIST_URL = "https://www.vsj.ca/calendrier/"
29 +PAGES_MAX = int(os.environ.get("VSJ_PAGES", "12"))
30 +
31 +# la classe exacte est « event__single ... » — ne pas confondre avec les
32 +# sous-blocs event__single--image / --content
33 +_CARD_RE = re.compile(
34 + r'<div[^>]*class="event__single(?:\s[^"]*)?"[^>]*>(.*?)(?=<div[^>]*class="event__single(?:\s[^"]*)?"|<!-- \.facetwp-template|$)',
35 + re.S)
36 +_DATE_RE = re.compile(r'class="event__date">([^<]+)</h4>')
37 +_TITLE_RE = re.compile(
38 + r'class="event__title">\s*<a href="([^"]+)">([^<]+)</a>', re.S)
39 +_TIME_RE = re.compile(r'class="event__time">(?:<i[^>]*></i>)?([^<]+)</div>')
40 +_PLACE_RE = re.compile(r'class="event__place">(?:<i[^>]*></i>)?([^<]+)</span>')
41 +_DESC_RE = re.compile(r'class="event__desc">\s*<p>(.*?)</p>', re.S)
42 +# rendu FacetWP côté serveur : <img src='...'> (pas de lazy-load)
43 +_IMG_RE = re.compile(
44 + r"<img[^>]+src=['\"](https?://[^'\"]*wp-content/uploads/[^'\"]+)['\"]|"
45 + r"data-lazy-src=\"([^\"]+)\"")
46 +
47 +
48 +class SaintJeromeConnector(BaseConnector):
49 + source_id = "saintjerome"
50 + request_delay = 0.8
51 +
52 + def _parse_page(self, html: str) -> list[dict]:
53 + rows: list[dict] = []
54 + for block in _CARD_RE.findall(html):
55 + t = _TITLE_RE.search(block)
56 + d = _DATE_RE.search(block)
57 + if not t or not d:
58 + continue
59 + start = parse_date_fr(d.group(1))
60 + if not start:
61 + continue
62 + tm = _TIME_RE.search(block)
63 + start_time = parse_time(tm.group(1)) if tm else None
64 + pl = _PLACE_RE.search(block)
65 + venue = address = ""
66 + if pl:
67 + # « Hôtel de ville (300, rue Parent) » → lieu + adresse
68 + m = re.match(r"^(.*?)\s*(?:\(([^)]+)\))?\s*$",
69 + clean_text(_html.unescape(pl.group(1))))
70 + venue, address = m.group(1), m.group(2) or ""
71 + desc = _DESC_RE.search(block)
72 + img = _IMG_RE.search(block)
73 + image = (img.group(1) or img.group(2)) if img else ""
74 + rows.append({
75 + "url": t.group(1),
76 + "title": clean_text(_html.unescape(t.group(2))),
77 + "start": start,
78 + "start_time": start_time,
79 + "venue": venue,
80 + "address": address,
81 + "description": clean_text(re.sub(
82 + r"<[^>]+>", " ", _html.unescape(desc.group(1)))) if desc else "",
83 + "image": image,
84 + })
85 + return rows
86 +
87 + def fetch(self) -> list[Event]:
88 + rows: list[dict] = []
89 + today = date.today().isoformat()
90 + for page in range(1, PAGES_MAX + 1):
91 + params = {"fwp_calendar_dates": today}
92 + if page > 1:
93 + params["fwp_paged"] = str(page)
94 + batch = self._parse_page(self.get(LIST_URL, params=params).text)
95 + rows += batch
96 + if len(batch) < 10:
97 + break
98 + events: list[Event] = []
99 + seen: set[str] = set()
100 + for r in rows:
101 + slug = r["url"].rstrip("/").rsplit("/", 1)[-1]
102 + ext = f"{slug}-{r['start']}"
103 + if ext in seen:
104 + continue
105 + seen.add(ext)
106 + events.append(Event(
107 + source=self.source_id,
108 + external_id=ext,
109 + url=r["url"],
110 + title=r["title"],
111 + description=r["description"],
112 + raw_categories=[r["title"]],
113 + venue=r["venue"],
114 + address=r["address"],
115 + city="Saint-Jérôme",
116 + region="Laurentides",
117 + start_date=r["start"],
118 + start_time=r["start_time"],
119 + end_date=r["start"],
120 + organizer="Ville de Saint-Jérôme",
121 + image=r["image"],
122 + ))
123 + return events
added sortika/connectors/shawinigan.py +153 −0
@@ -0,0 +1,153 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/shawinigan.py : connecteur Ville de Shawinigan — événements
5 +# Source : https://www.shawinigan.ca/loisirs-et-culture/evenements/
6 +# calendrier/ (WordPress + plugin maison « tri-evenements »).
7 +# Extraction: archive rendue serveur (cartes « tri-evenement-single » :
8 +# date FR, titre, extrait, image, url fiche datée
9 +# /evenements/AAAA/MM/JJ/<slug>/), pagination /calendrier/
10 +# page/N. Enrichissement par fiche (date de fin « Date de
11 +# fin : ... », lieu « Lieu : ... », heure si publiée) — CACHE
12 +# INCRÉMENTAL disque, plafond SHA_FICHES par sync.
13 +# Accès : site municipal public, robots.txt permissif (wp-admin
14 +# seulement). GET directs throttlés, identification honnête.
15 +# Prix : non publié par la source → jamais inventé.
16 +# -----------------------------------------------------------------------------
17 +from __future__ import annotations
18 +
19 +import html as _html
20 +import os
21 +import re
22 +
23 +from ..normalize import clean_text, parse_date_fr, parse_time
24 +from ..schema import Event
25 +from .base import BaseConnector
26 +
27 +LIST_URL = "https://www.shawinigan.ca/loisirs-et-culture/evenements/calendrier/"
28 +PAGES_MAX = int(os.environ.get("SHA_PAGES", "6"))
29 +FICHES_MAX = int(os.environ.get("SHA_FICHES", "40"))
30 +
31 +# cartes : « -single » (prochain événement) et « -list » (liste « À venir »)
32 +_CARD_RE = re.compile(
33 + r'<article[^>]*class="tri-evenement-evenement tri-evenement-(?:single|list)"[^>]*>(.*?)</article>',
34 + re.S)
35 +_TITLE_RE = re.compile(
36 + r'tri-evenement-titre"><a href="([^"]+)"[^>]*>(.*?)</a>', re.S)
37 +_DATE_RE = re.compile(r'tri-evenement-date">.*?</i>\s*(.*?)\s*</p>', re.S)
38 +_DESC_RE = re.compile(r'tri-evenement-desc">(.*?)</p>', re.S)
39 +_IMG_RE = re.compile(r'<img[^>]+src="([^"]+)"')
40 +_URL_DATE_RE = re.compile(r"/evenements/(\d{4})/(\d{2})/(\d{2})/")
41 +# fiche
42 +_F_FIN_RE = re.compile(r"Date de fin\s*:\s*(.*?)\s*</p>", re.S)
43 +_F_LIEU_RE = re.compile(r"Lieu\s*:\s*(.*?)\s*</p>", re.S)
44 +_F_HEURE_RE = re.compile(r"Heure\s*:\s*(.*?)\s*</p>", re.S)
45 +
46 +
47 +class ShawiniganConnector(BaseConnector):
48 + source_id = "shawinigan"
49 + request_delay = 0.8
50 +
51 + def _parse_page(self, html: str) -> list[dict]:
52 + rows: list[dict] = []
53 + for block in _CARD_RE.findall(html):
54 + t = _TITLE_RE.search(block)
55 + if not t:
56 + continue
57 + url = t.group(1)
58 + start = None
59 + m = _URL_DATE_RE.search(url) # URL datée = source fiable
60 + if m:
61 + start = f"{m.group(1)}-{m.group(2)}-{m.group(3)}"
62 + else:
63 + d = _DATE_RE.search(block)
64 + start = parse_date_fr(clean_text(d.group(1))) if d else None
65 + if not start:
66 + continue
67 + desc = _DESC_RE.search(block)
68 + img = _IMG_RE.search(block)
69 + rows.append({
70 + "url": url,
71 + "title": clean_text(_html.unescape(
72 + re.sub(r"<[^>]+>", " ", t.group(2)))),
73 + "start": start,
74 + "description": clean_text(_html.unescape(re.sub(
75 + r"<[^>]+>", " ", desc.group(1)))) if desc else "",
76 + "image": img.group(1) if img else "",
77 + })
78 + return rows
79 +
80 + def _fiche(self, url: str) -> dict:
81 + html = self.get(url).text
82 + out: dict = {}
83 + m = _F_FIN_RE.search(html)
84 + if m:
85 + end = parse_date_fr(clean_text(re.sub(r"<[^>]+>", " ", m.group(1))))
86 + if end:
87 + out["end"] = end
88 + m = _F_LIEU_RE.search(html)
89 + if m:
90 + out["venue"] = clean_text(_html.unescape(
91 + re.sub(r"<[^>]+>", " ", m.group(1))))
92 + m = _F_HEURE_RE.search(html)
93 + if m:
94 + t = parse_time(clean_text(re.sub(r"<[^>]+>", " ", m.group(1))))
95 + if t:
96 + out["start_time"] = t
97 + return out
98 +
99 + def fetch(self) -> list[Event]:
100 + rows: list[dict] = []
101 + seen_urls: set[str] = set()
102 + for page in range(1, PAGES_MAX + 1):
103 + url = LIST_URL if page == 1 else f"{LIST_URL}page/{page}/"
104 + try:
105 + batch = self._parse_page(self.get(url).text)
106 + except Exception:
107 + if page > 1: # fin de pagination (404)
108 + break
109 + raise
110 + new = [r for r in batch if r["url"] not in seen_urls]
111 + if not new:
112 + break
113 + seen_urls.update(r["url"] for r in new)
114 + rows += new
115 + cache = self.load_cache()
116 + fetched = 0
117 + for r in rows:
118 + # « 2026/09/05/<slug> » : la date de l'URL fait partie de la clé
119 + key = "/".join(r["url"].rstrip("/").split("/")[-4:])
120 + r["key"] = key
121 + if key not in cache and fetched < FICHES_MAX:
122 + try:
123 + cache[key] = self._fiche(r["url"])
124 + fetched += 1
125 + except Exception as exc: # fiche cassée ≠ source cassée
126 + print(f"[sorti-ka] shawinigan : fiche {key} ignorée : {exc}")
127 + if fetched:
128 + self.save_cache(cache)
129 + events: list[Event] = []
130 + seen: set[str] = set()
131 + for r in rows:
132 + ext = r["key"].replace("/", "-")
133 + if ext in seen:
134 + continue
135 + seen.add(ext)
136 + extra = cache.get(r["key"]) or {}
137 + events.append(Event(
138 + source=self.source_id,
139 + external_id=ext,
140 + url=r["url"],
141 + title=r["title"],
142 + description=r["description"],
143 + raw_categories=[r["title"]],
144 + venue=extra.get("venue", ""),
145 + city="Shawinigan",
146 + region="Mauricie",
147 + start_date=r["start"],
148 + start_time=extra.get("start_time"),
149 + end_date=extra.get("end") or r["start"],
150 + organizer="Ville de Shawinigan",
151 + image=r["image"],
152 + ))
153 + return events
added sortika/connectors/showpass.py +151 −0
@@ -0,0 +1,151 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/showpass.py : connecteur Showpass (showpass.com) — billetterie
5 +# Source : billetterie canadienne (surtout AB/C.-B., mais des dizaines
6 +# d'événements QC : soirées, visites, festivals, Candlelight…).
7 +# Extraction: API JSON publique /api/public/events/ (paginée), filtrée par
8 +# points géographiques QC via location__point_location=
9 +# LAT,LNG,RAYON + starts_on__gte=aujourd'hui. Chaque événement
10 +# porte location.province → on ne garde QUE le Québec (le rayon
11 +# autour de Gatineau attrape Ottawa, vérifié live 2026-08-25).
12 +# Accès : robots.txt « Allow: /api/ » (API publique explicitement
13 +# autorisée) ; GET throttlés 0,8 s, identification honnête.
14 +# Prix : ticket_types[].price publiés par la billetterie → price_min
15 +# (plus bas tarif payant), is_free=True si TOUS les tarifs à 0 $.
16 +# Heure : local_starts_on (fuseau de l'événement) prioritaire, sinon
17 +# starts_on UTC converti heure de Montréal (utc_to_local).
18 +# -----------------------------------------------------------------------------
19 +from __future__ import annotations
20 +
21 +import os
22 +
23 +from ..normalize import _key, clean_text, parse_time, utc_to_local
24 +from ..schema import Event
25 +from .base import BaseConnector
26 +
27 +BASE = "https://www.showpass.com"
28 +API = f"{BASE}/api/public/events/"
29 +
30 +# Points de couverture du Québec habité (lat, lng, rayon km) — l'API ne filtre
31 +# que par point+rayon, pas par province : on couvre le territoire par cercles
32 +# puis on filtre location.province côté connecteur (jamais d'événement ON/É.-U.).
33 +POINTS: list[tuple[float, float, int]] = [
34 + (45.5019, -73.5674, 100), # Grand Montréal + Montérégie + Laurentides sud
35 + (46.8131, -71.2075, 120), # Québec + Chaudière-Appalaches + Charlevoix
36 + (45.4765, -75.7013, 60), # Gatineau / Outaouais (Ottawa filtrée ensuite)
37 + (45.4042, -71.8929, 80), # Sherbrooke / Estrie
38 + (46.3432, -72.5430, 80), # Trois-Rivières / Mauricie + Centre-du-Québec
39 + (48.4280, -71.0686, 120), # Saguenay–Lac-Saint-Jean
40 + (48.4488, -68.5236, 150), # Bas-Saint-Laurent
41 + (48.2359, -79.0289, 120), # Abitibi-Témiscamingue
42 + (48.8354, -64.4816, 150), # Gaspésie
43 + (50.2033, -66.3816, 150), # Côte-Nord
44 +]
45 +
46 +PAGE_SIZE = int(os.environ.get("SHOWPASS_PAGE_SIZE", "100"))
47 +MAX_PER_POINT = int(os.environ.get("SHOWPASS_MAX_PER_POINT", "2000"))
48 +
49 +
50 +class ShowpassConnector(BaseConnector):
51 + source_id = "showpass"
52 + request_delay = 0.8
53 +
54 + # -- API ----------------------------------------------------------------
55 + def _point_events(self, lat: float, lng: float, radius: int,
56 + today: str) -> list[dict]:
57 + """Tous les événements à venir autour d'un point (pagination `next`)."""
58 + out: list[dict] = []
59 + url: str | None = API
60 + params: dict | None = {
61 + "location__point_location": f"{lat},{lng},{radius}",
62 + "starts_on__gte": today, "page_size": PAGE_SIZE,
63 + }
64 + while url and len(out) < MAX_PER_POINT:
65 + data = self.get_json(url, params=params)
66 + out += data.get("results") or []
67 + url, params = data.get("next"), None # `next` porte déjà tout
68 + return out
69 +
70 + # -- normalisation --------------------------------------------------------
71 + def _to_event(self, e: dict) -> Event | None:
72 + loc = e.get("location") or {}
73 + if _key(str(loc.get("province") or "")) not in ("quebec", "qc"):
74 + return None # hors Québec : hors périmètre
75 + venue_obj = e.get("venue") or {}
76 +
77 + # heure locale : local_starts_on porte le fuseau de l'événement
78 + start_date, start_time = None, None
79 + local = e.get("local_starts_on") or ""
80 + if local:
81 + start_date, start_time = local[:10], parse_time(local)
82 + else:
83 + start_date, start_time = utc_to_local(e.get("starts_on"))
84 + end_date, end_time = utc_to_local(e.get("ends_on"))
85 +
86 + # tarifs réels publiés par la billetterie (jamais inventés)
87 + prices, labels = [], []
88 + for t in e.get("ticket_types") or []:
89 + try:
90 + p = float(t.get("price"))
91 + except (TypeError, ValueError):
92 + continue
93 + prices.append(p)
94 + labels.append(f"{clean_text(t.get('name') or '')} {p:g} $")
95 + is_free, price_min, price_label = None, None, ""
96 + if prices:
97 + price_label = " · ".join(labels[:5]) + (" · …" if len(labels) > 5 else "")
98 + if max(prices) == 0:
99 + is_free, price_min = True, None
100 + else:
101 + is_free = False
102 + price_min = min(p for p in prices if p > 0)
103 +
104 + lat = lng = None
105 + pos = str(loc.get("position") or "")
106 + if "," in pos:
107 + lat, lng = pos.split(",", 1)
108 +
109 + return Event(
110 + source=self.source_id,
111 + external_id=str(e.get("id")),
112 + url=e.get("frontend_details_url")
113 + or f"{BASE}/{e.get('slug', '')}/",
114 + title=e.get("name") or "",
115 + description=clean_text(e.get("description_without_html")
116 + or "")[:2000],
117 + raw_categories=[e.get("name") or ""],
118 + venue=clean_text(loc.get("name") or venue_obj.get("name") or ""),
119 + address=clean_text(loc.get("street_name") or ""),
120 + city=clean_text(loc.get("city") or ""),
121 + postal_code=loc.get("postal_code") or "",
122 + lat=lat, lng=lng,
123 + start_date=start_date, start_time=start_time,
124 + end_date=end_date, end_time=end_time,
125 + status="soldout" if (e.get("sold_out")
126 + or e.get("inventory_sold_out")) else "",
127 + is_free=is_free, price_min=price_min, price_label=price_label,
128 + organizer=clean_text(venue_obj.get("name") or ""),
129 + website=venue_obj.get("web_address") or "",
130 + image=e.get("image") or e.get("image_banner") or "",
131 + )
132 +
133 + # -- pipeline -------------------------------------------------------------
134 + def fetch(self) -> list[Event]:
135 + from datetime import date
136 + today = date.today().isoformat()
137 + events: list[Event] = []
138 + seen: set[str] = set()
139 + for lat, lng, radius in POINTS:
140 + try:
141 + batch = self._point_events(lat, lng, radius, today)
142 + except Exception as exc: # un point cassé ne bloque pas la source
143 + print(f"[sorti-ka] showpass : point ({lat},{lng}) ignoré : {exc}")
144 + continue
145 + for raw in batch:
146 + ev = self._to_event(raw)
147 + if ev is None or ev.external_id in seen:
148 + continue # hors QC, ou déjà vu (cercles se chevauchent)
149 + seen.add(ev.external_id)
150 + events.append(ev)
151 + return events
added sortika/connectors/signelaval.py +172 −0
@@ -0,0 +1,172 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/signelaval.py : connecteur Signé Laval (signelaval.com)
5 +# Source : calendrier culturel officiel de Laval (Culture Laval) —
6 +# bibliothèques, spectacles, expositions, plein air (~400 fiches).
7 +# Extraction: sitemap public /sitemap/events/N.xml puis fiches rendues
8 +# serveur : JSON-LD Event complet (CMS Footlight / artsdata.ca)
9 +# avec dates ISO horodatées (fuseau -04:00), lieu, adresse,
10 +# ville, image IIIF, organisateur. Certains champs Footlight
11 +# sont des LISTES multilingues (["…", {"@language": "fr",
12 +# "@value": "…"}]) → _val privilégie la variante française
13 +# (bug corrigé à la validation live 2026-08-25).
14 +# GET directs, pas d'anti-bot.
15 +# INCRÉMENTAL avec cache disque (data/signelaval_cache.json) :
16 +# nouvelles fiches d'abord, re-visite roulante après 7 jours,
17 +# plafonné à SIGNELAVAL_MAX_FETCH fiches/sync.
18 +# Accès : robots.txt « User-agent: * / Disallow: (vide) » + sitemap
19 +# public → scraping permis, identification honnête.
20 +# Prix : offers = AggregateOffer sans montant sur les fiches vérifiées
21 +# → is_free inconnu (jamais inventé).
22 +# -----------------------------------------------------------------------------
23 +from __future__ import annotations
24 +
25 +import json
26 +import re
27 +import time
28 +
29 +from ..normalize import clean_text, parse_time
30 +from ..schema import Event
31 +from .base import BaseConnector
32 +
33 +BASE = "https://signelaval.com"
34 +SITEMAP_INDEX = f"{BASE}/sitemap.xml"
35 +
36 +MAX_FETCH = int(__import__("os").environ.get("SIGNELAVAL_MAX_FETCH", "200"))
37 +REFRESH_AFTER = 7 * 86400 # re-visite roulante d'une fiche (secondes)
38 +
39 +_LOC_RE = re.compile(r"<loc>([^<]+)</loc>")
40 +_LD_RE = re.compile(r'<script type="application/ld\+json"[^>]*>(.*?)</script>',
41 + re.S)
42 +
43 +
44 +def _val(x) -> str:
45 + """Valeur d'un champ JSON-LD Footlight : littéral, {"@value": …} ou LISTE
46 + multilingue (ex. ["Rythme latin", {"@language": "fr", "@value": "…"}]) —
47 + on privilégie la variante française, sinon la première non vide."""
48 + if isinstance(x, list):
49 + fallback = ""
50 + for item in x:
51 + if (isinstance(item, dict)
52 + and str(item.get("@language", "")).startswith("fr")):
53 + v = _val(item)
54 + if v:
55 + return v
56 + fallback = fallback or _val(item)
57 + return fallback
58 + if isinstance(x, dict):
59 + return str(x.get("@value") or "")
60 + return str(x or "")
61 +
62 +
63 +class SigneLavalConnector(BaseConnector):
64 + source_id = "signelaval"
65 + request_delay = 0.8
66 +
67 + # -- sitemap ----------------------------------------------------------
68 + def _fiches(self) -> list[str]:
69 + idx = self.get(SITEMAP_INDEX).text
70 + shards = [u for u in _LOC_RE.findall(idx) if "/sitemap/events/" in u]
71 + urls: list[str] = []
72 + for shard in shards:
73 + urls += [u for u in _LOC_RE.findall(self.get(shard).text)
74 + if "/evenements/" in u]
75 + return urls
76 +
77 + # -- fiche ------------------------------------------------------------
78 + def _parse_fiche(self, url: str, html: str) -> dict | None:
79 + """JSON-LD Event (Footlight) → ligne normalisable, ou None."""
80 + data = None
81 + for blob in _LD_RE.findall(html):
82 + try:
83 + d = json.loads(blob)
84 + except ValueError:
85 + continue
86 + if d.get("@type") == "Event":
87 + data = d
88 + break
89 + if not data:
90 + return None
91 + title = clean_text(_val(data.get("name")))
92 + if not title:
93 + return None
94 + start = _val(data.get("startDate"))
95 + end = _val(data.get("endDate"))
96 + loc = data.get("location") or {}
97 + addr = loc.get("address") or {}
98 + img = data.get("image") or {}
99 + image = (img.get("http://schema.org/url")
100 + or img.get("thumbnailUrl") or "") if isinstance(img, dict) else ""
101 + org = data.get("organizer") or {}
102 + # /fr/evenements/<id>/<slug> → id stable
103 + parts = url.rstrip("/").split("/")
104 + ext = parts[-2] if len(parts) >= 2 else parts[-1]
105 + return {
106 + "external_id": ext, "url": url, "title": title,
107 + "description": clean_text(_val(data.get("description")))[:2000],
108 + "raw_categories": [title],
109 + "venue": clean_text(_val(loc.get("name"))),
110 + "address": clean_text(_val(addr.get("streetAddress"))),
111 + "city": clean_text(_val(addr.get("addressLocality"))) or "Laval",
112 + "postal_code": _val(addr.get("postalCode")),
113 + "start_date": start[:10] if start else None,
114 + "start_time": parse_time(start),
115 + "end_date": end[:10] if end else None,
116 + "end_time": parse_time(end),
117 + "status": ("cancelled"
118 + if "cancel" in str(data.get("eventStatus", "")).lower()
119 + else ""),
120 + "organizer": clean_text(_val(org.get("name"))),
121 + "website": data.get("url") or "",
122 + "image": image,
123 + }
124 +
125 + # -- pipeline ---------------------------------------------------------
126 + def fetch(self) -> list[Event]:
127 + cache = self.load_cache()
128 + urls = self._fiches()
129 + current = set(urls)
130 + cache = {u: c for u, c in cache.items() if u in current}
131 +
132 + now = time.time()
133 + candidates = sorted(
134 + (u for u in urls
135 + if u not in cache
136 + or now - cache[u].get("fetched_at", 0) > REFRESH_AFTER),
137 + key=lambda u: cache.get(u, {}).get("fetched_at", 0))[:MAX_FETCH]
138 +
139 + def worker(url, session):
140 + return self._parse_fiche(url, session.get(url, timeout=20).text)
141 +
142 + for url, row in self.fetch_many(candidates, worker, max_workers=4).items():
143 + cache[url] = {"fetched_at": now, "row": row}
144 + self.save_cache(cache)
145 +
146 + events: list[Event] = []
147 + seen: set[str] = set()
148 + for entry in cache.values():
149 + row = entry.get("row")
150 + if not row or row["external_id"] in seen:
151 + continue
152 + seen.add(row["external_id"])
153 + events.append(Event(
154 + source=self.source_id,
155 + external_id=row["external_id"],
156 + url=row["url"], title=row["title"],
157 + description=row.get("description", ""),
158 + raw_categories=row.get("raw_categories") or [],
159 + venue=row.get("venue", ""),
160 + address=row.get("address", ""),
161 + city=row.get("city", ""),
162 + postal_code=row.get("postal_code", ""),
163 + start_date=row.get("start_date"),
164 + start_time=row.get("start_time"),
165 + end_date=row.get("end_date"),
166 + end_time=row.get("end_time"),
167 + status=row.get("status", ""),
168 + organizer=row.get("organizer", ""),
169 + website=row.get("website", ""),
170 + image=row.get("image", ""),
171 + ))
172 + return events
modified sortika/connectors/sitq.py +68 −21
@@ -12,8 +12,18 @@
12 12 # Contacts : TelephonePrincipals / Courriels (moyens de communication) →
13 13 # ligne « Renseignements : … » en description (le flux syndiqué
14 14 # ne publie AUCUN groupe Descriptions* ni bloc tarifs — vérifié
15 −# sur l'union des clés des 1 598 fiches, 2026-08 ; is_free et
16 −# price_* restent donc inconnus, jamais inventés).
15 +# sur l'union des clés des 1 598 fiches, 2026-08, re-vérifié
16 +# 2026-08-25 : aucune heure non plus, tous les Datedebut/Datefin
17 +# à T00:00:00 ; is_free/price_*/start_time restent donc
18 +# inconnus, jamais inventés).
19 +# Fiches : le site officiel de chaque événement (SiteInternets) est
20 +# visité incrémentalement (cache disque, plafond SITQ_IMG_MAX)
21 +# pour og:image ET og:description (v2 — ~60 % des 40 sites
22 +# sondés la publient, 2026-08-25) ; AUCUN des 40 sites sondés
23 +# ne publie de JSON-LD Event → l'heure n'est pas récupérable à
24 +# coût raisonnable. La fiche quebecoriginal.com n'est pas
25 +# dérivable du SyndicObjectID (301 → bonjourquebec.com → 404,
26 +# vérifié 2026-08-25).
17 27 # https://www.donneesquebec.ca/recherche/dataset/sit-quebec-evenements
18 28 # -----------------------------------------------------------------------------
19 29 from __future__ import annotations
@@ -21,16 +31,35 @@ from __future__ import annotations
21 31 import os
22 32 import re
23 33
34 +from ..normalize import clean_text
24 35 from ..schema import Event
25 36 from .base import BaseConnector
26 37
27 38 FEED = ("https://api-v3.tourinsoft.com/api/syndications/mto.tourinsoft.com/"
28 39 "c6ab1c9e-f594-463e-b036-5a029197f3a9?format=json")
29 40
30 −# le flux ne contient pas de photos → enrichissement og:image du site officiel
31 −# de chaque événement (cache disque, plafonné par sync via SITQ_IMG_MAX)
41 +# le flux ne contient ni photos ni textes → enrichissement og:image +
42 +# og:description du site officiel de chaque événement (cache disque,
43 +# plafonné par sync via SITQ_IMG_MAX)
32 44 IMG_MAX = int(os.environ.get("SITQ_IMG_MAX", "200"))
45 +SITE_V = 2 # version du parseur de site (v1 : image seule → backfill desc)
33 46 _OG_IMG_RE = re.compile(r'property="og:image"\s+content="([^"]*)"')
47 +_OG_DESC_RE = re.compile(r'property="og:description"\s+content="([^"]*)"')
48 +
49 +
50 +def _parse_site(html: str) -> dict:
51 + """og:image + og:description du site officiel d'un événement (v2)."""
52 + m = _OG_IMG_RE.search(html)
53 + img = m.group(1) if m else ""
54 + if img.startswith("//"):
55 + img = "https:" + img
56 + if not img.startswith("http"):
57 + img = ""
58 + d = _OG_DESC_RE.search(html)
59 + desc = clean_text(d.group(1)) if d else ""
60 + if len(desc) < 30: # slogan/nom de site : pas une description utile
61 + desc = ""
62 + return {"image": img, "desc": desc, "v": SITE_V}
34 63
35 64
36 65 def _first(lst, *keys, default=None):
@@ -65,34 +94,52 @@ class SitqConnector(BaseConnector):
65 94 timeout = 15
66 95
67 96 def _enrich_images(self, events: list[Event]) -> None:
68 − """og:image du site web officiel — incrémental, cache disque, pool de
69 − threads (8), tolérant (sites hétérogènes : échec ou absence = ""
70 − mémorisé, jamais re-visité). Événements à venir seulement."""
97 + """og:image + og:description du site web officiel — incrémental,
98 + cache disque, pool de threads (8), tolérant (sites hétérogènes :
99 + échec ou absence = entrée vide mémorisée, jamais re-visitée).
100 + Événements à venir seulement. Les entrées héritées v1 (chaîne image
101 + seule) sont re-visitées en priorité basse pour acquérir la
102 + description, dans le même plafond SITQ_IMG_MAX."""
71 103 from datetime import date
72 104 today = date.today().isoformat()
73 105 cache = self.load_cache()
74 − to_fetch = []
106 + to_fetch, legacy = [], []
75 107 for ev in events:
76 108 site = ev.website
77 − if not site or site in cache or site in to_fetch:
109 + if not site or site in to_fetch or site in legacy:
78 110 continue
79 111 last_day = (ev.end_date or ev.start_date or "")[:10]
80 − if (not last_day or last_day >= today) and len(to_fetch) < IMG_MAX:
81 − to_fetch.append(site)
112 + if last_day and last_day < today:
113 + continue
114 + entry = cache.get(site)
115 + if entry is None:
116 + to_fetch.append(site) # jamais visité : prioritaire
117 + elif isinstance(entry, str) and entry:
118 + legacy.append(site) # v1 avec image : backfill desc
119 + to_fetch = (to_fetch + legacy)[:IMG_MAX]
82 120
83 121 def worker(url, session):
84 − m = _OG_IMG_RE.search(session.get(url, timeout=10).text)
85 − img = m.group(1) if m else ""
86 − if img.startswith("//"):
87 − img = "https:" + img
88 − return img if img.startswith("http") else ""
89 −
90 − for url, img in self.fetch_many(to_fetch, worker, max_workers=8).items():
91 − cache[url] = img or ""
122 + return _parse_site(session.get(url, timeout=10).text)
123 +
124 + for url, info in self.fetch_many(to_fetch, worker, max_workers=8).items():
125 + if info is None: # échec : garder l'image v1
126 + old = cache.get(url)
127 + info = {"image": old if isinstance(old, str) else "",
128 + "desc": "", "v": SITE_V}
129 + cache[url] = info
92 130 self.save_cache(cache)
93 131 for ev in events:
94 − if not ev.image and ev.website:
95 − ev.image = cache.get(ev.website) or ""
132 + entry = cache.get(ev.website) if ev.website else None
133 + if isinstance(entry, str): # cache hérité v1 : image seule
134 + entry = {"image": entry}
135 + if not isinstance(entry, dict):
136 + continue
137 + if not ev.image:
138 + ev.image = entry.get("image") or ""
139 + desc = entry.get("desc") or ""
140 + if desc: # résumé publié par le site officiel + coordonnées
141 + ev.description = (f"{desc} — {ev.description}"
142 + if ev.description else desc)
96 143
97 144 def fetch(self) -> list[Event]:
98 145 payload = self.get_json(FEED)
added sortika/connectors/soreltracy.py +156 −0
@@ -0,0 +1,156 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/soreltracy.py : connecteur Ville de Sorel-Tracy — événements
5 +# Source : https://ville.sorel-tracy.qc.ca/evenements (OctoberCMS,
6 +# rendu serveur — même famille que rouyn-noranda/joliette).
7 +# Extraction: pages liste /evenements puis /evenements/2, /3... (arrêt à la
8 +# première page sans carte) → cartes « thumb-event » : date FR
9 +# (« 25 août 2026 » ou « Du 29 juillet 2026 au 26 août 2026 »),
10 +# titre, catégorie, image, url fiche. Enrichissement par fiche
11 +# /evenement/<slug> (heures, lieu, coût « Gratuit » — blocs
12 +# event-info__text) — CACHE INCRÉMENTAL disque, plafond
13 +# SOR_FICHES par sync.
14 +# Accès : site municipal public, GET directs throttlés, identification
15 +# honnête ; robots.txt permissif.
16 +# Prix : la fiche publie le coût (souvent « Gratuit ») → price_label
17 +# fidèle, jamais inventé.
18 +# -----------------------------------------------------------------------------
19 +from __future__ import annotations
20 +
21 +import html as _html
22 +import os
23 +import re
24 +
25 +from ..normalize import clean_text, parse_date_range_fr, parse_time
26 +from ..schema import Event
27 +from .base import BaseConnector
28 +
29 +BASE = "https://ville.sorel-tracy.qc.ca"
30 +LIST_URL = BASE + "/evenements"
31 +PAGES_MAX = int(os.environ.get("SOR_PAGES", "8"))
32 +FICHES_MAX = int(os.environ.get("SOR_FICHES", "40"))
33 +
34 +_CARD_RE = re.compile(
35 + r'<a href="(https://ville\.sorel-tracy\.qc\.ca/evenement/[^"]+)"[^>]*class="thumb-event[^"]*"[^>]*>(.*?)</a>',
36 + re.S)
37 +_DATE_RE = re.compile(r'card__date">\s*(.*?)\s*</div>', re.S)
38 +_TITLE_RE = re.compile(r'thumb-event__title">\s*(.*?)\s*</div>', re.S)
39 +_CAT_RE = re.compile(r'thumb-event__category">\s*(.*?)\s*</div>', re.S)
40 +_IMG_RE = re.compile(r'<img[^>]+src="([^"]+)"')
41 +
42 +
43 +class SorelTracyConnector(BaseConnector):
44 + source_id = "soreltracy"
45 + request_delay = 0.8
46 +
47 + def _parse_page(self, html: str) -> list[dict]:
48 + rows: list[dict] = []
49 + for url, block in _CARD_RE.findall(html):
50 + t = _TITLE_RE.search(block)
51 + d = _DATE_RE.search(block)
52 + if not t or not d:
53 + continue
54 + start, end = parse_date_range_fr(
55 + clean_text(_html.unescape(d.group(1))))
56 + if not start:
57 + continue
58 + cat = _CAT_RE.search(block)
59 + img = _IMG_RE.search(block)
60 + image = img.group(1) if img else ""
61 + if image.startswith("/"):
62 + image = BASE + image
63 + rows.append({
64 + "url": url,
65 + "title": clean_text(_html.unescape(
66 + re.sub(r"<[^>]+>", " ", t.group(1)))),
67 + "start": start,
68 + "end": end or start,
69 + "category": clean_text(_html.unescape(
70 + cat.group(1))) if cat else "",
71 + "image": image,
72 + })
73 + return rows
74 +
75 + def _fiche(self, url: str) -> dict:
76 + """Heures, lieu et coût depuis les blocs event-info de la fiche
77 + (classement par contenu : heure si « N h », coût si gratuit/$,
78 + lieu sinon — la date est déjà connue de la carte)."""
79 + html = self.get(url).text
80 + out: dict = {}
81 + texts: list[str] = []
82 + for m in re.finditer(
83 + r'event-info__text"[^>]*>\s*(?:<a[^>]*>)?(.*?)(?:</a>)?\s*</div>',
84 + html, re.S):
85 + txt = clean_text(_html.unescape(re.sub(r"<[^>]+>", " ", m.group(1))))
86 + if txt:
87 + texts.append(txt)
88 + for txt in texts:
89 + low = txt.lower()
90 + if "hours" not in out and re.search(r"\d{1,2}\s*h(\s|$|\d)", low):
91 + out["hours"] = txt
92 + elif "price_label" not in out and (
93 + "gratuit" in low or "$" in txt or "payant" in low):
94 + out["price_label"] = txt
95 + elif "venue" not in out and not parse_date_range_fr(txt)[0]:
96 + out["venue"] = txt
97 + return out
98 +
99 + def fetch(self) -> list[Event]:
100 + rows: list[dict] = []
101 + for page in range(1, PAGES_MAX + 1):
102 + url = LIST_URL if page == 1 else f"{LIST_URL}/{page}"
103 + try:
104 + batch = self._parse_page(self.get(url).text)
105 + except Exception:
106 + if page > 1: # fin de pagination (404)
107 + break
108 + raise
109 + if not batch:
110 + break
111 + rows += batch
112 + cache = self.load_cache()
113 + fetched = 0
114 + for r in rows:
115 + slug = r["url"].rstrip("/").rsplit("/", 1)[-1]
116 + r["slug"] = slug
117 + if slug not in cache and fetched < FICHES_MAX:
118 + try:
119 + cache[slug] = self._fiche(r["url"])
120 + fetched += 1
121 + except Exception as exc: # fiche cassée ≠ source cassée
122 + print(f"[sorti-ka] soreltracy : fiche {slug} ignorée : {exc}")
123 + if fetched:
124 + self.save_cache(cache)
125 + events: list[Event] = []
126 + seen: set[str] = set()
127 + for r in rows:
128 + ext = f"{r['slug']}-{r['start']}"
129 + if ext in seen:
130 + continue
131 + seen.add(ext)
132 + extra = cache.get(r["slug"]) or {}
133 + hours = extra.get("hours", "")
134 + start_time = parse_time(hours)
135 + end_time = None
136 + m = re.search(r"\bà\s+(\d{1,2}\s*h(?:\s*\d{2})?)", hours)
137 + if m:
138 + end_time = parse_time(m.group(1))
139 + events.append(Event(
140 + source=self.source_id,
141 + external_id=ext,
142 + url=r["url"],
143 + title=r["title"],
144 + raw_categories=[r["category"]] if r["category"] else [r["title"]],
145 + venue=extra.get("venue", ""),
146 + city="Sorel-Tracy",
147 + region="Montérégie",
148 + start_date=r["start"],
149 + start_time=start_time,
150 + end_date=r["end"],
151 + end_time=end_time,
152 + price_label=extra.get("price_label", ""),
153 + organizer="Ville de Sorel-Tracy",
154 + image=r["image"],
155 + ))
156 + return events
added sortika/connectors/terrebonne.py +120 −0
@@ -0,0 +1,120 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/terrebonne.py : connecteur Ville de Terrebonne — calendrier
5 +# Source : https://terrebonne.ca/calendrier-des-evenements/ (WordPress).
6 +# Extraction: API REST WordPress publique /wp-json/wp/v2/event (champs ACF
7 +# start_date/end_date « YYYY-MM-DD HH:MM:SS », place.name,
8 +# isFull, hours) + taxonomie /wp-json/wp/v2/event_category.
9 +# Cloudflare bloque tout GET non navigateur (403, consigné
10 +# 2026-08-18) → chaque requête passe par Scrapfly (asp=true,
11 +# SANS rendu JS : la réponse est du JSON), ~3-5 appels/sync.
12 +# Vérifié 2026-08-25 (Scrapfly 200, JSON complet).
13 +# Accès : API publique du site municipal (aucune auth) ; robots.txt
14 +# n'interdit pas /wp-json/. Anti-bot contourné avec volumes
15 +# minimes et cadence quotidienne au plus — usage mesuré.
16 +# Prix : non publié par la source → jamais inventé. isFull=True →
17 +# status "soldout" (complet, publié par la ville).
18 +# -----------------------------------------------------------------------------
19 +from __future__ import annotations
20 +
21 +import json
22 +import os
23 +
24 +from ..normalize import clean_text, parse_date_iso, parse_time
25 +from ..schema import Event
26 +from .base import BaseConnector
27 +
28 +API = ("https://terrebonne.ca/wp-json/wp/v2/event"
29 + "?per_page=100&page={page}&_embed=wp:featuredmedia")
30 +TAXO = "https://terrebonne.ca/wp-json/wp/v2/event_category?per_page=100"
31 +PAGES_MAX = int(os.environ.get("TER_PAGES", "5"))
32 +
33 +
34 +class TerrebonneConnector(BaseConnector):
35 + source_id = "terrebonne"
36 + request_delay = 1.0
37 +
38 + def _get_json_scrapfly(self, url: str):
39 + """JSON via Scrapfly (asp, sans rendu JS) — Cloudflare bloque le direct."""
40 + return json.loads(self.get_rendered(url, render_js=False))
41 +
42 + def _categories(self) -> dict[int, str]:
43 + try:
44 + terms = self._get_json_scrapfly(TAXO)
45 + return {int(t["id"]): clean_text(t.get("name") or "")
46 + for t in terms if t.get("id")}
47 + except Exception as exc: # taxonomie indisponible ≠ source cassée
48 + print(f"[sorti-ka] terrebonne : taxonomie ignorée : {exc}")
49 + return {}
50 +
51 + @staticmethod
52 + def _image(rec: dict) -> str:
53 + for media in (rec.get("_embedded") or {}).get("wp:featuredmedia") or []:
54 + if isinstance(media, dict) and media.get("source_url"):
55 + return media["source_url"]
56 + return ""
57 +
58 + def _rows(self) -> list[dict]:
59 + out, page = [], 1
60 + while page <= PAGES_MAX:
61 + try:
62 + batch = self._get_json_scrapfly(API.format(page=page))
63 + except Exception:
64 + if page > 1: # « rest_post_invalid_page_number » = fin
65 + break
66 + raise
67 + if not isinstance(batch, list) or not batch:
68 + break
69 + out += batch
70 + if len(batch) < 100:
71 + break
72 + page += 1
73 + return out
74 +
75 + def fetch(self) -> list[Event]:
76 + rows = self._rows()
77 + cat_names = self._categories()
78 + events: list[Event] = []
79 + for rec in rows:
80 + title = clean_text((rec.get("title") or {}).get("rendered") or "")
81 + link = rec.get("link") or ""
82 + acf = rec.get("acf") or {}
83 + start_raw = str(acf.get("start_date") or "")
84 + start = parse_date_iso(start_raw)
85 + if not title or not link or not start:
86 + continue
87 + start_time = parse_time(start_raw)
88 + if start_time == "00:00":
89 + start_time = None
90 + end_raw = str(acf.get("end_date") or "")
91 + end = parse_date_iso(end_raw)
92 + end_time = parse_time(end_raw)
93 + if end_time == "00:00":
94 + end_time = None
95 + if not start_time: # repli : champ texte « hours » de la fiche
96 + start_time = parse_time(str(acf.get("hours") or ""))
97 + place = acf.get("place") or {}
98 + venue = clean_text(place.get("name") or "") if isinstance(place, dict) else ""
99 + cats = [cat_names[c] for c in rec.get("event_category") or []
100 + if c in cat_names]
101 + events.append(Event(
102 + source=self.source_id,
103 + external_id=str(rec.get("id")),
104 + url=link,
105 + title=title,
106 + description=clean_text(
107 + (rec.get("content") or {}).get("rendered") or ""),
108 + raw_categories=cats or [title],
109 + venue=venue,
110 + city="Terrebonne",
111 + region="Lanaudière",
112 + start_date=start,
113 + start_time=start_time,
114 + end_date=end or start,
115 + end_time=end_time,
116 + status="soldout" if acf.get("isFull") else "",
117 + organizer="Ville de Terrebonne",
118 + image=self._image(rec),
119 + ))
120 + return events
modified sortika/connectors/ticketacces.py +45 −12
@@ -16,11 +16,15 @@
16 16 # calendrier par catégorie (4 GET) : titre + sous-titre, date
17 17 # et heure FR (« Samedi 22 août 2026 à 12 h 30 »), « Salle,
18 18 # Ville », image, lien fiche. Fiches détail incrémentales
19 −# (cache data/ticketacces_cache.json, TA_DETAIL_MAX/sync,
20 −# re-visite 7 j) : og:description, adresse civique, prix
21 −# plancher si les ventes sont ouvertes. 1 Event par
22 −# représentation (une même fiche multi-dates = plusieurs
23 −# séances, patron lepointdevente).
19 +# (cache data/ticketacces_cache.json, TA_DETAIL_MAX/sync —
20 +# 47 par défaut = 1 fiche par organisation/hôte, re-visite
21 +# 7 j, version de parse « v » bumpée quand le parseur
22 +# s'enrichit → re-visite progressive, patron lavitrine) :
23 +# og:description, adresse civique de la salle (bloc
24 +# « Salle | 315, rue X, Ville » des représentations, ville
25 +# finale retirée), prix plancher si les ventes sont
26 +# ouvertes. 1 Event par représentation (une même fiche
27 +# multi-dates = plusieurs séances, patron lepointdevente).
24 28 # Accès : billetterie publique. robots.txt (lu 2026-08-21, identique
25 29 # sur tous les hôtes *.ticketacces.net) : Crawl-delay: 120 +
26 30 # Disallow /achat/ et representations/prix.cfm (jamais
@@ -47,8 +51,11 @@ PORTAL = "https://www.ticketacces.net/fr/portail/index.cfm"
47 51 CALENDAR = ("https://www.ticketacces.net/fr/portail/evenements/calendrier.cfm"
48 52 "?RepresentationCategoriePrincipaleID={cat}")
49 53 CATEGORIES = {1: "Spectacles", 2: "Cinéma", 3: "Sport", 4: "Autre"}
50 −DETAIL_MAX = int(os.environ.get("TA_DETAIL_MAX", "25"))
54 +# 47 organisations/hôtes au Québec, ≤ 1 fiche par hôte par passage → 47 fiches
55 +# par défaut = un passage complet sans jamais attendre le Crawl-delay par hôte
56 +DETAIL_MAX = int(os.environ.get("TA_DETAIL_MAX", "47"))
51 57 REFRESH_AFTER = 7 * 86400
58 +DETAIL_V = 2 # version du parseur de fiche (bump = re-visite)
52 59 TTL = float(os.environ.get("TA_TTL_HOURS", "6")) * 3600
53 60 HOST_DELAY = 120.0 # robots.txt : Crawl-delay: 120 (par hôte)
54 61
@@ -65,6 +72,15 @@ _IMG_RE = re.compile(
65 72 r'<img src="([^"]+)"', re.S)
66 73 _OG_DESC_RE = re.compile(r'property="og:description" content="([^"]*)"')
67 74 _PRICE_RE = re.compile(r"(\d+(?:,\d{2}))\s*\$")
75 +# adresse civique de la salle dans les blocs de représentation (« Théâtre de
76 +# la Bordée | 315, rue Saint-Joseph Est, Québec ») : segment commençant par
77 +# un numéro civique ET contenant un mot de voirie — la version 1 du parseur
78 +# ne testait que le PREMIER segment numérique de la page (souvent un titre
79 +# « 5 BALLES… » ou une durée « 1h40 ») → 3 adresses sur 317 fiches.
80 +_ADDR_SEG_RE = re.compile(r"\|\s*(\d{1,5}(?:,)?\s[^|]{3,90}?)\s*\|")
81 +_STREET_RE = re.compile(
82 + r"\b(rue|avenue|av\.|boulevard|boul\.|chemin|ch\.|route|rang|place|"
83 + r"mont[ée]e|c[oô]te|carr[ée]|promenade|all[ée]e)\b", re.I)
68 84 # hors Québec (constaté 2026-08-21) : 12 organisations au Nouveau-Brunswick
69 85 # (comtés) + 1 en Ontario (« Ottawa et sa campagne » = Hawkesbury) — les
70 86 # libellés de région du portail n'utilisent pas les noms de provinces.
@@ -111,11 +127,24 @@ class TicketAccesConnector(BaseConnector):
111 127 plain = re.sub(r"<[^>]+>", "|", html)
112 128 plain = re.sub(r"[\t\r\n ]+", " ", plain)
113 129 addr = ""
114 − m = re.search(r"\|\s*(\d+[^|]{3,70}?)\s*\|", plain)
115 − if m and re.search(r"(rue|avenue|boulevard|chemin|route|rang|place)",
116 − m.group(1), re.I):
117 − addr = clean_text(m.group(1))
130 + for m in _ADDR_SEG_RE.finditer(plain):
131 + seg = m.group(1)
132 + if not _STREET_RE.search(seg):
133 + continue # « 1h40 Récit… », « 5 BALLES… »
134 + seg = clean_text(seg)
135 + # la source suffixe souvent la ville (« …, Québec ») : on la
136 + # retire si la fin n'a ni chiffre ni mot de voirie — l'Event
137 + # porte déjà la ville, geocode.py préfère l'adresse nue
138 + head, _, tail = seg.rpartition(",")
139 + if head and not any(c.isdigit() for c in tail) \
140 + and not _STREET_RE.search(tail):
141 + seg = head.strip()
142 + addr = seg
143 + break
144 + # « 0,00 $ » = ligne tarifaire vide (constaté live 2026-08-25, lsq) —
145 + # jamais un prix plancher réel, on l'écarte (patron ticketpro c > 0)
118 146 prices = [float(p.replace(",", ".")) for p in _PRICE_RE.findall(plain)]
147 + prices = [p for p in prices if p > 0]
119 148 return {
120 149 "description": clean_text(desc.group(1)) if desc else "",
121 150 "address": addr,
@@ -178,11 +207,15 @@ class TicketAccesConnector(BaseConnector):
178 207 for r in rows.values()}
179 208 details = {k: v for k, v in cache.items()
180 209 if k not in ("_meta", "_rows") and k in fiches}
210 + # jamais vue > parseur périmé (v < DETAIL_V, patron lavitrine) > plus
211 + # ancienne — le bump de DETAIL_V re-visite progressivement le stock
181 212 stale = sorted(
182 213 (k for k in fiches
183 214 if k not in details
215 + or details[k].get("v", 1) < DETAIL_V
184 216 or now - details[k].get("ts", 0) > REFRESH_AFTER),
185 − key=lambda k: details.get(k, {}).get("ts", 0))
217 + key=lambda k: (details.get(k, {}).get("v", 1) if k in details else 0,
218 + details.get(k, {}).get("ts", 0)))
186 219 seen_hosts: set[str] = set()
187 220 picked: list[str] = []
188 221 for k in stale:
@@ -198,7 +231,7 @@ class TicketAccesConnector(BaseConnector):
198 231 detail = self._parse_detail(self._get_cp1252(fiches[k][1]))
199 232 except Exception:
200 233 detail = {}
201 − details[k] = {"ts": now, "detail": detail}
234 + details[k] = {"ts": now, "v": DETAIL_V, "detail": detail}
202 235
203 236 cache = dict(details)
204 237 cache["_meta"] = {"ts": now}
modified sortika/connectors/ticketpro.py +99 −7
@@ -18,8 +18,19 @@
18 18 # dédup inter-tenants par id de page (ids globaux). Les lieux
19 19 # de restauration (soupers-spectacles) et le musée à créneaux
20 20 # sont exclus par config (exclure_lieux). Filtre province:
21 −# provinceState == QC. startingPrice est toujours 0 sur ces
22 −# tenants → aucun prix émis (jamais inventé).
21 +# provinceState == QC. startingPrice et prices[] sont toujours
22 +# vides dans la LISTE, mais les tarifs réels sont publics en
23 +# 2 GET par spectacle (vérifié 2026-08-25) :
24 +# /api/v1/pages/<id> (détail → itemsDates.items[].dates[] =
25 +# séances avec saleState + URL relative de l'offre) puis
26 +# /api/v1<url de la 1re séance à venir> (?options=prices) →
27 +# itemHours[].priceLevels[].price_min en CENTS (taxes et
28 +# frais inclus : taxIncluded/serviceChargesIncluded=true).
29 +# Prix plancher de la page appliqué à ses séances + statut
30 +# PAR séance (saleState SOLDOUT/CANCELLED…). Enrichissement
31 +# incrémental : cache data/ticketpro_cache.json,
32 +# TP_PRICE_MAX pages/sync, re-visite 3 j, version de parse
33 +# « v » (patron lavitrine).
23 34 # Accès : billetterie publique, API du site officiel, GET throttlés
24 35 # 1 s, identification honnête, lien d'achat vers la fiche
25 36 # officielle /fr/pages/<id> du tenant.
@@ -27,15 +38,22 @@
27 38 from __future__ import annotations
28 39
29 40 import json
41 +import os
30 42 import re
43 +import time
31 44 from datetime import date
32 45 from pathlib import Path
33 46
34 47 from ..normalize import clean_text, strip_accents
35 −from ..schema import Event
48 +from ..schema import Event, normalize_status
36 49 from .base import BaseConnector
37 50
38 51 CONFIG_PATH = Path(__file__).resolve().parents[2] / "data" / "ticketpro_tenants.json"
52 +# enrichissement tarifs : 2 GET par page (détail + offre de la 1re séance à
53 +# venir) — plafonné par sync, re-visité pour suivre soldout/changements de prix
54 +PRICE_MAX = int(os.environ.get("TP_PRICE_MAX", "40"))
55 +REFRESH_AFTER = 3 * 86400
56 +PRICE_V = 1 # version du parseur d'offre (bump = re-visite)
39 57
40 58 _TAG_RE = re.compile(r"<[^>]+>")
41 59 _BLOCK_RE = re.compile(r"<(style|script)[^>]*>.*?</\1>", re.S | re.I)
@@ -65,8 +83,43 @@ class TicketproConnector(BaseConnector):
65 83 except Exception:
66 84 return {"tenants": [], "exclure_lieux": []}
67 85
68 − def _events_from_page(self, tenant: str, page: dict,
69 − excluded: list[str]) -> list[Event]:
86 + # -- tarifs & statuts par séance (2 GET par page, incrémental) -------------
87 + def _fetch_prices(self, tenant: str, page_id) -> dict:
88 + """{price_min: float|None, states: {"AAAA-MM-JJTHH:MM": saleState}}.
89 +
90 + Détail /api/v1/pages/<id> → itemsDates (séances + saleState) ; l'offre
91 + de la 1re séance à venir (?options=prices) donne les priceLevels en
92 + cents. Le plancher d'une page vaut pour ses séances (patron
93 + ticketacces : prix plancher de la fiche). Jamais inventé : pages sans
94 + itemsDates (cartes-cadeaux…) ou sans priceLevels → aucun prix.
95 + """
96 + detail = self.get_json(
97 + f"https://{tenant}.ticketpro.ca/api/v1/pages/{page_id}")
98 + seances = [d for item in (detail.get("itemsDates") or {}).get("items")
99 + or [] for d in item.get("dates") or []]
100 + states = {str(s.get("date"))[:16]: str(s.get("saleState") or "")
101 + for s in seances if s.get("date")}
102 + today = date.today().isoformat()
103 + upcoming = sorted((s for s in seances
104 + if s.get("url") and str(s.get("date"))[:10] >= today),
105 + key=lambda s: str(s.get("date")))
106 + # 1re séance encore en vente, sinon 1re à venir (offre = mêmes niveaux)
107 + best = next((s for s in upcoming if s.get("saleState") == "ONSALE"),
108 + upcoming[0] if upcoming else None)
109 + price_min = None
110 + if best is not None:
111 + offer = self.get_json(
112 + f"https://{tenant}.ticketpro.ca/api/v1{best['url']}")
113 + cents = [pl.get("price_min")
114 + for ih in offer.get("itemHours") or []
115 + for pl in ih.get("priceLevels") or []]
116 + cents = [c for c in cents if isinstance(c, (int, float)) and c > 0]
117 + if cents:
118 + price_min = round(min(cents) / 100, 2)
119 + return {"price_min": price_min, "states": states}
120 +
121 + def _events_from_page(self, tenant: str, page: dict, excluded: list[str],
122 + enrich: dict | None = None) -> list[Event]:
70 123 title = clean_text(page.get("title") or "")
71 124 dates = page.get("dates") or []
72 125 loc = (page.get("locations") or [{}])[0] or {}
@@ -92,6 +145,10 @@ class TicketproConnector(BaseConnector):
92 145 description = _strip_html(page.get("description")
93 146 or page.get("miniDescription") or "")
94 147
148 + enrich = enrich or {}
149 + price_min = enrich.get("price_min")
150 + states = enrich.get("states") or {}
151 +
95 152 today = date.today().isoformat()
96 153 events: list[Event] = []
97 154 for d in dates:
@@ -101,6 +158,12 @@ class TicketproConnector(BaseConnector):
101 158 day, hh, mm = m.group(1), m.group(2), m.group(3)
102 159 if day < today: # l'API conserve les dates passées
103 160 continue
161 + # statut PAR séance (saleState SOLDOUT/CANCELLED…) quand la page
162 + # ne porte pas déjà un drapeau global — jamais inventé. Certaines
163 + # séances du détail sont datées SANS heure (constaté 2026-08-25,
164 + # ~10 % des clés) : leur saleState vaut alors pour la journée.
165 + seance_status = status or normalize_status(
166 + states.get(f"{day}T{hh}:{mm}") or states.get(day, ""))
104 167 events.append(Event(
105 168 source=self.source_id,
106 169 external_id=f"{page_id}-{day}T{hh}{mm}",
@@ -116,8 +179,10 @@ class TicketproConnector(BaseConnector):
116 179 lng=loc.get("longitude"),
117 180 start_date=day,
118 181 start_time=f"{hh}:{mm}",
119 − status=status,
182 + status=seance_status,
120 183 artists=[artist] if artist else [],
184 + is_free=False if price_min else None,
185 + price_min=price_min,
121 186 image=image,
122 187 ))
123 188 return events
@@ -129,6 +194,11 @@ class TicketproConnector(BaseConnector):
129 194 if not tenants:
130 195 return []
131 196
197 + cache = self.load_cache()
198 + now = time.time()
199 + budget = PRICE_MAX
200 + today = date.today().isoformat()
201 +
132 202 events: list[Event] = []
133 203 seen_pages: set = set() # ids de page globaux inter-tenants
134 204 for tenant in tenants:
@@ -142,5 +212,27 @@ class TicketproConnector(BaseConnector):
142 212 if page.get("id") in seen_pages:
143 213 continue
144 214 seen_pages.add(page.get("id"))
145 − events += self._events_from_page(tenant, page, excluded)
215 + # tarifs & statuts par séance : incrémental, plafonné/sync —
216 + # seulement pour les pages ayant au moins une séance à venir
217 + key = str(page.get("id"))
218 + entry = cache.get(key)
219 + has_future = any(str(d)[:10] >= today
220 + for d in page.get("dates") or [])
221 + stale = (entry is None or entry.get("v", 0) < PRICE_V
222 + or now - entry.get("ts", 0) > REFRESH_AFTER)
223 + if budget > 0 and has_future and stale:
224 + budget -= 1
225 + try:
226 + entry = self._fetch_prices(tenant, page.get("id"))
227 + except Exception as exc:
228 + print(f"[sorti-ka] ticketpro : tarifs {tenant}/{key} "
229 + f"ignorés ({exc})")
230 + entry = {"price_min": None, "states": {}}
231 + entry.update(ts=now, v=PRICE_V)
232 + cache[key] = entry
233 + events += self._events_from_page(tenant, page, excluded, entry)
234 + # ne garder que les pages encore publiées (le cache suit la source)
235 + cache = {k: v for k, v in cache.items()
236 + if k in {str(p) for p in seen_pages}}
237 + self.save_cache(cache)
146 238 return events
added sortika/connectors/tourismelaval.py +111 −0
@@ -0,0 +1,111 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/tourismelaval.py : Tourisme Laval (ATR, vague ATR 2026-08)
5 +# Source : tourismelaval.com/explorer-possibilites/evenements/ — calendrier
6 +# officiel de l'ATR (WordPress + FacetWP, CPT « event », cartes
7 +# RENDUES SERVEUR ; vérifié 2026-08-25 : 21 événements sur
8 +# 2 pages). NB : le calendrier « tribe » du même site
9 +# (/calendrier/) est mort à la source (derniers événements
10 +# décembre 2025) ; le CPT event est le vivant. L'API REST
11 +# wp/v2/event est verrouillée (401 Kadence Security) → parse des
12 +# cartes SSR.
13 +# Extraction: GET /explorer-possibilites/evenements/ (+ /page/N/) →
14 +# <article class="grid__item item--event post-<id>"> : lien,
15 +# image, catégorie, titre, extrait, plage de dates FR
16 +# (« 18 août 2026 - 25 août 2026 »). Pas de fiche détail
17 +# (adresse/heure non structurées sur les fiches, vérifié).
18 +# Accès : pages publiques — GET throttlés, identification honnête, lien
19 +# vers la fiche originale. Ni prix ni heure structurés → jamais
20 +# inventés. Ville = Laval (périmètre de l'ATR, île de Laval).
21 +# -----------------------------------------------------------------------------
22 +from __future__ import annotations
23 +
24 +import os
25 +import re
26 +
27 +from ..normalize import clean_text, parse_date_range_fr
28 +from ..schema import Event
29 +from .base import BaseConnector
30 +
31 +BASE = "https://www.tourismelaval.com"
32 +LIST_URL = BASE + "/explorer-possibilites/evenements/"
33 +LVT_PAGE_MAX = int(os.environ.get("LVT_PAGE_MAX", "5"))
34 +
35 +_ARTICLE_RE = re.compile(
36 + r'<article class="grid__item item--event post-(\d+)(.*?)</article>', re.S)
37 +_LINK_RE = re.compile(r'<a class="item__inner" href="([^"]+)"')
38 +_IMG_RE = re.compile(r'<img[^>]+src="([^"]+)"')
39 +_CAT_RE = re.compile(r'post__cat">\s*<p[^>]*>([^<]*)</p>')
40 +_TITLE_RE = re.compile(r'grid__title"[^>]*>([^<]*)</h5>')
41 +_EXCERPT_RE = re.compile(r'post__excerpt"\s*>\s*(.*?)</div>', re.S)
42 +_DATE_RE = re.compile(r'post__date">.*?<div[^>]*>([^<]*)</div>', re.S)
43 +
44 +
45 +class TourismeLavalConnector(BaseConnector):
46 + source_id = "tourismelaval"
47 + request_delay = 0.8
48 +
49 + def _articles(self, html: str) -> list[dict]:
50 + out = []
51 + for m in _ARTICLE_RE.finditer(html):
52 + eid, body = m.groups()
53 + link = _LINK_RE.search(body)
54 + title = _TITLE_RE.search(body)
55 + if not (link and title):
56 + continue
57 + date_m = _DATE_RE.search(body)
58 + start = end = None
59 + if date_m:
60 + # « 18 août 2026 - 25 août 2026 » ou « 27 juin 2026 »
61 + parts = [p.strip() for p in date_m.group(1).split(" - ")]
62 + start, _ = parse_date_range_fr(parts[0])
63 + end = (parse_date_range_fr(parts[1])[0]
64 + if len(parts) > 1 else start)
65 + img = _IMG_RE.search(body)
66 + cat = _CAT_RE.search(body)
67 + excerpt = _EXCERPT_RE.search(body)
68 + out.append({
69 + "external_id": eid,
70 + "url": link.group(1),
71 + "title": clean_text(title.group(1)),
72 + "raw_categories": [clean_text(cat.group(1)) if cat else "",
73 + clean_text(title.group(1))],
74 + "description": clean_text(excerpt.group(1)) if excerpt else "",
75 + "start_date": start, "end_date": end,
76 + "image": img.group(1) if img else "",
77 + })
78 + return out
79 +
80 + def fetch(self) -> list[Event]:
81 + events: list[Event] = []
82 + seen: set[str] = set()
83 + for page in range(1, LVT_PAGE_MAX + 1):
84 + url = LIST_URL if page == 1 else f"{LIST_URL}page/{page}/"
85 + try:
86 + articles = self._articles(self.get(url).text)
87 + except Exception:
88 + break
89 + new = [a for a in articles if a["external_id"] not in seen]
90 + if not new:
91 + break
92 + for a in new:
93 + seen.add(a["external_id"])
94 + if not a["title"]:
95 + continue
96 + events.append(Event(
97 + source=self.source_id,
98 + external_id=a["external_id"],
99 + url=a["url"],
100 + title=a["title"],
101 + description=a["description"],
102 + raw_categories=[c for c in a["raw_categories"] if c],
103 + city="Laval",
104 + tourist_region="Laval",
105 + start_date=a["start_date"],
106 + end_date=a["end_date"],
107 + image=a["image"],
108 + ))
109 + if len(articles) < 12: # page incomplète (12/page) = dernière
110 + break
111 + return events
added sortika/connectors/troisrivieres.py +91 −0
@@ -0,0 +1,91 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/troisrivieres.py : connecteur Ville de Trois-Rivières — activités
5 +# Source : https://www.v3r.net/activites-et-loisirs/calendrier-des-activites
6 +# (l'API REST WP /wp-json est verrouillée en 401 — consigné
7 +# 2026-08-18 — mais le calendrier AngularJS interroge un
8 +# endpoint AJAX public du thème).
9 +# Extraction: POST JSON /ajax/wp-queries/archive-activites.php (même appel
10 +# que le frontend, découvert dans ActivitesController.js) —
11 +# 1 requête : title, startDate/endDate « dd/mm/yyyy »,
12 +# description (= NOM DU LIEU, vérifié sur les 17 fiches live
13 +# 2026-08-25), imageUrl, url de la fiche. Heure non publiée
14 +# par l'endpoint → jamais inventée.
15 +# Accès : endpoint public du site municipal (aucune auth) ; robots.txt
16 +# n'interdit pas /ajax/. GET/POST directs, identification
17 +# honnête, 1 requête par sync.
18 +# Prix : non publié par l'endpoint → jamais inventé.
19 +# -----------------------------------------------------------------------------
20 +from __future__ import annotations
21 +
22 +import re
23 +import time
24 +
25 +from ..normalize import clean_text
26 +from ..schema import Event
27 +from .base import BaseConnector
28 +
29 +API = "https://www.v3r.net/ajax/wp-queries/archive-activites.php"
30 +QUERY = {
31 + "postType": "activites",
32 + "orderBy": "meta_value date",
33 + "order": "DESC",
34 + "filters": {"category": "", "startDate": "", "endDate": ""},
35 +}
36 +_DDMMYYYY_RE = re.compile(r"^(\d{1,2})/(\d{1,2})/(\d{4})$")
37 +
38 +
39 +def _iso(raw: str | None) -> str | None:
40 + """« 25/08/2026 » (format de l'endpoint) → « 2026-08-25 »."""
41 + m = _DDMMYYYY_RE.match(str(raw or "").strip())
42 + if not m:
43 + return None
44 + d, mo, y = int(m.group(1)), int(m.group(2)), int(m.group(3))
45 + if not (1 <= d <= 31 and 1 <= mo <= 12 and 1990 <= y <= 2100):
46 + return None
47 + return f"{y:04d}-{mo:02d}-{d:02d}"
48 +
49 +
50 +class TroisRivieresConnector(BaseConnector):
51 + source_id = "troisrivieres"
52 + request_delay = 0.8
53 +
54 + def _post_json(self) -> dict:
55 + self._throttle()
56 + resp = self.session.post(API, json={"query": QUERY},
57 + timeout=self.timeout)
58 + self._last_request = time.time()
59 + resp.raise_for_status()
60 + return resp.json()
61 +
62 + def fetch(self) -> list[Event]:
63 + payload = self._post_json()
64 + events: list[Event] = []
65 + seen: set[str] = set()
66 + for rec in payload.get("data") or []:
67 + title = clean_text(rec.get("title") or "")
68 + url = rec.get("url") or ""
69 + start = _iso(rec.get("startDate"))
70 + if not title or not url or not start:
71 + continue
72 + slug = url.rstrip("/").rsplit("/", 1)[-1]
73 + ext = f"{slug}-{start}"
74 + if ext in seen:
75 + continue
76 + seen.add(ext)
77 + events.append(Event(
78 + source=self.source_id,
79 + external_id=ext,
80 + url=url,
81 + title=title,
82 + raw_categories=[title],
83 + venue=clean_text(rec.get("description") or ""),
84 + city="Trois-Rivières",
85 + region="Mauricie",
86 + start_date=start,
87 + end_date=_iso(rec.get("endDate")) or start,
88 + organizer="Ville de Trois-Rivières",
89 + image=rec.get("imageUrl") or "",
90 + ))
91 + return events
modified sortika/connectors/tuxedobillet.py +169 −20
@@ -16,13 +16,27 @@
16 16 # salles bougent lentement). Résultat mis en cache disque
17 17 # (data/tuxedobillet_cache.json) et réémis entre deux runs.
18 18 # Tenants configurés dans data/tuxedo_tenants.json (Québec
19 −# seulement, ville vérifiée). La liste ne publie ni heure ni
20 −# prix → jamais inventés (heure « non confirmée » côté front).
19 +# seulement, ville vérifiée).
20 +# FICHES /main/<slug> rendues via Scrapfly render_js (le GET
21 +# direct ne renvoie que la coquille Angular, vérifié
22 +# 2026-08-25) : chaque fiche publie ses SÉANCES (jour + date
23 +# + HEURE « 20H00 » + étiquette « COMPLET »), la catégorie et
24 +# la description → 1 Event par séance datée, heure et statut
25 +# soldout captés. Enrichissement incrémental (clé « fiches »
26 +# du cache, TUX_FICHE_MAX/sync, re-visite 7 j, version de
27 +# parse « v » — patron lavitrine ; v2 : abréviations FR
28 +# « janv./févr. » comprises, tuiles et séances de janvier-
29 +# février récupérées). Les fiches ne publient NI
30 +# prix NI adresse de salle (vérifié : le tarif n'apparaît
31 +# qu'au choix des sièges) → jamais inventés.
21 32 # Accès : billetteries publiques ; rendu navigateur modéré (1 page par
22 −# tenant par run, ~1 run/jour), UA/robots gérés par Apify,
23 −# lien d'achat = la fiche officielle du tenant.
33 +# tenant par run Apify ~1/jour + TUX_FICHE_MAX fiches
34 +# Scrapfly/sync throttlées), lien d'achat = la fiche
35 +# officielle du tenant.
24 36 # Secrets : APIFY_TOKEN requis (dans .env) — sans lui, le connecteur
25 37 # réémet le cache et n'échoue que s'il n'y a aucun cache.
38 +# SCRAPFLY_API_KEY requis pour l'enrichissement des fiches —
39 +# sans lui, la liste seule est émise (comportement d'avant).
26 40 # -----------------------------------------------------------------------------
27 41 from __future__ import annotations
28 42
@@ -32,19 +46,41 @@ import re
32 46 import time
33 47 from pathlib import Path
34 48
35 −from ..normalize import clean_text, parse_date_fr
36 −from ..schema import Event
37 −from .base import BaseConnector
49 +import requests
50 +
51 +from ..normalize import clean_text, parse_date_fr, parse_time, strip_accents
52 +from ..schema import Event, normalize_status
53 +from .base import SCRAPFLY_API, BaseConnector
38 54
39 55 ACTOR = "gorgeous_thistle~ka-tuxedo"
40 56 RUN_URL = f"https://api.apify.com/v2/acts/{ACTOR}/runs"
41 57 CONFIG_PATH = Path(__file__).resolve().parents[2] / "data" / "tuxedo_tenants.json"
42 58 TTL = float(os.environ.get("TUX_TTL_HOURS", "20")) * 3600
43 59 POLL_TIMEOUT = int(os.environ.get("TUX_POLL_TIMEOUT", "1500")) # s
44 −# le tile affiche parfois l'anglais selon le fuseau du proxy — mois EN → FR
60 +# fiches détail (séances + heure + statut + catégorie + description) rendues
61 +# via Scrapfly : plafond par sync, re-visite 7 j, version de parse « v »
62 +FICHE_MAX = int(os.environ.get("TUX_FICHE_MAX", "20"))
63 +FICHE_REFRESH = 7 * 86400
64 +FICHE_V = 2 # v2 : mois « janv./févr. » compris (séances jan-fév récupérées)
65 +# le tile affiche parfois l'anglais selon le fuseau du proxy — mois EN → FR ;
66 +# + abréviations FR « janv. »/« févr. » (tuiles ET fiches, constaté live
67 +# 2026-08-25) que normalize.parse_date_fr ne connaît pas → séances de
68 +# janvier/février silencieusement écartées sans cette table
45 69 _EN_MONTHS = {"jan": "jan", "feb": "fev", "mar": "mars", "apr": "avr",
46 70 "may": "mai", "jun": "juin", "jul": "juil", "aug": "aout",
47 − "sep": "sept", "oct": "oct", "nov": "nov", "dec": "dec"}
71 + "sep": "sept", "oct": "oct", "nov": "nov", "dec": "dec",
72 + "janv": "jan", "fevr": "fev"}
73 +
74 +# fragments de la fiche rendue (classes stables tuxCmsEdit__webEventSelection)
75 +_F_CAT_RE = re.compile(
76 + r'webEventSelection--category"[^>]*>\s*<span[^>]*>\s*([^<]+?)\s*</span>')
77 +_F_DESC_RE = re.compile(r'webEventSelection--description"[^>]*>(.*?)</div>',
78 + re.S)
79 +# un bouton de séance par représentation : jour / date / heure (+ étiquette
80 +# « COMPLET » en surimpression) — découpage par app-event-summary
81 +_F_DATE_RE = re.compile(r'class="date"[^>]*>\s*([^<]+?)\s*<')
82 +_F_HEURE_RE = re.compile(r'class="heure"[^>]*>\s*([^<]+?)\s*<')
83 +_F_OVER_RE = re.compile(r'webEventDateButton--overLabel"[^>]*>\s*([^<]+?)\s*<')
48 84
49 85
50 86 class TuxedoBilletConnector(BaseConnector):
@@ -85,12 +121,74 @@ class TuxedoBilletConnector(BaseConnector):
85 121
86 122 @staticmethod
87 123 def _parse_date(raw: str) -> str | None:
88 − """« 4 sept. 2026 » / « 4 Sep 2026 » → ISO (mois EN traduits)."""
89 − k = clean_text(raw).lower()
124 + """« 4 sept. 2026 » / « 4 Sep 2026 » / « 15 janv. 2027 » → ISO.
125 +
126 + Accents retirés AVANT la table (« févr. » → « fevr » sinon le token
127 + [a-z]+ se fragmente), mois EN et abréviations FR inconnues de
128 + normalize traduits vers des clés que parse_date_fr comprend.
129 + """
130 + k = strip_accents(clean_text(raw)).lower()
90 131 k = re.sub(r"[a-z]+", lambda m: _EN_MONTHS.get(m.group(0), m.group(0)),
91 132 k, count=2)
92 133 return parse_date_fr(k)
93 134
135 + # -- fiches détail (Scrapfly rendu JS, incrémental) -------------------------
136 + def _render_fiche(self, url: str) -> str:
137 + """HTML rendu d'une fiche /main/<slug> (SPA : rendu JS obligatoire).
138 +
139 + Appel Scrapfly direct plutôt que get_rendered() : il faut attendre le
140 + composant Angular (wait_for_selector) et tolérer le statut 422 que
141 + Scrapfly renvoie quand l'attente expire alors que le contenu est là.
142 + """
143 + key = os.environ.get("SCRAPFLY_API_KEY")
144 + if not key:
145 + raise RuntimeError("SCRAPFLY_API_KEY manquant (voir .env)")
146 + self._throttle()
147 + resp = requests.get(SCRAPFLY_API, params={
148 + "key": key, "url": url, "country": "ca", "asp": "true",
149 + "render_js": "true", "rendering_wait": "8000",
150 + "wait_for_selector": ".tuxCmsEdit__webEventSelection--title",
151 + }, timeout=150)
152 + self._last_request = time.time()
153 + try:
154 + content = (resp.json().get("result") or {}).get("content") or ""
155 + except Exception:
156 + content = ""
157 + if "webEventSelection--" not in content:
158 + raise RuntimeError(f"fiche non rendue (HTTP {resp.status_code})")
159 + return content
160 +
161 + def _parse_fiche(self, html: str) -> dict:
162 + """Fiche rendue → catégorie, description, séances datées + heure/statut."""
163 + cat = _F_CAT_RE.search(html)
164 + desc = _F_DESC_RE.search(html)
165 + seances: list[dict] = []
166 + seen: set[tuple] = set()
167 + for chunk in re.split(r"app-event-summary", html)[1:]:
168 + d = _F_DATE_RE.search(chunk)
169 + date_iso = self._parse_date(d.group(1)) if d else None
170 + if not date_iso:
171 + continue # bouton sans date sûre
172 + h = _F_HEURE_RE.search(chunk)
173 + over = _F_OVER_RE.search(chunk)
174 + heure = parse_time(h.group(1)) if h else None
175 + if (date_iso, heure) in seen: # rendu responsive dupliqué
176 + continue
177 + seen.add((date_iso, heure))
178 + seances.append({
179 + "date": date_iso,
180 + "time": heure,
181 + # « COMPLET » → soldout (normalisé, jamais inventé)
182 + "status": normalize_status(clean_text(over.group(1))
183 + if over else ""),
184 + })
185 + return {
186 + "category": clean_text(cat.group(1)) if cat else "",
187 + "description": clean_text(re.sub(r"<[^>]+>", " ", desc.group(1)))
188 + if desc else "",
189 + "seances": seances,
190 + }
191 +
94 192 def fetch(self) -> list[Event]:
95 193 tenants = {t["domaine"]: t for t in self._load_tenants()}
96 194 if not tenants:
@@ -100,7 +198,8 @@ class TuxedoBilletConnector(BaseConnector):
100 198 if now - cache.get("ts", 0) > TTL:
101 199 try:
102 200 items = self._run_actor(sorted(tenants))
103 − cache = {"ts": now, "items": items}
201 + cache = {"ts": now, "items": items,
202 + "fiches": cache.get("fiches") or {}}
104 203 self.save_cache(cache)
105 204 except Exception as exc:
106 205 if not cache.get("items"):
@@ -108,6 +207,35 @@ class TuxedoBilletConnector(BaseConnector):
108 207 print(f"[sorti-ka] tuxedobillet : run Apify échoué ({exc}) — "
109 208 "réémission du cache")
110 209
210 + # fiches détail incrémentales : jamais vue > parseur périmé > plus
211 + # ancienne — chaque fiche coûte un rendu Scrapfly, plafonné par sync
212 + urls = {f"{it['tenant']}-{it['slug']}": it.get("url", "")
213 + for it in cache.get("items", [])
214 + if it.get("tenant") in tenants and it.get("slug")
215 + and it.get("url")}
216 + fiches = {k: v for k, v in (cache.get("fiches") or {}).items()
217 + if k in urls}
218 + stale = sorted(
219 + (k for k in urls
220 + if k not in fiches
221 + or fiches[k].get("v", 0) < FICHE_V
222 + or now - fiches[k].get("ts", 0) > FICHE_REFRESH),
223 + key=lambda k: (fiches.get(k, {}).get("v", 0) if k in fiches else -1,
224 + fiches.get(k, {}).get("ts", 0)))
225 + if stale and not os.environ.get("SCRAPFLY_API_KEY"):
226 + print("[sorti-ka] tuxedobillet : SCRAPFLY_API_KEY absent — "
227 + "fiches non enrichies (liste seule)")
228 + stale = []
229 + for k in stale[:FICHE_MAX]:
230 + try:
231 + row = self._parse_fiche(self._render_fiche(urls[k]))
232 + except Exception as exc:
233 + print(f"[sorti-ka] tuxedobillet : fiche {k} non enrichie ({exc})")
234 + row = None # re-tentée après FICHE_REFRESH
235 + fiches[k] = {"ts": now, "v": FICHE_V, "row": row}
236 + cache["fiches"] = fiches
237 + self.save_cache(cache)
238 +
111 239 events: list[Event] = []
112 240 seen: set[str] = set()
113 241 for it in cache.get("items", []):
@@ -120,22 +248,43 @@ class TuxedoBilletConnector(BaseConnector):
120 248 if key in seen:
121 249 continue
122 250 seen.add(key)
123 − date_iso = self._parse_date(it.get("date_raw", ""))
124 − if not date_iso: # tuile sans date sûre
125 − continue
126 251 subtitle = clean_text(it.get("subtitle", ""))
127 − events.append(Event(
252 + fiche = (fiches.get(key) or {}).get("row") or {}
253 + base = dict(
128 254 source=self.source_id,
129 − external_id=key,
130 255 url=it.get("url", ""),
131 256 # le sous-titre est tantôt l'artiste, tantôt une série — on le
132 257 # garde dans le titre mais jamais comme artiste (pas inventé)
133 258 title=f"{title} — {subtitle}" if subtitle else title,
134 − raw_categories=[title],
259 + description=fiche.get("description", ""),
260 + raw_categories=[fiche["category"]] if fiche.get("category")
261 + else [title],
135 262 venue=tenant["nom"],
136 263 city=tenant["ville"],
137 − start_date=date_iso,
138 264 organizer=tenant["nom"],
139 265 image=it.get("image", ""),
140 − ))
266 + )
267 + seances = fiche.get("seances") or []
268 + if seances:
269 + # 1 Event par séance datée de la fiche (heure + statut) ; les
270 + # séances multiples d'un même jour restent distinctes
271 + per_day: dict[str, int] = {}
272 + for s in seances:
273 + per_day[s["date"]] = per_day.get(s["date"], 0) + 1
274 + for s in seances:
275 + ext = f"{key}-{s['date']}"
276 + if per_day[s["date"]] > 1 and s.get("time"):
277 + ext += f"-{s['time'].replace(':', '')}"
278 + events.append(Event(
279 + external_id=ext,
280 + start_date=s["date"],
281 + start_time=s.get("time"),
282 + status=s.get("status", ""),
283 + **base,
284 + ))
285 + continue
286 + date_iso = self._parse_date(it.get("date_raw", ""))
287 + if not date_iso: # tuile sans date sûre
288 + continue
289 + events.append(Event(external_id=key, start_date=date_iso, **base))
141 290 return events
added sortika/connectors/victoriaville.py +136 −0
@@ -0,0 +1,136 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/victoriaville.py : connecteur Ville de Victoriaville — « Quoi
5 +# faire à Victo » (calendrier municipal officiel).
6 +# Source : https://victoriaville.ca/calendriers-cours-et-ateliers/
7 +# quoi-faire-a-victo (Craft CMS, rendu serveur).
8 +# Extraction: la page liste UNE SEMAINE de séances, jour par jour
9 +# (sections id="day-YYYY-MM-DD", ~115 <article> par semaine :
10 +# titre, lieu, heures « De 9 h à 17 h », image CloudFront,
11 +# lien fiche ?date=YYYY-MM-DD). Navigation par
12 +# ?dateDebut=YYYY-MM-DD → on couvre VIC_WEEKS semaines
13 +# (défaut 4) = 1 GET/semaine. Vérifié 2026-08-25.
14 +# Accès : page municipale publique, robots.txt permissif (Craft :
15 +# /cpresources/ et /vendor/ seulement). GET directs throttlés,
16 +# identification honnête.
17 +# Prix : non publié dans la liste → jamais inventé.
18 +# -----------------------------------------------------------------------------
19 +from __future__ import annotations
20 +
21 +import os
22 +import re
23 +from datetime import date, timedelta
24 +
25 +from ..normalize import clean_text, parse_time
26 +from ..schema import Event
27 +from .base import BaseConnector
28 +
29 +LIST_URL = ("https://victoriaville.ca/calendriers-cours-et-ateliers/"
30 + "quoi-faire-a-victo")
31 +WEEKS = int(os.environ.get("VIC_WEEKS", "4"))
32 +
33 +_DAY_RE = re.compile(r'id="day-(\d{4}-\d{2}-\d{2})"')
34 +_ARTICLE_RE = re.compile(
35 + r'<article class="thumbnails-listing-item">(.*?)</article>', re.S)
36 +_LINK_RE = re.compile(
37 + r'href="(https://victoriaville\.ca/[^"?]+)\?date=(\d{4}-\d{2}-\d{2})"')
38 +_TITLE_RE = re.compile(
39 + r'thumbnails-listing-item-title[^>]*>\s*(.*?)\s*</h3>', re.S)
40 +_LIEU_RE = re.compile(
41 + r'thumbnails-listing-item-lieu[^>]*>\s*(.*?)\s*</p>', re.S)
42 +_HOURS_RE = re.compile(
43 + r'thumbnails-listing-item-hours[^>]*>(.*?)</p>', re.S)
44 +_IMG_RE = re.compile(r'<img[^>]+src="([^"]+)"')
45 +# « De 9 h à 17 h », « 19 h 30 » — début puis fin éventuelle
46 +_RANGE_RE = re.compile(
47 + r"(\d{1,2}\s*h(?:\s*\d{2})?)\s*(?:à|-|–)\s*(\d{1,2}\s*h(?:\s*\d{2})?)", re.I)
48 +
49 +
50 +class VictoriavilleConnector(BaseConnector):
51 + source_id = "victoriaville"
52 + request_delay = 1.0
53 +
54 + def _parse_week(self, html: str) -> list[dict]:
55 + """Découpe la page en sections-jour puis en cartes-séance."""
56 + rows: list[dict] = []
57 + marks = [(m.start(), m.group(1)) for m in _DAY_RE.finditer(html)]
58 + for i, (pos, day) in enumerate(marks):
59 + end = marks[i + 1][0] if i + 1 < len(marks) else len(html)
60 + for block in _ARTICLE_RE.findall(html[pos:end]):
61 + link = _LINK_RE.search(block)
62 + title = _TITLE_RE.search(block)
63 + if not link or not title:
64 + continue
65 + hours_txt = ""
66 + m = _HOURS_RE.search(block)
67 + if m:
68 + hours_txt = clean_text(re.sub(r"<[^>]+>", " ", m.group(1)))
69 + start_time = end_time = None
70 + rng = _RANGE_RE.search(hours_txt)
71 + if rng:
72 + start_time = parse_time(rng.group(1))
73 + end_time = parse_time(rng.group(2))
74 + else:
75 + start_time = parse_time(hours_txt)
76 + # « 00h00 » = placeholder « toute la journée » (vérifié live
77 + # 2026-08-25 : 28 cartes/semaine) → heure jamais inventée
78 + if start_time == "00:00":
79 + start_time = None
80 + if end_time == "00:00":
81 + end_time = None
82 + lieu = _LIEU_RE.search(block)
83 + img = _IMG_RE.search(block)
84 + rows.append({
85 + "url": link.group(1),
86 + "date": day,
87 + "title": clean_text(re.sub(r"<[^>]+>", "", title.group(1))),
88 + "venue": clean_text(re.sub(
89 + r"<[^>]+>", "", lieu.group(1))) if lieu else "",
90 + "start_time": start_time,
91 + "end_time": end_time,
92 + "image": img.group(1) if img else "",
93 + })
94 + return rows
95 +
96 + def fetch(self) -> list[Event]:
97 + rows: list[dict] = []
98 + today = date.today()
99 + for week in range(WEEKS):
100 + url = LIST_URL
101 + params = None
102 + if week:
103 + params = {"dateDebut": (today + timedelta(days=7 * week))
104 + .isoformat(), "recherche": "",
105 + "programmationSpeciale": ""}
106 + try:
107 + rows += self._parse_week(self.get(url, params=params).text)
108 + except Exception as exc: # une semaine cassée ≠ source cassée
109 + if week == 0:
110 + raise
111 + print(f"[sorti-ka] victoriaville : semaine +{week} ignorée : {exc}")
112 + events: list[Event] = []
113 + seen: set[str] = set()
114 + for r in rows:
115 + slug = r["url"].rstrip("/").rsplit("/", 1)[-1]
116 + ext = f"{slug}-{r['date']}"
117 + if ext in seen or not r["title"]:
118 + continue
119 + seen.add(ext)
120 + events.append(Event(
121 + source=self.source_id,
122 + external_id=ext,
123 + url=f"{r['url']}?date={r['date']}",
124 + title=r["title"],
125 + raw_categories=[r["title"]],
126 + venue=r["venue"],
127 + city="Victoriaville",
128 + region="Centre-du-Québec",
129 + start_date=r["date"],
130 + start_time=r["start_time"],
131 + end_date=r["date"],
132 + end_time=r["end_time"],
133 + organizer="Ville de Victoriaville",
134 + image=r["image"],
135 + ))
136 + return events
added sortika/connectors/zeffy.py +252 −0
@@ -0,0 +1,252 @@
1 +# -----------------------------------------------------------------------------
2 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
3 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +# connectors/zeffy.py : connecteur Zeffy (zeffy.com) — billetterie OSBL
5 +# Source : billetterie gratuite très utilisée par les OSBL québécois
6 +# (galas, spectacles-bénéfice, marches, ateliers communautaires).
7 +# Extraction: 1) sitemap des organisations (api.zeffy.com/sitemap/
8 +# organizations/sitemap-0.xml) → pages d'organisation fr-CA/en-CA
9 +# (~4 400, dont ~1 300 fr-CA) ; 2) chaque page d'organisation est
10 +# rendue serveur (Next.js __NEXT_DATA__ → organizationPage.
11 +# upcomingEvents : titre, description, adresse postale complète,
12 +# occurrences startUtc/endUtc, bannière, URL de billetterie) —
13 +# AUCUN rendu JS requis ; 3) fiches billetterie des événements QC
14 +# retenus : JSON-LD (offers[].price) → tarifs réels publiés.
15 +# Périmètre QUÉBEC : on ne garde que les événements datés dont
16 +# l'adresse porte « , QC » (les organisations fr-CA/en-CA
17 +# couvrent tout le Canada). Le sitemap des BILLETS (55 000+ URLs
18 +# fr-CA mêlant dons/adhésions/archives, sans dates) est ignoré :
19 +# la page d'organisation est la seule surface « à venir » fiable.
20 +# INCRÉMENTAL avec cache disque (data/zeffy_cache.json) :
21 +# nouvelles organisations d'abord, re-visite après 7 jours,
22 +# plafonds ZEFFY_ORG_MAX pages org et ZEFFY_DETAIL_MAX fiches
23 +# prix par sync.
24 +# Accès : robots.txt « User-agent: * / Disallow: /*=$ » (seules les URLs
25 +# finissant par « = » sont exclues — aucune de celles qu'on
26 +# visite) + sitemaps publiés ; GET throttlés, identification
27 +# honnête, pas d'anti-bot constaté (sondé 2026-08-25).
28 +# Prix : offers[].price du JSON-LD de la fiche (tarifs affichés par
29 +# l'organisme ; Zeffy suggère un pourboire plateforme au paiement,
30 +# non inclus). is_free=True si TOUS les tarifs publiés sont à 0 $.
31 +# Jamais inventé (événement sans fiche lisible → prix inconnu).
32 +# -----------------------------------------------------------------------------
33 +from __future__ import annotations
34 +
35 +import json
36 +import os
37 +import re
38 +import time
39 +
40 +from ..normalize import clean_text, utc_to_local
41 +from ..schema import Event
42 +from .base import BaseConnector
43 +
44 +ORG_SITEMAP = "https://api.zeffy.com/sitemap/organizations/sitemap-0.xml"
45 +
46 +LOCALES = tuple(l.strip() for l in os.environ.get(
47 + "ZEFFY_LOCALES", "fr-CA,en-CA").split(",") if l.strip())
48 +ORG_MAX = int(os.environ.get("ZEFFY_ORG_MAX", "400"))
49 +DETAIL_MAX = int(os.environ.get("ZEFFY_DETAIL_MAX", "80"))
50 +REFRESH_AFTER = 7 * 86400
51 +
52 +_LOC_RE = re.compile(r"<loc>([^<]+)</loc>")
53 +_NEXT_RE = re.compile(r'__NEXT_DATA__"[^>]*>(\{.*?\})</script>', re.S)
54 +_LD_RE = re.compile(r'<script type="application/ld\+json"[^>]*>(.*?)</script>',
55 + re.S)
56 +# adresse Zeffy : « 69 Rue Wellington N, Sherbrooke, QC J1H 5A9, Canada »
57 +_QC_PART_RE = re.compile(r"^(?:QC|Québec|Quebec)\b\s*(.*)$")
58 +
59 +
60 +def _split_address(raw: str) -> tuple[str, str, str, str] | None:
61 + """(rue, ville, code postal, adresse nettoyée) si l'adresse est au Québec,
62 + sinon None — le filtre de périmètre provincial du connecteur."""
63 + parts = [p.strip() for p in (raw or "").split(",") if p.strip()]
64 + for i, part in enumerate(parts):
65 + m = _QC_PART_RE.match(part)
66 + if m and i >= 1:
67 + street = ", ".join(parts[:i - 1])
68 + city = parts[i - 1]
69 + postal = m.group(1).strip()
70 + return street, city, postal, raw.strip()
71 + return None
72 +
73 +
74 +def _field_fr(fields: list[dict] | None) -> dict:
75 + """ticketingFields : privilégie la variante FR, sinon la première."""
76 + fields = fields or []
77 + for f in fields:
78 + if str(f.get("locale") or "").upper().startswith("FR"):
79 + return f
80 + return fields[0] if fields else {}
81 +
82 +
83 +class ZeffyConnector(BaseConnector):
84 + source_id = "zeffy"
85 + request_delay = 0.8
86 +
87 + # -- découverte (sitemap des organisations) -------------------------------
88 + def _org_urls(self) -> list[str]:
89 + xml = self.get(ORG_SITEMAP).text
90 + return sorted({
91 + u for u in _LOC_RE.findall(xml)
92 + if any(f"/{loc}/organizations/" in u for loc in LOCALES)})
93 +
94 + # -- page d'organisation ---------------------------------------------------
95 + def _parse_org(self, url: str, html: str) -> list[dict]:
96 + m = _NEXT_RE.search(html)
97 + if not m:
98 + return []
99 + try:
100 + page = (json.loads(m.group(1)).get("props", {})
101 + .get("pageProps", {}).get("organizationPage")) or {}
102 + except ValueError:
103 + return []
104 + org_name = clean_text((page.get("organization") or {}).get("name")
105 + or "")
106 + rows: list[dict] = []
107 + for ev in page.get("upcomingEvents") or []:
108 + if ev.get("formCategory") != "Event":
109 + continue # dons, adhésions, tirages : hors périmètre
110 + qc = _split_address(str(ev.get("address") or ""))
111 + if not qc:
112 + continue # sans adresse québécoise : hors périmètre
113 + street, city, postal, _ = qc
114 + field = _field_fr(ev.get("ticketingFields"))
115 + title = clean_text(field.get("title") or "")
116 + ticket_url = ev.get("url") or ""
117 + if not (title and ticket_url):
118 + continue
119 + for occ in ev.get("occurrences") or []:
120 + start_date, start_time = utc_to_local(occ.get("startUtc"))
121 + end_date, end_time = utc_to_local(occ.get("endUtc"))
122 + if not start_date:
123 + continue # occurrence sans date publiée : rejetée
124 + rows.append({
125 + "external_id": f"{ev.get('id')}:{occ.get('id')}",
126 + "url": ticket_url, "title": title,
127 + "description": clean_text(field.get("description")
128 + or "")[:2000],
129 + "raw_categories": [title],
130 + "address": street, "city": city, "postal_code": postal,
131 + "start_date": start_date, "start_time": start_time,
132 + "end_date": end_date, "end_time": end_time,
133 + "organizer": org_name,
134 + "image": ev.get("bannerUrl") or "",
135 + })
136 + return rows
137 +
138 + # -- fiche billetterie (tarifs publiés) ------------------------------------
139 + def _parse_fiche_prices(self, html: str) -> dict | None:
140 + """JSON-LD de la fiche → {is_free, price_min, price_label}, ou None."""
141 + for blob in _LD_RE.findall(html):
142 + try:
143 + d = json.loads(blob.strip())
144 + except ValueError:
145 + continue
146 + offers = d.get("offers")
147 + if not isinstance(offers, list) or not offers:
148 + continue
149 + prices, labels = [], []
150 + for o in offers:
151 + try:
152 + p = float(o.get("price"))
153 + except (TypeError, ValueError):
154 + continue
155 + prices.append(p)
156 + labels.append(f"{clean_text(o.get('name') or '')} {p:g} $")
157 + if not prices:
158 + continue
159 + label = " · ".join(labels[:5]) + (" · …" if len(labels) > 5 else "")
160 + if max(prices) == 0:
161 + return {"is_free": True, "price_min": None,
162 + "price_label": label}
163 + return {"is_free": False,
164 + "price_min": min(p for p in prices if p > 0),
165 + "price_label": label}
166 + return None
167 +
168 + # -- pipeline ---------------------------------------------------------------
169 + def fetch(self) -> list[Event]:
170 + cache = self.load_cache()
171 + try:
172 + org_urls = self._org_urls()
173 + except Exception as exc: # sitemap indisponible → on garde le cache
174 + print(f"[sorti-ka] zeffy : sitemap organisations indisponible : {exc}")
175 + org_urls = []
176 + if org_urls:
177 + current = {f"org::{u}" for u in org_urls}
178 + cache = {k: v for k, v in cache.items()
179 + if not k.startswith("org::") or k in current}
180 + else:
181 + org_urls = [k[5:] for k in cache if k.startswith("org::")]
182 +
183 + now = time.time()
184 + candidates = sorted(
185 + (u for u in org_urls
186 + if f"org::{u}" not in cache
187 + or now - cache[f"org::{u}"].get("fetched_at", 0) > REFRESH_AFTER),
188 + key=lambda u: cache.get(f"org::{u}", {}).get("fetched_at", 0)
189 + )[:ORG_MAX]
190 +
191 + def worker(url, session):
192 + return self._parse_org(url, session.get(url, timeout=30).text)
193 +
194 + for url, rows in self.fetch_many(candidates, worker,
195 + max_workers=4).items():
196 + if rows is None: # page cassée : on garde l'état précédent
197 + rows = cache.get(f"org::{url}", {}).get("rows", [])
198 + cache[f"org::{url}"] = {"fetched_at": now, "rows": rows}
199 +
200 + # lignes courantes (dédupliquées inter-organisations)
201 + rows_by_id: dict[str, dict] = {}
202 + for key, entry in cache.items():
203 + if not key.startswith("org::"):
204 + continue
205 + for row in entry.get("rows", []):
206 + rows_by_id.setdefault(row["external_id"], row)
207 +
208 + # enrichissement prix : fiches billetterie des événements QC retenus
209 + ticket_urls = sorted({r["url"] for r in rows_by_id.values()})
210 + to_fetch = [u for u in ticket_urls
211 + if f"fiche::{u}" not in cache
212 + or now - cache[f"fiche::{u}"].get("fetched_at", 0)
213 + > REFRESH_AFTER][:DETAIL_MAX]
214 +
215 + def price_worker(url, session):
216 + return self._parse_fiche_prices(session.get(url, timeout=30).text)
217 +
218 + for url, prices in self.fetch_many(to_fetch, price_worker,
219 + max_workers=4).items():
220 + if prices is None: # fiche illisible : on garde l'existant
221 + prices = cache.get(f"fiche::{url}", {}).get("prices")
222 + cache[f"fiche::{url}"] = {"fetched_at": now, "prices": prices}
223 + # purge des fiches prix orphelines (événements disparus)
224 + keep = {f"fiche::{u}" for u in ticket_urls}
225 + cache = {k: v for k, v in cache.items()
226 + if not k.startswith("fiche::") or k in keep}
227 + self.save_cache(cache)
228 +
229 + events: list[Event] = []
230 + for row in rows_by_id.values():
231 + prices = (cache.get(f"fiche::{row['url']}", {}).get("prices")
232 + or {})
233 + events.append(Event(
234 + source=self.source_id,
235 + external_id=row["external_id"],
236 + url=row["url"], title=row["title"],
237 + description=row.get("description", ""),
238 + raw_categories=row.get("raw_categories") or [],
239 + address=row.get("address", ""),
240 + city=row.get("city", ""),
241 + postal_code=row.get("postal_code", ""),
242 + start_date=row.get("start_date"),
243 + start_time=row.get("start_time"),
244 + end_date=row.get("end_date"),
245 + end_time=row.get("end_time"),
246 + is_free=prices.get("is_free"),
247 + price_min=prices.get("price_min"),
248 + price_label=prices.get("price_label", ""),
249 + organizer=row.get("organizer", ""),
250 + image=row.get("image", ""),
251 + ))
252 + return events
added tests/fixtures/abitibi_liste.html +3251 −0
@@ -0,0 +1,3251 @@
1 +<!doctype html>
2 +<html lang="fr-CA" prefix="og: https://ogp.me/ns#">
3 +<head>
4 + <meta charset="UTF-8" />
5 + <meta name="viewport" content="width=device-width, initial-scale=1" />
6 + <link rel="profile" href="https://gmpg.org/xfn/11">
7 + <link rel="alternate" href="https://abitibi-temiscamingue.org/festivals-et-evenements/" hreflang="fr" />
8 +<link rel="alternate" href="https://abitibi-temiscamingue.org/en/festivals-and-events/" hreflang="en" />
9 +
10 +<!-- Search Engine Optimization by Rank Math PRO - https://rankmath.com/ -->
11 +<title>Nos festivals et événements en Abitibi-Témiscamingue</title>
12 +<meta name="description" content="Les festivals et événements sont bien vivants chez nous. Viens faire ton tour; il y en a pour tous les goûts, toutes les passions et toutes les saisons!"/>
13 +<meta name="robots" content="follow, index, max-snippet:-1, max-video-preview:-1, max-image-preview:large"/>
14 +<link rel="canonical" href="https://abitibi-temiscamingue.org/festivals-et-evenements/" />
15 +<meta property="og:locale" content="fr_CA" />
16 +<meta property="og:type" content="article" />
17 +<meta property="og:title" content="Nos festivals et événements en Abitibi-Témiscamingue" />
18 +<meta property="og:description" content="Les festivals et événements sont bien vivants chez nous. Viens faire ton tour; il y en a pour tous les goûts, toutes les passions et toutes les saisons!" />
19 +<meta property="og:url" content="https://abitibi-temiscamingue.org/festivals-et-evenements/" />
20 +<meta property="og:site_name" content="Abitibi-Témiscamingue" />
21 +<meta property="og:updated_time" content="2026-07-09T10:02:24-04:00" />
22 +<meta property="og:image" content="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/rodeo-cowboy-chevaux-festival-western-lasarre-jean-caron.jpg" />
23 +<meta property="og:image:secure_url" content="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/rodeo-cowboy-chevaux-festival-western-lasarre-jean-caron.jpg" />
24 +<meta property="og:image:width" content="1920" />
25 +<meta property="og:image:height" content="1080" />
26 +<meta property="og:image:alt" content="Quatre hommes à cheval, sur des montures brunes et blanches, dans l’arène de rodéo du Festival équestre et rodéo professionnel de La Sarre" />
27 +<meta property="og:image:type" content="image/jpeg" />
28 +<meta property="article:published_time" content="2026-01-16T09:38:21-05:00" />
29 +<meta property="article:modified_time" content="2026-07-09T10:02:24-04:00" />
30 +<meta name="twitter:card" content="summary_large_image" />
31 +<meta name="twitter:title" content="Nos festivals et événements en Abitibi-Témiscamingue" />
32 +<meta name="twitter:description" content="Les festivals et événements sont bien vivants chez nous. Viens faire ton tour; il y en a pour tous les goûts, toutes les passions et toutes les saisons!" />
33 +<meta name="twitter:image" content="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/rodeo-cowboy-chevaux-festival-western-lasarre-jean-caron.jpg" />
34 +<meta name="twitter:label1" content="Time to read" />
35 +<meta name="twitter:data1" content="Less than a minute" />
36 +<script type="application/ld+json" class="rank-math-schema-pro">{"@context":"https://schema.org","@graph":[{"@type":"Place","@id":"https://abitibi-temiscamingue.org/#place","address":{"@type":"PostalAddress","streetAddress":"155 ave Dallaire, bureau 100","addressLocality":"Rouyn-Noranda","addressRegion":"Qu\u00e9bec","addressCountry":"CA","postalCode":"J9X 4T3"}},{"@type":"Organization","@id":"https://abitibi-temiscamingue.org/#organization","name":"Tourisme Abitibi-T\u00e9miscamingue","url":"https://abitibi-temiscamingue.org/","address":{"@type":"PostalAddress","streetAddress":"155 ave Dallaire, bureau 100","addressLocality":"Rouyn-Noranda","addressRegion":"Qu\u00e9bec","addressCountry":"CA","postalCode":"J9X 4T3"},"contactPoint":[{"@type":"ContactPoint","telephone":"+1-819-762-8181","contactType":"customer support"}],"location":{"@id":"https://abitibi-temiscamingue.org/#place"}},{"@type":"WebSite","@id":"https://abitibi-temiscamingue.org/#website","url":"https://abitibi-temiscamingue.org","name":"Abitibi-T\u00e9miscamingue","publisher":{"@id":"https://abitibi-temiscamingue.org/#organization"},"inLanguage":"fr-CA"},{"@type":"ImageObject","@id":"https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/rodeo-cowboy-chevaux-festival-western-lasarre-jean-caron.jpg","url":"https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/rodeo-cowboy-chevaux-festival-western-lasarre-jean-caron.jpg","width":"1920","height":"1080","caption":"Quatre hommes \u00e0 cheval, sur des montures brunes et blanches, dans l\u2019ar\u00e8ne de rod\u00e9o du Festival \u00e9questre et rod\u00e9o professionnel de La Sarre","inLanguage":"fr-CA"},{"@type":"WebPage","@id":"https://abitibi-temiscamingue.org/festivals-et-evenements/#webpage","url":"https://abitibi-temiscamingue.org/festivals-et-evenements/","name":"Nos festivals et \u00e9v\u00e9nements en Abitibi-T\u00e9miscamingue","datePublished":"2026-01-16T09:38:21-05:00","dateModified":"2026-07-09T10:02:24-04:00","isPartOf":{"@id":"https://abitibi-temiscamingue.org/#website"},"primaryImageOfPage":{"@id":"https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/rodeo-cowboy-chevaux-festival-western-lasarre-jean-caron.jpg"},"inLanguage":"fr-CA"},{"@type":"Person","@id":"https://abitibi-temiscamingue.org/festivals-et-evenements/#author","name":"Tourisme Abitibi-T\u00e9miscamingue","image":{"@type":"ImageObject","@id":"https://secure.gravatar.com/avatar/f9ce69cf3d473b735bd06de2471a42781ab6887c33b6c78a01719e28dc764015?s=96&amp;d=mm&amp;r=g","url":"https://secure.gravatar.com/avatar/f9ce69cf3d473b735bd06de2471a42781ab6887c33b6c78a01719e28dc764015?s=96&amp;d=mm&amp;r=g","caption":"Tourisme Abitibi-T\u00e9miscamingue","inLanguage":"fr-CA"},"worksFor":{"@id":"https://abitibi-temiscamingue.org/#organization"}},{"@type":"Article","headline":"Nos festivals et \u00e9v\u00e9nements en Abitibi-T\u00e9miscamingue","keywords":"Festivals et \u00e9v\u00e9nements","datePublished":"2026-01-16T09:38:21-05:00","dateModified":"2026-07-09T10:02:24-04:00","author":{"@id":"https://abitibi-temiscamingue.org/festivals-et-evenements/#author","name":"Tourisme Abitibi-T\u00e9miscamingue"},"publisher":{"@id":"https://abitibi-temiscamingue.org/#organization"},"description":"Les festivals et \u00e9v\u00e9nements sont bien vivants chez nous. Viens faire ton tour; il y en a pour tous les go\u00fbts, toutes les passions et toutes les saisons!","name":"Nos festivals et \u00e9v\u00e9nements en Abitibi-T\u00e9miscamingue","@id":"https://abitibi-temiscamingue.org/festivals-et-evenements/#richSnippet","isPartOf":{"@id":"https://abitibi-temiscamingue.org/festivals-et-evenements/#webpage"},"image":{"@id":"https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/rodeo-cowboy-chevaux-festival-western-lasarre-jean-caron.jpg"},"inLanguage":"fr-CA","mainEntityOfPage":{"@id":"https://abitibi-temiscamingue.org/festivals-et-evenements/#webpage"}}]}</script>
37 +<!-- /Rank Math WordPress SEO plugin -->
38 +
39 +<link rel='dns-prefetch' href='//maps.googleapis.com' />
40 +<link rel='dns-prefetch' href='//unpkg.com' />
41 +<link rel='dns-prefetch' href='//www.googletagmanager.com' />
42 +<link rel="alternate" type="application/rss+xml" title="Tourisme Abitibi-Témiscamingue &raquo; Feed" href="https://abitibi-temiscamingue.org/feed/" />
43 +<link rel="alternate" type="application/rss+xml" title="Tourisme Abitibi-Témiscamingue &raquo; Comments Feed" href="https://abitibi-temiscamingue.org/comments/feed/" />
44 +<link rel="alternate" title="oEmbed (JSON)" type="application/json+oembed" href="https://abitibi-temiscamingue.org/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fabitibi-temiscamingue.org%2Ffestivals-et-evenements%2F&#038;lang=fr" />
45 +<link rel="alternate" title="oEmbed (XML)" type="text/xml+oembed" href="https://abitibi-temiscamingue.org/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fabitibi-temiscamingue.org%2Ffestivals-et-evenements%2F&#038;format=xml&#038;lang=fr" />
46 +
47 +<link data-optimized="2" rel="stylesheet" href="https://abitibi-temiscamingue.org/wp-content/litespeed/css/9ba37895054dfb7078c96a48d3682d87.css?ver=96be5" />
48 +
49 +
50 +
51 +
52 +
53 +
54 +
55 +
56 +
57 +
58 +
59 +
60 +
61 +
62 +
63 +
64 +<script src="https://abitibi-temiscamingue.org/wp-includes/js/jquery/jquery.min.js?ver=3.7.1" id="jquery-core-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
65 +<script src="https://abitibi-temiscamingue.org/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.4.1" id="jquery-migrate-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
66 +
67 +<!-- Google tag (gtag.js) snippet added by Site Kit -->
68 +<!-- Google Analytics snippet added by Site Kit -->
69 +<script src="https://www.googletagmanager.com/gtag/js?id=GT-TXHQDSW" id="google_gtagjs-js" async type="0316cf47fcdef64099276bd1-text/javascript"></script>
70 +<script id="google_gtagjs-js-after" type="0316cf47fcdef64099276bd1-text/javascript">
71 +window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}
72 +gtag("set","linker",{"domains":["abitibi-temiscamingue.org"]});
73 +gtag("js", new Date());
74 +gtag("set", "developer_id.dZTNiMT", true);
75 +gtag("config", "GT-TXHQDSW");
76 + window._googlesitekit = window._googlesitekit || {}; window._googlesitekit.throttledEvents = []; window._googlesitekit.gtagEvent = (name, data) => { var key = JSON.stringify( { name, data } ); if ( !! window._googlesitekit.throttledEvents[ key ] ) { return; } window._googlesitekit.throttledEvents[ key ] = true; setTimeout( () => { delete window._googlesitekit.throttledEvents[ key ]; }, 5 ); gtag( "event", name, { ...data, event_source: "site-kit" } ); };
77 +//# sourceURL=google_gtagjs-js-after
78 +</script>
79 +<link rel="https://api.w.org/" href="https://abitibi-temiscamingue.org/wp-json/" /><link rel="alternate" title="JSON" type="application/json" href="https://abitibi-temiscamingue.org/wp-json/wp/v2/pages/6235" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="https://abitibi-temiscamingue.org/xmlrpc.php?rsd" />
80 +<meta name="generator" content="WordPress 6.9.7" />
81 +<link rel='shortlink' href='https://abitibi-temiscamingue.org/?p=6235' />
82 +<meta name="generator" content="Site Kit by Google 1.176.0" /> <link rel="icon" type="image/png" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/favicons/favicon-96x96.png" sizes="96x96" />
83 + <link rel="icon" type="image/svg+xml" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/favicons/favicon.svg" />
84 + <link rel="shortcut icon" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/favicons/favicon.ico" />
85 + <link rel="apple-touch-icon" sizes="180x180" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/favicons/apple-touch-icon.png" />
86 + <link rel="manifest" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/favicons/site.webmanifest" />
87 +
88 +
89 + <link rel="preload" as="image" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/images/textures/attrat-motif-01.png">
90 + <link rel="preload" as="image" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/images/textures/attrat-motif-02.png">
91 + <link rel="preload" as="image" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/images/textures/attrat-motif-03.png">
92 + <link rel="preload" as="image" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/images/textures/attrat-motif-04.png">
93 + </head>
94 +<body data-cmplz=1 class="wp-singular page-template-default page page-id-6235 wp-embed-responsive wp-theme-lebleu saison-ete">
95 + <div class="preloader">
96 + <div class="preloader-svg">
97 + <svg
98 + class="logo-svg"
99 + viewBox="0 0 124 30"
100 + role="img"
101 + >
102 + <defs>
103 + <clipPath id="logoClip">
104 + <path d="M22.7901 11.6159C22.7876 11.568 22.7447 11.5151 22.7422 11.4672C22.7245 11.2025 22.7245 11.0084 22.6691 10.7437C22.6565 10.6832 22.6741 10.6177 22.6615 10.5572C22.6439 10.4589 22.5758 10.2824 22.5733 10.2018C22.5733 10.1539 22.6136 10.1337 22.6111 10.1009C22.6111 10.0732 22.5708 10.0304 22.5632 9.99254C22.5405 9.90432 22.5531 9.81357 22.538 9.73291C22.5002 9.53125 22.4044 9.26153 22.3464 9.05231C22.3162 8.94391 22.2859 8.83804 22.2683 8.72965C22.2481 8.60109 22.1952 8.54312 22.1523 8.4322C21.9708 7.95074 21.7969 7.28778 21.4591 6.8769C21.439 6.8517 21.4238 6.83405 21.4112 6.80632C21.1516 6.23916 20.5542 5.61653 20.0601 5.16784C19.7425 4.88047 19.4425 4.66117 19.0619 4.43178C18.6107 4.15954 18.1696 3.97049 17.6654 3.77387C17.3377 3.64531 17.0176 3.54952 16.6722 3.4739C16.6193 3.4613 16.5311 3.4008 16.4807 3.39576C16.4479 3.39072 16.4176 3.40836 16.3672 3.4008C16.1983 3.37811 15.9664 3.26216 15.8177 3.25207C15.7799 3.24955 15.7547 3.27224 15.7194 3.26972C15.6765 3.26972 15.6135 3.23191 15.5656 3.22183C15.3236 3.17645 15.0438 3.16637 14.7918 3.13864C14.5901 3.11595 14.3002 3.05294 14.1036 3.04789C14.0456 3.04789 13.9902 3.07058 13.9347 3.06806C13.8818 3.06554 13.8339 3.02773 13.7885 3.02521C13.6902 3.01765 13.5037 3.04285 13.3978 3.04033C13.3348 3.04033 13.2844 3.01008 13.2188 3.00756C13.1432 3.00756 13.0751 3.03277 12.9869 3.02269C12.7777 2.99748 12.7121 3.02017 12.5231 3.02269C12.4626 3.02269 12.4046 3 12.3441 3C12.2735 3 12.2055 3.02521 12.1299 3.02269C11.9232 3.01008 11.8475 3.02269 11.6484 3.03781C11.5803 3.04285 11.5224 3.02017 11.4518 3.02269C11.3862 3.02269 11.3358 3.05294 11.2728 3.05546C10.814 3.06302 10.3653 3.11847 9.91412 3.18401C9.72759 3.21174 9.51585 3.21678 9.33435 3.24955C9.28142 3.25964 9.22092 3.30753 9.16546 3.31761C9.07472 3.33274 8.91087 3.31761 8.83525 3.33778C8.79491 3.34786 8.76214 3.4134 8.70417 3.41844C8.65879 3.42348 8.59073 3.39324 8.53024 3.41088C8.50251 3.41844 8.49747 3.46382 8.44957 3.47642C8.40924 3.4865 8.36387 3.47894 8.32354 3.48902C8.16977 3.52431 8.06642 3.57725 7.93282 3.62262C7.83451 3.65539 7.73368 3.64279 7.63033 3.69573C7.45892 3.78899 7.32532 3.80412 7.15139 3.8747C7.1035 3.89486 7.09593 3.93268 7.07577 3.94276C7.05056 3.95536 7.01275 3.9478 6.98754 3.95788C6.71026 4.07888 6.45819 4.23516 6.18594 4.35616C6.08259 4.40153 6.06747 4.47464 6.00193 4.52001C5.91118 4.58303 5.81287 4.57799 5.70448 4.67882C5.58097 4.79477 5.53307 4.79729 5.40956 4.89056C5.32637 4.95358 5.31377 5.00903 5.25831 5.05945C5.2205 5.09474 5.13227 5.12499 5.0869 5.16028C4.99363 5.23086 4.95582 5.33673 4.8928 5.39219C4.79702 5.47789 4.72139 5.5157 4.6256 5.62914C4.45167 5.83332 4.29791 6.00977 4.15927 6.23159C4.12398 6.28957 4.01558 6.39796 3.96769 6.47106C3.92736 6.53408 3.87946 6.59458 3.84417 6.64752C3.82149 6.68281 3.82149 6.7307 3.80132 6.76095C3.77863 6.79372 3.71814 6.80632 3.70805 6.82901C3.69041 6.87186 3.72066 6.92732 3.69293 6.97773C3.66772 7.02059 3.59966 7.01554 3.58454 7.05083C3.56941 7.0836 3.57445 7.14158 3.55429 7.18696C3.48875 7.34072 3.38288 7.54742 3.30473 7.70119C3.28709 7.73396 3.24172 7.77177 3.23415 7.80202C3.21651 7.88268 3.29717 7.94318 3.13332 7.99107C3.12828 8.06922 3.12828 8.21038 3.09299 8.27844C3.07787 8.30617 3.0451 8.32129 3.02493 8.35406C2.972 8.45237 2.972 8.61622 2.92914 8.71705C2.91654 8.7473 2.87873 8.76242 2.86865 8.78511C2.851 8.83804 2.87621 8.91366 2.86108 8.97164C2.851 9.00441 2.81319 9.02962 2.79807 9.06743C2.76278 9.15566 2.78546 9.22372 2.77286 9.2943C2.75017 9.40773 2.64682 9.60183 2.63422 9.71274C2.62918 9.75055 2.6569 9.78332 2.65186 9.82869C2.6443 9.89675 2.58128 9.99002 2.56868 10.0556C2.54599 10.1539 2.55859 10.2774 2.54347 10.3757C2.52583 10.4917 2.47289 10.6328 2.45777 10.7614C2.44768 10.837 2.43004 10.8924 2.42247 10.9529C2.41491 10.9983 2.44516 11.0387 2.44012 11.084C2.43508 11.1319 2.39223 11.1849 2.39223 11.2479C2.39223 11.2882 2.41995 11.3437 2.41491 11.3764C2.40987 11.4067 2.3645 11.447 2.35946 11.4873C2.34685 11.5831 2.3771 11.7167 2.36702 11.805C2.3645 11.8503 2.33425 11.8856 2.32921 11.936C2.32417 11.989 2.34937 12.0444 2.34433 12.0797C2.33929 12.1175 2.29392 12.1453 2.29392 12.1856C2.29392 12.231 2.29392 12.3494 2.29392 12.3898C2.29644 12.4276 2.33173 12.4604 2.32921 12.5082C2.32669 12.5561 2.28131 12.5889 2.27375 12.6418C2.26115 12.7376 2.28635 12.9116 2.2914 13.0124C2.29392 13.0628 2.31156 13.204 2.30904 13.2317C2.30652 13.267 2.26619 13.2972 2.25611 13.3376C2.23846 13.4258 2.26367 13.7485 2.27375 13.8518C2.27879 13.8896 2.30904 13.9325 2.30652 13.9703C2.304 14.0056 2.26619 14.0308 2.25863 14.0635C2.23594 14.1467 2.28131 14.1896 2.28383 14.2526C2.28383 14.2879 2.25611 14.3207 2.25611 14.3559C2.25358 14.4366 2.30652 14.5954 2.30904 14.6786C2.30904 14.7391 2.27627 14.7744 2.27123 14.8248C2.26367 14.8954 2.30652 14.9332 2.30904 14.9861C2.31156 15.0643 2.29644 15.092 2.30652 15.1928C2.32164 15.329 2.3166 15.455 2.32417 15.5886C2.32669 15.6415 2.36198 15.6919 2.36198 15.7348C2.36198 15.8281 2.33425 15.944 2.34433 16.0423C2.34937 16.0827 2.39475 16.123 2.39727 16.1759C2.40231 16.2263 2.3771 16.2642 2.37962 16.307C2.37962 16.3599 2.425 16.4204 2.43256 16.4709C2.48549 16.8238 2.48801 17.1767 2.56364 17.527C2.59641 17.6783 2.67455 17.8522 2.69976 18.0035C2.71236 18.0816 2.69976 18.185 2.72244 18.2631C2.73505 18.3085 2.79554 18.3639 2.80563 18.4017C2.82075 18.4824 2.80059 18.5732 2.83336 18.6866C2.89385 18.8882 2.96443 19.0823 3.02241 19.2815C3.05266 19.3823 3.14593 19.5109 3.16105 19.5638C3.17366 19.6092 3.15853 19.652 3.17366 19.6999C3.20643 19.8083 3.34507 20.015 3.40052 20.131C3.58958 20.5242 3.72822 20.8317 4.01054 21.1872C4.0786 21.2703 4.0786 21.3485 4.13154 21.4266C4.19708 21.5275 4.36345 21.6938 4.44663 21.7997C4.56511 21.9459 4.70375 22.0845 4.84239 22.2207C5.38939 22.75 6.11536 23.2668 6.87159 23.5819C7.18668 23.7129 7.54715 23.8793 7.8698 23.9801C7.96811 24.0104 8.10423 24.018 8.1975 24.0482C8.25295 24.0659 8.30589 24.1188 8.36639 24.1289C8.7445 24.1919 9.10749 24.2927 9.48812 24.3507C9.53097 24.3583 9.57887 24.3482 9.62424 24.3557C9.66961 24.3633 9.72507 24.4112 9.7528 24.4112C9.78809 24.4112 9.80069 24.3658 9.85867 24.3658C9.92421 24.3658 10.0881 24.4263 10.1838 24.4389C10.2897 24.4515 10.4334 24.4414 10.5342 24.4591C10.5821 24.4667 10.6325 24.5095 10.683 24.5145C10.7409 24.5196 10.7712 24.4868 10.8241 24.4843C10.9628 24.4793 11.1518 24.5448 11.3232 24.5297C11.356 24.5271 11.3837 24.5019 11.414 24.5019C11.4619 24.5019 11.5476 24.5599 11.6106 24.5599C11.6938 24.5599 11.7971 24.5423 11.8979 24.5448C11.9811 24.5448 12.1047 24.575 12.1828 24.5725C12.2307 24.5725 12.266 24.5297 12.324 24.5297C12.3744 24.5297 12.4349 24.57 12.4853 24.57C12.5382 24.57 12.6038 24.5423 12.6643 24.5423C12.7802 24.5423 12.9667 24.575 13.0777 24.5725C13.1129 24.5725 13.1256 24.5322 13.1835 24.5271C13.2566 24.5221 13.3121 24.5599 13.3801 24.5574C13.428 24.5574 13.4709 24.5297 13.5238 24.5271C13.6499 24.5196 13.8137 24.5423 13.9322 24.5271C13.9801 24.5221 14.0179 24.4868 14.0582 24.4843C14.1112 24.4818 14.1641 24.5171 14.2347 24.5145C14.2927 24.512 14.3279 24.4767 14.3784 24.4692C14.4414 24.4616 14.512 24.4742 14.5725 24.4692C14.7464 24.449 14.9077 24.4036 15.0867 24.4414C15.1119 24.4364 15.1043 24.3809 15.1447 24.3683C15.19 24.3557 15.3816 24.3683 15.4598 24.3633C15.5127 24.3583 15.5984 24.3406 15.6513 24.3305C16.1404 24.2347 16.6294 24.1264 17.1083 23.9877C17.1361 23.9801 17.1613 23.9499 17.1966 23.9449C17.2419 23.9373 17.2923 23.9499 17.3327 23.9398C17.3705 23.9297 17.378 23.8793 17.4209 23.8642C17.4688 23.8465 17.5167 23.8592 17.5646 23.8491C17.6226 23.8365 17.6427 23.7835 17.6805 23.7684C17.7486 23.7407 17.8721 23.7407 17.9603 23.7054C18.041 23.6726 18.1015 23.6046 18.1922 23.5718C18.2553 23.5491 18.3485 23.5441 18.409 23.5138C18.4342 23.5012 18.472 23.4458 18.4973 23.4331C18.7367 23.3096 19.0064 23.2012 19.2333 23.055C19.2636 23.0349 19.2888 22.9794 19.3392 22.9592C19.3795 22.9416 19.4274 22.9492 19.4526 22.9366C19.4778 22.924 19.4854 22.8685 19.5131 22.8408C19.5484 22.8055 19.6114 22.8004 19.6467 22.7752C19.677 22.7525 19.687 22.7122 19.7148 22.6946C19.7475 22.6744 19.8206 22.6694 19.8459 22.6442C19.8711 22.6189 19.861 22.5761 19.8862 22.5509C19.9064 22.5307 19.982 22.5131 20.0047 22.4954C20.0324 22.4727 20.0248 22.4349 20.0601 22.4072C20.0904 22.3845 20.1483 22.3795 20.1836 22.3492C20.2341 22.3039 20.3198 22.1551 20.3803 22.1123C20.413 22.0896 20.4634 22.0972 20.4962 22.0593C20.5315 22.0215 20.5164 21.9711 20.5391 21.9358C20.5517 21.9182 20.6248 21.898 20.655 21.8652C20.834 21.6787 20.9726 21.4518 21.1466 21.2653C21.2222 21.1846 21.2751 21.0233 21.3407 20.93C21.4516 20.7788 21.4793 20.6906 21.5675 20.5444C21.5902 20.5091 21.6331 20.4914 21.6532 20.4536C21.686 20.3856 21.7036 20.2545 21.7339 20.199C21.744 20.1789 21.8045 20.1663 21.8272 20.1108C21.8877 19.9596 21.9557 19.8058 22.0061 19.6546C22.0414 19.5537 22.0263 19.5714 22.0868 19.4857C22.1347 19.4201 22.1019 19.2714 22.1372 19.2033C22.1498 19.1756 22.2154 19.168 22.228 19.1302C22.2431 19.0874 22.2103 19.0471 22.2154 19.0042C22.2204 18.9639 22.2632 18.9311 22.2784 18.8933C22.3389 18.742 22.3641 18.5127 22.417 18.3438C22.4599 18.2076 22.4649 18.0791 22.4977 17.9404C22.5128 17.8799 22.5582 17.8144 22.5657 17.7464C22.5758 17.6657 22.5506 17.5548 22.5657 17.4817C22.5758 17.4413 22.6288 17.4489 22.6388 17.4086C22.6489 17.3632 22.6187 17.3203 22.6187 17.2775C22.6187 17.217 22.6615 17.0985 22.6716 17.0254C22.6993 16.8389 22.7094 16.6549 22.7397 16.4784C22.8077 16.0827 22.8002 15.6844 22.8329 15.2886C22.8733 14.782 22.843 14.235 22.8506 13.7283C22.8607 13.0225 22.8254 12.3116 22.78 11.6109L22.7901 11.6159ZM21.5574 14.0837C21.5726 14.3156 21.5423 14.4744 21.5398 14.6912C21.5398 14.8374 21.5675 14.9735 21.5574 15.1248C21.5373 15.4323 21.4793 15.76 21.4667 16.065C21.4667 16.1028 21.4894 16.1381 21.4869 16.181C21.4843 16.2289 21.4415 16.2541 21.4364 16.2868C21.4314 16.3247 21.4642 16.3599 21.4617 16.3978C21.4617 16.4456 21.4213 16.5187 21.4138 16.5742C21.4037 16.665 21.4188 16.7708 21.3936 16.8616C21.3835 16.9044 21.3407 16.9347 21.3407 16.9775C21.3407 17.0204 21.3835 17.0582 21.3759 17.1061C21.3709 17.1489 21.3154 17.1767 21.3054 17.2246C21.2953 17.2725 21.3104 17.3304 21.3028 17.3808C21.2902 17.4539 21.2373 17.5951 21.2373 17.6581C21.2373 17.6959 21.2902 17.759 21.2474 17.7917C21.2272 17.8068 21.1768 17.8043 21.1642 17.8346C21.1516 17.8673 21.1818 17.9102 21.1793 17.948C21.1718 18.1093 21.0911 18.2001 21.0583 18.3286C21.0382 18.4068 21.0457 18.4925 21.013 18.5807C21.0004 18.6185 20.96 18.6538 20.9499 18.6891C20.9399 18.7345 20.955 18.7824 20.9424 18.8277C20.9323 18.8605 20.8819 18.9059 20.8617 18.9513C20.8315 19.0244 20.8289 19.1126 20.7987 19.1882C20.7634 19.2815 20.6878 19.3924 20.6525 19.4882C20.6399 19.521 20.65 19.5588 20.6374 19.5915C20.6197 19.6344 20.5617 19.6394 20.5466 19.6621C20.5239 19.6999 20.5466 19.7554 20.534 19.7957C20.5214 19.8335 20.4483 19.8461 20.4256 19.8814C20.408 19.9117 20.413 20.015 20.3803 20.0755C20.3424 20.1511 20.2542 20.2091 20.2038 20.3074C20.0273 20.6528 19.74 20.9376 19.4602 21.2376C19.3896 21.3157 19.3215 21.3863 19.2384 21.4619C19.2056 21.4897 19.1577 21.5048 19.13 21.5325C19.1022 21.5602 19.0997 21.598 19.077 21.6182C19.0543 21.6384 18.9989 21.6434 18.9712 21.6636C18.9434 21.6863 18.9384 21.7216 18.9081 21.7442C18.8754 21.7669 18.8048 21.7695 18.7796 21.7921C18.7519 21.8148 18.7392 21.8905 18.7014 21.9106C18.6712 21.9283 18.6157 21.9207 18.5779 21.9409C18.5628 21.9484 18.4065 22.0493 18.3989 22.0568C18.2805 22.1602 18.3359 22.1123 18.1948 22.1803C18.0486 22.2534 17.8797 22.3644 17.7259 22.4349C17.61 22.4879 17.1411 22.692 17.0478 22.7097C16.9949 22.7198 16.9193 22.7021 16.8739 22.7273C16.8336 22.7525 16.8563 22.8004 16.8184 22.8105C16.7756 22.8231 16.7025 22.8055 16.647 22.8156C16.6193 22.8206 16.5739 22.8534 16.5311 22.866C16.3672 22.9114 16.1404 22.9744 15.979 23.0097C15.8933 23.0298 15.7547 23.0273 15.669 23.0475C15.3892 23.1181 15.248 23.1231 14.9657 23.1584C14.9077 23.1659 14.8623 23.2063 14.8018 23.2138C14.7035 23.2264 14.5271 23.2113 14.4162 23.2214C14.3708 23.2264 14.3279 23.2643 14.2801 23.2693C14.2246 23.2743 14.1439 23.2517 14.0708 23.2592C13.8641 23.2769 13.6196 23.3147 13.4104 23.3172C13.1533 23.3222 12.9012 23.3172 12.6441 23.3172C12.1954 23.3147 11.7442 23.3172 11.2905 23.3021C11.2325 23.3021 11.172 23.2617 11.1266 23.2592C11.0258 23.2517 10.8418 23.2718 10.7409 23.2643C10.6703 23.2592 10.6098 23.2164 10.5367 23.2138C10.383 23.2088 10.3931 23.2138 10.2292 23.1886C9.84354 23.1231 9.29654 23.0802 8.93608 22.9794C8.8781 22.9643 8.82516 22.9139 8.77979 22.9013C8.62855 22.866 8.4773 22.8534 8.31597 22.8004C8.17733 22.7551 8.10423 22.7248 7.95803 22.6845C7.85468 22.6568 7.66814 22.5206 7.56227 22.5005C7.35053 22.4576 7.43371 22.4677 7.26986 22.377C7.13374 22.3013 6.96233 22.2434 6.8363 22.1627C6.74303 22.1047 6.6548 22.019 6.55901 21.9535C6.35736 21.8199 6.14813 21.7039 5.97924 21.5401C5.65407 21.225 5.41712 20.9427 5.15496 20.5897C5.09194 20.504 4.96086 20.378 4.91297 20.2948C4.88524 20.2494 4.90037 20.2041 4.88272 20.1612C4.87012 20.1285 4.80458 20.078 4.78189 20.0402C4.56763 19.6873 4.38865 19.1832 4.2601 18.8C4.22481 18.6967 4.13406 18.5227 4.11137 18.4295C4.09625 18.3715 4.11894 18.306 4.10381 18.248C4.04331 18.0085 3.92736 17.769 3.87694 17.5195C3.85174 17.3934 3.8593 17.2422 3.83913 17.1162C3.83157 17.0657 3.77611 17.0103 3.77359 16.9826C3.77107 16.9599 3.80888 16.9296 3.80636 16.8843C3.80132 16.7859 3.72318 16.6246 3.71814 16.5187C3.71562 16.4683 3.74082 16.4305 3.73326 16.3776C3.7257 16.3247 3.66772 16.2793 3.66268 16.2289C3.65764 16.1608 3.68033 16.0549 3.67024 15.9743C3.66268 15.9163 3.6047 15.7625 3.60974 15.7373C3.61479 15.702 3.66016 15.6919 3.67276 15.6718C3.70553 15.6163 3.61983 15.5583 3.61227 15.5029C3.59966 15.4121 3.61983 15.2836 3.60974 15.1878C3.60218 15.1298 3.5316 15.0113 3.57193 14.9685C3.58454 14.9559 3.61731 14.9534 3.62235 14.9458C3.66016 14.8954 3.57698 14.8147 3.57445 14.7668C3.56941 14.661 3.58454 14.545 3.57445 14.4341C3.56941 14.3736 3.51396 14.3005 3.54925 14.24C3.56185 14.2198 3.60974 14.2072 3.60974 14.1745C3.60974 14.1442 3.55933 14.0988 3.55681 14.0585C3.55429 13.9829 3.5795 13.8594 3.57445 13.7863C3.56941 13.7132 3.54421 13.6249 3.52152 13.5544L3.6047 13.4661C3.6047 13.4258 3.55933 13.388 3.55429 13.3527C3.54673 13.2569 3.58202 13.1183 3.57193 13.0048C3.56689 12.9494 3.51648 12.8662 3.54421 12.8259C3.55933 12.8007 3.62235 12.7981 3.62487 12.7628C3.62487 12.7351 3.58454 12.6797 3.58202 12.6418C3.57193 12.5511 3.60722 12.4326 3.60722 12.3419C3.60722 12.2839 3.55681 12.1679 3.58706 12.1276C3.6047 12.1049 3.6526 12.11 3.66016 12.0671C3.66772 12.0268 3.62487 11.9865 3.62235 11.9386C3.61731 11.8428 3.63999 11.5907 3.65008 11.4899C3.65512 11.4495 3.71057 11.4218 3.71562 11.3739C3.71814 11.3235 3.67276 11.2781 3.6778 11.2302C3.68033 11.1924 3.72066 11.1596 3.73074 11.1143C3.75091 11.021 3.7383 10.9051 3.75091 10.8143C3.77611 10.6505 3.8114 10.5194 3.82401 10.353C3.83409 10.2371 3.92484 10.0631 3.94248 9.94465C3.95257 9.87155 3.92232 9.7808 3.96517 9.71526C3.98029 9.69257 4.02315 9.67493 4.03071 9.65224C4.04331 9.61443 4.01306 9.57914 4.01558 9.54133C4.01558 9.52621 4.05088 9.36488 4.05844 9.34471C4.07104 9.30186 4.11641 9.28169 4.12146 9.25144C4.13406 9.20103 4.11641 9.15566 4.1391 9.09264C4.20212 8.93383 4.24245 8.75234 4.31555 8.60109C4.33572 8.55824 4.3937 8.49774 4.40126 8.46749C4.41134 8.42716 4.39622 8.37927 4.40882 8.34398C4.4189 8.31625 4.47436 8.29608 4.48696 8.26079C4.49957 8.23054 4.48192 8.20281 4.49201 8.16752C4.51973 8.07678 4.63821 7.89781 4.6861 7.80202C4.734 7.70875 4.76677 7.67346 4.80962 7.59784C4.84743 7.5323 4.83483 7.41886 4.90541 7.37097C4.92557 7.35837 4.96338 7.35585 4.97851 7.34072C5.00372 7.30795 4.98607 7.24997 5.00876 7.20964C5.01632 7.192 5.21294 6.91219 5.22554 6.89959C5.37427 6.7433 5.55324 6.54921 5.69692 6.38284C5.73473 6.33746 5.7549 6.2694 5.80279 6.22907C5.84817 6.19126 5.94143 6.16353 5.98429 6.1232C6.02966 6.07783 6.06243 5.98708 6.12545 5.94171C6.17082 5.90894 6.24644 5.89381 6.2893 5.86356C6.32207 5.84088 6.33971 5.7955 6.36996 5.7703C6.5691 5.59889 7.00267 5.34177 7.25474 5.21573C7.3354 5.1754 7.49673 5.13255 7.55219 5.09726C7.5774 5.08213 7.58244 5.05189 7.62025 5.03172C7.68075 5.00147 7.74124 5.01912 7.79922 5.00399C7.86476 4.98635 7.85468 4.93593 7.88241 4.92333C7.93534 4.8956 8.09667 4.883 8.15969 4.86283C8.24791 4.8351 8.39412 4.75192 8.50755 4.72671C8.62098 4.7015 8.65879 4.68386 8.75962 4.65613C8.83777 4.63344 8.90331 4.64857 8.9638 4.63344C8.99909 4.62588 9.03691 4.57294 9.09236 4.56538C9.27134 4.53765 9.33687 4.54774 9.51333 4.48724C9.62424 4.44943 9.71751 4.47716 9.82338 4.46203C9.96958 4.44439 10.146 4.38641 10.3124 4.36876C10.3527 4.36372 10.3981 4.38137 10.4485 4.37381C10.4989 4.36624 10.5342 4.32843 10.5821 4.32339C10.6199 4.32087 10.6577 4.34608 10.7006 4.34608C10.7561 4.34608 10.8544 4.30827 10.9325 4.30322C11.0384 4.29566 11.172 4.31331 11.2703 4.3007C11.3207 4.29314 11.3434 4.25785 11.3963 4.25533C11.4619 4.25281 11.5299 4.3007 11.5904 4.29566C11.6232 4.29566 11.656 4.26289 11.7013 4.25785C11.8173 4.24273 11.9862 4.26037 12.1097 4.25785C12.1651 4.25785 12.2256 4.23264 12.2735 4.23264C12.3366 4.23264 12.3996 4.28054 12.4525 4.28306C12.5004 4.28306 12.5508 4.23769 12.5937 4.23264C12.6416 4.2276 12.7273 4.25533 12.7878 4.25785C12.9062 4.26289 13.0575 4.23769 13.1634 4.24273C13.2239 4.24525 13.2768 4.28558 13.3423 4.2881C13.4028 4.2881 13.4482 4.25785 13.5011 4.25785C13.602 4.25785 13.7784 4.3007 13.8944 4.30322C13.9624 4.30322 14.0255 4.28054 14.0885 4.2881C14.1338 4.29314 14.1691 4.32339 14.217 4.33095C14.5422 4.37381 14.885 4.38137 15.2127 4.4343C15.3262 4.45195 15.5077 4.51749 15.611 4.53261C15.6564 4.53765 15.7068 4.51749 15.7648 4.52001C15.8681 4.52757 15.974 4.60571 16.0421 4.6158C16.0773 4.62084 16.1076 4.60319 16.1404 4.60823C16.1782 4.61328 16.2412 4.65865 16.2966 4.67125C16.3445 4.68134 16.3849 4.66117 16.4202 4.67125C16.4605 4.68134 16.516 4.72923 16.5613 4.74688C16.9949 4.9082 17.5066 5.01155 17.8948 5.24094C18.0334 5.32413 17.983 5.31656 18.157 5.37454C18.3561 5.44008 18.4544 5.57872 18.593 5.68711C18.6611 5.74005 18.7594 5.76021 18.8224 5.80559C18.8905 5.856 18.9888 5.95179 19.0644 6.01481C19.3039 6.21143 19.4854 6.38032 19.677 6.60214C19.8736 6.83153 20.0752 7.10629 20.2366 7.34828C20.2643 7.39114 20.2769 7.43651 20.3072 7.47936C20.3298 7.50961 20.3878 7.54238 20.4029 7.57515C20.4231 7.62304 20.3803 7.65329 20.4029 7.70623C20.413 7.73396 20.4836 7.78941 20.5113 7.83731C20.6701 8.10199 20.7382 8.37927 20.8491 8.64647C20.8642 8.68176 20.9172 8.71705 20.9273 8.75738C20.9399 8.80779 20.9096 8.84056 20.9172 8.88089C20.9298 8.94391 21.0079 9.06239 21.0306 9.13801C21.0987 9.34723 21.134 9.56654 21.1844 9.77828C21.1945 9.82113 21.2449 9.86146 21.2474 9.88415C21.2524 9.9144 21.2096 9.93457 21.202 9.95725C21.1894 9.99758 21.2726 10.1791 21.2852 10.2446C21.3028 10.3303 21.2953 10.4362 21.3079 10.5143C21.318 10.5673 21.386 10.6303 21.3886 10.6807C21.3886 10.7135 21.3583 10.7437 21.3583 10.7916C21.3633 10.8874 21.444 11.0613 21.444 11.1546C21.444 11.1924 21.4012 11.2277 21.4012 11.2554C21.4012 11.2907 21.4566 11.3537 21.4642 11.4016C21.4692 11.4344 21.449 11.4747 21.4516 11.51C21.4843 11.9108 21.5171 12.3091 21.5373 12.7124C21.5499 12.9796 21.57 13.2569 21.5826 13.5266C21.5902 13.7056 21.5398 13.9098 21.5524 14.0888L21.5574 14.0837Z" fill="black"/>
105 + <path d="M18.1343 15.4348C17.7007 15.1979 17.2798 14.9559 16.9092 14.6357C16.8638 14.5979 16.8286 14.5072 16.7882 14.4618C16.6521 14.303 16.5689 14.3459 16.4983 14.1089C16.4857 14.0635 16.4278 13.8896 16.4227 13.8619C16.4151 13.8165 16.4353 13.6653 16.4479 13.6224C16.4706 13.5468 16.5513 13.5392 16.6017 13.4913C16.6319 13.4586 16.6521 13.3981 16.6723 13.3829C16.7302 13.3426 17.0806 13.2695 17.1714 13.2393C17.2445 13.2166 17.4234 13.1712 17.4739 13.1485C17.4991 13.1359 17.7259 12.9544 17.7511 12.9267C17.7839 12.8914 17.8822 12.6418 17.8847 12.5965C17.8847 12.5637 17.7839 12.294 17.7612 12.2536C17.678 12.1049 17.368 11.9864 17.2067 11.9713C16.9294 11.9461 16.7151 12.0192 16.4479 12.0167C16.3017 12.0167 16.3471 12.0343 16.2513 12.052C15.916 12.115 15.3514 12.0898 15.1018 11.8302C14.943 11.6663 14.8548 11.5403 14.7489 11.3437C14.5397 10.9605 14.4288 10.59 14.2498 10.2018C14.2221 10.1413 14.149 10.0556 14.1339 9.99758C14.1188 9.94212 14.144 9.9144 14.1364 9.87154C14.1263 9.81357 14.0532 9.75307 14.028 9.69257C13.9927 9.60182 13.9902 9.48839 13.9675 9.39764C13.9473 9.3195 13.8843 9.26404 13.8642 9.19851C13.7306 8.78762 13.6121 8.36414 13.5138 7.9457C13.4986 7.8852 13.4382 7.83226 13.418 7.77176C13.3071 7.4239 13.3046 7.27014 12.9794 7.03319C12.8912 6.96765 12.8785 7.00294 12.7979 6.98277C12.7449 6.96765 12.687 6.91723 12.6416 6.90463C12.5962 6.89455 12.2383 6.98025 12.1879 7.00546C12.1425 7.03067 11.8904 7.30291 11.8602 7.35584C11.8123 7.43651 11.7946 7.57011 11.7568 7.64573C11.7417 7.67598 11.6988 7.69362 11.6862 7.73395C11.6661 7.79949 11.6913 7.87764 11.6736 7.93561C11.6635 7.9709 11.6106 7.99107 11.593 8.03392C11.5526 8.12719 11.5728 8.26331 11.5476 8.34649C11.5325 8.39691 11.4543 8.52043 11.4316 8.60361C11.356 8.85821 11.2779 9.10776 11.2048 9.35731C11.1821 9.43546 11.172 9.50352 11.1518 9.57158C11.1367 9.62451 11.0611 9.67997 11.0434 9.73038C11.0233 9.79088 11.0283 9.88163 11.0082 9.94465C10.9653 10.0808 10.8342 10.3883 10.7712 10.5269C10.7561 10.5597 10.7132 10.5874 10.7031 10.6202C10.6855 10.6782 10.7284 10.7336 10.7157 10.7866C10.7107 10.8093 10.625 10.8193 10.5998 10.8521C10.5695 10.895 10.5746 10.9832 10.5544 11.0386C10.4889 11.2252 10.2645 11.6134 10.1259 11.752C10.0402 11.8377 9.75282 12.0369 9.64191 12.052C9.5436 12.0671 9.37471 12.0243 9.29909 12.0318C9.2638 12.0369 9.24111 12.0822 9.1907 12.0822C9.1428 12.0822 9.10247 12.0419 9.05962 12.0369C8.99156 12.0293 8.88065 12.052 8.79746 12.0444C8.55547 12.0217 8.26307 11.9234 8.01099 11.9562C7.95301 11.9638 7.92529 11.9965 7.88243 12.0066C7.80933 12.0217 7.63288 12.0268 7.57994 12.0595C7.53205 12.0873 7.53961 12.1352 7.49424 12.1705C7.46147 12.1982 7.40601 12.2007 7.38837 12.2234C7.36568 12.2486 7.3682 12.3116 7.35056 12.3469C7.33039 12.3822 7.27493 12.4024 7.26989 12.4477C7.26485 12.4856 7.30014 12.5183 7.30518 12.5536C7.31527 12.6116 7.29258 12.6645 7.30014 12.7175C7.32787 12.9216 7.46147 12.9595 7.60767 13.0527C7.65052 13.0805 7.67825 13.1309 7.73623 13.1561C7.96814 13.2645 8.27567 13.2443 8.50002 13.4081C8.63109 13.5039 8.75461 13.7283 8.71428 13.8871C8.61849 14.2652 8.30844 14.5551 8.01855 14.7971C7.94293 14.8601 7.88495 14.9231 7.82194 14.976C7.76396 15.0239 7.64044 15.0466 7.56734 15.097C7.50936 15.1349 7.46399 15.213 7.40853 15.2407C7.33795 15.276 7.22704 15.2937 7.14133 15.3315C7.05563 15.3718 6.83632 15.5205 6.77583 15.5357C6.73045 15.5483 6.66239 15.5306 6.62206 15.5558C6.59433 15.5735 6.6019 15.634 6.5666 15.6667C6.52375 15.7045 6.43553 15.6869 6.40528 15.7197C6.37503 15.7474 6.38763 15.8155 6.36242 15.8558C6.33218 15.9062 6.25403 15.8986 6.23639 15.9314C6.22126 16.1507 6.16581 16.5187 6.39015 16.6473C6.75314 16.8515 7.13377 16.7431 7.49424 16.6145C7.66061 16.5566 7.68582 16.5313 7.82446 16.4456C7.89756 16.4003 7.99587 16.3977 8.06393 16.365C8.15467 16.3221 8.437 16.1079 8.53783 16.0348C8.63361 15.9667 8.63361 15.944 8.70167 15.886C8.73444 15.8583 8.8025 15.8407 8.84788 15.8079C8.93862 15.7424 9.21339 15.5029 9.28397 15.4222C9.31674 15.3869 9.33438 15.3189 9.36211 15.2962C9.38984 15.2735 9.44277 15.2685 9.47302 15.2382C9.51335 15.2004 9.51083 15.15 9.5436 15.1122C9.57385 15.0769 9.61923 15.0744 9.63939 15.0542C9.70493 14.9887 9.82341 14.7996 9.89399 14.7164C9.99482 14.5929 10.3628 14.1467 10.4763 14.0812C10.5242 14.0535 10.7813 13.9224 10.8267 13.9123C10.8947 13.8972 10.9451 13.9299 11.0082 13.9173C11.0485 13.9098 11.0611 13.8694 11.0989 13.8644C11.1695 13.8543 11.4871 13.9829 11.5577 14.0283C11.6535 14.0887 11.7493 14.2375 11.7971 14.3408C11.8602 14.4769 11.9434 14.8979 11.9459 15.0492C11.9459 15.1349 11.9106 15.2407 11.9131 15.3163C11.9131 15.3642 11.9484 15.4121 11.9459 15.4575C11.9434 15.4953 11.9081 15.5256 11.9081 15.5835C11.9081 15.6768 11.9459 15.8054 11.9459 15.9037C11.9459 16.0045 11.9106 16.1079 11.9131 16.191C11.9131 16.2389 11.9484 16.2868 11.9459 16.3322C11.9434 16.37 11.9106 16.4003 11.9081 16.4582C11.9055 16.5515 11.9434 16.6826 11.9459 16.7784C11.9459 16.8868 11.9106 16.9952 11.9131 17.0834C11.9131 17.1313 11.9484 17.1792 11.9459 17.2246C11.9434 17.2624 11.9081 17.2926 11.9081 17.3506C11.9081 17.4388 11.9434 17.5623 11.9459 17.6531C11.9459 17.7615 11.9106 17.8699 11.9131 17.9581C11.9131 18.006 11.9484 18.0539 11.9459 18.0993C11.9434 18.1371 11.9106 18.1673 11.9081 18.2253C11.9055 18.3236 11.9459 18.4597 11.9459 18.5631C11.9459 18.6614 11.9106 18.7672 11.9131 18.8504C11.9131 18.8983 11.9484 18.9462 11.9459 18.9916C11.9434 19.0294 11.9081 19.0597 11.9081 19.1176C11.9081 19.2285 11.9484 19.3747 11.956 19.4932C11.961 19.5663 11.9333 19.6596 11.9459 19.7428C11.9534 19.7982 12.0089 19.8537 12.0114 19.9091C12.0139 19.9545 11.9887 19.9873 12.0013 20.0427C12.0089 20.0755 12.145 20.2671 12.1753 20.2973C12.2458 20.3704 12.5332 20.4889 12.629 20.4788C12.687 20.4738 13.0273 20.2671 13.0575 20.2167C13.0827 20.1763 13.1331 20.0226 13.1483 19.9696C13.2365 19.6747 13.2088 19.521 13.2088 19.2285C13.2088 18.9613 13.234 18.7068 13.2264 18.4421C13.2188 18.1471 13.2365 17.8598 13.2264 17.5674C13.2163 17.2674 13.234 16.975 13.2264 16.675C13.2188 16.3801 13.2365 16.0927 13.2264 15.8003C13.2163 15.5004 13.2264 15.2029 13.2365 14.9055C13.2441 14.6786 13.2617 14.6786 13.3247 14.4719C13.3424 14.419 13.3651 14.245 13.3777 14.2223C13.3928 14.1971 13.4634 14.1921 13.4961 14.1618C13.5289 14.1316 13.5314 14.0812 13.5566 14.061C13.5844 14.0383 13.6549 14.0358 13.6978 14.0056C13.7306 13.9829 13.7356 13.9526 13.7583 13.9375C13.8616 13.8619 14.2851 13.8972 14.4036 13.9476C14.5548 14.0131 14.701 14.1719 14.8246 14.2753C14.8699 14.3131 14.9355 14.3333 14.9859 14.3812C15.1144 14.5022 15.2833 14.7794 15.4043 14.9256C15.8808 15.4953 16.3824 15.9163 17.0075 16.302C17.3327 16.5011 17.5848 16.6171 17.9578 16.7053C18.1066 16.7406 18.3259 16.8011 18.4746 16.7507C18.5048 16.7406 18.5301 16.7103 18.5527 16.7053C18.5981 16.6927 18.6788 16.7128 18.7141 16.6776C18.7342 16.6599 18.7418 16.5969 18.772 16.5666C18.8023 16.5364 18.8502 16.5313 18.8704 16.5036C18.9334 16.423 18.9309 16.1936 18.9082 16.0927C18.8124 15.6642 18.462 15.6062 18.1318 15.4247L18.1343 15.4348ZM13.4608 12.0444C13.4533 12.0898 13.4205 12.12 13.4129 12.1553C13.4029 12.2032 13.4205 12.2637 13.4003 12.3041C13.3852 12.3368 13.2995 12.352 13.2667 12.3847C13.2415 12.4125 13.234 12.4553 13.2113 12.473C13.1583 12.5082 12.876 12.5939 12.8054 12.604C12.7223 12.6141 12.6643 12.5813 12.5962 12.5889C12.5332 12.5965 12.513 12.6519 12.4374 12.6444C12.3895 12.6393 12.3467 12.5939 12.3013 12.5813C12.0265 12.5032 11.9106 12.5007 11.7291 12.2436C11.656 12.1402 11.6963 12.0948 11.6938 11.9764C11.6888 11.6638 11.7719 11.5504 11.8778 11.2706C11.8929 11.2327 11.8829 11.1849 11.898 11.1496C11.9106 11.1193 11.9736 11.0891 11.9862 11.0588C12.0013 11.021 11.9912 10.9807 12.0089 10.9403C12.0442 10.8597 12.1148 10.7891 12.1601 10.716C12.1853 10.6782 12.1904 10.6227 12.2131 10.59C12.2206 10.5799 12.4021 10.4488 12.4198 10.4387C12.4651 10.416 12.6063 10.3858 12.6517 10.4034C12.6567 10.4034 12.6592 10.4261 12.6693 10.4337C13.0575 10.6883 13.0777 10.9 13.2592 11.2857C13.2793 11.3311 13.3499 11.3865 13.3625 11.4319C13.3751 11.4747 13.3323 11.5025 13.3348 11.5478C13.3348 11.5831 13.3978 11.6613 13.4155 11.7167C13.4407 11.7949 13.4709 11.9688 13.4583 12.0469L13.4608 12.0444Z" fill="black"/>
106 + <path d="M33.1932 10.6883H36.9667L37.6473 12.5259H39.4976L36.1626 3.66553H34.0906L30.7556 12.5259H32.5151L33.1957 10.6883H33.1932ZM34.8191 6.29718L35.0812 5.38972L35.3408 6.29718L36.4651 9.33216H33.6948L34.8191 6.29718Z" fill="black"/>
107 + <path d="M41.7008 11.7898C42.152 12.4125 42.7645 12.725 43.5384 12.725C44.4257 12.725 45.134 12.4049 45.6684 11.7671C46.2028 11.1294 46.47 10.2144 46.47 9.02457C46.47 8.26079 46.3516 7.60287 46.1171 7.05587C45.8827 6.50887 45.5449 6.08791 45.1063 5.79046C44.6677 5.49553 44.1434 5.34681 43.5359 5.34681C43.0242 5.34681 42.578 5.49553 42.1999 5.79046C42.0108 5.93666 41.8445 6.11563 41.6983 6.32738V3.14368H40.0295V12.5259H41.6983V11.7873L41.7008 11.7898ZM42.0915 7.31551C42.3511 6.92479 42.7343 6.72817 43.2384 6.72817C43.7426 6.72817 44.1005 6.92479 44.3652 7.32055C44.6299 7.71631 44.7635 8.28347 44.7635 9.02205C44.7635 9.76063 44.6324 10.3429 44.3728 10.7412C44.1131 11.142 43.735 11.3411 43.2384 11.3411C42.7419 11.3411 42.3637 11.142 42.0991 10.7412C41.8344 10.3404 41.7033 9.76819 41.7033 9.02205C41.7033 8.27591 41.8319 7.70622 42.094 7.31551H42.0915Z" fill="black"/>
108 + <path d="M48.9605 5.54346H47.2917V12.5284H48.9605V5.54346Z" fill="black"/>
109 + <path d="M48.9605 3.14368H47.2917V4.75947H48.9605V3.14368Z" fill="black"/>
110 + <path d="M50.314 10.3504C50.314 11.1319 50.4855 11.7217 50.8283 12.1225C51.1711 12.5208 51.7005 12.7225 52.4113 12.7225C52.6382 12.7225 52.8423 12.7073 53.0238 12.6771C53.2053 12.6468 53.3415 12.604 53.4272 12.5536V11.3285H53.1146C52.6533 11.3285 52.3508 11.2327 52.2021 11.0411C52.0534 10.8495 51.9803 10.5723 51.9803 10.2068V6.83148H53.4524V5.54086H51.9803V3.94019H50.3115V5.54338H49.4116V6.834H50.3115V10.353L50.314 10.3504Z" fill="black"/>
111 + <path d="M55.711 5.54346H54.0422V12.5284H55.711V5.54346Z" fill="black"/>
112 + <path d="M55.711 3.14368H54.0422V4.75947H55.711V3.14368Z" fill="black"/>
113 + <path d="M60.3492 12.7225C61.234 12.7225 61.9448 12.4024 62.4792 11.7646C63.0136 11.1269 63.2808 10.2118 63.2808 9.02205C63.2808 8.25827 63.1649 7.60035 62.9279 7.05335C62.6935 6.50635 62.3557 6.08538 61.9171 5.78794C61.4785 5.49301 60.9542 5.34429 60.3467 5.34429C59.835 5.34429 59.3888 5.49301 59.0107 5.78794C58.8216 5.93414 58.6553 6.11563 58.5091 6.32486V3.14368H56.8403V12.5259H58.5091V11.7873C58.9603 12.4099 59.5728 12.7225 60.3467 12.7225H60.3492ZM58.9023 7.31551C59.1645 6.92479 59.5451 6.72817 60.0492 6.72817C60.5534 6.72817 60.9113 6.92479 61.176 7.32055C61.4407 7.71631 61.5743 8.28347 61.5743 9.02205C61.5743 9.76063 61.4432 10.3429 61.1836 10.7412C60.9239 11.142 60.5458 11.3411 60.0492 11.3411C59.5527 11.3411 59.1745 11.142 58.9099 10.7412C58.6452 10.3404 58.5116 9.76819 58.5116 9.02205C58.5116 8.27591 58.6427 7.70622 58.9023 7.31551Z" fill="black"/>
114 + <path d="M65.7688 3.14368H64.1001V4.75947H65.7688V3.14368Z" fill="black"/>
115 + <path d="M65.7688 5.54346H64.1001V12.5284H65.7688V5.54346Z" fill="black"/>
116 + <path d="M38.4161 16.5137V15.0693H35.5627H33.8032H30.9497V16.5137H33.8032V23.9298H35.5627V16.5137H38.4161Z" fill="black"/>
117 + <path d="M42.1368 22.4828C41.8722 22.6643 41.5445 22.7575 41.1537 22.7575C40.6067 22.7575 40.1883 22.5584 39.8959 22.1576C39.6489 21.8198 39.5077 21.3585 39.4699 20.7762H44.3979V20.3855C44.3979 19.6293 44.2618 18.984 43.9946 18.4446C43.7249 17.9051 43.3443 17.4892 42.8477 17.1943C42.3511 16.8993 41.7663 16.7506 41.0882 16.7506C40.4101 16.7506 39.8329 16.8943 39.3287 17.1817C38.8246 17.469 38.4364 17.8875 38.1616 18.4395C37.8894 18.9916 37.7507 19.6621 37.7507 20.4536C37.7507 21.2451 37.8869 21.8627 38.1616 22.4147C38.4364 22.9668 38.8296 23.3902 39.3413 23.6852C39.853 23.9801 40.458 24.1288 41.1537 24.1288C41.9881 24.1288 42.6964 23.9171 43.2838 23.4961C43.8711 23.0752 44.2593 22.4601 44.4509 21.6509H42.6914C42.5855 22.024 42.4015 22.3038 42.1368 22.4853V22.4828ZM41.0882 18.1169C41.61 18.1169 42.0083 18.3135 42.2881 18.7092C42.4746 18.9739 42.5956 19.3142 42.6586 19.7327H39.5127C39.5758 19.3192 39.6967 18.9789 39.8782 18.7168C40.1555 18.316 40.5614 18.1169 41.0907 18.1169H41.0882Z" fill="black"/>
118 + <path d="M40.2942 16.1633H41.8041L43.2132 14.5475H41.3504L40.2942 16.1633Z" fill="black"/>
119 + <path d="M54.0877 17.0304C53.7448 16.8439 53.3516 16.7506 52.9079 16.7506C52.4214 16.7506 51.9879 16.8666 51.6047 17.096C51.2795 17.2926 51.0123 17.5749 50.7981 17.9379C50.788 17.9152 50.7804 17.8925 50.7704 17.8698C50.5889 17.4968 50.3343 17.2169 50.0091 17.0304C49.6839 16.8439 49.3033 16.7506 48.8697 16.7506C48.4084 16.7506 48.0001 16.8666 47.6446 17.096C47.3623 17.28 47.1228 17.5396 46.9287 17.8673V16.9447H45.26V23.9297H46.9287V19.8763C46.9287 19.6419 46.959 19.4176 47.0195 19.2058C47.08 18.9916 47.1707 18.805 47.2942 18.6462C47.4152 18.4849 47.5589 18.3588 47.7228 18.2681C47.8891 18.1774 48.0757 18.132 48.2824 18.132C48.5244 18.132 48.7311 18.1874 48.8949 18.3009C49.0613 18.4143 49.1823 18.5807 49.2604 18.8025C49.3386 19.0243 49.3789 19.2966 49.3789 19.6167V23.9297H51.0476V19.8763C51.0476 19.6419 51.0779 19.4176 51.1384 19.2058C51.1989 18.9916 51.2896 18.805 51.4131 18.6462C51.5341 18.4849 51.6778 18.3588 51.8442 18.2681C52.008 18.1774 52.1971 18.132 52.4038 18.132C52.6559 18.132 52.8626 18.1874 53.0239 18.3009C53.1852 18.4143 53.3037 18.5807 53.3818 18.8025C53.46 19.0243 53.4978 19.2966 53.4978 19.6167V23.9297H55.1665V19.2764C55.1665 18.7117 55.0733 18.2429 54.8867 17.8698C54.7002 17.4968 54.4355 17.2169 54.0927 17.0304H54.0877Z" fill="black"/>
120 + <path d="M57.9771 16.9447H56.3083V23.9297H57.9771V16.9447Z" fill="black"/>
121 + <path d="M57.9771 14.5475H56.3083V16.1633H57.9771V14.5475Z" fill="black"/>
122 + <path d="M64.037 20.3201C63.7774 20.1814 63.49 20.073 63.1775 19.9949C62.8649 19.9167 62.5523 19.8537 62.2397 19.8058C61.9272 19.7579 61.6373 19.7 61.3726 19.6369C61.1079 19.5714 60.8962 19.4781 60.7399 19.3571C60.5836 19.2361 60.5055 19.0572 60.5055 18.8227C60.5055 18.606 60.5987 18.4245 60.7853 18.2757C60.9718 18.127 61.2289 18.0539 61.5617 18.0539C61.9599 18.0539 62.2801 18.1447 62.5195 18.3262C62.759 18.5076 62.9128 18.8127 62.9834 19.2387H64.6521C64.5916 18.6564 64.4202 18.1825 64.1379 17.8195C63.8555 17.454 63.5026 17.1843 63.0766 17.0103C62.6506 16.8364 62.1692 16.7482 61.6297 16.7482C61.0222 16.7482 60.5181 16.854 60.1173 17.0607C59.7165 17.27 59.4165 17.5372 59.2174 17.8623C59.0182 18.1875 58.9174 18.5429 58.9174 18.9236C58.9174 19.3319 58.9956 19.6596 59.1518 19.9067C59.3081 20.1537 59.5148 20.3503 59.7719 20.494C60.0291 20.6377 60.3089 20.7461 60.6113 20.8192C60.9138 20.8923 61.2214 20.9553 61.5314 21.0082C61.8389 21.0612 62.1213 21.1166 62.3784 21.1771C62.6355 21.2376 62.8422 21.3284 62.9985 21.4519C63.1548 21.5729 63.2329 21.7468 63.2329 21.9737C63.2329 22.2257 63.1296 22.4324 62.9203 22.5913C62.7111 22.7526 62.3986 22.8332 61.9826 22.8332C61.5667 22.8332 61.1886 22.7299 60.9088 22.5207C60.6265 22.3114 60.4551 22.0216 60.3946 21.6485H58.7258C58.8468 22.4904 59.1972 23.1156 59.7745 23.5189C60.3517 23.9222 61.0701 24.1239 61.9322 24.1239C62.5221 24.1239 63.0388 24.0281 63.4825 23.8365C63.9261 23.6449 64.274 23.3802 64.5311 23.0425C64.7882 22.7022 64.9168 22.3114 64.9168 21.8703C64.9168 21.462 64.8361 21.1368 64.6748 20.8923C64.5135 20.6503 64.3042 20.4587 64.0421 20.3175L64.037 20.3201Z" fill="black"/>
123 + <path d="M69.5752 22.45C69.3307 22.6441 69.0332 22.7425 68.6753 22.7425C68.1459 22.7425 67.7401 22.5458 67.4628 22.1501C67.1855 21.7543 67.0469 21.1872 67.0469 20.4486C67.0469 19.71 67.1779 19.1176 67.4451 18.7219C67.7098 18.3261 68.1031 18.1295 68.6249 18.1295C68.9727 18.1295 69.2752 18.2328 69.5298 18.4345C69.7844 18.6387 69.9533 18.9664 70.0314 19.4176H71.7002C71.6296 18.8277 71.4531 18.3337 71.1658 17.9379C70.8784 17.5422 70.5154 17.2447 70.0768 17.0456C69.6382 16.8464 69.1542 16.7456 68.6223 16.7456C67.9543 16.7456 67.372 16.8893 66.8755 17.1767C66.3789 17.464 65.9982 17.8825 65.7285 18.4345C65.4588 18.9865 65.3252 19.6571 65.3252 20.4486C65.3252 21.2401 65.4588 21.8552 65.7285 22.4022C65.9982 22.9492 66.3789 23.3726 66.8679 23.6726C67.3594 23.9726 67.9468 24.1213 68.6349 24.1213C69.4264 24.1213 70.102 23.9121 70.6692 23.4962C71.2338 23.0802 71.5943 22.4324 71.7506 21.5552H70.0692C69.981 21.956 69.8172 22.2534 69.5726 22.4475L69.5752 22.45Z" fill="black"/>
124 + <path d="M78.5011 22.2232V19.5134C78.5011 18.974 78.3927 18.4976 78.1759 18.0791C77.9591 17.6607 77.6314 17.3355 77.1928 17.1011C76.7542 16.8666 76.2123 16.7482 75.5695 16.7482C74.9796 16.7482 74.4629 16.849 74.0243 17.0481C73.5856 17.2473 73.2327 17.5321 72.9681 17.9027C72.7034 18.2707 72.532 18.7169 72.4538 19.2387H74.1352C74.1881 18.9084 74.3217 18.6412 74.5385 18.4371C74.7553 18.2329 75.0653 18.1321 75.4636 18.1321C75.9249 18.1321 76.2652 18.248 76.487 18.485C76.7089 18.7194 76.8198 19.0622 76.8198 19.5134V19.6848C76.2677 19.6899 75.7636 19.7126 75.3149 19.7529C74.8057 19.8008 74.3091 19.9117 73.8226 20.0856C73.2832 20.2696 72.8824 20.5318 72.6177 20.8746C72.353 21.2174 72.2219 21.6283 72.2219 22.1047C72.2219 22.5484 72.3202 22.9189 72.5219 23.2189C72.721 23.5189 72.9983 23.7458 73.3487 23.897C73.7016 24.0482 74.1074 24.1239 74.5662 24.1239C74.8964 24.1239 75.2267 24.0659 75.5569 23.9474C75.8871 23.8289 76.1896 23.6474 76.4618 23.4004C76.5979 23.2769 76.724 23.1357 76.8374 22.9744C76.8424 23.0727 76.8399 23.1735 76.8475 23.2719C76.8651 23.5365 76.908 23.7558 76.9786 23.9298H78.7128C78.6523 23.7483 78.6019 23.5239 78.5641 23.2592C78.5238 22.9946 78.5061 22.6492 78.5061 22.2232H78.5011ZM75.9224 22.6391C75.6451 22.803 75.335 22.8862 74.9973 22.8862C74.6595 22.8862 74.4024 22.808 74.2032 22.6518C74.0041 22.4955 73.9033 22.2812 73.9033 22.014C73.9033 21.8325 73.9486 21.6611 74.0394 21.5048C74.1301 21.3485 74.2889 21.2099 74.5158 21.0889C74.7427 20.9679 75.0578 20.8746 75.4661 20.8091C75.8266 20.7511 76.2803 20.7209 76.8223 20.7158V21.0133C76.8223 21.3611 76.7416 21.6787 76.5803 21.9636C76.419 22.251 76.1997 22.4778 75.9224 22.6417V22.6391Z" fill="black"/>
125 + <path d="M88.5286 17.0304C88.1857 16.8439 87.7925 16.7506 87.3489 16.7506C86.8624 16.7506 86.4288 16.8666 86.0456 17.096C85.7205 17.2926 85.4533 17.5774 85.239 17.9379C85.2289 17.9152 85.2214 17.8925 85.2113 17.8698C85.0273 17.4968 84.7752 17.2169 84.45 17.0304C84.1248 16.8439 83.7442 16.7506 83.3106 16.7506C82.8493 16.7506 82.441 16.8666 82.0855 17.096C81.8032 17.28 81.5638 17.5396 81.3697 17.8673V16.9447H79.7009V23.9297H81.3697V19.8763C81.3697 19.6419 81.3999 19.4176 81.4604 19.2058C81.5209 18.9916 81.6116 18.805 81.7352 18.6462C81.8562 18.4849 81.9998 18.3588 82.1637 18.2681C82.3301 18.1774 82.5166 18.132 82.7233 18.132C82.9653 18.132 83.172 18.1874 83.3358 18.3009C83.5022 18.4143 83.6232 18.5807 83.7013 18.8025C83.7795 19.0243 83.8198 19.2966 83.8198 19.6167V23.9297H85.4886V19.8763C85.4886 19.6419 85.5188 19.4176 85.5793 19.2058C85.6398 18.9916 85.7305 18.805 85.8541 18.6462C85.9751 18.4849 86.1187 18.3588 86.2851 18.2681C86.449 18.1774 86.638 18.132 86.8447 18.132C87.0968 18.132 87.3035 18.1874 87.4648 18.3009C87.6261 18.4143 87.7446 18.5807 87.8228 18.8025C87.9009 19.0243 87.9387 19.2966 87.9387 19.6167V23.9297H89.6074V19.2764C89.6074 18.7117 89.5142 18.2429 89.3276 17.8698C89.1411 17.4968 88.8764 17.2169 88.5336 17.0304H88.5286Z" fill="black"/>
126 + <path d="M92.418 16.9447H90.7493V23.9297H92.418V16.9447Z" fill="black"/>
127 + <path d="M92.418 14.5475H90.7493V16.1633H92.418V14.5475Z" fill="black"/>
128 + <path d="M98.5232 17.0304C98.1703 16.8439 97.7594 16.7506 97.2906 16.7506C96.8217 16.7506 96.3705 16.8666 95.9949 17.096C95.6849 17.285 95.4278 17.5572 95.2185 17.9026V16.9472H93.5498V23.9322H95.2185V19.8789C95.2185 19.6444 95.2538 19.4201 95.3219 19.2083C95.3925 18.9941 95.4908 18.8075 95.6219 18.6487C95.7529 18.4874 95.9117 18.3614 96.0983 18.2706C96.2848 18.1799 96.4915 18.1345 96.7184 18.1345C97.0057 18.1345 97.2402 18.19 97.4217 18.3034C97.6032 18.4168 97.7393 18.5832 97.8325 18.805C97.9233 19.0268 97.9687 19.2991 97.9687 19.6192V23.9322H99.6374V19.2789C99.6374 18.7143 99.5391 18.2454 99.345 17.8723C99.1484 17.4993 98.8761 17.2195 98.5232 17.0329V17.0304Z" fill="black"/>
129 + <path d="M105.241 17.6632C104.803 17.0532 104.165 16.7482 103.325 16.7482C102.486 16.7482 101.75 17.0582 101.233 17.6808C100.716 18.3009 100.457 19.1958 100.457 20.3579C100.457 21.104 100.573 21.7443 100.802 22.2787C101.032 22.8131 101.362 23.2265 101.793 23.5164C102.221 23.8088 102.733 23.9525 103.323 23.9525C104.112 23.9525 104.742 23.6474 105.211 23.0324C105.221 23.0198 105.229 23.0072 105.236 22.9946V24.1743C105.236 24.7314 105.108 25.1221 104.85 25.3464C104.593 25.5733 104.258 25.6867 103.84 25.6867C103.421 25.6867 103.091 25.5884 102.816 25.3918C102.544 25.1977 102.38 24.9154 102.327 24.5524H100.646C100.767 25.3868 101.12 26.0043 101.702 26.4026C102.284 26.8034 103.005 27.0026 103.865 27.0026C104.447 27.0026 104.966 26.8967 105.423 26.6849C105.879 26.4707 106.239 26.1556 106.504 25.7321C106.769 25.3111 106.902 24.7843 106.902 24.1491V16.9423H105.234V17.6607L105.241 17.6632ZM104.843 22.009C104.578 22.3871 104.198 22.5761 103.704 22.5761C103.209 22.5761 102.831 22.3846 102.569 22.0039C102.31 21.6208 102.178 21.0738 102.178 20.3629C102.178 19.6521 102.31 19.0899 102.569 18.7068C102.829 18.3262 103.207 18.1346 103.704 18.1346C104.2 18.1346 104.568 18.3287 104.838 18.7143C105.108 19.1 105.241 19.6495 105.241 20.3629C105.241 21.0763 105.108 21.6334 104.843 22.0115V22.009Z" fill="black"/>
130 + <path d="M112.395 20.9981C112.395 21.2326 112.36 21.4544 112.291 21.6636C112.221 21.8728 112.125 22.0594 111.999 22.2232C111.873 22.3896 111.722 22.5156 111.543 22.6064C111.364 22.6971 111.162 22.7425 110.938 22.7425C110.661 22.7425 110.431 22.687 110.255 22.5736C110.076 22.4602 109.945 22.2938 109.856 22.072C109.768 21.8501 109.725 21.5829 109.725 21.2704V16.9448H108.057V21.6107C108.057 22.1678 108.152 22.6316 108.344 23.0046C108.536 23.3777 108.808 23.6575 109.158 23.8441C109.509 24.0306 109.912 24.1239 110.363 24.1239C110.842 24.1239 111.266 24.0104 111.634 23.7836C111.936 23.5995 112.188 23.3323 112.397 22.987V23.9272H114.066V16.9423H112.397V20.9956L112.395 20.9981Z" fill="black"/>
131 + <path d="M119.823 21.6484C119.718 22.0215 119.534 22.2988 119.269 22.4828C119.004 22.6668 118.677 22.7575 118.286 22.7575C117.739 22.7575 117.32 22.5584 117.028 22.1576C116.781 21.8198 116.64 21.3585 116.602 20.7762H121.53V20.3855C121.53 19.6293 121.394 18.984 121.127 18.4446C120.857 17.9051 120.474 17.4892 119.98 17.1943C119.486 16.8993 118.898 16.7506 118.22 16.7506C117.542 16.7506 116.965 16.8943 116.461 17.1817C115.957 17.469 115.568 17.8875 115.294 18.4395C115.019 18.9916 114.883 19.6621 114.883 20.4536C114.883 21.2451 115.019 21.8627 115.294 22.4147C115.568 22.9668 115.962 23.3902 116.473 23.6852C116.985 23.9801 117.59 24.1288 118.286 24.1288C119.12 24.1288 119.831 23.9171 120.416 23.4961C121.003 23.0752 121.391 22.4601 121.583 21.6509H119.823V21.6484ZM118.22 18.1169C118.742 18.1169 119.14 18.3135 119.42 18.7092C119.607 18.9739 119.728 19.3142 119.791 19.7327H116.645C116.708 19.3192 116.829 18.9789 117.01 18.7168C117.288 18.316 117.691 18.1169 118.223 18.1169H118.22Z" fill="black"/>
132 + </clipPath>
133 + </defs>
134 + <rect width="124" height="30" fill="#050505" />
135 + <g clip-path="url(#logoClip)">
136 + <image class="logo-layer layer-1" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/images/textures/blanc.png" x="0" y="0" width="124" height="30" preserveAspectRatio="xMidYMid slice" />
137 + <image class="logo-layer layer-2" href="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/images/textures/orange.png" x="0" y="0" width="124" height="30" preserveAspectRatio="xMidYMid slice" />
138 + </g>
139 + </svg>
140 + </div>
141 + </div>
142 + <svg class="svg-filtre">
143 + <filter id="filter_wiggle" x="0" y="0" width="100%" height="100%" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
144 + <!-- <feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood>
145 + <feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend>
146 + <feTurbulence type="fractalNoise" baseFrequency="0.08426443487405777 0.08426443487405777" numOctaves="3" seed="4928"></feTurbulence>
147 + <feDisplacementMap in="shape" scale="2.0767955780029297" xChannelSelector="R" yChannelSelector="G" result="displacedImage" width="100%" height="100%"></feDisplacementMap>
148 + <feMerge result="effect1_texture_1080_6463">
149 + <feMergeNode in="displacedImage"></feMergeNode>
150 + </feMerge> -->
151 + </filter>
152 + </svg>
153 + <a href="#" id="scroll-top" class="back-to-top-btn"><i class="fa-solid fa-arrow-up"></i></a>
154 + <nav class="slide-menu" id="menu-right">
155 + <div class="controls">
156 + <a class="drawer-logo" href="https://abitibi-temiscamingue.org"><img src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/images/Tag-Blanc.svg" alt="Tourisme Abitibi-Témiscamingue" class="img-fluid" width="32" height="32" /></a>
157 + <a href="https://abitibi-temiscamingue.org/en/festivals-and-events/" class="" hreflang="en" title="English">
158 + English </a>
159 + <button type="button" class="btn-round-outline slide-menu__control" data-action="close"></button>
160 + </div>
161 + <ul id="primary-menu-list" class="menu-wrapper"><li id="menu-item-784" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-784"><a href="#">Découvrir</a>
162 +<ul class="sub-menu">
163 + <li id="menu-item-2839" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-2839"><a href="https://abitibi-temiscamingue.org/decouvrir-la-region/">L’Abitibi-Témiscamingue</a></li>
164 + <li id="menu-item-11860" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-11860"><a href="https://abitibi-temiscamingue.org/tourisme-autochtone-anicinabek/">Tourisme autochtone</a></li>
165 + <li id="menu-item-2836" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-2836"><a href="https://abitibi-temiscamingue.org/nos-territoires/">Nos territoires</a>
166 + <ul class="sub-menu">
167 + <li id="menu-item-14113" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-14113"><a href="https://abitibi-temiscamingue.org/nos-territoires/abitibi-ouest/">Abitibi-Ouest</a></li>
168 + <li id="menu-item-7000" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7000"><a href="https://abitibi-temiscamingue.org/nos-territoires/amos-harricana/">Amos-Harricana </a></li>
169 + <li id="menu-item-2837" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-2837"><a href="https://abitibi-temiscamingue.org/nos-territoires/rouyn-noranda/">Rouyn-Noranda</a></li>
170 + <li id="menu-item-7001" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7001"><a href="https://abitibi-temiscamingue.org/nos-territoires/temiscamingue/">Témiscamingue </a></li>
171 + <li id="menu-item-7002" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7002"><a href="https://abitibi-temiscamingue.org/nos-territoires/vallee-de-lor/">Vallée-de-l’Or </a></li>
172 + </ul>
173 +</li>
174 + <li id="menu-item-2838" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-2838"><a href="https://abitibi-temiscamingue.org/nos-saisons/">Nos saisons</a>
175 + <ul class="sub-menu">
176 + <li id="menu-item-789" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-789"><a href="https://abitibi-temiscamingue.org/nos-saisons/printemps/">Printemps</a></li>
177 + <li id="menu-item-790" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-790"><a href="https://abitibi-temiscamingue.org/nos-saisons/ete/">Été</a></li>
178 + <li id="menu-item-791" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-791"><a href="https://abitibi-temiscamingue.org/nos-saisons/automne/">Automne</a></li>
179 + <li id="menu-item-793" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-793"><a href="https://abitibi-temiscamingue.org/nos-saisons/hiver/">Hiver</a></li>
180 + </ul>
181 +</li>
182 + <li id="menu-item-8431" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8431"><a href="https://abitibi-temiscamingue.org/nos-incontournables/">Nos incontournables</a></li>
183 + <li id="menu-item-12008" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-12008"><a href="https://abitibi-temiscamingue.org/nos-circuits/">Nos circuits</a></li>
184 +</ul>
185 +</li>
186 +<li id="menu-item-785" class="menu-item menu-item-type-custom menu-item-object-custom current-menu-ancestor current-menu-parent menu-item-has-children menu-item-785"><a href="https://abitibi-temiscamingue.org/planifier/">Planifier</a>
187 +<ul class="sub-menu">
188 + <li id="menu-item-7690" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-7690"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/">Quoi faire</a>
189 + <ul class="sub-menu">
190 + <li id="menu-item-7627" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-7627"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/">Sports et plein air</a>
191 + <ul class="sub-menu">
192 + <li id="menu-item-7623" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7623"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/activites-nautiques/">Activités nautiques</a></li>
193 + <li id="menu-item-7629" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7629"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/velo/">Vélo</a></li>
194 + <li id="menu-item-7624" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7624"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/golf/">Golf</a></li>
195 + <li id="menu-item-7628" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7628"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/sports-dhiver/">Sports d&#8217;hiver</a></li>
196 + <li id="menu-item-7626" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7626"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/randonnee/">Randonnée</a></li>
197 + <li id="menu-item-7625" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7625"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/parcs-reserve-faunique/">Parcs et réserve faunique</a></li>
198 + </ul>
199 +</li>
200 + <li id="menu-item-7617" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7617"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/activites-divertissement/">Activités et divertissement</a></li>
201 + <li id="menu-item-7630" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7630"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/art-culture-et-patrimoine/">Art, culture et patrimoine</a></li>
202 + <li id="menu-item-7618" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7618"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/agrotourisme/">Agrotourisme et terroir</a></li>
203 + <li id="menu-item-7622" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7622"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/motoneige/">Motoneige</a></li>
204 + <li id="menu-item-7619" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-7619"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/chasse-peche-et-sejour-en-pourvoirie/">Chasse et pêche</a>
205 + <ul class="sub-menu">
206 + <li id="menu-item-7620" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7620"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/chasse-peche-et-sejour-en-pourvoirie/chasse/">Chasse</a></li>
207 + <li id="menu-item-7621" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7621"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/chasse-peche-et-sejour-en-pourvoirie/peche/">Pêche</a></li>
208 + <li id="menu-item-7631" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7631"><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/chasse-peche-et-sejour-en-pourvoirie/sejour-en-pourvoirie/">Séjour en pourvoirie</a></li>
209 + </ul>
210 +</li>
211 + </ul>
212 +</li>
213 + <li id="menu-item-7637" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-7637"><a href="https://abitibi-temiscamingue.org/planifier/ou-manger/">Où manger</a>
214 + <ul class="sub-menu">
215 + <li id="menu-item-7080" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7080"><a href="https://abitibi-temiscamingue.org/planifier/ou-manger/restaurants/">Restaurants</a></li>
216 + <li id="menu-item-7077" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7077"><a href="https://abitibi-temiscamingue.org/planifier/ou-manger/cafes/">Cafés</a></li>
217 + <li id="menu-item-7079" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7079"><a href="https://abitibi-temiscamingue.org/planifier/ou-manger/patisseries-et-boulangeries/">Pâtisseries et boulangeries</a></li>
218 + <li id="menu-item-7078" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7078"><a href="https://abitibi-temiscamingue.org/planifier/ou-manger/microbrasseries-bars-distilleries/">Microbrasseries, bars et distilleries</a></li>
219 + <li id="menu-item-7084" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7084"><a href="https://abitibi-temiscamingue.org/planifier/ou-manger/traiteurs/">Traiteurs</a></li>
220 + </ul>
221 +</li>
222 + <li id="menu-item-7154" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-7154"><a href="https://abitibi-temiscamingue.org/planifier/ou-dormir/">Où dormir</a>
223 + <ul class="sub-menu">
224 + <li id="menu-item-7003" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7003"><a href="https://abitibi-temiscamingue.org/planifier/ou-dormir/campings/">Campings et prêts-à-camper</a></li>
225 + <li id="menu-item-7004" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7004"><a href="https://abitibi-temiscamingue.org/planifier/ou-dormir/chalets-et-appartements/">Chalets, appartements et résidences de tourisme</a></li>
226 + <li id="menu-item-7005" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7005"><a href="https://abitibi-temiscamingue.org/planifier/ou-dormir/gites-touristiques/">Gîtes touristiques</a></li>
227 + <li id="menu-item-7006" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7006"><a href="https://abitibi-temiscamingue.org/planifier/ou-dormir/hotels/">Hôtels et motels</a></li>
228 + <li id="menu-item-7007" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7007"><a href="https://abitibi-temiscamingue.org/planifier/ou-dormir/pourvoiries/">Pourvoiries</a></li>
229 + </ul>
230 +</li>
231 + <li id="menu-item-7693" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7693"><a href="https://abitibi-temiscamingue.org/planifier/services/">Services</a></li>
232 + <li id="menu-item-7803" class="menu-item menu-item-type-post_type menu-item-object-page current-menu-item page_item page-item-6235 current_page_item menu-item-7803"><a href="https://abitibi-temiscamingue.org/festivals-et-evenements/" aria-current="page">Festivals et événements</a></li>
233 + <li id="menu-item-12220" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-12220"><a href="https://abitibi-temiscamingue.org/planifier/tourisme-affaires/">Tourisme d’affaires</a></li>
234 + <li id="menu-item-8434" class="menu-item menu-item-type-post_type_archive menu-item-object-forfaits menu-item-8434"><a href="https://abitibi-temiscamingue.org/offres-et-forfaits/">Offres et forfaits</a></li>
235 + <li id="menu-item-8433" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8433"><a href="https://abitibi-temiscamingue.org/mon-itineraire/">Construire ton itinéraire</a></li>
236 + <li id="menu-item-7632" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-7632"><a href="https://abitibi-temiscamingue.org/informations-pratiques/">Infos pratiques</a>
237 + <ul class="sub-menu">
238 + <li id="menu-item-7633" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7633"><a href="https://abitibi-temiscamingue.org/informations-pratiques/se-deplacer/">Se déplacer</a></li>
239 + <li id="menu-item-7634" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7634"><a href="https://abitibi-temiscamingue.org/informations-pratiques/documents-brochures-liens-utiles/">Documents, brochures et liens utiles</a></li>
240 + <li id="menu-item-7635" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7635"><a href="https://abitibi-temiscamingue.org/informations-pratiques/bureaux-dinformation-touristique/">Bureaux d’information touristique</a></li>
241 + <li id="menu-item-7644" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7644"><a href="https://abitibi-temiscamingue.org/informations-pratiques/faq/">Foire aux questions (FAQ)</a></li>
242 + </ul>
243 +</li>
244 +</ul>
245 +</li>
246 +<li id="menu-item-786" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-786"><a href="#">S&#8217;inspirer</a>
247 +<ul class="sub-menu">
248 + <li id="menu-item-8432" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8432"><a href="https://abitibi-temiscamingue.org/blogue/">Blogue</a></li>
249 + <li id="menu-item-7761" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7761"><a href="https://abitibi-temiscamingue.org/vos-moments/">#abitibitemiscamingue</a></li>
250 + <li id="menu-item-14054" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-14054"><a href="https://abitibi-temiscamingue.org/informations-pratiques/faq/">Foire aux questions (FAQ)</a></li>
251 + <li id="menu-item-14709" class="menu-item menu-item-type-post_type_archive menu-item-object-auteurs menu-item-14709"><a href="https://abitibi-temiscamingue.org/nos-collaborateurs/">Nos collaborateurs</a></li>
252 +</ul>
253 +</li>
254 +</ul> <div class="social-links">
255 + <p class="h5">Suivez-nous</p>
256 + <ul class="custom-ul">
257 + <li>
258 + <a href="https://facebook.com/Abitibi.Temiscamingue" target="_blank">
259 + <svg
260 + data-icon="facebook-Circle"
261 + viewBox="0 0 52 52"
262 + width="52"
263 + height="52"
264 + class=""
265 + fill="none" xmlns="http://www.w3.org/2000/svg">
266 + <rect x="1.55" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8" />
267 + <path d="M25.55 13.4004C23.147 13.4004 20.7979 14.113 18.7998 15.448C16.8018 16.7831 15.2445 18.6807 14.3249 20.9008C13.4053 23.1209 13.1647 25.5639 13.6335 27.9207C14.1023 30.2776 15.2594 32.4425 16.9587 34.1417C18.6579 35.8409 20.8228 36.9981 23.1797 37.4669C25.5365 37.9357 27.9795 37.6951 30.1996 36.7755C32.4197 35.8559 34.3173 34.2986 35.6524 32.3006C36.9874 30.3025 37.7 27.9534 37.7 25.5504C37.6964 22.3291 36.4152 19.2408 34.1374 16.963C31.8596 14.6852 28.7713 13.404 25.55 13.4004ZM26.9 34.9025V28.7004H29.15C29.508 28.7004 29.8514 28.5582 30.1046 28.305C30.3578 28.0518 30.5 27.7084 30.5 27.3504C30.5 26.9923 30.3578 26.649 30.1046 26.3958C29.8514 26.1426 29.508 26.0004 29.15 26.0004H26.9V23.7504C26.9 23.3923 27.0422 23.049 27.2954 22.7958C27.5486 22.5426 27.892 22.4004 28.25 22.4004H30.05C30.408 22.4004 30.7514 22.2582 31.0046 22.005C31.2578 21.7518 31.4 21.4084 31.4 21.0504C31.4 20.6923 31.2578 20.349 31.0046 20.0958C30.7514 19.8426 30.408 19.7004 30.05 19.7004H28.25C27.1759 19.7004 26.1457 20.1271 25.3862 20.8866C24.6267 21.6461 24.2 22.6763 24.2 23.7504V26.0004H21.95C21.592 26.0004 21.2486 26.1426 20.9954 26.3958C20.7422 26.649 20.6 26.9923 20.6 27.3504C20.6 27.7084 20.7422 28.0518 20.9954 28.305C21.2486 28.5582 21.592 28.7004 21.95 28.7004H24.2V34.9025C21.8354 34.5612 19.6878 33.3369 18.1894 31.476C16.6911 29.6151 15.9532 27.2557 16.1243 24.8727C16.2954 22.4897 17.3627 20.2599 19.1116 18.6322C20.8604 17.0044 23.1609 16.0994 25.55 16.0994C27.9391 16.0994 30.2396 17.0044 31.9885 18.6322C33.7373 20.2599 34.8046 22.4897 34.9757 24.8727C35.1468 27.2557 34.409 29.6151 32.9106 31.476C31.4122 33.3369 29.2646 34.5612 26.9 34.9025Z" fill="currentColor" /> </svg>
268 + </a>
269 + </li>
270 + <li>
271 + <a href="https://www.instagram.com/abitibi.temiscamingue/?hl=fr" target="_blank">
272 + <svg
273 + data-icon="instagram-Circle"
274 + viewBox="0 0 52 52"
275 + width="52"
276 + height="52"
277 + class=""
278 + fill="none" xmlns="http://www.w3.org/2000/svg">
279 + <rect x="1.55" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8" />
280 + <path d="M25.55 20.1504C24.482 20.1504 23.438 20.4671 22.5499 21.0605C21.6619 21.6538 20.9698 22.4972 20.5611 23.4839C20.1523 24.4706 20.0454 25.5564 20.2538 26.6039C20.4621 27.6514 20.9764 28.6136 21.7316 29.3688C22.4868 30.124 23.449 30.6383 24.4965 30.8466C25.544 31.055 26.6298 30.9481 27.6165 30.5393C28.6032 30.1306 29.4466 29.4385 30.0399 28.5505C30.6333 27.6624 30.95 26.6184 30.95 25.5504C30.9485 24.1187 30.3791 22.746 29.3667 21.7337C28.3544 20.7213 26.9817 20.1519 25.55 20.1504ZM25.55 28.2504C25.016 28.2504 24.494 28.092 24.05 27.7954C23.606 27.4987 23.2599 27.077 23.0555 26.5836C22.8512 26.0903 22.7977 25.5474 22.9019 25.0236C23.0061 24.4999 23.2632 24.0188 23.6408 23.6412C24.0184 23.2636 24.4995 23.0064 25.0233 22.9023C25.547 22.7981 26.0899 22.8516 26.5833 23.0559C27.0766 23.2603 27.4983 23.6063 27.795 24.0504C28.0917 24.4944 28.25 25.0164 28.25 25.5504C28.25 26.2665 27.9655 26.9532 27.4592 27.4596C26.9528 27.9659 26.2661 28.2504 25.55 28.2504ZM30.95 13.4004H20.15C18.3604 13.4025 16.6448 14.1143 15.3793 15.3797C14.1139 16.6451 13.4021 18.3608 13.4 20.1504V30.9504C13.4021 32.74 14.1139 34.4556 15.3793 35.7211C16.6448 36.9865 18.3604 37.6983 20.15 37.7004H30.95C32.7396 37.6983 34.4553 36.9865 35.7207 35.7211C36.9861 34.4556 37.6979 32.74 37.7 30.9504V20.1504C37.6979 18.3608 36.9861 16.6451 35.7207 15.3797C34.4553 14.1143 32.7396 13.4025 30.95 13.4004ZM35 30.9504C35 32.0245 34.5733 33.0546 33.8138 33.8142C33.0543 34.5737 32.0241 35.0004 30.95 35.0004H20.15C19.0759 35.0004 18.0457 34.5737 17.2862 33.8142C16.5267 33.0546 16.1 32.0245 16.1 30.9504V20.1504C16.1 19.0763 16.5267 18.0461 17.2862 17.2866C18.0457 16.5271 19.0759 16.1004 20.15 16.1004H30.95C32.0241 16.1004 33.0543 16.5271 33.8138 17.2866C34.5733 18.0461 35 19.0763 35 20.1504V30.9504ZM33.2 19.7004C33.2 20.0564 33.0944 20.4044 32.8967 20.7004C32.6989 20.9964 32.4177 21.2271 32.0888 21.3634C31.7599 21.4996 31.398 21.5353 31.0488 21.4658C30.6997 21.3964 30.379 21.2249 30.1272 20.9732C29.8755 20.7214 29.704 20.4007 29.6346 20.0516C29.5651 19.7024 29.6008 19.3405 29.737 19.0116C29.8733 18.6827 30.104 18.4015 30.4 18.2037C30.696 18.006 31.044 17.9004 31.4 17.9004C31.8774 17.9004 32.3352 18.09 32.6728 18.4276C33.0104 18.7652 33.2 19.223 33.2 19.7004Z" fill="currentColor" /> </svg>
281 + </a>
282 + </li>
283 + <li>
284 + <a href="https://www.youtube.com/@abitibi-temiscamingue" target="_blank">
285 + <svg
286 + data-icon="youtube-Circle"
287 + viewBox="0 0 52 52"
288 + width="52"
289 + height="52"
290 + class=""
291 + fill="none" xmlns="http://www.w3.org/2000/svg">
292 + <rect x="1.55" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8" />
293 + <path d="M30.3301 24.416L24.0301 20.366C23.8262 20.2349 23.5908 20.1612 23.3485 20.1524C23.1063 20.1437 22.8661 20.2004 22.6533 20.3165C22.4405 20.4327 22.2629 20.6039 22.1392 20.8124C22.0154 21.0208 21.9501 21.2588 21.95 21.5012V29.6012C21.9501 29.8436 22.0154 30.0815 22.1392 30.29C22.2629 30.4984 22.4405 30.6697 22.6533 30.7858C22.8661 30.9019 23.1063 30.9586 23.3485 30.9499C23.5908 30.9412 23.8262 30.8674 24.0301 30.7363L30.3301 26.6863C30.5201 26.564 30.6763 26.396 30.7845 26.1977C30.8927 25.9994 30.9494 25.7771 30.9494 25.5512C30.9494 25.3253 30.8927 25.103 30.7845 24.9046C30.6763 24.7063 30.5201 24.5383 30.3301 24.416ZM24.65 27.1262V23.9762L27.1036 25.5512L24.65 27.1262ZM37.9486 18.8574C37.8244 18.3733 37.5869 17.9257 37.2557 17.5513C36.9246 17.177 36.5093 16.8866 36.044 16.7042C32.12 15.191 25.9201 15.2012 25.55 15.2012C25.1799 15.2012 18.98 15.191 15.056 16.7064C14.5907 16.8889 14.1754 17.1792 13.8442 17.5536C13.5131 17.9279 13.2756 18.3756 13.1514 18.8597C12.8544 20.0072 12.5 22.0997 12.5 25.5512C12.5 29.0027 12.8544 31.0952 13.1514 32.2427C13.2756 32.7268 13.5131 33.1744 13.8442 33.5488C14.1754 33.9231 14.5907 34.2135 15.056 34.3959C18.8236 35.8505 24.6905 35.9012 25.4791 35.9012H25.6209C26.4084 35.9012 32.2764 35.8505 36.044 34.3959C36.5093 34.2135 36.9246 33.9231 37.2557 33.5488C37.5869 33.1744 37.8244 32.7268 37.9486 32.2427C38.2456 31.0952 38.6 29.0027 38.6 25.5512C38.6 22.0997 38.2456 20.0072 37.9486 18.8597V18.8574ZM35.3375 31.5654C35.3213 31.6344 35.289 31.6986 35.2432 31.7528C35.1975 31.8069 35.1396 31.8495 35.0742 31.877C31.7127 33.1742 26.0304 33.2012 25.586 33.2012H25.5421C25.4825 33.2012 19.5076 33.2203 16.0291 31.877C15.9632 31.8499 15.9046 31.8076 15.8582 31.7534C15.8119 31.6992 15.7791 31.6348 15.7625 31.5654C15.551 30.7509 15.2 28.8857 15.2 25.5512C15.2 22.2167 15.5544 20.3514 15.7625 19.5369C15.7787 19.4679 15.811 19.4037 15.8568 19.3496C15.9025 19.2954 15.9604 19.2528 16.0257 19.2253C19.5076 17.882 25.4814 17.9012 25.541 17.9012H25.5579C25.6186 17.9012 31.5924 17.882 35.0709 19.2253C35.1368 19.2524 35.1954 19.2948 35.2417 19.349C35.2881 19.4032 35.3209 19.4676 35.3375 19.5369C35.549 20.3514 35.9 22.2167 35.9 25.5512C35.9 28.8857 35.5456 30.7509 35.3375 31.5654Z" fill="currentColor" /> </svg>
294 + </a>
295 + </li>
296 + <li>
297 + <a href="https://www.pinterest.ca/AbitibiTemiscamingue/" target="_blank">
298 + <svg
299 + data-icon="pinterest-Circle"
300 + viewBox="0 0 52 52"
301 + width="52"
302 + height="52"
303 + class=""
304 + fill="none" xmlns="http://www.w3.org/2000/svg">
305 + <rect x="1.5498" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8"/>
306 + <path d="M36.8001 23.7512C36.8001 26.4062 35.8675 28.8396 34.1732 30.6002C32.5836 32.2529 30.4247 33.2012 28.2501 33.2012C26.512 33.2012 25.2373 32.731 24.325 32.1539L23.2641 36.6606C23.225 36.8347 23.1517 36.9993 23.0486 37.145C22.9455 37.2906 22.8145 37.4143 22.6632 37.509C22.512 37.6037 22.3434 37.6674 22.1674 37.6966C21.9913 37.7257 21.8112 37.7197 21.6375 37.6788C21.4638 37.6379 21.3 37.5629 21.1554 37.4583C21.0109 37.3537 20.8885 37.2214 20.7953 37.0692C20.7022 36.917 20.6402 36.7478 20.6129 36.5715C20.5855 36.3951 20.5934 36.2151 20.6361 36.0419L24.2361 20.7419C24.2752 20.5678 24.3485 20.4031 24.4516 20.2575C24.5547 20.1119 24.6857 19.9882 24.837 19.8935C24.9882 19.7988 25.1568 19.735 25.3328 19.7059C25.5089 19.6768 25.689 19.6828 25.8627 19.7237C26.0364 19.7646 26.2002 19.8395 26.3448 19.9442C26.4893 20.0488 26.6117 20.181 26.7048 20.3333C26.798 20.4855 26.86 20.6546 26.8873 20.831C26.9147 21.0073 26.9068 21.1873 26.8641 21.3606L25.0135 29.2277C25.4061 29.698 26.3556 30.5012 28.2501 30.5012C31.1256 30.5012 34.1001 27.9756 34.1001 23.7512C34.0995 22.5782 33.8291 21.421 33.3099 20.3691C32.7907 19.3172 32.0365 18.3988 31.1057 17.6849C30.1749 16.971 29.0923 16.4808 27.9418 16.252C26.7913 16.0233 25.6035 16.0622 24.4704 16.3658C23.3373 16.6693 22.2892 17.2294 21.4071 18.0027C20.525 18.776 19.8326 19.7418 19.3834 20.8254C18.9341 21.909 18.7401 23.0815 18.8163 24.252C18.8924 25.4226 19.2368 26.56 19.8227 27.5762C19.9881 27.8852 20.0268 28.2464 19.9308 28.5834C19.8347 28.9204 19.6113 29.2069 19.3078 29.3822C19.0044 29.5575 18.6447 29.608 18.3047 29.5229C17.9647 29.4378 17.6712 29.2238 17.4861 28.9262C16.6925 27.5514 16.2259 26.0124 16.1222 24.4283C16.0185 22.8442 16.2806 21.2575 16.8882 19.7909C17.4958 18.3244 18.4326 17.0172 19.6262 15.9705C20.8197 14.9239 22.238 14.1658 23.7714 13.755C25.3048 13.3441 26.9121 13.2915 28.4691 13.6012C30.026 13.9108 31.4909 14.5745 32.7504 15.5408C34.0098 16.5072 35.0301 17.7502 35.7324 19.1739C36.4346 20.5976 36.7999 22.1638 36.8001 23.7512Z" fill="currentColor"/> </svg>
307 + </a>
308 + </li>
309 + <li>
310 + <a href="https://ca.linkedin.com/company/tourismeat" target="_blank">
311 + <svg
312 + data-icon="linkedin-Circle"
313 + viewBox="0 0 52 52"
314 + width="52"
315 + height="52"
316 + class=""
317 + fill="none" xmlns="http://www.w3.org/2000/svg">
318 + <rect x="1.55" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8" />
319 + <path d="M35.45 13.4004H15.65C15.0533 13.4004 14.481 13.6374 14.059 14.0594C13.6371 14.4814 13.4 15.0537 13.4 15.6504V35.4504C13.4 36.0471 13.6371 36.6194 14.059 37.0414C14.481 37.4633 15.0533 37.7004 15.65 37.7004H35.45C36.0467 37.7004 36.619 37.4633 37.041 37.0414C37.4629 36.6194 37.7 36.0471 37.7 35.4504V15.6504C37.7 15.0537 37.4629 14.4814 37.041 14.0594C36.619 13.6374 36.0467 13.4004 35.45 13.4004ZM35 35.0004H16.1V16.1004H35V35.0004ZM23.75 30.9504V24.6504C23.7503 24.3698 23.8379 24.0963 24.0008 23.8679C24.1637 23.6395 24.3937 23.4675 24.6588 23.3758C24.924 23.2841 25.2111 23.2773 25.4803 23.3563C25.7495 23.4354 25.9874 23.5963 26.1609 23.8168C26.8466 23.4574 27.6134 23.2809 28.3872 23.3045C29.161 23.3281 29.9157 23.551 30.5782 23.9515C31.2407 24.352 31.7887 24.9167 32.1691 25.591C32.5496 26.2652 32.7496 27.0262 32.75 27.8004V30.9504C32.75 31.3084 32.6078 31.6518 32.3546 31.905C32.1014 32.1582 31.758 32.3004 31.4 32.3004C31.042 32.3004 30.6986 32.1582 30.4454 31.905C30.1922 31.6518 30.05 31.3084 30.05 30.9504V27.8004C30.05 27.323 29.8604 26.8652 29.5228 26.5276C29.1852 26.19 28.7274 26.0004 28.25 26.0004C27.7726 26.0004 27.3148 26.19 26.9772 26.5276C26.6396 26.8652 26.45 27.323 26.45 27.8004V30.9504C26.45 31.3084 26.3078 31.6518 26.0546 31.905C25.8014 32.1582 25.458 32.3004 25.1 32.3004C24.742 32.3004 24.3986 32.1582 24.1454 31.905C23.8922 31.6518 23.75 31.3084 23.75 30.9504ZM21.95 24.6504V30.9504C21.95 31.3084 21.8078 31.6518 21.5546 31.905C21.3014 32.1582 20.958 32.3004 20.6 32.3004C20.242 32.3004 19.8986 32.1582 19.6454 31.905C19.3922 31.6518 19.25 31.3084 19.25 30.9504V24.6504C19.25 24.2923 19.3922 23.949 19.6454 23.6958C19.8986 23.4426 20.242 23.3004 20.6 23.3004C20.958 23.3004 21.3014 23.4426 21.5546 23.6958C21.8078 23.949 21.95 24.2923 21.95 24.6504ZM18.8 20.1504C18.8 19.7944 18.9056 19.4464 19.1034 19.1504C19.3011 18.8544 19.5823 18.6236 19.9112 18.4874C20.2401 18.3512 20.602 18.3155 20.9512 18.385C21.3003 18.4544 21.6211 18.6259 21.8728 18.8776C22.1245 19.1293 22.296 19.4501 22.3654 19.7992C22.4349 20.1484 22.3992 20.5103 22.263 20.8392C22.1267 21.1681 21.896 21.4492 21.6 21.647C21.304 21.8448 20.956 21.9504 20.6 21.9504C20.1226 21.9504 19.6648 21.7607 19.3272 21.4232C18.9896 21.0856 18.8 20.6278 18.8 20.1504Z" fill="currentColor" /> </svg>
320 + </a>
321 + </li>
322 + </ul>
323 + </div>
324 + </nav>
325 + <div class="has-smooth" id="has_smooth"></div> <div id="smooth-wrapper">
326 + <header class="quanto-header main-header dark-header" id="sticky-menu">
327 + <div class="sticky-wrap">
328 + <div class="sticky-active">
329 + <div class="container custom-container">
330 + <div class="row gx-3 align-items-center justify-content-between">
331 + <div class="col col-lg-3">
332 + <div class="header-logo">
333 + <a href="https://abitibi-temiscamingue.org">
334 + <svg viewBox="0 0 239 48" fill="none" xmlns="http://www.w3.org/2000/svg">
335 + <path d="M41.0802 17.2318C41.0751 17.136 40.9894 17.0301 40.9844 16.9344C40.9491 16.405 40.9491 16.0168 40.8382 15.4874C40.813 15.3665 40.8483 15.2354 40.8231 15.1144C40.7878 14.9178 40.6516 14.5649 40.6466 14.4035C40.6466 14.3077 40.7273 14.2674 40.7222 14.2019C40.7222 14.1464 40.6416 14.0607 40.6264 13.9851C40.5811 13.8086 40.6063 13.6271 40.576 13.4658C40.5004 13.0625 40.3088 12.5231 40.1929 12.1046C40.1324 11.8878 40.0719 11.6761 40.0366 11.4593C39.9962 11.2022 39.8904 11.0862 39.8047 10.8644C39.4417 9.90148 39.0938 8.57557 38.4183 7.75381C38.3779 7.70339 38.3477 7.6681 38.3225 7.61265C37.8032 6.47831 36.6084 5.23306 35.6202 4.33568C34.985 3.76095 34.3851 3.32234 33.6238 2.86356C32.7214 2.31908 31.8391 1.94097 30.8308 1.54774C30.1754 1.29062 29.5352 1.09904 28.8445 0.9478C28.7386 0.922592 28.5622 0.801596 28.4613 0.791513C28.3958 0.78143 28.3353 0.816721 28.2345 0.801596C27.8967 0.756223 27.4329 0.524315 27.1354 0.504149C27.0598 0.499107 27.0094 0.544481 26.9388 0.539439C26.8531 0.539439 26.7271 0.463817 26.6313 0.443651C26.1473 0.352904 25.5877 0.332738 25.0835 0.277282C24.6802 0.231908 24.1004 0.105871 23.7072 0.0957883C23.5912 0.0957883 23.4803 0.141162 23.3694 0.13612C23.2635 0.131079 23.1678 0.0554564 23.077 0.0504149C22.8804 0.0352904 22.5073 0.0857053 22.2956 0.0806638C22.1695 0.0806638 22.0687 0.0201659 21.9376 0.0151245C21.7864 0.0151245 21.6503 0.0655393 21.4738 0.0453734C21.0554 -0.00504149 20.9243 0.0403319 20.5462 0.0453734C20.4252 0.0453734 20.3092 0 20.1882 0C20.0471 0 19.911 0.0504149 19.7597 0.0453734C19.3463 0.020166 19.1951 0.0453734 18.7968 0.0756223C18.6607 0.0857053 18.5447 0.0403319 18.4036 0.0453734C18.2725 0.0453734 18.1716 0.105871 18.0456 0.110913C17.1281 0.126037 16.2307 0.23695 15.3282 0.368029C14.9552 0.423485 14.5317 0.433568 14.1687 0.499107C14.0628 0.519273 13.9418 0.615061 13.8309 0.635227C13.6494 0.665476 13.3217 0.635227 13.1705 0.675559C13.0898 0.695725 13.0243 0.826804 12.9083 0.836887C12.8176 0.84697 12.6815 0.786472 12.5605 0.821762C12.505 0.836887 12.4949 0.927634 12.3991 0.952841C12.3185 0.973007 12.2277 0.957883 12.1471 0.978049C11.8395 1.04863 11.6328 1.1545 11.3656 1.24525C11.169 1.31079 10.9674 1.28558 10.7607 1.39145C10.4178 1.57799 10.1506 1.60823 9.80278 1.7494C9.70699 1.78973 9.69187 1.86535 9.65154 1.88552C9.60112 1.91072 9.5255 1.8956 9.47508 1.91577C8.92052 2.15776 8.41637 2.47033 7.87189 2.71232C7.66519 2.80307 7.63494 2.94927 7.50386 3.04002C7.32237 3.16605 7.12575 3.15597 6.90897 3.35763C6.66193 3.58954 6.56614 3.59458 6.31911 3.78112C6.15274 3.90715 6.12754 4.01807 6.01662 4.1189C5.941 4.18948 5.76455 4.24997 5.6738 4.32055C5.48727 4.46172 5.41164 4.67346 5.28561 4.78437C5.09403 4.95578 4.94279 5.0314 4.75121 5.25827C4.40335 5.66663 4.09582 6.01954 3.81853 6.46319C3.74795 6.57914 3.53117 6.79593 3.43538 6.94213C3.35472 7.06817 3.25893 7.18916 3.18835 7.29503C3.14297 7.36561 3.14297 7.4614 3.10264 7.5219C3.05727 7.58744 2.93627 7.61265 2.91611 7.65802C2.88082 7.74372 2.94132 7.85464 2.88586 7.95547C2.83544 8.04117 2.69932 8.03109 2.66908 8.10167C2.63883 8.16721 2.64891 8.28316 2.60858 8.37391C2.4775 8.68144 2.26576 9.09484 2.10947 9.40237C2.07418 9.46791 1.98343 9.54354 1.96831 9.60403C1.93302 9.76536 2.09435 9.88636 1.76665 9.98215C1.75657 10.1384 1.75657 10.4208 1.68598 10.5569C1.65574 10.6123 1.5902 10.6426 1.54986 10.7081C1.44399 10.9047 1.44399 11.2324 1.35829 11.4341C1.33308 11.4946 1.25746 11.5248 1.23729 11.5702C1.202 11.6761 1.25242 11.8273 1.22217 11.9433C1.202 12.0088 1.12638 12.0592 1.09613 12.1349C1.02555 12.3113 1.07092 12.4474 1.04572 12.5886C1.00034 12.8155 0.793642 13.2037 0.768434 13.4255C0.758351 13.5011 0.813808 13.5666 0.803725 13.6574C0.7886 13.7935 0.662563 13.98 0.637356 14.1111C0.591982 14.3077 0.61719 14.5548 0.586941 14.7514C0.55165 14.9833 0.445779 15.2656 0.41553 15.5227C0.395364 15.674 0.360074 15.7849 0.344949 15.9059C0.329825 15.9966 0.390323 16.0773 0.38024 16.168C0.370157 16.2638 0.284451 16.3697 0.284451 16.4957C0.284451 16.5764 0.339908 16.6873 0.329825 16.7529C0.319742 16.8134 0.228995 16.894 0.218912 16.9747C0.193705 17.1663 0.254203 17.4335 0.234037 17.6099C0.228995 17.7007 0.168497 17.7712 0.158414 17.8721C0.148331 17.9779 0.198746 18.0889 0.188663 18.1594C0.17858 18.2351 0.0878335 18.2905 0.0878335 18.3712C0.0878335 18.4619 0.0878335 18.6989 0.0878335 18.7795C0.092875 18.8552 0.163456 18.9207 0.158414 19.0165C0.153373 19.1123 0.062626 19.1778 0.0475016 19.2837C0.0222941 19.4753 0.072709 19.8231 0.082792 20.0248C0.0878335 20.1256 0.123124 20.4079 0.118082 20.4634C0.113041 20.534 0.0323771 20.5945 0.0122112 20.6751C-0.0230792 20.8516 0.0273356 21.4969 0.0475016 21.7036C0.0575845 21.7792 0.118082 21.8649 0.113041 21.9406C0.107999 22.0111 0.0323771 22.0615 0.0172526 22.1271C-0.0281207 22.2935 0.062626 22.3792 0.0676675 22.5052C0.0676675 22.5758 0.0122112 22.6413 0.0122112 22.7119C0.00716968 22.8732 0.113041 23.1908 0.118082 23.3572C0.118082 23.4782 0.0525431 23.5488 0.0424601 23.6496C0.0273356 23.7908 0.113041 23.8664 0.118082 23.9723C0.123124 24.1286 0.092875 24.184 0.113041 24.3857C0.14329 24.6579 0.133207 24.91 0.148331 25.1772C0.153373 25.2831 0.223954 25.3839 0.223954 25.4696C0.223954 25.6561 0.168497 25.888 0.188663 26.0847C0.198746 26.1653 0.289493 26.246 0.294534 26.3519C0.304617 26.4527 0.254203 26.5283 0.259244 26.614C0.259244 26.7199 0.349991 26.8409 0.365115 26.9417C0.470986 27.6475 0.476028 28.3533 0.627273 29.0541C0.692812 29.3566 0.849098 29.7044 0.899513 30.0069C0.92472 30.1632 0.899513 30.3699 0.944886 30.5262C0.970094 30.617 1.09109 30.7279 1.11126 30.8035C1.1415 30.9648 1.10117 31.1463 1.16671 31.3732C1.28771 31.7765 1.42887 32.1647 1.54482 32.563C1.60532 32.7646 1.79186 33.0217 1.82211 33.1276C1.84731 33.2184 1.81706 33.3041 1.84731 33.3999C1.91285 33.6166 2.19013 34.03 2.30105 34.2619C2.67916 35.0484 2.95644 35.6635 3.52109 36.3743C3.65721 36.5407 3.65721 36.697 3.76308 36.8533C3.89416 37.0549 4.22689 37.3877 4.39326 37.5994C4.63021 37.8918 4.9075 38.1691 5.18478 38.4413C6.27878 39.5001 7.73073 40.5336 9.24317 41.1637C9.87336 41.4259 10.5943 41.7586 11.2396 41.9603C11.4362 42.0208 11.7085 42.0359 11.895 42.0964C12.0059 42.1317 12.1118 42.2376 12.2328 42.2577C12.989 42.3838 13.715 42.5854 14.4762 42.7014C14.5619 42.7165 14.6577 42.6964 14.7485 42.7115C14.8392 42.7266 14.9501 42.8224 15.0056 42.8224C15.0762 42.8224 15.1014 42.7316 15.2173 42.7316C15.3484 42.7316 15.6761 42.8526 15.8677 42.8778C16.0794 42.9031 16.3668 42.8829 16.5685 42.9182C16.6642 42.9333 16.7651 43.019 16.8659 43.0291C16.9819 43.0392 17.0424 42.9736 17.1482 42.9686C17.4255 42.9585 17.8036 43.0896 18.1464 43.0593C18.212 43.0543 18.2674 43.0039 18.3279 43.0039C18.4237 43.0039 18.5951 43.1198 18.7212 43.1198C18.8875 43.1198 19.0942 43.0845 19.2959 43.0896C19.4623 43.0896 19.7093 43.1501 19.8656 43.145C19.9614 43.145 20.032 43.0593 20.1479 43.0593C20.2487 43.0593 20.3697 43.14 20.4706 43.14C20.5764 43.14 20.7075 43.0845 20.8285 43.0845C21.0604 43.0845 21.4335 43.1501 21.6553 43.145C21.7259 43.145 21.7511 43.0644 21.8671 43.0543C22.0133 43.0442 22.1242 43.1198 22.2603 43.1148C22.3561 43.1148 22.4418 43.0593 22.5477 43.0543C22.7997 43.0392 23.1274 43.0845 23.3644 43.0543C23.4602 43.0442 23.5358 42.9736 23.6165 42.9686C23.7223 42.9636 23.8282 43.0341 23.9694 43.0291C24.0853 43.0241 24.1559 42.9535 24.2567 42.9383C24.3828 42.9232 24.5239 42.9484 24.6449 42.9383C24.9928 42.898 25.3154 42.8073 25.6734 42.8829C25.7238 42.8728 25.7087 42.7619 25.7893 42.7367C25.8801 42.7115 26.2632 42.7367 26.4195 42.7266C26.5254 42.7165 26.6968 42.6812 26.8027 42.6611C27.7807 42.4695 28.7588 42.2527 29.7167 41.9754C29.7721 41.9603 29.8225 41.8998 29.8931 41.8897C29.9839 41.8746 30.0847 41.8998 30.1653 41.8796C30.241 41.8595 30.2561 41.7586 30.3418 41.7284C30.4376 41.6931 30.5334 41.7183 30.6292 41.6981C30.7451 41.6729 30.7854 41.5671 30.8611 41.5368C30.9972 41.4814 31.2442 41.4814 31.4207 41.4108C31.582 41.3452 31.703 41.2091 31.8845 41.1436C32.0105 41.0982 32.1971 41.0881 32.3181 41.0276C32.3685 41.0024 32.4441 40.8915 32.4945 40.8663C32.9735 40.6193 33.5129 40.4025 33.9666 40.1101C34.0271 40.0697 34.0775 39.9588 34.1784 39.9185C34.259 39.8832 34.3548 39.8983 34.4052 39.8731C34.4557 39.8479 34.4708 39.737 34.5262 39.6815C34.5968 39.611 34.7229 39.6009 34.7934 39.5505C34.8539 39.5051 34.8741 39.4244 34.9296 39.3891C34.9951 39.3488 35.1413 39.3387 35.1917 39.2883C35.2421 39.2379 35.222 39.1522 35.2724 39.1018C35.3127 39.0614 35.464 39.0261 35.5093 38.9909C35.5648 38.9455 35.5497 38.8699 35.6202 38.8144C35.6807 38.769 35.7967 38.7589 35.8673 38.6985C35.9681 38.6077 36.1395 38.3103 36.2605 38.2246C36.326 38.1792 36.4269 38.1943 36.4924 38.1187C36.563 38.0431 36.5327 37.9422 36.5781 37.8717C36.6033 37.8364 36.7495 37.796 36.81 37.7305C37.168 37.3574 37.4453 36.9037 37.7931 36.5306C37.9444 36.3693 38.0502 36.0466 38.1813 35.8601C38.4031 35.5576 38.4586 35.3812 38.635 35.0887C38.6804 35.0182 38.7661 34.9829 38.8065 34.9073C38.872 34.7711 38.9073 34.509 38.9678 34.3981C38.9879 34.3577 39.1089 34.3325 39.1543 34.2216C39.2753 33.9191 39.4114 33.6116 39.5123 33.3091C39.5828 33.1074 39.5526 33.1427 39.6736 32.9713C39.7694 32.8402 39.7038 32.5428 39.7744 32.4067C39.7996 32.3512 39.9307 32.3361 39.9559 32.2605C39.9862 32.1748 39.9206 32.0941 39.9307 32.0084C39.9408 31.9277 40.0265 31.8622 40.0567 31.7866C40.1777 31.4841 40.2282 31.0253 40.334 30.6875C40.4197 30.4153 40.4298 30.1582 40.4954 29.8809C40.5256 29.7599 40.6163 29.6288 40.6315 29.4927C40.6516 29.3314 40.6012 29.1095 40.6315 28.9633C40.6516 28.8827 40.7575 28.8978 40.7777 28.8171C40.7978 28.7264 40.7373 28.6407 40.7373 28.555C40.7373 28.434 40.8231 28.197 40.8432 28.0508C40.8987 27.6778 40.9188 27.3097 40.9793 26.9568C41.1155 26.1653 41.1003 25.3688 41.1659 24.5772C41.2465 23.5639 41.186 22.4699 41.2012 21.4566C41.2213 20.045 41.1507 18.6233 41.06 17.2217L41.0802 17.2318ZM38.6149 22.1674C38.6451 22.6312 38.5846 22.9489 38.5796 23.3824C38.5796 23.6748 38.635 23.9471 38.6149 24.2496C38.5745 24.8646 38.4586 25.52 38.4334 26.13C38.4334 26.2057 38.4788 26.2762 38.4737 26.3619C38.4687 26.4577 38.383 26.5081 38.3729 26.5737C38.3628 26.6493 38.4283 26.7199 38.4233 26.7955C38.4233 26.8913 38.3426 27.0375 38.3275 27.1484C38.3074 27.3299 38.3376 27.5416 38.2872 27.7231C38.267 27.8088 38.1813 27.8693 38.1813 27.955C38.1813 28.0408 38.267 28.1164 38.2519 28.2122C38.2418 28.2979 38.1309 28.3533 38.1107 28.4491C38.0906 28.5449 38.1208 28.6609 38.1057 28.7617C38.0805 28.9079 37.9746 29.1902 37.9746 29.3162C37.9746 29.3919 38.0805 29.5179 37.9948 29.5834C37.9544 29.6137 37.8536 29.6087 37.8284 29.6692C37.8032 29.7347 37.8637 29.8204 37.8587 29.896C37.8435 30.2187 37.6822 30.4002 37.6167 30.6573C37.5763 30.8136 37.5915 30.985 37.5259 31.1614C37.5007 31.2371 37.42 31.3076 37.3999 31.3782C37.3797 31.469 37.41 31.5648 37.3848 31.6555C37.3646 31.721 37.2638 31.8118 37.2234 31.9025C37.1629 32.0487 37.1579 32.2252 37.0974 32.3764C37.0268 32.563 36.8756 32.7848 36.805 32.9764C36.7798 33.0419 36.7999 33.1175 36.7747 33.1831C36.7394 33.2688 36.6235 33.2789 36.5932 33.3242C36.5479 33.3999 36.5932 33.5108 36.568 33.5914C36.5428 33.667 36.3966 33.6923 36.3513 33.7628C36.316 33.8233 36.326 34.03 36.2605 34.151C36.1849 34.3023 36.0084 34.4182 35.9076 34.6148C35.5547 35.3055 34.98 35.8752 34.4204 36.4752C34.2792 36.6314 34.1431 36.7726 33.9767 36.9239C33.9112 36.9793 33.8154 37.0096 33.7599 37.065C33.7045 37.1205 33.6994 37.1961 33.6541 37.2364C33.6087 37.2768 33.4978 37.2868 33.4423 37.3272C33.3869 37.3725 33.3768 37.4431 33.3163 37.4885C33.2507 37.5339 33.1096 37.5389 33.0592 37.5843C33.0037 37.6297 32.9785 37.7809 32.9029 37.8212C32.8424 37.8565 32.7315 37.8414 32.6558 37.8817C32.6256 37.8969 32.313 38.0985 32.2979 38.1136C32.0609 38.3203 32.1719 38.2246 31.8895 38.3607C31.5971 38.5069 31.2593 38.7287 30.9518 38.8699C30.7199 38.9757 29.7822 39.3841 29.5957 39.4194C29.4898 39.4396 29.3385 39.4043 29.2478 39.4547C29.1671 39.5051 29.2125 39.6009 29.1369 39.621C29.0512 39.6463 28.905 39.611 28.7941 39.6311C28.7386 39.6412 28.6479 39.7068 28.5622 39.732C28.2345 39.8227 27.7807 39.9487 27.4581 40.0193C27.2867 40.0597 27.0094 40.0546 26.838 40.0949C26.2784 40.2361 25.996 40.2462 25.4314 40.3168C25.3154 40.3319 25.2247 40.4126 25.1037 40.4277C24.9071 40.4529 24.5542 40.4226 24.3323 40.4428C24.2416 40.4529 24.1559 40.5285 24.0601 40.5386C23.9492 40.5487 23.7879 40.5033 23.6417 40.5184C23.2283 40.5537 22.7392 40.6293 22.3208 40.6344C21.8066 40.6445 21.3024 40.6344 20.7882 40.6344C19.8908 40.6293 18.9884 40.6344 18.0809 40.6041C17.9649 40.6041 17.844 40.5235 17.7532 40.5184C17.5515 40.5033 17.1835 40.5436 16.9819 40.5285C16.8407 40.5184 16.7197 40.4327 16.5735 40.4277C16.266 40.4176 16.2861 40.4277 15.9584 40.3773C15.1871 40.2462 14.0931 40.1605 13.3722 39.9588C13.2562 39.9286 13.1503 39.8277 13.0596 39.8025C12.7571 39.732 12.4546 39.7068 12.1319 39.6009C11.8547 39.5101 11.7085 39.4496 11.4161 39.369C11.2094 39.3135 10.8363 39.0413 10.6245 39.0009C10.2011 38.9152 10.3674 38.9354 10.0397 38.7539C9.76749 38.6027 9.42467 38.4867 9.17259 38.3254C8.98606 38.2094 8.80961 38.038 8.61803 37.9069C8.21471 37.6397 7.79627 37.4078 7.45849 37.0801C6.80814 36.45 6.33424 35.8853 5.80992 35.1795C5.68388 35.0081 5.42173 34.756 5.32594 34.5896C5.27048 34.4989 5.30073 34.4081 5.26544 34.3224C5.24023 34.2569 5.10916 34.1561 5.06378 34.0805C4.63526 33.3746 4.27731 32.3663 4.02019 31.6C3.94961 31.3933 3.76812 31.0455 3.72275 30.8589C3.6925 30.743 3.73787 30.6119 3.70762 30.496C3.58663 30.017 3.35472 29.5381 3.25389 29.039C3.20347 28.7869 3.2186 28.4844 3.17827 28.2323C3.16314 28.1315 3.05223 28.0206 3.04719 27.9651C3.04215 27.9198 3.11777 27.8593 3.11273 27.7685C3.10264 27.5719 2.94636 27.2492 2.93627 27.0375C2.93123 26.9367 2.98165 26.861 2.96652 26.7552C2.9514 26.6493 2.83544 26.5586 2.82536 26.4577C2.81528 26.3216 2.86065 26.1099 2.84049 25.9485C2.82536 25.8326 2.70941 25.525 2.71949 25.4746C2.72957 25.4041 2.82032 25.3839 2.84553 25.3436C2.91107 25.2326 2.73966 25.1167 2.72453 25.0058C2.69932 24.8243 2.73966 24.5672 2.71949 24.3756C2.70437 24.2596 2.5632 24.0227 2.64387 23.937C2.66907 23.9118 2.73461 23.9067 2.7447 23.8916C2.82032 23.7908 2.65395 23.6294 2.64891 23.5337C2.63883 23.3219 2.66908 23.09 2.64891 22.8682C2.63883 22.7472 2.52791 22.601 2.59849 22.48C2.6237 22.4397 2.71949 22.4145 2.71949 22.3489C2.71949 22.2884 2.61866 22.1977 2.61362 22.117C2.60858 21.9658 2.65899 21.7187 2.64891 21.5725C2.63883 21.4263 2.58841 21.2499 2.54304 21.1087L2.70941 20.9323C2.70941 20.8516 2.61866 20.776 2.60858 20.7054C2.59345 20.5138 2.66403 20.2365 2.64387 20.0097C2.63378 19.8988 2.53295 19.7324 2.58841 19.6517C2.61866 19.6013 2.7447 19.5963 2.74974 19.5257C2.74974 19.4702 2.66908 19.3593 2.66403 19.2837C2.64387 19.1022 2.71445 18.8652 2.71445 18.6838C2.71445 18.5678 2.61362 18.3359 2.67412 18.2552C2.70941 18.2099 2.8052 18.2199 2.82032 18.1342C2.83544 18.0536 2.74974 17.9729 2.7447 17.8771C2.73461 17.6855 2.77999 17.1814 2.80015 16.9797C2.81024 16.8991 2.92115 16.8436 2.93123 16.7478C2.93627 16.647 2.84553 16.5562 2.85561 16.4605C2.86065 16.3848 2.94132 16.3193 2.96148 16.2285C3.00181 16.042 2.97661 15.8101 3.00181 15.6286C3.05223 15.3009 3.12281 15.0388 3.14802 14.706C3.16818 14.4741 3.34968 14.1262 3.38497 13.8893C3.40513 13.7431 3.34463 13.5616 3.43034 13.4305C3.46059 13.3851 3.54629 13.3499 3.56142 13.3045C3.58663 13.2289 3.52613 13.1583 3.53117 13.0827C3.53117 13.0524 3.60175 12.7298 3.61687 12.6894C3.64208 12.6037 3.73283 12.5634 3.74291 12.5029C3.76812 12.4021 3.73283 12.3113 3.7782 12.1853C3.90424 11.8677 3.9849 11.5047 4.13111 11.2022C4.17144 11.1165 4.28739 10.9955 4.30252 10.935C4.32268 10.8543 4.29243 10.7585 4.31764 10.688C4.33781 10.6325 4.44872 10.5922 4.47393 10.5216C4.49914 10.4611 4.46384 10.4056 4.48401 10.335C4.53947 10.1536 4.77642 9.79561 4.87221 9.60403C4.96799 9.4175 5.03353 9.34692 5.11924 9.19567C5.19486 9.06459 5.16965 8.83773 5.31081 8.74194C5.35115 8.71673 5.42677 8.71169 5.45702 8.68144C5.50743 8.6159 5.47214 8.49995 5.51752 8.41928C5.53264 8.38399 5.92588 7.82439 5.95108 7.79918C6.24853 7.48661 6.60648 7.09841 6.89384 6.76568C6.96946 6.67493 7.0098 6.53881 7.10558 6.45815C7.19633 6.38252 7.38287 6.32707 7.46857 6.2464C7.55932 6.15566 7.62486 5.97416 7.75089 5.88342C7.84164 5.81788 7.99289 5.78763 8.07859 5.72713C8.14413 5.68176 8.17942 5.59101 8.23992 5.5406C8.6382 5.19777 9.50533 4.68354 10.0095 4.43147C10.1708 4.3508 10.4935 4.2651 10.6044 4.19452C10.6548 4.16427 10.6649 4.10377 10.7405 4.06344C10.8615 4.00294 10.9825 4.03823 11.0984 4.00798C11.2295 3.97269 11.2094 3.87186 11.2648 3.84665C11.3707 3.7912 11.6933 3.76599 11.8194 3.72566C11.9958 3.6702 12.2882 3.50383 12.5151 3.45342C12.742 3.403 12.8176 3.36771 13.0192 3.31226C13.1755 3.26688 13.3066 3.29713 13.4276 3.26688C13.4982 3.25176 13.5738 3.14589 13.6847 3.13076C14.0427 3.07531 14.1737 3.09547 14.5267 2.97448C14.7485 2.89886 14.935 2.95431 15.1468 2.92406C15.4392 2.88877 15.7921 2.77282 16.1248 2.73753C16.2055 2.72744 16.2962 2.76273 16.397 2.74761C16.4979 2.73249 16.5685 2.65686 16.6642 2.64678C16.7399 2.64174 16.8155 2.69215 16.9012 2.69215C17.0121 2.69215 17.2087 2.61653 17.365 2.60645C17.5768 2.59132 17.844 2.62661 18.0406 2.60141C18.1414 2.58628 18.1868 2.5157 18.2926 2.51066C18.4237 2.50562 18.5598 2.60141 18.6808 2.59132C18.7464 2.59132 18.8119 2.52578 18.9027 2.5157C19.1346 2.48545 19.4724 2.52074 19.7194 2.5157C19.8303 2.5157 19.9513 2.46529 20.0471 2.46529C20.1731 2.46529 20.2992 2.56108 20.405 2.56612C20.5008 2.56612 20.6016 2.47537 20.6873 2.46529C20.7831 2.4552 20.9545 2.51066 21.0755 2.5157C21.3125 2.52578 21.615 2.47537 21.8267 2.48545C21.9477 2.49049 22.0536 2.57116 22.1847 2.5762C22.3057 2.5762 22.3964 2.5157 22.5023 2.5157C22.7039 2.5157 23.0568 2.60141 23.2888 2.60645C23.4249 2.60645 23.5509 2.56108 23.677 2.5762C23.7677 2.58628 23.8383 2.64678 23.9341 2.66191C24.5844 2.74761 25.2701 2.76274 25.9255 2.86861C26.1523 2.9039 26.5153 3.03498 26.722 3.06522C26.8128 3.07531 26.9136 3.03498 27.0295 3.04002C27.2362 3.05514 27.448 3.21143 27.5841 3.23159C27.6547 3.24168 27.7152 3.20639 27.7807 3.21647C27.8563 3.22655 27.9824 3.3173 28.0933 3.34251C28.1891 3.36267 28.2697 3.32234 28.3403 3.34251C28.421 3.36267 28.5319 3.45846 28.6227 3.49375C29.4898 3.81641 30.5132 4.02311 31.2896 4.48188C31.5669 4.64825 31.466 4.63313 31.8139 4.74908C32.2122 4.88016 32.4088 5.15744 32.6861 5.37423C32.8222 5.4801 33.0188 5.52043 33.1449 5.61118C33.281 5.71201 33.4776 5.90358 33.6288 6.02962C34.1078 6.42285 34.4708 6.76063 34.8539 7.20429C35.2472 7.66306 35.6505 8.21258 35.9731 8.69657C36.0286 8.78227 36.0538 8.87302 36.1143 8.95872C36.1597 9.01922 36.2756 9.08476 36.3059 9.1503C36.3462 9.24609 36.2605 9.30659 36.3059 9.41246C36.326 9.46791 36.4672 9.57883 36.5227 9.67461C36.8403 10.204 36.9764 10.7585 37.1982 11.2929C37.2285 11.3635 37.3343 11.4341 37.3545 11.5148C37.3797 11.6156 37.3192 11.6811 37.3343 11.7618C37.3596 11.8878 37.5158 12.1248 37.5612 12.276C37.6973 12.6945 37.7679 13.1331 37.8687 13.5566C37.8889 13.6423 37.9897 13.7229 37.9948 13.7683C38.0049 13.8288 37.9192 13.8691 37.904 13.9145C37.8788 13.9952 38.0452 14.3582 38.0704 14.4892C38.1057 14.6606 38.0906 14.8724 38.1158 15.0287C38.1359 15.1345 38.2721 15.2606 38.2771 15.3614C38.2771 15.427 38.2166 15.4874 38.2166 15.5832C38.2267 15.7748 38.388 16.1227 38.388 16.3092C38.388 16.3848 38.3023 16.4554 38.3023 16.5109C38.3023 16.5815 38.4132 16.7075 38.4283 16.8033C38.4384 16.8688 38.3981 16.9495 38.4031 17.0201C38.4687 17.8217 38.5342 18.6182 38.5745 19.4249C38.5998 19.9592 38.6401 20.5138 38.6653 21.0533C38.6804 21.4112 38.5796 21.8196 38.6048 22.1775L38.6149 22.1674Z" fill="currentColor"/>
336 + <path d="M31.7686 24.8706C30.9015 24.3967 30.0595 23.9127 29.3184 23.2725C29.2277 23.1969 29.1571 23.0154 29.0764 22.9246C28.8042 22.607 28.6378 22.6927 28.4967 22.2188C28.4715 22.1281 28.3555 21.7802 28.3454 21.7247C28.3303 21.634 28.3706 21.3315 28.3958 21.2458C28.4412 21.0946 28.6025 21.0794 28.7034 20.9836C28.7639 20.9181 28.8042 20.7971 28.8445 20.7669C28.9605 20.6862 29.6613 20.54 29.8427 20.4795C29.9889 20.4341 30.3469 20.3434 30.4477 20.298C30.4981 20.2728 30.9519 19.9098 31.0023 19.8543C31.0678 19.7838 31.2644 19.2847 31.2695 19.1939C31.2695 19.1284 31.0678 18.5889 31.0225 18.5083C30.8561 18.2108 30.236 17.9739 29.9133 17.9436C29.3588 17.8932 28.9302 18.0394 28.3958 18.0344C28.1034 18.0344 28.1942 18.0697 28.0026 18.105C27.3321 18.231 26.2028 18.1806 25.7037 17.6613C25.3861 17.3336 25.2096 17.0815 24.9979 16.6883C24.5794 15.922 24.3576 15.1809 23.9997 14.4045C23.9442 14.2835 23.798 14.1121 23.7678 13.9961C23.7375 13.8852 23.7879 13.8298 23.7728 13.7441C23.7526 13.6281 23.6064 13.5071 23.556 13.3861C23.4854 13.2046 23.4804 12.9778 23.435 12.7963C23.3947 12.64 23.2686 12.5291 23.2283 12.398C22.9611 11.5762 22.7242 10.7293 22.5275 9.89237C22.4973 9.77137 22.3763 9.6655 22.336 9.54451C22.1141 8.84878 22.1091 8.54125 21.4588 8.06735C21.2823 7.93627 21.2571 8.00685 21.0958 7.96652C20.9899 7.93627 20.8739 7.83544 20.7832 7.81023C20.6924 7.79007 19.9766 7.96148 19.8757 8.01189C19.785 8.06231 19.2808 8.60679 19.2203 8.71266C19.1245 8.87399 19.0893 9.14119 19.0136 9.29243C18.9834 9.35293 18.8977 9.38822 18.8725 9.46888C18.8321 9.59996 18.8826 9.75625 18.8473 9.8722C18.8271 9.94278 18.7212 9.98312 18.6859 10.0688C18.6053 10.2554 18.6456 10.5276 18.5952 10.694C18.5649 10.7948 18.4087 11.0418 18.3633 11.2082C18.212 11.7174 18.0557 12.2165 17.9095 12.7156C17.8642 12.8719 17.844 13.008 17.8037 13.1441C17.7734 13.25 17.6222 13.3609 17.5869 13.4617C17.5466 13.5827 17.5566 13.7642 17.5163 13.8903C17.4306 14.1625 17.1684 14.7776 17.0424 15.0549C17.0122 15.1204 16.9265 15.1758 16.9063 15.2414C16.871 15.3573 16.9567 15.4683 16.9315 15.5741C16.9214 15.6195 16.75 15.6397 16.6996 15.7052C16.6391 15.7909 16.6492 15.9674 16.6088 16.0783C16.4778 16.4513 16.0291 17.2277 15.7518 17.505C15.5804 17.6764 15.0056 18.0747 14.7838 18.105C14.5872 18.1352 14.2494 18.0495 14.0982 18.0646C14.0276 18.0747 13.9822 18.1654 13.8814 18.1654C13.7856 18.1654 13.7049 18.0848 13.6192 18.0747C13.4831 18.0596 13.2613 18.105 13.0949 18.0898C12.6109 18.0445 12.0261 17.8478 11.522 17.9134C11.406 17.9285 11.3506 17.994 11.2649 18.0142C11.1187 18.0445 10.7658 18.0545 10.6599 18.1201C10.5641 18.1755 10.5792 18.2713 10.4885 18.3419C10.4229 18.3974 10.312 18.4024 10.2767 18.4478C10.2314 18.4982 10.2364 18.6242 10.2011 18.6948C10.1608 18.7654 10.0499 18.8057 10.0398 18.8965C10.0297 18.9721 10.1003 19.0376 10.1104 19.1082C10.1305 19.2242 10.0852 19.33 10.1003 19.4359C10.1557 19.8443 10.4229 19.9199 10.7153 20.1064C10.801 20.1619 10.8565 20.2627 10.9725 20.3131C11.4363 20.5299 12.0513 20.4896 12.5 20.8173C12.7622 21.0088 13.0092 21.4575 12.9286 21.7752C12.737 22.5314 12.1169 23.1111 11.5371 23.5951C11.3859 23.7212 11.2699 23.8472 11.1439 23.9531C11.0279 24.0489 10.7809 24.0942 10.6347 24.1951C10.5187 24.2707 10.428 24.427 10.3171 24.4824C10.1759 24.553 9.95408 24.5883 9.78267 24.6639C9.61126 24.7446 9.17265 25.042 9.05165 25.0723C8.96091 25.0975 8.82479 25.0622 8.74412 25.1126C8.68867 25.1479 8.70379 25.2689 8.63321 25.3344C8.5475 25.4101 8.37105 25.3748 8.31055 25.4403C8.25006 25.4958 8.27526 25.6319 8.22485 25.7126C8.16435 25.8134 8.00807 25.7983 7.97277 25.8638C7.94253 26.3024 7.83161 27.0385 8.28031 27.2956C9.00628 27.7039 9.76754 27.4872 10.4885 27.23C10.8212 27.1141 10.8716 27.0637 11.1489 26.8923C11.2951 26.8015 11.4917 26.7965 11.6279 26.7309C11.8093 26.6452 12.374 26.2167 12.5757 26.0705C12.7672 25.9344 12.7672 25.889 12.9033 25.7731C12.9689 25.7176 13.105 25.6823 13.1958 25.6168C13.3772 25.4857 13.9268 25.0067 14.0679 24.8454C14.1335 24.7748 14.1688 24.6387 14.2242 24.5933C14.2797 24.548 14.3855 24.5379 14.446 24.4774C14.5267 24.4018 14.5217 24.3009 14.5872 24.2253C14.6477 24.1547 14.7385 24.1497 14.7788 24.1094C14.9099 23.9783 15.1468 23.6002 15.288 23.4338C15.4896 23.1868 16.2257 22.2944 16.4526 22.1633C16.5483 22.1079 17.0626 21.8457 17.1533 21.8256C17.2894 21.7953 17.3903 21.8609 17.5163 21.8357C17.597 21.8205 17.6222 21.7399 17.6978 21.7298C17.839 21.7096 18.4742 21.9667 18.6154 22.0575C18.8069 22.1785 18.9985 22.4759 19.0943 22.6826C19.2203 22.9549 19.3867 23.7968 19.3917 24.0993C19.3917 24.2707 19.3212 24.4824 19.3262 24.6337C19.3262 24.7295 19.3968 24.8253 19.3917 24.916C19.3867 24.9916 19.3161 25.0521 19.3161 25.1681C19.3161 25.3546 19.3917 25.6117 19.3917 25.8083C19.3917 26.01 19.3212 26.2167 19.3262 26.3831C19.3262 26.4789 19.3968 26.5746 19.3917 26.6654C19.3867 26.741 19.3212 26.8015 19.3161 26.9175C19.3111 27.104 19.3867 27.3662 19.3917 27.5577C19.3917 27.7745 19.3212 27.9913 19.3262 28.1678C19.3262 28.2635 19.3968 28.3593 19.3917 28.4501C19.3867 28.5257 19.3161 28.5862 19.3161 28.7022C19.3161 28.8786 19.3867 29.1256 19.3917 29.3071C19.3917 29.5239 19.3212 29.7407 19.3262 29.9172C19.3262 30.0129 19.3968 30.1087 19.3917 30.1995C19.3867 30.2751 19.3212 30.3356 19.3161 30.4516C19.3111 30.6482 19.3917 30.9204 19.3917 31.1271C19.3917 31.3237 19.3212 31.5355 19.3262 31.7018C19.3262 31.7976 19.3968 31.8934 19.3917 31.9842C19.3867 32.0598 19.3161 32.1203 19.3161 32.2362C19.3161 32.4581 19.3968 32.7505 19.4119 32.9874C19.422 33.1336 19.3665 33.3202 19.3917 33.4865C19.4069 33.5974 19.5178 33.7084 19.5228 33.8193C19.5279 33.91 19.4774 33.9755 19.5027 34.0865C19.5178 34.152 19.79 34.5352 19.8505 34.5957C19.9917 34.7419 20.5664 34.9788 20.758 34.9586C20.8739 34.9486 21.5545 34.5352 21.615 34.4343C21.6655 34.3537 21.7663 34.0461 21.7965 33.9403C21.973 33.3504 21.9175 33.0429 21.9175 32.4581C21.9175 31.9237 21.9679 31.4145 21.9528 30.8851C21.9377 30.2953 21.973 29.7205 21.9528 29.1357C21.9327 28.5358 21.9679 27.951 21.9528 27.351C21.9377 26.7612 21.973 26.1865 21.9528 25.6016C21.9327 25.0017 21.9528 24.4068 21.973 23.8119C21.9881 23.3582 22.0234 23.3582 22.1494 22.9448C22.1847 22.8389 22.2301 22.491 22.2553 22.4457C22.2856 22.3953 22.4267 22.3852 22.4923 22.3247C22.5578 22.2642 22.5628 22.1633 22.6133 22.123C22.6687 22.0776 22.8099 22.0726 22.8956 22.0121C22.9611 21.9667 22.9712 21.9062 23.0166 21.876C23.2233 21.7247 24.0702 21.7953 24.3072 21.8961C24.6097 22.0272 24.9021 22.3448 25.1491 22.5515C25.2399 22.6272 25.3709 22.6675 25.4718 22.7633C25.7289 23.0053 26.0667 23.5598 26.3087 23.8522C27.2615 24.9916 28.2648 25.8335 29.515 26.6049C30.1654 27.0032 30.6695 27.2351 31.4157 27.4115C31.7131 27.4821 32.1517 27.6031 32.4492 27.5023C32.5097 27.4821 32.5601 27.4216 32.6055 27.4115C32.6962 27.3863 32.8576 27.4267 32.9281 27.3561C32.9685 27.3208 32.9836 27.1948 33.0441 27.1343C33.1046 27.0738 33.2004 27.0637 33.2407 27.0082C33.3667 26.8469 33.3617 26.3881 33.3163 26.1865C33.1248 25.3294 32.424 25.2134 31.7636 24.8505L31.7686 24.8706ZM22.4217 18.0898C22.4066 18.1806 22.341 18.2411 22.3259 18.3117C22.3057 18.4074 22.341 18.5284 22.3007 18.6091C22.2704 18.6746 22.099 18.7049 22.0335 18.7704C21.9831 18.8259 21.9679 18.9116 21.9226 18.9469C21.8167 19.0175 21.2521 19.1889 21.1109 19.209C20.9445 19.2292 20.8286 19.1637 20.6924 19.1788C20.5664 19.1939 20.5261 19.3048 20.3748 19.2897C20.279 19.2796 20.1933 19.1889 20.1026 19.1637C19.5531 19.0074 19.3212 19.0023 18.9582 18.4881C18.812 18.2814 18.8926 18.1907 18.8876 17.9537C18.8775 17.3286 19.0439 17.1017 19.2556 16.5421C19.2859 16.4665 19.2657 16.3707 19.296 16.3001C19.3212 16.2396 19.4472 16.1791 19.4724 16.1186C19.5027 16.043 19.4825 15.9623 19.5178 15.8817C19.5884 15.7203 19.7295 15.5792 19.8203 15.433C19.8707 15.3573 19.8808 15.2464 19.9261 15.1809C19.9413 15.1607 20.3043 14.8986 20.3395 14.8784C20.4303 14.833 20.7126 14.7725 20.8034 14.8078C20.8134 14.8078 20.8185 14.8532 20.8387 14.8683C21.615 15.3775 21.6554 15.801 22.0184 16.5723C22.0587 16.6631 22.1999 16.774 22.2251 16.8647C22.2503 16.9505 22.1646 17.0059 22.1696 17.0967C22.1696 17.1672 22.2956 17.3235 22.3309 17.4344C22.3813 17.5907 22.4418 17.9386 22.4166 18.0949L22.4217 18.0898Z" fill="currentColor"/>
337 + <path d="M61.8864 15.3776H69.4335L70.7947 19.0529H74.4951L67.8252 1.33203H63.6811L57.0112 19.0529H60.5302L61.8914 15.3776H61.8864ZM65.1381 6.59534L65.6624 4.78041L66.1817 6.59534L68.4302 12.6653H62.8896L65.1381 6.59534Z" fill="currentColor"/>
338 + <path d="M78.9013 17.5794C79.8038 18.8247 81.0289 19.4498 82.5766 19.4498C84.3512 19.4498 85.7678 18.8095 86.8366 17.534C87.9054 16.2585 88.4398 14.4285 88.4398 12.0489C88.4398 10.5213 88.2029 9.2055 87.734 8.1115C87.2652 7.01749 86.5896 6.17557 85.7124 5.58067C84.8352 4.99082 83.7865 4.69337 82.5715 4.69337C81.5481 4.69337 80.6558 4.99082 79.8996 5.58067C79.5214 5.87308 79.1887 6.23102 78.8963 6.65451V0.287109H75.5588V19.0515H78.8963V17.5744L78.9013 17.5794ZM79.6828 8.63077C80.202 7.84934 80.9684 7.4561 81.9766 7.4561C82.9849 7.4561 83.7008 7.84934 84.2302 8.64085C84.7596 9.43237 85.0268 10.5667 85.0268 12.0439C85.0268 13.521 84.7646 14.6856 84.2453 15.4822C83.726 16.2837 82.9698 16.682 81.9766 16.682C80.9835 16.682 80.2273 16.2837 79.6979 15.4822C79.1685 14.6806 78.9064 13.5361 78.9064 12.0439C78.9064 10.5516 79.1635 9.4122 79.6878 8.63077H79.6828Z" fill="currentColor"/>
339 + <path d="M93.4207 5.08594H90.0833V19.0559H93.4207V5.08594Z" fill="currentColor"/>
340 + <path d="M93.4207 0.287109H90.0833V3.5187H93.4207V0.287109Z" fill="currentColor"/>
341 + <path d="M96.1281 14.7014C96.1281 16.2642 96.4709 17.4439 97.1566 18.2455C97.8422 19.0421 98.9009 19.4454 100.323 19.4454C100.776 19.4454 101.185 19.4152 101.548 19.3547C101.911 19.2942 102.183 19.2085 102.354 19.1076V16.6575H101.729C100.807 16.6575 100.202 16.4659 99.9042 16.0827C99.6067 15.6996 99.4605 15.145 99.4605 14.414V7.66345H102.405V5.0822H99.4605V1.88086H96.123V5.08724H94.3232V7.66849H96.123V14.7064L96.1281 14.7014Z" fill="currentColor"/>
342 + <path d="M106.922 5.08594H103.584V19.0559H106.922V5.08594Z" fill="currentColor"/>
343 + <path d="M106.922 0.287109H103.584V3.5187H106.922V0.287109Z" fill="currentColor"/>
344 + <path d="M116.198 19.4448C117.968 19.4448 119.39 18.8045 120.458 17.529C121.527 16.2535 122.062 14.4234 122.062 12.0439C122.062 10.5163 121.83 9.20046 121.356 8.10646C120.887 7.01245 120.211 6.17052 119.334 5.57563C118.457 4.98578 117.408 4.68833 116.193 4.68833C115.17 4.68833 114.278 4.98578 113.521 5.57563C113.143 5.86804 112.811 6.23102 112.518 6.64947V0.287109H109.181V19.0515H112.518V17.5744C113.421 18.8196 114.646 19.4448 116.193 19.4448H116.198ZM113.305 8.63077C113.829 7.84934 114.59 7.4561 115.598 7.4561C116.607 7.4561 117.323 7.84934 117.852 8.64085C118.381 9.43237 118.649 10.5667 118.649 12.0439C118.649 13.521 118.386 14.6856 117.867 15.4822C117.348 16.2837 116.592 16.682 115.598 16.682C114.605 16.682 113.849 16.2837 113.32 15.4822C112.79 14.6806 112.523 13.5361 112.523 12.0439C112.523 10.5516 112.785 9.4122 113.305 8.63077Z" fill="currentColor"/>
345 + <path d="M127.038 0.287109H123.7V3.5187H127.038V0.287109Z" fill="currentColor"/>
346 + <path d="M127.038 5.08594H123.7V19.0559H127.038V5.08594Z" fill="currentColor"/>
347 + <path d="M72.3323 27.0274V24.1387H66.6253H63.1064H57.3994V27.0274H63.1064V41.8595H66.6253V27.0274H72.3323Z" fill="currentColor"/>
348 + <path d="M79.7737 38.9643C79.2443 39.3273 78.5889 39.5139 77.8075 39.5139C76.7135 39.5139 75.8766 39.1156 75.2918 38.314C74.7977 37.6384 74.5154 36.7158 74.4398 35.5513H84.2959V34.7698C84.2959 33.2574 84.0236 31.9668 83.4892 30.8879C82.9498 29.809 82.1885 28.9772 81.1954 28.3873C80.2022 27.7974 79.0326 27.5 77.6764 27.5C76.3202 27.5 75.1657 27.7874 74.1574 28.3621C73.1491 28.9368 72.3728 29.7737 71.8232 30.8778C71.2787 31.9819 71.0015 33.3229 71.0015 34.9059C71.0015 36.489 71.2737 37.7241 71.8232 38.8282C72.3728 39.9323 73.1592 40.7793 74.1826 41.3691C75.2061 41.959 76.416 42.2564 77.8075 42.2564C79.4762 42.2564 80.8929 41.8329 82.0675 40.991C83.2422 40.1491 84.0186 38.919 84.4017 37.3007H80.8828C80.671 38.0468 80.303 38.6064 79.7737 38.9694V38.9643ZM77.6764 30.2325C78.72 30.2325 79.5165 30.6257 80.0761 31.4172C80.4492 31.9466 80.6912 32.6272 80.8172 33.4641H74.5255C74.6515 32.6373 74.8935 31.9567 75.2565 31.4324C75.811 30.6308 76.6227 30.2325 77.6814 30.2325H77.6764Z" fill="currentColor"/>
349 + <path d="M76.0881 26.3253H79.108L81.9262 23.0938H78.2005L76.0881 26.3253Z" fill="currentColor"/>
350 + <path d="M103.675 28.0596C102.99 27.6865 102.203 27.5 101.316 27.5C100.343 27.5 99.4757 27.7319 98.7094 28.1907C98.0591 28.5839 97.5247 29.1486 97.0962 29.8745C97.076 29.8292 97.0609 29.7838 97.0407 29.7384C96.6777 28.9923 96.1685 28.4327 95.5182 28.0596C94.8678 27.6865 94.1066 27.5 93.2394 27.5C92.3168 27.5 91.5001 27.7319 90.7893 28.1907C90.2246 28.5587 89.7457 29.078 89.3575 29.7334V27.8882H86.02V41.8582H89.3575V33.7514C89.3575 33.2826 89.418 32.8339 89.539 32.4104C89.66 31.9819 89.8415 31.6088 90.0885 31.2912C90.3305 30.9685 90.6179 30.7165 90.9455 30.535C91.2783 30.3535 91.6514 30.2627 92.0648 30.2627C92.5487 30.2627 92.9621 30.3736 93.2898 30.6005C93.6226 30.8274 93.8646 31.1601 94.0209 31.6038C94.1771 32.0474 94.2578 32.5919 94.2578 33.2322V41.8582H97.5953V33.7514C97.5953 33.2826 97.6558 32.8339 97.7768 32.4104C97.8978 31.9819 98.0793 31.6088 98.3263 31.2912C98.5683 30.9685 98.8556 30.7165 99.1884 30.535C99.5161 30.3535 99.8942 30.2627 100.308 30.2627C100.812 30.2627 101.225 30.3736 101.548 30.6005C101.87 30.8274 102.107 31.1601 102.264 31.6038C102.42 32.0474 102.496 32.5919 102.496 33.2322V41.8582H105.833V32.5516C105.833 31.4223 105.647 30.4846 105.273 29.7384C104.9 28.9923 104.371 28.4327 103.685 28.0596H103.675Z" fill="currentColor"/>
351 + <path d="M111.454 27.8906H108.117V41.8606H111.454V27.8906Z" fill="currentColor"/>
352 + <path d="M111.454 23.0938H108.117V26.3253H111.454V23.0938Z" fill="currentColor"/>
353 + <path d="M123.574 34.6399C123.055 34.3626 122.48 34.1458 121.855 33.9895C121.23 33.8332 120.605 33.7072 119.979 33.6114C119.354 33.5156 118.775 33.3997 118.245 33.2736C117.716 33.1426 117.292 32.956 116.98 32.714C116.667 32.472 116.511 32.1141 116.511 31.6452C116.511 31.2117 116.697 30.8487 117.071 30.5512C117.444 30.2538 117.958 30.1076 118.623 30.1076C119.42 30.1076 120.06 30.2891 120.539 30.6521C121.018 31.0151 121.326 31.6251 121.467 32.4771H124.804C124.683 31.3125 124.34 30.3647 123.776 29.6387C123.211 28.9077 122.505 28.3683 121.653 28.0204C120.801 27.6725 119.838 27.4961 118.759 27.4961C117.544 27.4961 116.536 27.7078 115.735 28.1212C114.933 28.5397 114.333 29.0741 113.935 29.7244C113.536 30.3748 113.335 31.0856 113.335 31.8469C113.335 32.6636 113.491 33.319 113.804 33.8131C114.116 34.3071 114.53 34.7004 115.044 34.9877C115.558 35.2751 116.118 35.4919 116.723 35.6381C117.328 35.7843 117.943 35.9103 118.563 36.0162C119.178 36.1221 119.743 36.233 120.257 36.354C120.771 36.475 121.184 36.6565 121.497 36.9035C121.81 37.1455 121.966 37.4934 121.966 37.9471C121.966 38.4512 121.759 38.8646 121.341 39.1823C120.922 39.5049 120.297 39.6662 119.465 39.6662C118.633 39.6662 117.877 39.4595 117.318 39.0411C116.753 38.6227 116.41 38.0429 116.289 37.2967H112.952C113.194 38.9806 113.894 40.2309 115.049 41.0375C116.203 41.8442 117.64 42.2475 119.364 42.2475C120.544 42.2475 121.578 42.0559 122.465 41.6728C123.352 41.2896 124.048 40.7602 124.562 40.0847C125.076 39.4041 125.334 38.6227 125.334 37.7404C125.334 36.9237 125.172 36.2733 124.85 35.7843C124.527 35.3003 124.108 34.9172 123.584 34.6348L123.574 34.6399Z" fill="currentColor"/>
354 + <path d="M134.65 38.9011C134.161 39.2893 133.566 39.4859 132.85 39.4859C131.792 39.4859 130.98 39.0926 130.425 38.3011C129.871 37.5096 129.593 36.3753 129.593 34.8981C129.593 33.421 129.856 32.2362 130.39 31.4447C130.919 30.6532 131.706 30.26 132.749 30.26C133.445 30.26 134.05 30.4667 134.559 30.87C135.069 31.2783 135.406 31.9337 135.563 32.8362H138.9C138.759 31.6565 138.406 30.6683 137.831 29.8768C137.257 29.0853 136.531 28.4904 135.653 28.0921C134.776 27.6938 133.808 27.4922 132.744 27.4922C131.408 27.4922 130.244 27.7796 129.251 28.3543C128.257 28.929 127.496 29.7659 126.957 30.87C126.417 31.9741 126.15 33.3151 126.15 34.8981C126.15 36.4812 126.417 37.7113 126.957 38.8053C127.496 39.8993 128.257 40.7463 129.236 41.3462C130.219 41.9461 131.393 42.2436 132.77 42.2436C134.353 42.2436 135.704 41.8251 136.838 40.9933C137.967 40.1614 138.688 38.8658 139.001 37.1113H135.638C135.462 37.9129 135.134 38.5078 134.645 38.896L134.65 38.9011Z" fill="currentColor"/>
355 + <path d="M152.502 38.4462V33.0266C152.502 31.9477 152.285 30.9949 151.852 30.158C151.418 29.3211 150.763 28.6708 149.886 28.2019C149.008 27.733 147.925 27.4961 146.639 27.4961C145.459 27.4961 144.426 27.6978 143.549 28.096C142.671 28.4943 141.965 29.064 141.436 29.8051C140.907 30.5412 140.564 31.4335 140.408 32.4771H143.77C143.876 31.8166 144.143 31.2823 144.577 30.8739C145.011 30.4655 145.631 30.2639 146.427 30.2639C147.35 30.2639 148.03 30.4958 148.474 30.9697C148.918 31.4385 149.14 32.1242 149.14 33.0266V33.3694C148.035 33.3795 147.027 33.4249 146.13 33.5055C145.111 33.6013 144.118 33.8232 143.145 34.171C142.066 34.5391 141.265 35.0634 140.735 35.749C140.206 36.4347 139.944 37.2564 139.944 38.2093C139.944 39.0966 140.14 39.8377 140.544 40.4376C140.942 41.0375 141.497 41.4913 142.197 41.7937C142.903 42.0962 143.715 42.2475 144.632 42.2475C145.293 42.2475 145.953 42.1315 146.614 41.8946C147.274 41.6576 147.879 41.2946 148.424 40.8006C148.696 40.5535 148.948 40.2712 149.175 39.9486C149.185 40.1452 149.18 40.3468 149.195 40.5435C149.23 41.0728 149.316 41.5114 149.457 41.8593H152.926C152.805 41.4963 152.704 41.0476 152.628 40.5183C152.548 39.9889 152.512 39.2982 152.512 38.4462H152.502ZM147.345 39.278C146.79 39.6057 146.17 39.7721 145.495 39.7721C144.819 39.7721 144.305 39.6158 143.906 39.3033C143.508 38.9907 143.307 38.5622 143.307 38.0278C143.307 37.6648 143.397 37.322 143.579 37.0094C143.76 36.6968 144.078 36.4195 144.532 36.1775C144.985 35.9355 145.616 35.749 146.432 35.6179C147.153 35.502 148.061 35.4415 149.145 35.4314V36.0263C149.145 36.722 148.983 37.3572 148.661 37.9269C148.338 38.5017 147.899 38.9554 147.345 39.2831V39.278Z" fill="currentColor"/>
356 + <path d="M172.557 28.0596C171.871 27.6865 171.085 27.5 170.198 27.5C169.225 27.5 168.358 27.7319 167.591 28.1907C166.941 28.5839 166.407 29.1536 165.978 29.8745C165.958 29.8292 165.943 29.7838 165.923 29.7384C165.555 28.9923 165.05 28.4327 164.4 28.0596C163.75 27.6865 162.988 27.5 162.121 27.5C161.199 27.5 160.382 27.7319 159.671 28.1907C159.106 28.5587 158.628 29.078 158.239 29.7334V27.8882H154.902V41.8582H158.239V33.7514C158.239 33.2826 158.3 32.8339 158.421 32.4104C158.542 31.9819 158.723 31.6088 158.97 31.2912C159.212 30.9685 159.5 30.7165 159.827 30.535C160.16 30.3535 160.533 30.2627 160.947 30.2627C161.431 30.2627 161.844 30.3736 162.172 30.6005C162.504 30.8274 162.746 31.1601 162.903 31.6038C163.059 32.0474 163.14 32.5919 163.14 33.2322V41.8582H166.477V33.7514C166.477 33.2826 166.538 32.8339 166.659 32.4104C166.78 31.9819 166.961 31.6088 167.208 31.2912C167.45 30.9685 167.737 30.7165 168.07 30.535C168.398 30.3535 168.776 30.2627 169.189 30.2627C169.694 30.2627 170.107 30.3736 170.43 30.6005C170.752 30.8274 170.989 31.1601 171.146 31.6038C171.302 32.0474 171.377 32.5919 171.377 33.2322V41.8582H174.715V32.5516C174.715 31.4223 174.528 30.4846 174.155 29.7384C173.782 28.9923 173.253 28.4327 172.567 28.0596H172.557Z" fill="currentColor"/>
357 + <path d="M180.336 27.8906H176.999V41.8606H180.336V27.8906Z" fill="currentColor"/>
358 + <path d="M180.336 23.0938H176.999V26.3253H180.336V23.0938Z" fill="currentColor"/>
359 + <path d="M192.546 28.0596C191.841 27.6865 191.019 27.5 190.081 27.5C189.143 27.5 188.241 27.7319 187.49 28.1907C186.87 28.5688 186.356 29.1133 185.937 29.804V27.8932H182.6V41.8632H185.937V33.7565C185.937 33.2876 186.008 32.8389 186.144 32.4155C186.285 31.9869 186.482 31.6139 186.744 31.2962C187.006 30.9736 187.323 30.7215 187.697 30.54C188.07 30.3585 188.483 30.2678 188.937 30.2678C189.511 30.2678 189.98 30.3787 190.343 30.6056C190.706 30.8324 190.979 31.1652 191.165 31.6088C191.347 32.0525 191.437 32.5969 191.437 33.2372V41.8632H194.775V32.5566C194.775 31.4273 194.578 30.4896 194.19 29.7435C193.797 28.9973 193.252 28.4377 192.546 28.0646V28.0596Z" fill="currentColor"/>
360 + <path d="M205.982 29.3262C205.105 28.1061 203.83 27.4961 202.151 27.4961C200.472 27.4961 199 28.1162 197.966 29.3614C196.933 30.6017 196.414 32.3914 196.414 34.7155C196.414 36.2078 196.645 37.4883 197.104 38.5571C197.563 39.6259 198.223 40.4527 199.086 41.0325C199.943 41.6173 200.966 41.9047 202.146 41.9047C203.724 41.9047 204.984 41.2946 205.922 40.0645C205.942 40.0393 205.957 40.0141 205.972 39.9889V42.3483C205.972 43.4625 205.715 44.2439 205.201 44.6926C204.687 45.1463 204.016 45.3732 203.179 45.3732C202.342 45.3732 201.682 45.1766 201.132 44.7834C200.588 44.3952 200.26 43.8305 200.154 43.1045H196.792C197.034 44.7733 197.739 46.0084 198.904 46.805C200.069 47.6066 201.511 48.0049 203.23 48.0049C204.394 48.0049 205.433 47.7931 206.345 47.3696C207.258 46.9411 207.979 46.3109 208.508 45.464C209.037 44.622 209.305 43.5684 209.305 42.2979V27.8843H205.967V29.3211L205.982 29.3262ZM205.186 38.0177C204.656 38.7739 203.895 39.152 202.907 39.152C201.919 39.152 201.163 38.7689 200.638 38.0076C200.119 37.2413 199.857 36.1473 199.857 34.7256C199.857 33.3039 200.119 32.1796 200.638 31.4133C201.158 30.6521 201.914 30.2689 202.907 30.2689C203.9 30.2689 204.636 30.6571 205.176 31.4285C205.715 32.1998 205.982 33.2988 205.982 34.7256C205.982 36.1523 205.715 37.2665 205.186 38.0227V38.0177Z" fill="currentColor"/>
361 + <path d="M220.29 35.9965C220.29 36.4654 220.219 36.909 220.083 37.3275C219.942 37.7459 219.75 38.119 219.498 38.4467C219.246 38.7794 218.944 39.0315 218.586 39.213C218.228 39.3945 217.824 39.4852 217.376 39.4852C216.821 39.4852 216.362 39.3743 216.009 39.1474C215.652 38.9206 215.389 38.5878 215.213 38.1442C215.036 37.7005 214.951 37.1661 214.951 36.541V27.8898H211.613V37.2216C211.613 38.3358 211.805 39.2634 212.188 40.0095C212.571 40.7557 213.116 41.3153 213.816 41.6884C214.517 42.0614 215.324 42.248 216.226 42.248C217.184 42.248 218.031 42.0211 218.767 41.5674C219.372 41.1993 219.876 40.6649 220.295 39.9742V41.8547H223.632V27.8848H220.295V35.9915L220.29 35.9965Z" fill="currentColor"/>
362 + <path d="M235.147 37.2956C234.935 38.0417 234.567 38.5963 234.038 38.9643C233.508 39.3324 232.853 39.5139 232.072 39.5139C230.978 39.5139 230.141 39.1156 229.556 38.314C229.062 37.6384 228.78 36.7158 228.704 35.5513H238.56V34.7698C238.56 33.2574 238.288 31.9668 237.753 30.8879C237.214 29.809 236.448 28.9772 235.46 28.3873C234.471 27.7974 233.297 27.5 231.941 27.5C230.584 27.5 229.43 27.7874 228.422 28.3621C227.413 28.9368 226.637 29.7737 226.087 30.8778C225.538 31.9819 225.266 33.3229 225.266 34.9059C225.266 36.489 225.538 37.7241 226.087 38.8282C226.637 39.9323 227.423 40.7793 228.447 41.3691C229.47 41.959 230.68 42.2564 232.072 42.2564C233.74 42.2564 235.162 41.8329 236.332 40.991C237.506 40.1491 238.283 38.919 238.666 37.3007H235.147V37.2956ZM231.941 30.2325C232.984 30.2325 233.781 30.6257 234.34 31.4172C234.713 31.9466 234.955 32.6272 235.081 33.4641H228.79C228.916 32.6373 229.158 31.9567 229.521 31.4324C230.075 30.6308 230.882 30.2325 231.946 30.2325H231.941Z" fill="currentColor"/>
363 + </svg>
364 + </a>
365 + </div>
366 + </div>
367 + <div class="col text-center text-lg-center col-lg-6 d-none d-lg-block hidden-print">
368 + <nav class="main-menu menu-style1 menu-desktop">
369 + <ul class="navbar-nav">
370 + <li class="nav-item dropdown megamenu-hover ">
371 + <a class="nav-link dropdown-toggle"
372 + href="#" target=""
373 + data-drawer-open="submenu-784"
374 + data-drawer-label="Découvrir"
375 + aria-haspopup="dialog"
376 + >
377 + Découvrir </a>
378 + </li>
379 + <li class="nav-item dropdown megamenu-hover ">
380 + <a class="nav-link dropdown-toggle"
381 + href="https://abitibi-temiscamingue.org/planifier/" target=""
382 + data-drawer-open="submenu-785"
383 + data-drawer-label="Planifier"
384 + aria-haspopup="dialog"
385 + >
386 + Planifier </a>
387 + </li>
388 + <li class="nav-item dropdown megamenu-hover ">
389 + <a class="nav-link dropdown-toggle"
390 + href="#" target=""
391 + data-drawer-open="submenu-786"
392 + data-drawer-label="S&#039;inspirer"
393 + aria-haspopup="dialog"
394 + >
395 + S'inspirer </a>
396 + </li>
397 + </ul>
398 + </nav>
399 + </div>
400 + <div class="col-auto ms-auto hidden-print">
401 + <nav class="main-menu menu-style1">
402 + <ul>
403 + <li class="d-none d-lg-inline-block"><a href="https://abitibi-temiscamingue.org/en/festivals-and-events/" class="btn-round-outline" hreflang="en" title="English">
404 + EN </a></li>
405 + <li><a href="https://abitibi-temiscamingue.org/mon-itineraire/" class="btn-round-outline"> <svg
406 + data-icon="Map"
407 + viewBox="0 0 22 20"
408 + width="22"
409 + height="20"
410 + class=""
411 + fill="none" xmlns="http://www.w3.org/2000/svg">
412 + <path d="M1.48333 4.99935L1.04921 4.23964C0.776584 4.39542 0.608334 4.68535 0.608334 4.99935H1.48333ZM1.48333 18.3327H0.608334C0.608334 18.6446 0.774372 18.9329 1.04415 19.0895C1.31392 19.246 1.64664 19.2471 1.91746 19.0924L1.48333 18.3327ZM13.9833 18.3327L13.592 19.1153L14.0109 19.3247L14.4175 19.0924L13.9833 18.3327ZM19.8167 14.9993L20.2508 15.7591C20.5234 15.6033 20.6917 15.3133 20.6917 14.9993H19.8167ZM19.8167 1.66602H20.6917C20.6917 1.3541 20.5256 1.06578 20.2559 0.909219C19.9861 0.752663 19.6534 0.751551 19.3825 0.906303L19.8167 1.66602ZM7.31667 1.66602L7.70798 0.883392L7.28913 0.673968L6.88254 0.906303L7.31667 1.66602ZM1.48333 4.99935H0.608334V18.3327H1.48333H2.35833V4.99935H1.48333ZM1.48333 18.3327L1.91746 19.0924L7.75079 15.7591L7.31667 14.9993L6.88254 14.2396L1.04921 17.573L1.48333 18.3327ZM7.31667 14.9993L6.92535 15.782L13.592 19.1153L13.9833 18.3327L14.3746 17.5501L7.70798 14.2167L7.31667 14.9993ZM13.9833 18.3327L14.4175 19.0924L20.2508 15.7591L19.8167 14.9993L19.3825 14.2396L13.5492 17.573L13.9833 18.3327ZM19.8167 14.9993H20.6917V1.66602H19.8167H18.9417V14.9993H19.8167ZM19.8167 1.66602L19.3825 0.906303L13.5492 4.23964L13.9833 4.99935L14.4175 5.75906L20.2508 2.42573L19.8167 1.66602ZM13.9833 4.99935L14.3746 4.21673L7.70798 0.883392L7.31667 1.66602L6.92535 2.44864L13.592 5.78197L13.9833 4.99935ZM7.31667 1.66602L6.88254 0.906303L1.04921 4.23964L1.48333 4.99935L1.91746 5.75906L7.75079 2.42573L7.31667 1.66602ZM7.31667 1.66602H6.44167V14.9993H7.31667H8.19167V1.66602H7.31667ZM13.9833 4.99935H13.1083V18.3327H13.9833H14.8583V4.99935H13.9833Z" fill="currentColor" /> </svg>
413 + </a></li>
414 + <li><a class="btn-round-outline" id="btn-search" href="https://abitibi-temiscamingue.org/?s=" aria-controls="lebleu-search-modal" aria-haspopup="dialog"> <svg
415 + data-icon="Search"
416 + viewBox="0 0 20 20"
417 + width="20"
418 + height="20"
419 + class=""
420 + fill="none" xmlns="http://www.w3.org/2000/svg">
421 + <path d="M17.5 17.5L13.875 13.875M15.8333 9.16667C15.8333 12.8486 12.8486 15.8333 9.16667 15.8333C5.48477 15.8333 2.5 12.8486 2.5 9.16667C2.5 5.48477 5.48477 2.5 9.16667 2.5C12.8486 2.5 15.8333 5.48477 15.8333 9.16667Z" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/> </svg>
422 + </a></li>
423 + </ul>
424 + </nav>
425 + </div>
426 + <div class="col-auto hidden-print">
427 + <a
428 + class="quanto-menu-large-toggle slide-menu__control d-inline-block d-lg-none"
429 + data-target="menu-right"
430 + data-action="toggle"
431 + >
432 + <span></span>
433 + </a>
434 + </div>
435 + </div>
436 + </div>
437 + </div>
438 + </div>
439 + </header>
440 + <nav class="desktop-drawer-menu" data-drawer-menu>
441 + <div class="drawer-overlay" data-drawer-action="close" aria-hidden="true"></div>
442 + <div class="drawer drawer-level drawer-level-2" id="desktop-drawer-level-2" role="dialog" aria-modal="true" aria-label="Menu">
443 + <div class="drawer-header">
444 + <a class="drawer-logo" href="https://abitibi-temiscamingue.org"><img src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/images/Tag-Blanc.svg" alt="Tourisme Abitibi-Témiscamingue" class="img-fluid" width="32" height="32" /></a>
445 + <button type="button" class="drawer-close" data-drawer-action="close" data-drawer-level="2" aria-label="Fermer">
446 + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
447 + <path d="M8.58981 10.0001L0.796875 2.20718L2.21109 0.792969L10.004 8.58582L17.7969 0.792969L19.2111 2.20718L11.4182 10.0001L19.2111 17.7929L17.7969 19.2072L10.004 11.4143L2.21109 19.2072L0.796875 17.7929L8.58981 10.0001Z" fill="currentColor"/>
448 + </svg>
449 + </button>
450 + </div>
451 +
452 + <div class="drawer-content">
453 + <ul class="drawer-list" data-level="2" data-submenu="submenu-784">
454 + <li class="drawer-item">
455 + <a
456 + href="https://abitibi-temiscamingue.org/decouvrir-la-region/"
457 + target=""
458 + >
459 + L’Abitibi-Témiscamingue </a>
460 + </li>
461 + <li class="drawer-item">
462 + <a
463 + href="https://abitibi-temiscamingue.org/tourisme-autochtone-anicinabek/"
464 + target=""
465 + >
466 + Tourisme autochtone </a>
467 + </li>
468 + <li class="drawer-item has-children">
469 + <a
470 + href="https://abitibi-temiscamingue.org/nos-territoires/"
471 + target=""
472 + type="button"
473 + class="drawer-subtoggle"
474 + data-submenu-target="submenu-2836"
475 + data-submenu-label="Nos territoires"
476 + data-submenu-url="https://abitibi-temiscamingue.org/nos-territoires/"
477 + data-submenu-level="3"
478 + aria-expanded="false"
479 + >
480 + Nos territoires <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
481 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
482 + </svg>
483 + </a>
484 + </li>
485 + <li class="drawer-item has-children">
486 + <a
487 + href="https://abitibi-temiscamingue.org/nos-saisons/"
488 + target=""
489 + type="button"
490 + class="drawer-subtoggle"
491 + data-submenu-target="submenu-2838"
492 + data-submenu-label="Nos saisons"
493 + data-submenu-url="https://abitibi-temiscamingue.org/nos-saisons/"
494 + data-submenu-level="3"
495 + aria-expanded="false"
496 + >
497 + Nos saisons <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
498 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
499 + </svg>
500 + </a>
501 + </li>
502 + <li class="drawer-item">
503 + <a
504 + href="https://abitibi-temiscamingue.org/nos-incontournables/"
505 + target=""
506 + >
507 + Nos incontournables </a>
508 + </li>
509 + <li class="drawer-item">
510 + <a
511 + href="https://abitibi-temiscamingue.org/nos-circuits/"
512 + target=""
513 + >
514 + Nos circuits </a>
515 + </li>
516 + </ul>
517 + <ul class="drawer-list" data-level="2" data-submenu="submenu-785">
518 + <li class="drawer-header">
519 + <a class="drawer-subtitle" href="https://abitibi-temiscamingue.org/planifier/">
520 + <span class="drawer-subtitle-text">Planifier</span>
521 + <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
522 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
523 + </svg>
524 + </a>
525 + </li>
526 + <li class="drawer-item has-children">
527 + <a
528 + href="https://abitibi-temiscamingue.org/planifier/quoi-faire/"
529 + target=""
530 + type="button"
531 + class="drawer-subtoggle"
532 + data-submenu-target="submenu-7690"
533 + data-submenu-label="Quoi faire"
534 + data-submenu-url="https://abitibi-temiscamingue.org/planifier/quoi-faire/"
535 + data-submenu-level="3"
536 + aria-expanded="false"
537 + >
538 + Quoi faire <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
539 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
540 + </svg>
541 + </a>
542 + </li>
543 + <li class="drawer-item has-children">
544 + <a
545 + href="https://abitibi-temiscamingue.org/planifier/ou-manger/"
546 + target=""
547 + type="button"
548 + class="drawer-subtoggle"
549 + data-submenu-target="submenu-7637"
550 + data-submenu-label="Où manger"
551 + data-submenu-url="https://abitibi-temiscamingue.org/planifier/ou-manger/"
552 + data-submenu-level="3"
553 + aria-expanded="false"
554 + >
555 + Où manger <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
556 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
557 + </svg>
558 + </a>
559 + </li>
560 + <li class="drawer-item has-children">
561 + <a
562 + href="https://abitibi-temiscamingue.org/planifier/ou-dormir/"
563 + target=""
564 + type="button"
565 + class="drawer-subtoggle"
566 + data-submenu-target="submenu-7154"
567 + data-submenu-label="Où dormir"
568 + data-submenu-url="https://abitibi-temiscamingue.org/planifier/ou-dormir/"
569 + data-submenu-level="3"
570 + aria-expanded="false"
571 + >
572 + Où dormir <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
573 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
574 + </svg>
575 + </a>
576 + </li>
577 + <li class="drawer-item">
578 + <a
579 + href="https://abitibi-temiscamingue.org/planifier/services/"
580 + target=""
581 + >
582 + Services </a>
583 + </li>
584 + <li class="drawer-item">
585 + <a
586 + href="https://abitibi-temiscamingue.org/festivals-et-evenements/"
587 + target=""
588 + >
589 + Festivals et événements </a>
590 + </li>
591 + <li class="drawer-item">
592 + <a
593 + href="https://abitibi-temiscamingue.org/planifier/tourisme-affaires/"
594 + target=""
595 + >
596 + Tourisme d’affaires </a>
597 + </li>
598 + <li class="drawer-item">
599 + <a
600 + href="https://abitibi-temiscamingue.org/offres-et-forfaits/"
601 + target=""
602 + >
603 + Offres et forfaits </a>
604 + </li>
605 + <li class="drawer-item">
606 + <a
607 + href="https://abitibi-temiscamingue.org/mon-itineraire/"
608 + target=""
609 + >
610 + Construire ton itinéraire </a>
611 + </li>
612 + <li class="drawer-item has-children">
613 + <a
614 + href="https://abitibi-temiscamingue.org/informations-pratiques/"
615 + target=""
616 + type="button"
617 + class="drawer-subtoggle"
618 + data-submenu-target="submenu-7632"
619 + data-submenu-label="Infos pratiques"
620 + data-submenu-url="https://abitibi-temiscamingue.org/informations-pratiques/"
621 + data-submenu-level="3"
622 + aria-expanded="false"
623 + >
624 + Infos pratiques <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
625 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
626 + </svg>
627 + </a>
628 + </li>
629 + </ul>
630 + <ul class="drawer-list" data-level="2" data-submenu="submenu-786">
631 + <li class="drawer-item">
632 + <a
633 + href="https://abitibi-temiscamingue.org/blogue/"
634 + target=""
635 + >
636 + Blogue </a>
637 + </li>
638 + <li class="drawer-item">
639 + <a
640 + href="https://abitibi-temiscamingue.org/vos-moments/"
641 + target=""
642 + >
643 + #abitibitemiscamingue </a>
644 + </li>
645 + <li class="drawer-item">
646 + <a
647 + href="https://abitibi-temiscamingue.org/informations-pratiques/faq/"
648 + target=""
649 + >
650 + Foire aux questions (FAQ) </a>
651 + </li>
652 + <li class="drawer-item">
653 + <a
654 + href="https://abitibi-temiscamingue.org/nos-collaborateurs/"
655 + target=""
656 + >
657 + Nos collaborateurs </a>
658 + </li>
659 + </ul>
660 + </div>
661 + </div>
662 + <div class="drawer drawer-level drawer-level-3" id="desktop-drawer-level-3" role="dialog" aria-modal="true" aria-label="Submenu">
663 + <div class="drawer-header">
664 + <a class="drawer-subtitle" data-submenu-title-level="3">
665 + <span class="drawer-subtitle-text"></span>
666 + <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
667 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
668 + </svg>
669 + </a>
670 + </div>
671 + <div class="drawer-content">
672 + <ul class="drawer-list" data-level="3" data-submenu="submenu-2836">
673 + <li class="drawer-item">
674 + <a
675 + href="https://abitibi-temiscamingue.org/nos-territoires/abitibi-ouest/"
676 + target=""
677 + >
678 + Abitibi-Ouest </a>
679 + </li>
680 + <li class="drawer-item">
681 + <a
682 + href="https://abitibi-temiscamingue.org/nos-territoires/amos-harricana/"
683 + target=""
684 + >
685 + Amos-Harricana  </a>
686 + </li>
687 + <li class="drawer-item">
688 + <a
689 + href="https://abitibi-temiscamingue.org/nos-territoires/rouyn-noranda/"
690 + target=""
691 + >
692 + Rouyn-Noranda </a>
693 + </li>
694 + <li class="drawer-item">
695 + <a
696 + href="https://abitibi-temiscamingue.org/nos-territoires/temiscamingue/"
697 + target=""
698 + >
699 + Témiscamingue  </a>
700 + </li>
701 + <li class="drawer-item">
702 + <a
703 + href="https://abitibi-temiscamingue.org/nos-territoires/vallee-de-lor/"
704 + target=""
705 + >
706 + Vallée-de-l’Or  </a>
707 + </li>
708 + </ul>
709 + <ul class="drawer-list" data-level="3" data-submenu="submenu-2838">
710 + <li class="drawer-item">
711 + <a
712 + href="https://abitibi-temiscamingue.org/nos-saisons/printemps/"
713 + target=""
714 + >
715 + Printemps </a>
716 + </li>
717 + <li class="drawer-item">
718 + <a
719 + href="https://abitibi-temiscamingue.org/nos-saisons/ete/"
720 + target=""
721 + >
722 + Été </a>
723 + </li>
724 + <li class="drawer-item">
725 + <a
726 + href="https://abitibi-temiscamingue.org/nos-saisons/automne/"
727 + target=""
728 + >
729 + Automne </a>
730 + </li>
731 + <li class="drawer-item">
732 + <a
733 + href="https://abitibi-temiscamingue.org/nos-saisons/hiver/"
734 + target=""
735 + >
736 + Hiver </a>
737 + </li>
738 + </ul>
739 + <ul class="drawer-list" data-level="3" data-submenu="submenu-7690">
740 + <li class="drawer-item has-children">
741 + <a
742 + href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/"
743 + target=""
744 + type="button"
745 + class="drawer-subtoggle"
746 + data-submenu-target="submenu-7627"
747 + data-submenu-label="Sports et plein air"
748 + data-submenu-url="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/"
749 + data-submenu-level="4"
750 + aria-expanded="false"
751 + >
752 + Sports et plein air <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
753 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
754 + </svg>
755 + </a>
756 + </li>
757 + <li class="drawer-item">
758 + <a
759 + href="https://abitibi-temiscamingue.org/planifier/quoi-faire/activites-divertissement/"
760 + target=""
761 + >
762 + Activités et divertissement </a>
763 + </li>
764 + <li class="drawer-item">
765 + <a
766 + href="https://abitibi-temiscamingue.org/planifier/quoi-faire/art-culture-et-patrimoine/"
767 + target=""
768 + >
769 + Art, culture et patrimoine </a>
770 + </li>
771 + <li class="drawer-item">
772 + <a
773 + href="https://abitibi-temiscamingue.org/planifier/quoi-faire/agrotourisme/"
774 + target=""
775 + >
776 + Agrotourisme et terroir </a>
777 + </li>
778 + <li class="drawer-item">
779 + <a
780 + href="https://abitibi-temiscamingue.org/planifier/quoi-faire/motoneige/"
781 + target=""
782 + >
783 + Motoneige </a>
784 + </li>
785 + <li class="drawer-item has-children">
786 + <a
787 + href="https://abitibi-temiscamingue.org/planifier/quoi-faire/chasse-peche-et-sejour-en-pourvoirie/"
788 + target=""
789 + type="button"
790 + class="drawer-subtoggle"
791 + data-submenu-target="submenu-7619"
792 + data-submenu-label="Chasse et pêche"
793 + data-submenu-url="https://abitibi-temiscamingue.org/planifier/quoi-faire/chasse-peche-et-sejour-en-pourvoirie/"
794 + data-submenu-level="4"
795 + aria-expanded="false"
796 + >
797 + Chasse et pêche <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
798 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
799 + </svg>
800 + </a>
801 + </li>
802 + </ul>
803 + <ul class="drawer-list" data-level="3" data-submenu="submenu-7637">
804 + <li class="drawer-item">
805 + <a
806 + href="https://abitibi-temiscamingue.org/planifier/ou-manger/restaurants/"
807 + target=""
808 + >
809 + Restaurants </a>
810 + </li>
811 + <li class="drawer-item">
812 + <a
813 + href="https://abitibi-temiscamingue.org/planifier/ou-manger/cafes/"
814 + target=""
815 + >
816 + Cafés </a>
817 + </li>
818 + <li class="drawer-item">
819 + <a
820 + href="https://abitibi-temiscamingue.org/planifier/ou-manger/patisseries-et-boulangeries/"
821 + target=""
822 + >
823 + Pâtisseries et boulangeries </a>
824 + </li>
825 + <li class="drawer-item">
826 + <a
827 + href="https://abitibi-temiscamingue.org/planifier/ou-manger/microbrasseries-bars-distilleries/"
828 + target=""
829 + >
830 + Microbrasseries, bars et distilleries </a>
831 + </li>
832 + <li class="drawer-item">
833 + <a
834 + href="https://abitibi-temiscamingue.org/planifier/ou-manger/traiteurs/"
835 + target=""
836 + >
837 + Traiteurs </a>
838 + </li>
839 + </ul>
840 + <ul class="drawer-list" data-level="3" data-submenu="submenu-7154">
841 + <li class="drawer-item">
842 + <a
843 + href="https://abitibi-temiscamingue.org/planifier/ou-dormir/campings/"
844 + target=""
845 + >
846 + Campings et prêts-à-camper </a>
847 + </li>
848 + <li class="drawer-item">
849 + <a
850 + href="https://abitibi-temiscamingue.org/planifier/ou-dormir/chalets-et-appartements/"
851 + target=""
852 + >
853 + Chalets, appartements et résidences de tourisme </a>
854 + </li>
855 + <li class="drawer-item">
856 + <a
857 + href="https://abitibi-temiscamingue.org/planifier/ou-dormir/gites-touristiques/"
858 + target=""
859 + >
860 + Gîtes touristiques </a>
861 + </li>
862 + <li class="drawer-item">
863 + <a
864 + href="https://abitibi-temiscamingue.org/planifier/ou-dormir/hotels/"
865 + target=""
866 + >
867 + Hôtels et motels </a>
868 + </li>
869 + <li class="drawer-item">
870 + <a
871 + href="https://abitibi-temiscamingue.org/planifier/ou-dormir/pourvoiries/"
872 + target=""
873 + >
874 + Pourvoiries </a>
875 + </li>
876 + </ul>
877 + <ul class="drawer-list" data-level="3" data-submenu="submenu-7632">
878 + <li class="drawer-item">
879 + <a
880 + href="https://abitibi-temiscamingue.org/informations-pratiques/se-deplacer/"
881 + target=""
882 + >
883 + Se déplacer </a>
884 + </li>
885 + <li class="drawer-item">
886 + <a
887 + href="https://abitibi-temiscamingue.org/informations-pratiques/documents-brochures-liens-utiles/"
888 + target=""
889 + >
890 + Documents, brochures et liens utiles </a>
891 + </li>
892 + <li class="drawer-item">
893 + <a
894 + href="https://abitibi-temiscamingue.org/informations-pratiques/bureaux-dinformation-touristique/"
895 + target=""
896 + >
897 + Bureaux d’information touristique </a>
898 + </li>
899 + <li class="drawer-item">
900 + <a
901 + href="https://abitibi-temiscamingue.org/informations-pratiques/faq/"
902 + target=""
903 + >
904 + Foire aux questions (FAQ) </a>
905 + </li>
906 + </ul>
907 + </div>
908 + </div>
909 + <div class="drawer drawer-level drawer-level-4" id="desktop-drawer-level-4" role="dialog" aria-modal="true" aria-label="Submenu">
910 + <div class="drawer-header">
911 + <a class="drawer-subtitle" data-submenu-title-level="4">
912 + <span class="drawer-subtitle-text"></span>
913 + <svg viewBox="0 0 20 20" class="" fill="none" xmlns="http://www.w3.org/2000/svg">
914 + <path d="M17.5383 10.6626L11.9133 16.2876C11.7372 16.4637 11.4983 16.5626 11.2492 16.5626C11.0001 16.5626 10.7613 16.4637 10.5852 16.2876C10.409 16.1114 10.3101 15.8726 10.3101 15.6235C10.3101 15.3744 10.409 15.1356 10.5852 14.9594L14.6094 10.9368H3.125C2.87636 10.9368 2.6379 10.838 2.46209 10.6622C2.28627 10.4864 2.1875 10.2479 2.1875 9.99928C2.1875 9.75064 2.28627 9.51218 2.46209 9.33637C2.6379 9.16055 2.87636 9.06178 3.125 9.06178H14.6094L10.5867 5.03678C10.4106 4.86066 10.3117 4.62179 10.3117 4.37272C10.3117 4.12365 10.4106 3.88478 10.5867 3.70866C10.7628 3.53254 11.0017 3.43359 11.2508 3.43359C11.4999 3.43359 11.7387 3.53254 11.9148 3.70866L17.5398 9.33366C17.6273 9.42087 17.6966 9.52449 17.7438 9.63858C17.7911 9.75266 17.8153 9.87495 17.8152 9.99843C17.8151 10.1219 17.7905 10.2442 17.743 10.3581C17.6955 10.4721 17.6259 10.5756 17.5383 10.6626Z" fill="currentColor"></path>
915 + </svg>
916 + </a>
917 + </div>
918 + <div class="drawer-content">
919 + <ul class="drawer-list" data-level="4" data-submenu="submenu-7627">
920 + <li class="drawer-item">
921 + <a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/activites-nautiques/" target="">
922 + Activités nautiques </a>
923 + </li>
924 + <li class="drawer-item">
925 + <a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/velo/" target="">
926 + Vélo </a>
927 + </li>
928 + <li class="drawer-item">
929 + <a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/golf/" target="">
930 + Golf </a>
931 + </li>
932 + <li class="drawer-item">
933 + <a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/sports-dhiver/" target="">
934 + Sports d&#8217;hiver </a>
935 + </li>
936 + <li class="drawer-item">
937 + <a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/randonnee/" target="">
938 + Randonnée </a>
939 + </li>
940 + <li class="drawer-item">
941 + <a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/sports-et-plein-air/parcs-reserve-faunique/" target="">
942 + Parcs et réserve faunique </a>
943 + </li>
944 + </ul>
945 + <ul class="drawer-list" data-level="4" data-submenu="submenu-7619">
946 + <li class="drawer-item">
947 + <a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/chasse-peche-et-sejour-en-pourvoirie/chasse/" target="">
948 + Chasse </a>
949 + </li>
950 + <li class="drawer-item">
951 + <a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/chasse-peche-et-sejour-en-pourvoirie/peche/" target="">
952 + Pêche </a>
953 + </li>
954 + <li class="drawer-item">
955 + <a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/chasse-peche-et-sejour-en-pourvoirie/sejour-en-pourvoirie/" target="">
956 + Séjour en pourvoirie </a>
957 + </li>
958 + </ul>
959 + </div>
960 + </div>
961 + </nav>
962 + <div id="lebleu-search-modal" class="lebleu-search-modal" aria-hidden="true">
963 + <div class="lebleu-search-modal__overlay" data-search-close></div>
964 + <div class="lebleu-search-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="lebleu-search-title">
965 + <button type="button" class="lebleu-search-modal__close" data-search-close aria-label="Close search">
966 + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
967 + <path d="M8.58981 10.0001L0.796875 2.20718L2.21109 0.792969L10.004 8.58582L17.7969 0.792969L19.2111 2.20718L11.4182 10.0001L19.2111 17.7929L17.7969 19.2072L10.004 11.4143L2.21109 19.2072L0.796875 17.7929L8.58981 10.0001Z" fill="currentColor"/>
968 + </svg>
969 + </button>
970 + <p id="lebleu-search-title" class="h2 lebleu-search-modal__title">Recherche</p>
971 + <form role="search" method="get" class="lebleu-search-modal__form" action="/">
972 + <input type="search" id="lebleu-search-input" class="lebleu-search-modal__input" name="s" aria-label="Recherche" value="" placeholder="Rechercher sur le site" />
973 + <button type="submit" class="quanto-link-btn btn-pill mt-0">Rechercher</button>
974 + </form>
975 + </div>
976 + </div>
977 + <div id="smooth-content">
978 + <div id="content">
979 + <section class="entete bg-color-secondary">
980 + <div class="quanto-hero5-section" style="padding:0px;">
981 + <div class="hero5-bg" data-bg-src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/rodeo-cowboy-chevaux-festival-western-lasarre-jean-caron.jpg"></div>
982 + <div class="container custom-container">
983 +
984 + <div class="row g-4 justify-content-between align-items-center">
985 + <div class="col-12 col-lg-10 col-xl-10 text-center offset-lg-1">
986 + <div class="quanto-hero5__info text-white">
987 + <h1
988 + class="fade-anim h2 font-secondary text-white"><p>Festivals et événements </p></h1>
989 + </div>
990 + </div>
991 + </div>
992 + </div>
993 + </div>
994 + </section>
995 + <section class="bloc-texte-section section-padding-top-bottom" data-section="bloc_texte">
996 + <div class="container">
997 + <div class="boite-de-texte fade-anim" data-direction="left" >
998 + <p class="h2" style="text-align: center"><span class="TextRun SCXW232105542 BCX0" data-contrast="auto"><span class="NormalTextRun SCXW232105542 BCX0">Ç</span><span class="NormalTextRun SCXW232105542 BCX0">a fête pour vrai.<br />
999 +Ç</span><span class="NormalTextRun SCXW232105542 BCX0">a surprend.</span></span></p>
1000 + </div>
1001 + </div>
1002 + </section><section class="bloc-texte-section quanto-about-section overflow-hidden section-padding-bottom" data-section="bloc_deux_colonnes_dephases">
1003 + <div class="container custom-container">
1004 + <div class="row justify-content-end justify-content-lg-start gy-5 gy-lg-0">
1005 + <div class="col-12 col-lg-6">
1006 + <div class="about-info boite-de-texte fade-anim" data-direction="left">
1007 + <p class="h3"><span class="TextRun SCXW49058930 BCX0" data-contrast="auto"><span class="NormalTextRun SCXW49058930 BCX0">En Abitibi-Témiscamingue, la vie de festivals et d’événements est bien vivante. </span></span></p>
1008 +<p class="h5"><span class="TextRun SCXW267129216 BCX0" data-contrast="auto"><span class="NormalTextRun SCXW267129216 BCX0">Musique, humour, cinéma, pow-wow, jeux, littérature, rodéos,<span> </span></span><a href="https://abitibi-temiscamingue.org/planifier/quoi-faire/agrotourisme/"><span class="NormalTextRun CommentStart CommentHighlightPipeRest CommentHighlightRest SCXW267129216 BCX0">saveurs locales</span></a><span class="NormalTextRun CommentHighlightRest SCXW267129216 BCX0">,<span> </span></span><span class="NormalTextRun CommentHighlightPipeRest SCXW267129216 BCX0">spectacles équestres, il y en a pour tous les goûts, toutes les passions et<span> </span></span></span><a href="https://abitibi-temiscamingue.org/nos-saisons/"><span class="TextRun Highlight SCXW267129216 BCX0" data-contrast="auto"><span class="NormalTextRun CommentStart CommentHighlightPipeRest CommentHighlightRest SCXW267129216 BCX0">toutes les saisons</span></span></a><span class="TextRun SCXW267129216 BCX0" data-contrast="auto"><span class="NormalTextRun CommentHighlightPipeRest SCXW267129216 BCX0">.</span></span><span class="EOP SCXW267129216 BCX0" data-ccp-props="{}"> </span></p>
1009 +<p><span data-contrast="auto">Chez nous, ça se passe autant sur de grands terrains extérieurs où la foule est en feu que dans de petites salles chaleureuses où des artistes émergents·es t’offrent des moments uniques.</span></p>
1010 + </div>
1011 + </div>
1012 + <div class="col-10 col-lg-6 offset-lg-6">
1013 + <div class="about-info boite-de-texte fade-anim" data-direction="left" data-delay="0.10">
1014 + <p><span data-contrast="auto">Parfois, on te surprend: un show dans une ruelle, un concert caché en forêt, un festival autour de </span><a href="https://abitibi-temiscamingue.org/evenements/danse-dla-quenouille/"><span data-contrast="auto">la forêt comestible</span></a><span data-contrast="auto"> (oui, on va jusque-là), une expo improvisée, une projection dans un lieu improbable.</span></p>
1015 +<p><span data-contrast="auto">On aime l’audace, la diversité et tout ce qui fait jaser.</span><span data-ccp-props="{}"> </span></p>
1016 +<p><span data-contrast="auto">Du très niché au populaire, nos festivals rassemblent le monde. On se retrouve, on célèbre, on partage, on découvre. </span><span data-contrast="auto"><a href="https://abitibi-temiscamingue.org/nos-saisons/ete/">Été</a>, <a href="https://abitibi-temiscamingue.org/nos-saisons/hiver/">hiver</a>, <a href="https://abitibi-temiscamingue.org/nos-saisons/printemps/">printemps</a> ou <a href="https://abitibi-temiscamingue.org/nos-saisons/automne/">automne</a></span><span data-contrast="auto">, on a toujours une bonne raison de se rassembler et de faire vibrer </span><a href="https://abitibi-temiscamingue.org/nos-territoires/"><span data-contrast="auto">nos villes et villages</span></a><span data-contrast="auto">.</span><span data-ccp-props="{}"> </span></p>
1017 + </div>
1018 + </div>
1019 + </div>
1020 + </div>
1021 + </section> <section class="overflow-hidden section-padding-bottom" data-section="carousel_images">
1022 + <div class="container custom-container">
1023 + <div class="row gap-4 justify-content-center">
1024 + <div class="col-10 col-lg-4 fade-anim" data-direction="right" >
1025 + <div class="swiper-cards" id="swiper-cards-6a8e06e544ccf">
1026 + <div class="swiper-wrapper">
1027 + <div class="swiper-slide">
1028 + <div class="img-card">
1029 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/fille-de-dos-chapeau-de-cowboy-festival-western-de-saint-bruno-de-guigues-christian-leduc.jpg " alt="Fille de dos avec un chapeau de cowboy au Festival western de Saint-Bruno-de-Guigues" />
1030 + <div class="btns-img">
1031 + <span class="tooltip-copyright">
1032 + <span>Christian Leduc</span>
1033 + <svg
1034 + data-icon="Copyright"
1035 + viewBox="0 0 24 24"
1036 + width="24"
1037 + height="24"
1038 + class=""
1039 + fill="none" xmlns="http://www.w3.org/2000/svg">
1040 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1041 + </span><span class="tooltip-copyright">
1042 + <span>Festival western de Guigues, Saint-Bruno-de-Guigues</span>
1043 + <svg
1044 + data-icon="MapPinSimpleArea"
1045 + viewBox="0 0 18 18"
1046 + width="18"
1047 + height="18"
1048 + class=""
1049 + fill="none" xmlns="http://www.w3.org/2000/svg">
1050 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1051 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1052 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1053 + </span> </div>
1054 + </div>
1055 + </div>
1056 + <div class="swiper-slide">
1057 + <div class="img-card">
1058 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/scene-lumiere-koriass-festival-de-musique-emergente-en-abitibi-temiscamingue-louis-jalbert.jpg " alt="Scène lumineuse avec Koriass lors du Festival de musique émergente en Abitibi-Témiscamingue" />
1059 + <div class="btns-img">
1060 + <span class="tooltip-copyright">
1061 + <span>Louis Jalbert</span>
1062 + <svg
1063 + data-icon="Copyright"
1064 + viewBox="0 0 24 24"
1065 + width="24"
1066 + height="24"
1067 + class=""
1068 + fill="none" xmlns="http://www.w3.org/2000/svg">
1069 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1070 + </span><span class="tooltip-copyright">
1071 + <span>Festival de musique émergente en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1072 + <svg
1073 + data-icon="MapPinSimpleArea"
1074 + viewBox="0 0 18 18"
1075 + width="18"
1076 + height="18"
1077 + class=""
1078 + fill="none" xmlns="http://www.w3.org/2000/svg">
1079 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1080 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1081 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1082 + </span> </div>
1083 + </div>
1084 + </div>
1085 + <div class="swiper-slide">
1086 + <div class="img-card">
1087 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/vue-nuit-passerelle-hotel-noranda-logo-festival-des-guitares-en-abitibi-temiscamingue-hugo-lacroix.jpg " alt="La vue du logo du Festival des guitares du monde en Abitibi-Témiscamingue sur la passerelle de l&#039;Hôtel Noranda et flashs de lumières" />
1088 + <div class="btns-img">
1089 + <span class="tooltip-copyright">
1090 + <span>Hugo Lacroix</span>
1091 + <svg
1092 + data-icon="Copyright"
1093 + viewBox="0 0 24 24"
1094 + width="24"
1095 + height="24"
1096 + class=""
1097 + fill="none" xmlns="http://www.w3.org/2000/svg">
1098 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1099 + </span><span class="tooltip-copyright">
1100 + <span>Festival des guitares du monde en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1101 + <svg
1102 + data-icon="MapPinSimpleArea"
1103 + viewBox="0 0 18 18"
1104 + width="18"
1105 + height="18"
1106 + class=""
1107 + fill="none" xmlns="http://www.w3.org/2000/svg">
1108 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1109 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1110 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1111 + </span> </div>
1112 + </div>
1113 + </div>
1114 + <div class="swiper-slide">
1115 + <div class="img-card">
1116 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/vue-scene-exterieure-gens-chaises-collines-festival-d-humour-en-abitibi-temiscamingue-cite-de-l-or-hugo-lacroix.jpg " alt="Vue de la scène extérieure du Festival d&#039;humour en Abitibi-Témiscamingue à Val-d&#039;Or avec la Cité de l&#039;Or en arrière-plan" />
1117 + <div class="btns-img">
1118 + <span class="tooltip-copyright">
1119 + <span>Hugo Lacroix</span>
1120 + <svg
1121 + data-icon="Copyright"
1122 + viewBox="0 0 24 24"
1123 + width="24"
1124 + height="24"
1125 + class=""
1126 + fill="none" xmlns="http://www.w3.org/2000/svg">
1127 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1128 + </span><span class="tooltip-copyright">
1129 + <span>Festival d'humour en Abitibi-Témiscamingue, Val-d'Or</span>
1130 + <svg
1131 + data-icon="MapPinSimpleArea"
1132 + viewBox="0 0 18 18"
1133 + width="18"
1134 + height="18"
1135 + class=""
1136 + fill="none" xmlns="http://www.w3.org/2000/svg">
1137 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1138 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1139 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1140 + </span> </div>
1141 + </div>
1142 + </div>
1143 + <div class="swiper-slide">
1144 + <div class="img-card">
1145 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/vue-theatre-du-cuivre-orginal-lumiere-festival-du-cinema-international-en-abitibi-temismicamingue-louis-jalbert.jpg " alt="Vue sur l&#039;orignal sur le toit du Théâtre du Cuivre pour le Festival du cinéma international en Abitibi-Témiscamingue, en hiver" />
1146 + <div class="btns-img">
1147 + <span class="tooltip-copyright">
1148 + <span>Louis Jalbert</span>
1149 + <svg
1150 + data-icon="Copyright"
1151 + viewBox="0 0 24 24"
1152 + width="24"
1153 + height="24"
1154 + class=""
1155 + fill="none" xmlns="http://www.w3.org/2000/svg">
1156 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1157 + </span><span class="tooltip-copyright">
1158 + <span>Festival du cinéma international en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1159 + <svg
1160 + data-icon="MapPinSimpleArea"
1161 + viewBox="0 0 18 18"
1162 + width="18"
1163 + height="18"
1164 + class=""
1165 + fill="none" xmlns="http://www.w3.org/2000/svg">
1166 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1167 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1168 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1169 + </span> </div>
1170 + </div>
1171 + </div>
1172 + <div class="swiper-slide">
1173 + <div class="img-card">
1174 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2024/07/Festival-h2o-foule-scene_sabrina-bizier.jpg " alt="foule et scène à un festival en extérieur" />
1175 + <div class="btns-img">
1176 + <span class="tooltip-copyright">
1177 + <span>Sabrina Bizier</span>
1178 + <svg
1179 + data-icon="Copyright"
1180 + viewBox="0 0 24 24"
1181 + width="24"
1182 + height="24"
1183 + class=""
1184 + fill="none" xmlns="http://www.w3.org/2000/svg">
1185 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1186 + </span><span class="tooltip-copyright">
1187 + <span>H2O le Festival, Amos</span>
1188 + <svg
1189 + data-icon="MapPinSimpleArea"
1190 + viewBox="0 0 18 18"
1191 + width="18"
1192 + height="18"
1193 + class=""
1194 + fill="none" xmlns="http://www.w3.org/2000/svg">
1195 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1196 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1197 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1198 + </span> </div>
1199 + </div>
1200 + </div>
1201 + <div class="swiper-slide">
1202 + <div class="img-card">
1203 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/enfant-fme-powwow-anichinaabe-christian-leduc.jpg " alt="Enfant anichinaabe portant des vêtements traditionnels de pow‑wow lors d’une représentation au Festival de musique émergente en Abitibi‑Témiscamingue, tourisme autochtone." />
1204 + <div class="btns-img">
1205 + <span class="tooltip-copyright">
1206 + <span>Christian Leduc</span>
1207 + <svg
1208 + data-icon="Copyright"
1209 + viewBox="0 0 24 24"
1210 + width="24"
1211 + height="24"
1212 + class=""
1213 + fill="none" xmlns="http://www.w3.org/2000/svg">
1214 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1215 + </span><span class="tooltip-copyright">
1216 + <span>Festival de musique émergente, Rouyn-Noranda</span>
1217 + <svg
1218 + data-icon="MapPinSimpleArea"
1219 + viewBox="0 0 18 18"
1220 + width="18"
1221 + height="18"
1222 + class=""
1223 + fill="none" xmlns="http://www.w3.org/2000/svg">
1224 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1225 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1226 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1227 + </span> </div>
1228 + </div>
1229 + </div>
1230 + <div class="swiper-slide">
1231 + <div class="img-card">
1232 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/01/festival-du-cinema-international-en-abitibi-temiscamingue-theatre-du-cuivre.jpg " alt="Vue d&#039;une foule au Théâtre du cuivre pendant le festival du cinéma en Abitibi-Témiscamingue." />
1233 + <div class="btns-img">
1234 + <span class="tooltip-copyright">
1235 + <span>Festival du cinéma international en Abitibi-Témiscamingue</span>
1236 + <svg
1237 + data-icon="Copyright"
1238 + viewBox="0 0 24 24"
1239 + width="24"
1240 + height="24"
1241 + class=""
1242 + fill="none" xmlns="http://www.w3.org/2000/svg">
1243 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1244 + </span> </div>
1245 + </div>
1246 + </div>
1247 + <div class="swiper-slide">
1248 + <div class="img-card">
1249 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/11/jeune-femme-festival-foule-musique-fme-rouyn-noranda-christian-leduc.jpg " alt="Jeune femme debout sur les épaules d&#039;un homme dans la foule au Festival de musique émergente." />
1250 + <div class="btns-img">
1251 + <span class="tooltip-copyright">
1252 + <span>Christian Leduc</span>
1253 + <svg
1254 + data-icon="Copyright"
1255 + viewBox="0 0 24 24"
1256 + width="24"
1257 + height="24"
1258 + class=""
1259 + fill="none" xmlns="http://www.w3.org/2000/svg">
1260 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1261 + </span><span class="tooltip-copyright">
1262 + <span>Festival de musique émergente en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1263 + <svg
1264 + data-icon="MapPinSimpleArea"
1265 + viewBox="0 0 18 18"
1266 + width="18"
1267 + height="18"
1268 + class=""
1269 + fill="none" xmlns="http://www.w3.org/2000/svg">
1270 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1271 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1272 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1273 + </span> </div>
1274 + </div>
1275 + </div>
1276 + <div class="swiper-slide">
1277 + <div class="img-card">
1278 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/spectacle-petit-theatre-vieux-noranda-foule.jpg " alt="Foule devant un spectacle de musique du FME au Petit Théâtre du Vieux Noranda." />
1279 + <div class="btns-img">
1280 + <span class="tooltip-copyright">
1281 + <span>Thomas Dufresne</span>
1282 + <svg
1283 + data-icon="Copyright"
1284 + viewBox="0 0 24 24"
1285 + width="24"
1286 + height="24"
1287 + class=""
1288 + fill="none" xmlns="http://www.w3.org/2000/svg">
1289 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1290 + </span><span class="tooltip-copyright">
1291 + <span>Festival de musique émergente en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1292 + <svg
1293 + data-icon="MapPinSimpleArea"
1294 + viewBox="0 0 18 18"
1295 + width="18"
1296 + height="18"
1297 + class=""
1298 + fill="none" xmlns="http://www.w3.org/2000/svg">
1299 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1300 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1301 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1302 + </span> </div>
1303 + </div>
1304 + </div>
1305 + <div class="swiper-slide">
1306 + <div class="img-card">
1307 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/03/FME-spectacle-rue-crowd-foule-lumiere-nuit.jpg " alt="Une foule est rassemblée devant une scène extérieure de spectacle au FME à Rouyn-Noranda." />
1308 + <div class="btns-img">
1309 + <span class="tooltip-copyright">
1310 + <span>Thomas Dufresne</span>
1311 + <svg
1312 + data-icon="Copyright"
1313 + viewBox="0 0 24 24"
1314 + width="24"
1315 + height="24"
1316 + class=""
1317 + fill="none" xmlns="http://www.w3.org/2000/svg">
1318 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1319 + </span><span class="tooltip-copyright">
1320 + <span>FME, Rouyn-Noranda</span>
1321 + <svg
1322 + data-icon="MapPinSimpleArea"
1323 + viewBox="0 0 18 18"
1324 + width="18"
1325 + height="18"
1326 + class=""
1327 + fill="none" xmlns="http://www.w3.org/2000/svg">
1328 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1329 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1330 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1331 + </span> </div>
1332 + </div>
1333 + </div>
1334 + <div class="swiper-slide">
1335 + <div class="img-card">
1336 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2024/06/FHAT-3_Marie-Claude-Robert.png " alt="Personnes qui rient au Festival d&#039;humour de l&#039;Abitibi-Témiscamingue" />
1337 + <div class="btns-img">
1338 + <span class="tooltip-copyright">
1339 + <span>Marie-Claude Robert</span>
1340 + <svg
1341 + data-icon="Copyright"
1342 + viewBox="0 0 24 24"
1343 + width="24"
1344 + height="24"
1345 + class=""
1346 + fill="none" xmlns="http://www.w3.org/2000/svg">
1347 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1348 + </span><span class="tooltip-copyright">
1349 + <span>Festival d'Humour de l'Abitibi-Témiscamingue, Val-d'Or</span>
1350 + <svg
1351 + data-icon="MapPinSimpleArea"
1352 + viewBox="0 0 18 18"
1353 + width="18"
1354 + height="18"
1355 + class=""
1356 + fill="none" xmlns="http://www.w3.org/2000/svg">
1357 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1358 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1359 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1360 + </span> </div>
1361 + </div>
1362 + </div>
1363 + <div class="swiper-slide">
1364 + <div class="img-card">
1365 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/fille-de-dos-chapeau-de-cowboy-festival-western-de-saint-bruno-de-guigues-christian-leduc.jpg " alt="Fille de dos avec un chapeau de cowboy au Festival western de Saint-Bruno-de-Guigues" />
1366 + <div class="btns-img">
1367 + <span class="tooltip-copyright">
1368 + <span>Christian Leduc</span>
1369 + <svg
1370 + data-icon="Copyright"
1371 + viewBox="0 0 24 24"
1372 + width="24"
1373 + height="24"
1374 + class=""
1375 + fill="none" xmlns="http://www.w3.org/2000/svg">
1376 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1377 + </span><span class="tooltip-copyright">
1378 + <span>Festival western de Guigues, Saint-Bruno-de-Guigues</span>
1379 + <svg
1380 + data-icon="MapPinSimpleArea"
1381 + viewBox="0 0 18 18"
1382 + width="18"
1383 + height="18"
1384 + class=""
1385 + fill="none" xmlns="http://www.w3.org/2000/svg">
1386 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1387 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1388 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1389 + </span> </div>
1390 + </div>
1391 + </div>
1392 + <div class="swiper-slide">
1393 + <div class="img-card">
1394 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/scene-lumiere-koriass-festival-de-musique-emergente-en-abitibi-temiscamingue-louis-jalbert.jpg " alt="Scène lumineuse avec Koriass lors du Festival de musique émergente en Abitibi-Témiscamingue" />
1395 + <div class="btns-img">
1396 + <span class="tooltip-copyright">
1397 + <span>Louis Jalbert</span>
1398 + <svg
1399 + data-icon="Copyright"
1400 + viewBox="0 0 24 24"
1401 + width="24"
1402 + height="24"
1403 + class=""
1404 + fill="none" xmlns="http://www.w3.org/2000/svg">
1405 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1406 + </span><span class="tooltip-copyright">
1407 + <span>Festival de musique émergente en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1408 + <svg
1409 + data-icon="MapPinSimpleArea"
1410 + viewBox="0 0 18 18"
1411 + width="18"
1412 + height="18"
1413 + class=""
1414 + fill="none" xmlns="http://www.w3.org/2000/svg">
1415 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1416 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1417 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1418 + </span> </div>
1419 + </div>
1420 + </div>
1421 + <div class="swiper-slide">
1422 + <div class="img-card">
1423 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/vue-nuit-passerelle-hotel-noranda-logo-festival-des-guitares-en-abitibi-temiscamingue-hugo-lacroix.jpg " alt="La vue du logo du Festival des guitares du monde en Abitibi-Témiscamingue sur la passerelle de l&#039;Hôtel Noranda et flashs de lumières" />
1424 + <div class="btns-img">
1425 + <span class="tooltip-copyright">
1426 + <span>Hugo Lacroix</span>
1427 + <svg
1428 + data-icon="Copyright"
1429 + viewBox="0 0 24 24"
1430 + width="24"
1431 + height="24"
1432 + class=""
1433 + fill="none" xmlns="http://www.w3.org/2000/svg">
1434 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1435 + </span><span class="tooltip-copyright">
1436 + <span>Festival des guitares du monde en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1437 + <svg
1438 + data-icon="MapPinSimpleArea"
1439 + viewBox="0 0 18 18"
1440 + width="18"
1441 + height="18"
1442 + class=""
1443 + fill="none" xmlns="http://www.w3.org/2000/svg">
1444 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1445 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1446 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1447 + </span> </div>
1448 + </div>
1449 + </div>
1450 + <div class="swiper-slide">
1451 + <div class="img-card">
1452 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/vue-scene-exterieure-gens-chaises-collines-festival-d-humour-en-abitibi-temiscamingue-cite-de-l-or-hugo-lacroix.jpg " alt="Vue de la scène extérieure du Festival d&#039;humour en Abitibi-Témiscamingue à Val-d&#039;Or avec la Cité de l&#039;Or en arrière-plan" />
1453 + <div class="btns-img">
1454 + <span class="tooltip-copyright">
1455 + <span>Hugo Lacroix</span>
1456 + <svg
1457 + data-icon="Copyright"
1458 + viewBox="0 0 24 24"
1459 + width="24"
1460 + height="24"
1461 + class=""
1462 + fill="none" xmlns="http://www.w3.org/2000/svg">
1463 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1464 + </span><span class="tooltip-copyright">
1465 + <span>Festival d'humour en Abitibi-Témiscamingue, Val-d'Or</span>
1466 + <svg
1467 + data-icon="MapPinSimpleArea"
1468 + viewBox="0 0 18 18"
1469 + width="18"
1470 + height="18"
1471 + class=""
1472 + fill="none" xmlns="http://www.w3.org/2000/svg">
1473 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1474 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1475 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1476 + </span> </div>
1477 + </div>
1478 + </div>
1479 + <div class="swiper-slide">
1480 + <div class="img-card">
1481 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/vue-theatre-du-cuivre-orginal-lumiere-festival-du-cinema-international-en-abitibi-temismicamingue-louis-jalbert.jpg " alt="Vue sur l&#039;orignal sur le toit du Théâtre du Cuivre pour le Festival du cinéma international en Abitibi-Témiscamingue, en hiver" />
1482 + <div class="btns-img">
1483 + <span class="tooltip-copyright">
1484 + <span>Louis Jalbert</span>
1485 + <svg
1486 + data-icon="Copyright"
1487 + viewBox="0 0 24 24"
1488 + width="24"
1489 + height="24"
1490 + class=""
1491 + fill="none" xmlns="http://www.w3.org/2000/svg">
1492 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1493 + </span><span class="tooltip-copyright">
1494 + <span>Festival du cinéma international en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1495 + <svg
1496 + data-icon="MapPinSimpleArea"
1497 + viewBox="0 0 18 18"
1498 + width="18"
1499 + height="18"
1500 + class=""
1501 + fill="none" xmlns="http://www.w3.org/2000/svg">
1502 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1503 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1504 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1505 + </span> </div>
1506 + </div>
1507 + </div>
1508 + <div class="swiper-slide">
1509 + <div class="img-card">
1510 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2024/07/Festival-h2o-foule-scene_sabrina-bizier.jpg " alt="foule et scène à un festival en extérieur" />
1511 + <div class="btns-img">
1512 + <span class="tooltip-copyright">
1513 + <span>Sabrina Bizier</span>
1514 + <svg
1515 + data-icon="Copyright"
1516 + viewBox="0 0 24 24"
1517 + width="24"
1518 + height="24"
1519 + class=""
1520 + fill="none" xmlns="http://www.w3.org/2000/svg">
1521 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1522 + </span><span class="tooltip-copyright">
1523 + <span>H2O le Festival, Amos</span>
1524 + <svg
1525 + data-icon="MapPinSimpleArea"
1526 + viewBox="0 0 18 18"
1527 + width="18"
1528 + height="18"
1529 + class=""
1530 + fill="none" xmlns="http://www.w3.org/2000/svg">
1531 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1532 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1533 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1534 + </span> </div>
1535 + </div>
1536 + </div>
1537 + <div class="swiper-slide">
1538 + <div class="img-card">
1539 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/enfant-fme-powwow-anichinaabe-christian-leduc.jpg " alt="Enfant anichinaabe portant des vêtements traditionnels de pow‑wow lors d’une représentation au Festival de musique émergente en Abitibi‑Témiscamingue, tourisme autochtone." />
1540 + <div class="btns-img">
1541 + <span class="tooltip-copyright">
1542 + <span>Christian Leduc</span>
1543 + <svg
1544 + data-icon="Copyright"
1545 + viewBox="0 0 24 24"
1546 + width="24"
1547 + height="24"
1548 + class=""
1549 + fill="none" xmlns="http://www.w3.org/2000/svg">
1550 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1551 + </span><span class="tooltip-copyright">
1552 + <span>Festival de musique émergente, Rouyn-Noranda</span>
1553 + <svg
1554 + data-icon="MapPinSimpleArea"
1555 + viewBox="0 0 18 18"
1556 + width="18"
1557 + height="18"
1558 + class=""
1559 + fill="none" xmlns="http://www.w3.org/2000/svg">
1560 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1561 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1562 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1563 + </span> </div>
1564 + </div>
1565 + </div>
1566 + <div class="swiper-slide">
1567 + <div class="img-card">
1568 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/01/festival-du-cinema-international-en-abitibi-temiscamingue-theatre-du-cuivre.jpg " alt="Vue d&#039;une foule au Théâtre du cuivre pendant le festival du cinéma en Abitibi-Témiscamingue." />
1569 + <div class="btns-img">
1570 + <span class="tooltip-copyright">
1571 + <span>Festival du cinéma international en Abitibi-Témiscamingue</span>
1572 + <svg
1573 + data-icon="Copyright"
1574 + viewBox="0 0 24 24"
1575 + width="24"
1576 + height="24"
1577 + class=""
1578 + fill="none" xmlns="http://www.w3.org/2000/svg">
1579 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1580 + </span> </div>
1581 + </div>
1582 + </div>
1583 + <div class="swiper-slide">
1584 + <div class="img-card">
1585 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/11/jeune-femme-festival-foule-musique-fme-rouyn-noranda-christian-leduc.jpg " alt="Jeune femme debout sur les épaules d&#039;un homme dans la foule au Festival de musique émergente." />
1586 + <div class="btns-img">
1587 + <span class="tooltip-copyright">
1588 + <span>Christian Leduc</span>
1589 + <svg
1590 + data-icon="Copyright"
1591 + viewBox="0 0 24 24"
1592 + width="24"
1593 + height="24"
1594 + class=""
1595 + fill="none" xmlns="http://www.w3.org/2000/svg">
1596 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1597 + </span><span class="tooltip-copyright">
1598 + <span>Festival de musique émergente en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1599 + <svg
1600 + data-icon="MapPinSimpleArea"
1601 + viewBox="0 0 18 18"
1602 + width="18"
1603 + height="18"
1604 + class=""
1605 + fill="none" xmlns="http://www.w3.org/2000/svg">
1606 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1607 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1608 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1609 + </span> </div>
1610 + </div>
1611 + </div>
1612 + <div class="swiper-slide">
1613 + <div class="img-card">
1614 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/spectacle-petit-theatre-vieux-noranda-foule.jpg " alt="Foule devant un spectacle de musique du FME au Petit Théâtre du Vieux Noranda." />
1615 + <div class="btns-img">
1616 + <span class="tooltip-copyright">
1617 + <span>Thomas Dufresne</span>
1618 + <svg
1619 + data-icon="Copyright"
1620 + viewBox="0 0 24 24"
1621 + width="24"
1622 + height="24"
1623 + class=""
1624 + fill="none" xmlns="http://www.w3.org/2000/svg">
1625 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1626 + </span><span class="tooltip-copyright">
1627 + <span>Festival de musique émergente en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1628 + <svg
1629 + data-icon="MapPinSimpleArea"
1630 + viewBox="0 0 18 18"
1631 + width="18"
1632 + height="18"
1633 + class=""
1634 + fill="none" xmlns="http://www.w3.org/2000/svg">
1635 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1636 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1637 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1638 + </span> </div>
1639 + </div>
1640 + </div>
1641 + <div class="swiper-slide">
1642 + <div class="img-card">
1643 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/03/FME-spectacle-rue-crowd-foule-lumiere-nuit.jpg " alt="Une foule est rassemblée devant une scène extérieure de spectacle au FME à Rouyn-Noranda." />
1644 + <div class="btns-img">
1645 + <span class="tooltip-copyright">
1646 + <span>Thomas Dufresne</span>
1647 + <svg
1648 + data-icon="Copyright"
1649 + viewBox="0 0 24 24"
1650 + width="24"
1651 + height="24"
1652 + class=""
1653 + fill="none" xmlns="http://www.w3.org/2000/svg">
1654 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1655 + </span><span class="tooltip-copyright">
1656 + <span>FME, Rouyn-Noranda</span>
1657 + <svg
1658 + data-icon="MapPinSimpleArea"
1659 + viewBox="0 0 18 18"
1660 + width="18"
1661 + height="18"
1662 + class=""
1663 + fill="none" xmlns="http://www.w3.org/2000/svg">
1664 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1665 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1666 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1667 + </span> </div>
1668 + </div>
1669 + </div>
1670 + <div class="swiper-slide">
1671 + <div class="img-card">
1672 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2024/06/FHAT-3_Marie-Claude-Robert.png " alt="Personnes qui rient au Festival d&#039;humour de l&#039;Abitibi-Témiscamingue" />
1673 + <div class="btns-img">
1674 + <span class="tooltip-copyright">
1675 + <span>Marie-Claude Robert</span>
1676 + <svg
1677 + data-icon="Copyright"
1678 + viewBox="0 0 24 24"
1679 + width="24"
1680 + height="24"
1681 + class=""
1682 + fill="none" xmlns="http://www.w3.org/2000/svg">
1683 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1684 + </span><span class="tooltip-copyright">
1685 + <span>Festival d'Humour de l'Abitibi-Témiscamingue, Val-d'Or</span>
1686 + <svg
1687 + data-icon="MapPinSimpleArea"
1688 + viewBox="0 0 18 18"
1689 + width="18"
1690 + height="18"
1691 + class=""
1692 + fill="none" xmlns="http://www.w3.org/2000/svg">
1693 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1694 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1695 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1696 + </span> </div>
1697 + </div>
1698 + </div>
1699 + <div class="swiper-slide">
1700 + <div class="img-card">
1701 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/fille-de-dos-chapeau-de-cowboy-festival-western-de-saint-bruno-de-guigues-christian-leduc.jpg " alt="Fille de dos avec un chapeau de cowboy au Festival western de Saint-Bruno-de-Guigues" />
1702 + <div class="btns-img">
1703 + <span class="tooltip-copyright">
1704 + <span>Christian Leduc</span>
1705 + <svg
1706 + data-icon="Copyright"
1707 + viewBox="0 0 24 24"
1708 + width="24"
1709 + height="24"
1710 + class=""
1711 + fill="none" xmlns="http://www.w3.org/2000/svg">
1712 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1713 + </span><span class="tooltip-copyright">
1714 + <span>Festival western de Guigues, Saint-Bruno-de-Guigues</span>
1715 + <svg
1716 + data-icon="MapPinSimpleArea"
1717 + viewBox="0 0 18 18"
1718 + width="18"
1719 + height="18"
1720 + class=""
1721 + fill="none" xmlns="http://www.w3.org/2000/svg">
1722 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1723 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1724 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1725 + </span> </div>
1726 + </div>
1727 + </div>
1728 + <div class="swiper-slide">
1729 + <div class="img-card">
1730 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/scene-lumiere-koriass-festival-de-musique-emergente-en-abitibi-temiscamingue-louis-jalbert.jpg " alt="Scène lumineuse avec Koriass lors du Festival de musique émergente en Abitibi-Témiscamingue" />
1731 + <div class="btns-img">
1732 + <span class="tooltip-copyright">
1733 + <span>Louis Jalbert</span>
1734 + <svg
1735 + data-icon="Copyright"
1736 + viewBox="0 0 24 24"
1737 + width="24"
1738 + height="24"
1739 + class=""
1740 + fill="none" xmlns="http://www.w3.org/2000/svg">
1741 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1742 + </span><span class="tooltip-copyright">
1743 + <span>Festival de musique émergente en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1744 + <svg
1745 + data-icon="MapPinSimpleArea"
1746 + viewBox="0 0 18 18"
1747 + width="18"
1748 + height="18"
1749 + class=""
1750 + fill="none" xmlns="http://www.w3.org/2000/svg">
1751 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1752 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1753 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1754 + </span> </div>
1755 + </div>
1756 + </div>
1757 + <div class="swiper-slide">
1758 + <div class="img-card">
1759 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/vue-nuit-passerelle-hotel-noranda-logo-festival-des-guitares-en-abitibi-temiscamingue-hugo-lacroix.jpg " alt="La vue du logo du Festival des guitares du monde en Abitibi-Témiscamingue sur la passerelle de l&#039;Hôtel Noranda et flashs de lumières" />
1760 + <div class="btns-img">
1761 + <span class="tooltip-copyright">
1762 + <span>Hugo Lacroix</span>
1763 + <svg
1764 + data-icon="Copyright"
1765 + viewBox="0 0 24 24"
1766 + width="24"
1767 + height="24"
1768 + class=""
1769 + fill="none" xmlns="http://www.w3.org/2000/svg">
1770 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1771 + </span><span class="tooltip-copyright">
1772 + <span>Festival des guitares du monde en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1773 + <svg
1774 + data-icon="MapPinSimpleArea"
1775 + viewBox="0 0 18 18"
1776 + width="18"
1777 + height="18"
1778 + class=""
1779 + fill="none" xmlns="http://www.w3.org/2000/svg">
1780 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1781 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1782 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1783 + </span> </div>
1784 + </div>
1785 + </div>
1786 + <div class="swiper-slide">
1787 + <div class="img-card">
1788 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/vue-scene-exterieure-gens-chaises-collines-festival-d-humour-en-abitibi-temiscamingue-cite-de-l-or-hugo-lacroix.jpg " alt="Vue de la scène extérieure du Festival d&#039;humour en Abitibi-Témiscamingue à Val-d&#039;Or avec la Cité de l&#039;Or en arrière-plan" />
1789 + <div class="btns-img">
1790 + <span class="tooltip-copyright">
1791 + <span>Hugo Lacroix</span>
1792 + <svg
1793 + data-icon="Copyright"
1794 + viewBox="0 0 24 24"
1795 + width="24"
1796 + height="24"
1797 + class=""
1798 + fill="none" xmlns="http://www.w3.org/2000/svg">
1799 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1800 + </span><span class="tooltip-copyright">
1801 + <span>Festival d'humour en Abitibi-Témiscamingue, Val-d'Or</span>
1802 + <svg
1803 + data-icon="MapPinSimpleArea"
1804 + viewBox="0 0 18 18"
1805 + width="18"
1806 + height="18"
1807 + class=""
1808 + fill="none" xmlns="http://www.w3.org/2000/svg">
1809 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1810 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1811 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1812 + </span> </div>
1813 + </div>
1814 + </div>
1815 + <div class="swiper-slide">
1816 + <div class="img-card">
1817 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/vue-theatre-du-cuivre-orginal-lumiere-festival-du-cinema-international-en-abitibi-temismicamingue-louis-jalbert.jpg " alt="Vue sur l&#039;orignal sur le toit du Théâtre du Cuivre pour le Festival du cinéma international en Abitibi-Témiscamingue, en hiver" />
1818 + <div class="btns-img">
1819 + <span class="tooltip-copyright">
1820 + <span>Louis Jalbert</span>
1821 + <svg
1822 + data-icon="Copyright"
1823 + viewBox="0 0 24 24"
1824 + width="24"
1825 + height="24"
1826 + class=""
1827 + fill="none" xmlns="http://www.w3.org/2000/svg">
1828 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1829 + </span><span class="tooltip-copyright">
1830 + <span>Festival du cinéma international en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1831 + <svg
1832 + data-icon="MapPinSimpleArea"
1833 + viewBox="0 0 18 18"
1834 + width="18"
1835 + height="18"
1836 + class=""
1837 + fill="none" xmlns="http://www.w3.org/2000/svg">
1838 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1839 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1840 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1841 + </span> </div>
1842 + </div>
1843 + </div>
1844 + <div class="swiper-slide">
1845 + <div class="img-card">
1846 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2024/07/Festival-h2o-foule-scene_sabrina-bizier.jpg " alt="foule et scène à un festival en extérieur" />
1847 + <div class="btns-img">
1848 + <span class="tooltip-copyright">
1849 + <span>Sabrina Bizier</span>
1850 + <svg
1851 + data-icon="Copyright"
1852 + viewBox="0 0 24 24"
1853 + width="24"
1854 + height="24"
1855 + class=""
1856 + fill="none" xmlns="http://www.w3.org/2000/svg">
1857 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1858 + </span><span class="tooltip-copyright">
1859 + <span>H2O le Festival, Amos</span>
1860 + <svg
1861 + data-icon="MapPinSimpleArea"
1862 + viewBox="0 0 18 18"
1863 + width="18"
1864 + height="18"
1865 + class=""
1866 + fill="none" xmlns="http://www.w3.org/2000/svg">
1867 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1868 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1869 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1870 + </span> </div>
1871 + </div>
1872 + </div>
1873 + <div class="swiper-slide">
1874 + <div class="img-card">
1875 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/enfant-fme-powwow-anichinaabe-christian-leduc.jpg " alt="Enfant anichinaabe portant des vêtements traditionnels de pow‑wow lors d’une représentation au Festival de musique émergente en Abitibi‑Témiscamingue, tourisme autochtone." />
1876 + <div class="btns-img">
1877 + <span class="tooltip-copyright">
1878 + <span>Christian Leduc</span>
1879 + <svg
1880 + data-icon="Copyright"
1881 + viewBox="0 0 24 24"
1882 + width="24"
1883 + height="24"
1884 + class=""
1885 + fill="none" xmlns="http://www.w3.org/2000/svg">
1886 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1887 + </span><span class="tooltip-copyright">
1888 + <span>Festival de musique émergente, Rouyn-Noranda</span>
1889 + <svg
1890 + data-icon="MapPinSimpleArea"
1891 + viewBox="0 0 18 18"
1892 + width="18"
1893 + height="18"
1894 + class=""
1895 + fill="none" xmlns="http://www.w3.org/2000/svg">
1896 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1897 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1898 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1899 + </span> </div>
1900 + </div>
1901 + </div>
1902 + <div class="swiper-slide">
1903 + <div class="img-card">
1904 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/01/festival-du-cinema-international-en-abitibi-temiscamingue-theatre-du-cuivre.jpg " alt="Vue d&#039;une foule au Théâtre du cuivre pendant le festival du cinéma en Abitibi-Témiscamingue." />
1905 + <div class="btns-img">
1906 + <span class="tooltip-copyright">
1907 + <span>Festival du cinéma international en Abitibi-Témiscamingue</span>
1908 + <svg
1909 + data-icon="Copyright"
1910 + viewBox="0 0 24 24"
1911 + width="24"
1912 + height="24"
1913 + class=""
1914 + fill="none" xmlns="http://www.w3.org/2000/svg">
1915 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1916 + </span> </div>
1917 + </div>
1918 + </div>
1919 + <div class="swiper-slide">
1920 + <div class="img-card">
1921 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/11/jeune-femme-festival-foule-musique-fme-rouyn-noranda-christian-leduc.jpg " alt="Jeune femme debout sur les épaules d&#039;un homme dans la foule au Festival de musique émergente." />
1922 + <div class="btns-img">
1923 + <span class="tooltip-copyright">
1924 + <span>Christian Leduc</span>
1925 + <svg
1926 + data-icon="Copyright"
1927 + viewBox="0 0 24 24"
1928 + width="24"
1929 + height="24"
1930 + class=""
1931 + fill="none" xmlns="http://www.w3.org/2000/svg">
1932 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1933 + </span><span class="tooltip-copyright">
1934 + <span>Festival de musique émergente en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1935 + <svg
1936 + data-icon="MapPinSimpleArea"
1937 + viewBox="0 0 18 18"
1938 + width="18"
1939 + height="18"
1940 + class=""
1941 + fill="none" xmlns="http://www.w3.org/2000/svg">
1942 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1943 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1944 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1945 + </span> </div>
1946 + </div>
1947 + </div>
1948 + <div class="swiper-slide">
1949 + <div class="img-card">
1950 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2025/12/spectacle-petit-theatre-vieux-noranda-foule.jpg " alt="Foule devant un spectacle de musique du FME au Petit Théâtre du Vieux Noranda." />
1951 + <div class="btns-img">
1952 + <span class="tooltip-copyright">
1953 + <span>Thomas Dufresne</span>
1954 + <svg
1955 + data-icon="Copyright"
1956 + viewBox="0 0 24 24"
1957 + width="24"
1958 + height="24"
1959 + class=""
1960 + fill="none" xmlns="http://www.w3.org/2000/svg">
1961 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1962 + </span><span class="tooltip-copyright">
1963 + <span>Festival de musique émergente en Abitibi-Témiscamingue, Rouyn-Noranda</span>
1964 + <svg
1965 + data-icon="MapPinSimpleArea"
1966 + viewBox="0 0 18 18"
1967 + width="18"
1968 + height="18"
1969 + class=""
1970 + fill="none" xmlns="http://www.w3.org/2000/svg">
1971 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1972 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
1973 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
1974 + </span> </div>
1975 + </div>
1976 + </div>
1977 + <div class="swiper-slide">
1978 + <div class="img-card">
1979 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/03/FME-spectacle-rue-crowd-foule-lumiere-nuit.jpg " alt="Une foule est rassemblée devant une scène extérieure de spectacle au FME à Rouyn-Noranda." />
1980 + <div class="btns-img">
1981 + <span class="tooltip-copyright">
1982 + <span>Thomas Dufresne</span>
1983 + <svg
1984 + data-icon="Copyright"
1985 + viewBox="0 0 24 24"
1986 + width="24"
1987 + height="24"
1988 + class=""
1989 + fill="none" xmlns="http://www.w3.org/2000/svg">
1990 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
1991 + </span><span class="tooltip-copyright">
1992 + <span>FME, Rouyn-Noranda</span>
1993 + <svg
1994 + data-icon="MapPinSimpleArea"
1995 + viewBox="0 0 18 18"
1996 + width="18"
1997 + height="18"
1998 + class=""
1999 + fill="none" xmlns="http://www.w3.org/2000/svg">
2000 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
2001 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
2002 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2003 + </span> </div>
2004 + </div>
2005 + </div>
2006 + <div class="swiper-slide">
2007 + <div class="img-card">
2008 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2024/06/FHAT-3_Marie-Claude-Robert.png " alt="Personnes qui rient au Festival d&#039;humour de l&#039;Abitibi-Témiscamingue" />
2009 + <div class="btns-img">
2010 + <span class="tooltip-copyright">
2011 + <span>Marie-Claude Robert</span>
2012 + <svg
2013 + data-icon="Copyright"
2014 + viewBox="0 0 24 24"
2015 + width="24"
2016 + height="24"
2017 + class=""
2018 + fill="none" xmlns="http://www.w3.org/2000/svg">
2019 + <path d="M12 1.875C9.99747 1.875 8.0399 2.46882 6.37486 3.58137C4.70981 4.69392 3.41206 6.27523 2.64572 8.12533C1.87939 9.97543 1.67888 12.0112 2.06955 13.9753C2.46023 15.9393 3.42454 17.7435 4.84055 19.1595C6.25656 20.5755 8.06066 21.5398 10.0247 21.9305C11.9888 22.3211 14.0246 22.1206 15.8747 21.3543C17.7248 20.5879 19.3061 19.2902 20.4186 17.6251C21.5312 15.9601 22.125 14.0025 22.125 12C22.122 9.3156 21.0543 6.74199 19.1562 4.84383C17.258 2.94567 14.6844 1.87798 12 1.875ZM12 19.875C10.4425 19.875 8.91993 19.4131 7.62489 18.5478C6.32985 17.6825 5.32049 16.4526 4.72445 15.0136C4.12841 13.5747 3.97246 11.9913 4.27632 10.4637C4.58018 8.93606 5.3302 7.53287 6.43154 6.43153C7.53288 5.3302 8.93607 4.58017 10.4637 4.27632C11.9913 3.97246 13.5747 4.12841 15.0136 4.72445C16.4526 5.32049 17.6825 6.32985 18.5478 7.62488C19.4131 8.91992 19.875 10.4425 19.875 12C19.8728 14.0879 19.0424 16.0896 17.566 17.566C16.0896 19.0424 14.0879 19.8728 12 19.875ZM15.8991 14.9259C15.285 15.7441 14.429 16.3484 13.4524 16.6532C12.4759 16.9579 11.4281 16.9478 10.4577 16.6241C9.48721 16.3004 8.64316 15.6796 8.04506 14.8496C7.44696 14.0197 7.12512 13.0226 7.12512 11.9995C7.12512 10.9765 7.44696 9.9794 8.04506 9.14943C8.64316 8.31945 9.48721 7.69866 10.4577 7.37498C11.4281 7.05129 12.4759 7.04112 13.4524 7.34588C14.429 7.65065 15.285 8.25492 15.8991 9.07312C15.991 9.19093 16.0586 9.32589 16.0979 9.47011C16.1371 9.61433 16.1472 9.76492 16.1276 9.91309C16.108 10.0613 16.059 10.204 15.9836 10.3331C15.9082 10.4621 15.8078 10.5748 15.6884 10.6647C15.5689 10.7545 15.4327 10.8196 15.2878 10.8562C15.1429 10.8928 14.9922 10.9002 14.8444 10.8778C14.6966 10.8555 14.5547 10.804 14.4271 10.7262C14.2994 10.6485 14.1886 10.5461 14.1009 10.425C13.7704 9.98425 13.3095 9.65867 12.7836 9.4944C12.2578 9.33013 11.6935 9.33548 11.1708 9.50971C10.6482 9.68393 10.1936 10.0182 9.87143 10.4651C9.54929 10.9121 9.37594 11.4491 9.37594 12C9.37594 12.5509 9.54929 13.0879 9.87143 13.5349C10.1936 13.9818 10.6482 14.3161 11.1708 14.4903C11.6935 14.6645 12.2578 14.6699 12.7836 14.5056C13.3095 14.3413 13.7704 14.0158 14.1009 13.575C14.1878 13.4521 14.2985 13.348 14.4263 13.2686C14.5542 13.1893 14.6966 13.1364 14.8453 13.1131C14.9939 13.0898 15.1457 13.0965 15.2917 13.133C15.4377 13.1694 15.575 13.2347 15.6953 13.3251C15.8156 13.4155 15.9165 13.5291 15.9921 13.6591C16.0677 13.7892 16.1165 13.9332 16.1355 14.0824C16.1545 14.2317 16.1434 14.3833 16.1028 14.5281C16.0621 14.673 15.9929 14.8083 15.8991 14.9259Z" fill="currentColor" /> </svg>
2020 + </span><span class="tooltip-copyright">
2021 + <span>Festival d'Humour de l'Abitibi-Témiscamingue, Val-d'Or</span>
2022 + <svg
2023 + data-icon="MapPinSimpleArea"
2024 + viewBox="0 0 18 18"
2025 + width="18"
2026 + height="18"
2027 + class=""
2028 + fill="none" xmlns="http://www.w3.org/2000/svg">
2029 + <path d="M8.75 6.5625C9.95812 6.5625 10.9375 5.58312 10.9375 4.375C10.9375 3.16688 9.95812 2.1875 8.75 2.1875C7.54188 2.1875 6.5625 3.16688 6.5625 4.375C6.5625 5.58312 7.54188 6.5625 8.75 6.5625Z" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
2030 + <path d="M8.75 6.5625V12.0313" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" />
2031 + <path d="M11.4844 9.50586C14.0533 9.91602 15.8594 10.8929 15.8594 12.0304C15.8594 13.5404 12.6766 14.7647 8.75 14.7647C4.82344 14.7647 1.64062 13.5404 1.64062 12.0304C1.64062 10.8929 3.44668 9.91738 6.01562 9.50586" stroke="currentColor" stroke-width="1.18125" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2032 + </span> </div>
2033 + </div>
2034 + </div>
2035 + </div>
2036 + <div class="slider-navigation row-padding-top d-flex align-items-center gap-3 justify-content-center">
2037 + <div class="prev-btn">
2038 + <svg
2039 + data-icon="CaretLeft"
2040 + viewBox="0 0 32 32"
2041 + width="32"
2042 + height="32"
2043 + class=""
2044 + fill="none" xmlns="http://www.w3.org/2000/svg">
2045 + <path d="M21.0613 24.9381C21.343 25.2199 21.5014 25.6021 21.5014 26.0006C21.5014 26.3992 21.343 26.7814 21.0613 27.0631C20.7795 27.3449 20.3973 27.5033 19.9988 27.5033C19.6002 27.5033 19.218 27.3449 18.9363 27.0631L8.93625 17.0631C8.79641 16.9238 8.68545 16.7582 8.60975 16.5759C8.53404 16.3935 8.49507 16.1981 8.49507 16.0006C8.49507 15.8032 8.53404 15.6077 8.60975 15.4254C8.68545 15.2431 8.79641 15.0775 8.93625 14.9381L18.9363 4.93815C19.218 4.65636 19.6002 4.49805 19.9988 4.49805C20.3973 4.49805 20.7795 4.65636 21.0613 4.93815C21.343 5.21994 21.5014 5.60213 21.5014 6.00065C21.5014 6.39916 21.343 6.78136 21.0613 7.06315L12.125 15.9994L21.0613 24.9381Z" fill="currentColor" /> </svg>
2046 + </div>
2047 + <div class="next-btn">
2048 + <svg
2049 + data-icon="CaretRight"
2050 + viewBox="0 0 32 32"
2051 + width="32"
2052 + height="32"
2053 + class=""
2054 + fill="none" xmlns="http://www.w3.org/2000/svg">
2055 + <path d="M23.0613 17.0612L13.0612 27.0612C12.7795 27.343 12.3973 27.5013 11.9987 27.5013C11.6002 27.5013 11.218 27.343 10.9362 27.0612C10.6545 26.7794 10.4961 26.3972 10.4961 25.9987C10.4961 25.6002 10.6545 25.218 10.9362 24.9362L19.875 15.9999L10.9387 7.0612C10.7992 6.92167 10.6885 6.75602 10.613 6.57372C10.5375 6.39141 10.4986 6.19602 10.4986 5.9987C10.4986 5.80137 10.5375 5.60598 10.613 5.42367C10.6885 5.24137 10.7992 5.07573 10.9387 4.9362C11.0783 4.79667 11.2439 4.68599 11.4262 4.61047C11.6085 4.53496 11.8039 4.49609 12.0012 4.49609C12.1986 4.49609 12.394 4.53496 12.5763 4.61047C12.7586 4.68599 12.9242 4.79667 13.0637 4.9362L23.0638 14.9362C23.2034 15.0757 23.3142 15.2414 23.3897 15.4239C23.4652 15.6063 23.5039 15.8018 23.5037 15.9992C23.5035 16.1966 23.4642 16.3921 23.3883 16.5743C23.3124 16.7565 23.2013 16.922 23.0613 17.0612Z" fill="currentColor" /> </svg>
2056 + </div>
2057 +</div></div>
2058 + </div>
2059 + </div>
2060 + </div>
2061 + <div class="bordure-bottom-shape">
2062 + <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 425 12">
2063 + <path class="st5" d="M-259,12.1h943V1.1s-147.1,5.4-239,5.4c-92.2,0-140.3-4.7-232.5-5.4C58.8,0,35.5,7-71.1,3.4c-30.5-1-187.9-2.2-187.9-2.2v10.9Z" fill="currentColor"/>
2064 + <path class="st3" d="M-259,12.1h943V1.1s-147.1,5.4-239,5.4c-92.2,0-140.3-4.7-232.5-5.4C58.8,0,35.5,7-71.1,3.4c-30.5-1-187.9-2.2-187.9-2.2v10.9Z" fill="currentColor"/>
2065 + </svg>
2066 +</div> </section>
2067 +<section class="bloc-texte-section quanto-about-section overflow-hidden bg-color-primary section-padding-top-bottom" data-section="bloc_deux_colonnes_dephases">
2068 + <div class="container">
2069 + <div class="row justify-content-end justify-content-lg-start gy-5 gy-lg-0">
2070 + <div class="col-12 col-lg-6">
2071 + <div class="about-info boite-de-texte fade-anim" data-direction="left">
2072 + <h2 class="h3"><span class="TextRun MacChromeBold SCXW185472684 BCX0" data-contrast="auto"><span class="NormalTextRun SCXW185472684 BCX0">Ce qui fait notre marque?</span></span></h2>
2073 +<p>La proximité, l’accueil, les rencontres vraies et cette énergie humaine qu’on ne retrouve nulle part ailleurs. Ici, la culture ne se consomme pas, elle se vit, elle se ressent, elle se partage.</p>
2074 +<p class="h5 h4">
2075 + </div>
2076 + </div>
2077 + <div class="col-10 col-lg-6 offset-lg-6">
2078 + <div class="about-info boite-de-texte fade-anim" data-direction="left" data-delay="0.10">
2079 + <p class="h5">Peu importe ce que tu aimes, tu vas être servi·e. Un événement va te trouver et pas l’inverse. Et qui sait? Tu repartiras peut-être avec une nouvelle tradition bien à toi.</p>
2080 + </div>
2081 + </div>
2082 + </div>
2083 + </div>
2084 + </section><section class="bloc-citation overflow-hidden bg-color-primary section-padding-bottom" data-section="bloc_citation">
2085 + <div class="container custom-container">
2086 + <div class="row justify-content-center">
2087 + <div class="col-12 col-lg-10">
2088 + <div class="boite-bg fade-anim" data-direction="left" data-couleur="vert">
2089 + <div class="content boite-de-texte">
2090 + <p class="h5" style="text-align: center;"><span class="TextRun SCXW131267701 BCX0" data-contrast="auto"><span class="NormalTextRun SCXW131267701 BCX0">En Abitibi-Témiscamingue, t</span><span class="NormalTextRun SCXW131267701 BCX0">u ne</span><span class="NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW131267701 BCX0"><span> </span>«</span><span class="NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW131267701 BCX0">vas</span><span class="NormalTextRun SCXW131267701 BCX0"><span> </span>pas à un<span> </span></span><span class="NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW131267701 BCX0">festival</span><span class="NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW131267701 BCX0">»</span><span class="NormalTextRun SCXW131267701 BCX0">, tu fais partie du festival.</span></span><span class="EOP SCXW131267701 BCX0" data-ccp-props="{}"> </span></p>
2091 + </div>
2092 + <div class="texture">
2093 + <svg viewBox="0 0 140 177" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
2094 + <path d="M5.96682e-05 248.245C4.418 242.366 8.21971 235.737 13.2444 230.714C17.1504 226.811 20.6487 222.693 24.1186 218.109C27.6075 213.495 31.7031 209.689 34.9075 204.637C36.9364 201.435 38.804 198.505 41.1457 195.711C42.0179 194.669 42.5489 193.404 43.2694 192.226C44.1511 190.795 45.2414 189.871 46.379 188.79C50.8065 184.575 54.959 180.03 59.3579 175.756C64.6007 170.666 69.578 164.757 73.4555 157.817C74.8871 155.257 75.5602 153.028 76.0532 149.893C76.66 146 77.3047 142.466 78.2243 138.67C78.8689 136.003 79.1723 133.083 80.1299 130.562C81.1064 127.992 82.0165 125.393 83.0119 122.814C84.5762 118.784 85.3631 114.365 86.6146 110.218C87.0317 108.836 87.8376 107.745 88.2831 106.363C88.8709 104.572 89.307 102.664 89.9138 100.854C90.2741 99.7734 90.7292 98.7026 91.061 97.6222C91.6393 95.7824 91.5445 93.7383 91.9616 91.8207C92.5778 88.92 93.1277 85.8928 93.3837 82.9044C93.6397 79.994 94.3697 77.58 94.8911 74.7182C96.1046 68.0213 99.5935 62.8428 102.011 56.8954C104.286 51.3081 106.429 45.2244 106.353 38.8778C106.325 36.5417 106.96 34.7215 106.988 32.4242C107.026 29.3191 107.548 26.4768 107.595 23.4496C107.652 19.1082 107.614 14.7571 107.595 10.4158C107.567 3.8162 109.207 -2.49139 109.434 -9.05207C109.501 -11.0962 110.051 -12.9456 110.088 -15.019C110.126 -17.1215 109.946 -19.263 110.041 -21.3655C110.117 -23.0495 110.629 -24.5777 110.705 -26.2519C110.762 -27.5271 110.695 -28.8412 110.695 -30.126C110.695 -36.0443 111.624 -41.9528 112.999 -47.5693C113.938 -51.4044 115.388 -55.7458 117.626 -58.627C120.508 -62.3454 123.978 -64.8762 127.116 -68.1079C131.676 -72.7996 136.226 -77.9099 140.379 -83.2441L140.379 248.255L0.0190416 248.255L5.96682e-05 248.245Z" fill="currentColor"/>
2095 + </svg>
2096 + </div>
2097 +</div> </div>
2098 + </div>
2099 + </div>
2100 + <div class="bordure-bottom-shape">
2101 + <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 425 12">
2102 + <path class="st5" d="M-259,12.1h943V1.1s-147.1,5.4-239,5.4c-92.2,0-140.3-4.7-232.5-5.4C58.8,0,35.5,7-71.1,3.4c-30.5-1-187.9-2.2-187.9-2.2v10.9Z" fill="currentColor"/>
2103 + <path class="st3" d="M-259,12.1h943V1.1s-147.1,5.4-239,5.4c-92.2,0-140.3-4.7-232.5-5.4C58.8,0,35.5,7-71.1,3.4c-30.5-1-187.9-2.2-187.9-2.2v10.9Z" fill="currentColor"/>
2104 + </svg>
2105 +</div></section> <section class="bloc-texte-section section-padding-top" data-section="image_fullwidth">
2106 + <div class="bordure-top-shape">
2107 + <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 425 12">
2108 + <path class="st5" d="M-259,0h943v10.9s-147.1-5.4-239-5.4c-92.2,0-140.3,4.7-232.5,5.4C58.8,12.1,35.5,5.1-71.1,8.7c-30.5,1-187.9,2.2-187.9,2.2V0Z" fill="currentColor" />
2109 + <path class="st3" d="M-259,0h943v10.9s-147.1-5.4-239-5.4c-92.2,0-140.3,4.7-232.5,5.4C58.8,12.1,35.5,5.1-71.1,8.7c-30.5,1-187.9,2.2-187.9,2.2V0Z" fill="currentColor" />
2110 + </svg>
2111 +</div> <div class="fade-anim" data-direction="left" >
2112 + <div class="img-card" data-ratio="fullwidth">
2113 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/06/2026-FestivalEvenements-Web-scaled.jpg " alt="Programmation festivals et événements 2026" />
2114 + </div>
2115 + </div>
2116 + </section>
2117 + <section class="section-padding-top-bottom" data-section="repertoire" id="repertoire-6a8e06e5468ca" data-preview-ajax-url="https://abitibi-temiscamingue.org/wp-admin/admin-ajax.php" data-preview-action="lebleu_repertoire_preview_html" data-markers-ajax-url="https://abitibi-temiscamingue.org/wp-admin/admin-ajax.php" data-markers-action="lebleu_repertoire_markers_json" data-base-filters="{&quot;categorie&quot;:[384],&quot;tag&quot;:[],&quot;territoire&quot;:[],&quot;saison&quot;:[]}" data-posts-per-page="12" data-map-bounds-excluded-ids="[489]">
2118 + <div class="container custom-container">
2119 + <div class="row g-4">
2120 + <div class="col-12 fade-anim" data-direction="left" >
2121 + <h2 class="h2 title">Choisis ton événement</h2>
2122 + </div>
2123 + <div class="col-12">
2124 + <div class="row filtre-repertoire">
2125 + <div class="col">
2126 + <nav class="nav nav-pills nav-fill nav-switch">
2127 + <a class="nav-link active" data-action="liste" aria-current="page" data-toggle="pill" >Liste</a>
2128 + <a class="nav-link" data-action="map" data-toggle="pill" >Carte</a>
2129 + </nav>
2130 + </div>
2131 + <div class="col-auto">
2132 + <!-- <a class="btn-round-outline" href=""> <svg
2133 + data-icon="ListMagnifyingGlass"
2134 + viewBox="0 0 29 29"
2135 + width="29"
2136 + height="29"
2137 + class=""
2138 + fill="none" xmlns="http://www.w3.org/2000/svg">
2139 + <path d="M3.15039 7.19961C3.15039 6.84157 3.29262 6.49819 3.5458 6.24502C3.79897 5.99184 4.14235 5.84961 4.50039 5.84961H24.3004C24.6584 5.84961 25.0018 5.99184 25.255 6.24502C25.5082 6.49819 25.6504 6.84157 25.6504 7.19961C25.6504 7.55765 25.5082 7.90103 25.255 8.1542C25.0018 8.40738 24.6584 8.54961 24.3004 8.54961H4.50039C4.14235 8.54961 3.79897 8.40738 3.5458 8.1542C3.29262 7.90103 3.15039 7.55765 3.15039 7.19961ZM4.50039 15.7496H11.7004C12.0584 15.7496 12.4018 15.6074 12.655 15.3542C12.9082 15.101 13.0504 14.7577 13.0504 14.3996C13.0504 14.0416 12.9082 13.6982 12.655 13.445C12.4018 13.1918 12.0584 13.0496 11.7004 13.0496H4.50039C4.14235 13.0496 3.79897 13.1918 3.5458 13.445C3.29262 13.6982 3.15039 14.0416 3.15039 14.3996C3.15039 14.7577 3.29262 15.101 3.5458 15.3542C3.79897 15.6074 4.14235 15.7496 4.50039 15.7496ZM13.5004 20.2496H4.50039C4.14235 20.2496 3.79897 20.3918 3.5458 20.645C3.29262 20.8982 3.15039 21.2416 3.15039 21.5996C3.15039 21.9577 3.29262 22.301 3.5458 22.5542C3.79897 22.8074 4.14235 22.9496 4.50039 22.9496H13.5004C13.8584 22.9496 14.2018 22.8074 14.455 22.5542C14.7082 22.301 14.8504 21.9577 14.8504 21.5996C14.8504 21.2416 14.7082 20.8982 14.455 20.645C14.2018 20.3918 13.8584 20.2496 13.5004 20.2496ZM27.0555 22.5547C26.9301 22.6806 26.7811 22.7805 26.617 22.8486C26.4529 22.9167 26.2769 22.9518 26.0993 22.9518C25.9216 22.9518 25.7457 22.9167 25.5816 22.8486C25.4175 22.7805 25.2684 22.6806 25.143 22.5547L23.109 20.5207C22.0657 21.1025 20.8472 21.2874 19.6783 21.0412C18.5094 20.7951 17.4689 20.1346 16.7488 19.1815C16.0287 18.2284 15.6775 17.047 15.7601 15.8553C15.8427 14.6636 16.3534 13.542 17.1981 12.6973C18.0427 11.8526 19.1644 11.3419 20.3561 11.2593C21.5478 11.1767 22.7291 11.5279 23.6822 12.248C24.6353 12.9681 25.2959 14.0086 25.542 15.1775C25.7882 16.3464 25.6033 17.5649 25.0215 18.6082L27.0555 20.6411C27.1815 20.7666 27.2815 20.9157 27.3498 21.0798C27.418 21.244 27.4531 21.4201 27.4531 21.5979C27.4531 21.7757 27.418 21.9518 27.3498 22.116C27.2815 22.2802 27.1815 22.4293 27.0555 22.5547ZM20.7004 18.4496C21.1454 18.4496 21.5804 18.3177 21.9504 18.0704C22.3204 17.8232 22.6088 17.4718 22.7791 17.0606C22.9494 16.6495 22.994 16.1971 22.9072 15.7607C22.8203 15.3242 22.6061 14.9233 22.2914 14.6086C21.9767 14.294 21.5758 14.0797 21.1393 13.9928C20.7029 13.906 20.2505 13.9506 19.8394 14.1209C19.4282 14.2912 19.0768 14.5796 18.8296 14.9496C18.5824 15.3196 18.4504 15.7546 18.4504 16.1996C18.4504 16.4951 18.5086 16.7877 18.6217 17.0606C18.7347 17.3336 18.9005 17.5817 19.1094 17.7906C19.3183 17.9995 19.5664 18.1653 19.8394 18.2783C20.1123 18.3914 20.4049 18.4496 20.7004 18.4496Z" fill="currentColor" /> </svg>
2140 + </a> -->
2141 + <a class="btn-round-outline" data-bs-toggle="offcanvas" data-bs-target="#offcanvasrepertoire6a8e06e5468ca" aria-controls="offcanvasrepertoire6a8e06e5468ca">
2142 + <svg
2143 + data-icon="ListMagnifyingGlass"
2144 + viewBox="0 0 29 29"
2145 + width="29"
2146 + height="29"
2147 + class=""
2148 + fill="none" xmlns="http://www.w3.org/2000/svg">
2149 + <path d="M3.15039 7.19961C3.15039 6.84157 3.29262 6.49819 3.5458 6.24502C3.79897 5.99184 4.14235 5.84961 4.50039 5.84961H24.3004C24.6584 5.84961 25.0018 5.99184 25.255 6.24502C25.5082 6.49819 25.6504 6.84157 25.6504 7.19961C25.6504 7.55765 25.5082 7.90103 25.255 8.1542C25.0018 8.40738 24.6584 8.54961 24.3004 8.54961H4.50039C4.14235 8.54961 3.79897 8.40738 3.5458 8.1542C3.29262 7.90103 3.15039 7.55765 3.15039 7.19961ZM4.50039 15.7496H11.7004C12.0584 15.7496 12.4018 15.6074 12.655 15.3542C12.9082 15.101 13.0504 14.7577 13.0504 14.3996C13.0504 14.0416 12.9082 13.6982 12.655 13.445C12.4018 13.1918 12.0584 13.0496 11.7004 13.0496H4.50039C4.14235 13.0496 3.79897 13.1918 3.5458 13.445C3.29262 13.6982 3.15039 14.0416 3.15039 14.3996C3.15039 14.7577 3.29262 15.101 3.5458 15.3542C3.79897 15.6074 4.14235 15.7496 4.50039 15.7496ZM13.5004 20.2496H4.50039C4.14235 20.2496 3.79897 20.3918 3.5458 20.645C3.29262 20.8982 3.15039 21.2416 3.15039 21.5996C3.15039 21.9577 3.29262 22.301 3.5458 22.5542C3.79897 22.8074 4.14235 22.9496 4.50039 22.9496H13.5004C13.8584 22.9496 14.2018 22.8074 14.455 22.5542C14.7082 22.301 14.8504 21.9577 14.8504 21.5996C14.8504 21.2416 14.7082 20.8982 14.455 20.645C14.2018 20.3918 13.8584 20.2496 13.5004 20.2496ZM27.0555 22.5547C26.9301 22.6806 26.7811 22.7805 26.617 22.8486C26.4529 22.9167 26.2769 22.9518 26.0993 22.9518C25.9216 22.9518 25.7457 22.9167 25.5816 22.8486C25.4175 22.7805 25.2684 22.6806 25.143 22.5547L23.109 20.5207C22.0657 21.1025 20.8472 21.2874 19.6783 21.0412C18.5094 20.7951 17.4689 20.1346 16.7488 19.1815C16.0287 18.2284 15.6775 17.047 15.7601 15.8553C15.8427 14.6636 16.3534 13.542 17.1981 12.6973C18.0427 11.8526 19.1644 11.3419 20.3561 11.2593C21.5478 11.1767 22.7291 11.5279 23.6822 12.248C24.6353 12.9681 25.2959 14.0086 25.542 15.1775C25.7882 16.3464 25.6033 17.5649 25.0215 18.6082L27.0555 20.6411C27.1815 20.7666 27.2815 20.9157 27.3498 21.0798C27.418 21.244 27.4531 21.4201 27.4531 21.5979C27.4531 21.7757 27.418 21.9518 27.3498 22.116C27.2815 22.2802 27.1815 22.4293 27.0555 22.5547ZM20.7004 18.4496C21.1454 18.4496 21.5804 18.3177 21.9504 18.0704C22.3204 17.8232 22.6088 17.4718 22.7791 17.0606C22.9494 16.6495 22.994 16.1971 22.9072 15.7607C22.8203 15.3242 22.6061 14.9233 22.2914 14.6086C21.9767 14.294 21.5758 14.0797 21.1393 13.9928C20.7029 13.906 20.2505 13.9506 19.8394 14.1209C19.4282 14.2912 19.0768 14.5796 18.8296 14.9496C18.5824 15.3196 18.4504 15.7546 18.4504 16.1996C18.4504 16.4951 18.5086 16.7877 18.6217 17.0606C18.7347 17.3336 18.9005 17.5817 19.1094 17.7906C19.3183 17.9995 19.5664 18.1653 19.8394 18.2783C20.1123 18.3914 20.4049 18.4496 20.7004 18.4496Z" fill="currentColor" /> </svg>
2150 + </a>
2151 + </div>
2152 + </div>
2153 + </div>
2154 + <div class="col-12 js-repertoire-active-section" hidden>
2155 + <p class="h6">Mes filtres actifs</p>
2156 + <ul class="list-tags mt-2 js-repertoire-active-filters"></ul>
2157 + </div>
2158 + </div>
2159 + <div class="js-repertoire-results">
2160 + <div class="row gx-3 gy-5 mt-4">
2161 + <div class="col-6 col-lg-3">
2162 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="16522">
2163 + <div class="quanto-blog-thumb">
2164 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/beltane-la-feste-paienne-par-le-cirque-collini-2/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/Beltane-La-Feste-Paienne.png?width=500');"></a>
2165 + <ul class="list-tags">
2166 + <li><span>Rouyn-Noranda <svg
2167 + data-icon="MapPinSimpleLine"
2168 + viewBox="0 0 14 14"
2169 + width="14"
2170 + height="14"
2171 + class=""
2172 + fill="none" xmlns="http://www.w3.org/2000/svg">
2173 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2174 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2175 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2176 + </span></li>
2177 + </ul>
2178 + </div>
2179 + <div class="quanto-blog-content">
2180 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/beltane-la-feste-paienne-par-le-cirque-collini-2/">Beltane &#8211; La Feste Païenne par le Cirque Collini</a></p>
2181 + <p class="h6 repertoire-preview-date" style="font-weight: 400; padding-top: 5px;">Du 26 au 30 août 2026 </p>
2182 + </div>
2183 +</div>
2184 + </div>
2185 + <div class="col-6 col-lg-3">
2186 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="290">
2187 + <div class="quanto-blog-thumb">
2188 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/circuit-a-la-decouverte-de-leau-anisipi/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/Anisipi-Moment-Factory-le-tipi-3.jpg?width=500');"></a>
2189 + <ul class="list-tags">
2190 + <li><span>Amos-Harricana <svg
2191 + data-icon="MapPinSimpleLine"
2192 + viewBox="0 0 14 14"
2193 + width="14"
2194 + height="14"
2195 + class=""
2196 + fill="none" xmlns="http://www.w3.org/2000/svg">
2197 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2198 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2199 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2200 + </span></li>
2201 + </ul>
2202 + </div>
2203 + <div class="quanto-blog-content">
2204 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/circuit-a-la-decouverte-de-leau-anisipi/">Circuit à la découverte de l&#8217;eau ANISIPI</a></p>
2205 + <p class="h6 repertoire-preview-date" style="font-weight: 400; padding-top: 5px;">Du 24 juin au 6 septembre 2026 </p>
2206 + </div>
2207 +</div>
2208 + </div>
2209 + <div class="col-6 col-lg-3">
2210 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="286">
2211 + <div class="quanto-blog-thumb">
2212 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/festival-de-musique-emergente-en-abitibi-temiscamingue-fme/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/A.FME-2024-Erika-Angell--Louis-Jalbert-2024-56.jpg?width=500');"></a>
2213 + <ul class="list-tags">
2214 + <li><span>Rouyn-Noranda <svg
2215 + data-icon="MapPinSimpleLine"
2216 + viewBox="0 0 14 14"
2217 + width="14"
2218 + height="14"
2219 + class=""
2220 + fill="none" xmlns="http://www.w3.org/2000/svg">
2221 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2222 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2223 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2224 + </span></li>
2225 + </ul>
2226 + </div>
2227 + <div class="quanto-blog-content">
2228 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/festival-de-musique-emergente-en-abitibi-temiscamingue-fme/">Festival de musique émergente en Abitibi-Témiscamingue (FME)</a></p>
2229 + <p class="h6 repertoire-preview-date" style="font-weight: 400; padding-top: 5px;">Du 3 au 6 septembre 2026 </p>
2230 + </div>
2231 +</div>
2232 + </div>
2233 + <div class="col-6 col-lg-3">
2234 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="274">
2235 + <div class="quanto-blog-thumb">
2236 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/la-foire-du-camionneur/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/Foire-0617-September-03--2023.jpg?width=500');"></a>
2237 + <ul class="list-tags">
2238 + <li><span>Amos-Harricana <svg
2239 + data-icon="MapPinSimpleLine"
2240 + viewBox="0 0 14 14"
2241 + width="14"
2242 + height="14"
2243 + class=""
2244 + fill="none" xmlns="http://www.w3.org/2000/svg">
2245 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2246 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2247 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2248 + </span></li>
2249 + </ul>
2250 + </div>
2251 + <div class="quanto-blog-content">
2252 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/la-foire-du-camionneur/">La foire du camionneur</a></p>
2253 + <p class="h6 repertoire-preview-date" style="font-weight: 400; padding-top: 5px;">Du 4 au 7 septembre 2026 </p>
2254 + </div>
2255 +</div>
2256 + </div>
2257 + <div class="col-6 col-lg-3">
2258 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="297">
2259 + <div class="quanto-blog-thumb">
2260 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/festivites-champetres/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/IMG-9033.JPG?width=500');"></a>
2261 + <ul class="list-tags">
2262 + <li><span>Amos-Harricana <svg
2263 + data-icon="MapPinSimpleLine"
2264 + viewBox="0 0 14 14"
2265 + width="14"
2266 + height="14"
2267 + class=""
2268 + fill="none" xmlns="http://www.w3.org/2000/svg">
2269 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2270 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2271 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2272 + </span></li>
2273 + </ul>
2274 + </div>
2275 + <div class="quanto-blog-content">
2276 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/festivites-champetres/">Festivités Champêtres</a></p>
2277 + <p class="h6 repertoire-preview-date" style="font-weight: 400; padding-top: 5px;">12 septembre 2026</p>
2278 + </div>
2279 +</div>
2280 + </div>
2281 + <div class="col-6 col-lg-3">
2282 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="16646">
2283 + <div class="quanto-blog-thumb">
2284 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/festival-de-contes-et-legendes-en-abitibi-temiscamingue-2/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/Trilogie.JPG?width=500');"></a>
2285 + <ul class="list-tags">
2286 + <li><span>Vallée-de-l'Or <svg
2287 + data-icon="MapPinSimpleLine"
2288 + viewBox="0 0 14 14"
2289 + width="14"
2290 + height="14"
2291 + class=""
2292 + fill="none" xmlns="http://www.w3.org/2000/svg">
2293 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2294 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2295 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2296 + </span></li>
2297 + </ul>
2298 + </div>
2299 + <div class="quanto-blog-content">
2300 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/festival-de-contes-et-legendes-en-abitibi-temiscamingue-2/">Festival de contes et légendes en Abitibi-Témiscamingue</a></p>
2301 + <p class="h6 repertoire-preview-date" style="font-weight: 400; padding-top: 5px;">Du 9 au 18 octobre 2026 </p>
2302 + </div>
2303 +</div>
2304 + </div>
2305 + <div class="col-6 col-lg-3">
2306 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="275">
2307 + <div class="quanto-blog-thumb">
2308 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/festival-du-cinema-international-en-abitibi-temiscamingue/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/FCIAT-7-Bloc-4-Theatre-du-Cuivre-0211-HDR.jpg?width=500');"></a>
2309 + <ul class="list-tags">
2310 + <li><span>Rouyn-Noranda <svg
2311 + data-icon="MapPinSimpleLine"
2312 + viewBox="0 0 14 14"
2313 + width="14"
2314 + height="14"
2315 + class=""
2316 + fill="none" xmlns="http://www.w3.org/2000/svg">
2317 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2318 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2319 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2320 + </span></li>
2321 + </ul>
2322 + </div>
2323 + <div class="quanto-blog-content">
2324 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/festival-du-cinema-international-en-abitibi-temiscamingue/">Festival du cinéma international en Abitibi-Témiscamingue</a></p>
2325 + <p class="h6 repertoire-preview-date" style="font-weight: 400; padding-top: 5px;">Du 31 octobre au 5 novembre 2026 </p>
2326 + </div>
2327 +</div>
2328 + </div>
2329 + <div class="col-6 col-lg-3">
2330 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="281">
2331 + <div class="quanto-blog-thumb">
2332 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/festival-de-musique-trad-val-dor/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/audree-giroux-festival-trad-val-dor-J2-10.jpg?width=500');"></a>
2333 + <ul class="list-tags">
2334 + <li><span>Vallée-de-l'Or <svg
2335 + data-icon="MapPinSimpleLine"
2336 + viewBox="0 0 14 14"
2337 + width="14"
2338 + height="14"
2339 + class=""
2340 + fill="none" xmlns="http://www.w3.org/2000/svg">
2341 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2342 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2343 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2344 + </span></li>
2345 + </ul>
2346 + </div>
2347 + <div class="quanto-blog-content">
2348 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/festival-de-musique-trad-val-dor/">Festival de musique TRAD Val d&#8217;Or</a></p>
2349 + <p class="h6 repertoire-preview-date" style="font-weight: 400; padding-top: 5px;">Du 4 au 8 novembre 2026 </p>
2350 + </div>
2351 +</div>
2352 + </div>
2353 + <div class="col-6 col-lg-3">
2354 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="16624">
2355 + <div class="quanto-blog-thumb">
2356 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/boomtown-theatre-deambulatoire/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/Boomtown-2021.jpg?width=500');"></a>
2357 + <ul class="list-tags">
2358 + <li><span>Rouyn-Noranda <svg
2359 + data-icon="MapPinSimpleLine"
2360 + viewBox="0 0 14 14"
2361 + width="14"
2362 + height="14"
2363 + class=""
2364 + fill="none" xmlns="http://www.w3.org/2000/svg">
2365 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2366 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2367 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2368 + </span></li>
2369 + </ul>
2370 + </div>
2371 + <div class="quanto-blog-content">
2372 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/boomtown-theatre-deambulatoire/">Boomtown &#8211; Théatre déambulatoire</a></p>
2373 + </div>
2374 +</div>
2375 + </div>
2376 + <div class="col-6 col-lg-3">
2377 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="291">
2378 + <div class="quanto-blog-thumb">
2379 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/carnaval-de-lorrainville/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/Carnval-de-lorrainville.jpg?width=500');"></a>
2380 + <ul class="list-tags">
2381 + <li><span>Témiscamingue <svg
2382 + data-icon="MapPinSimpleLine"
2383 + viewBox="0 0 14 14"
2384 + width="14"
2385 + height="14"
2386 + class=""
2387 + fill="none" xmlns="http://www.w3.org/2000/svg">
2388 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2389 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2390 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2391 + </span></li>
2392 + </ul>
2393 + </div>
2394 + <div class="quanto-blog-content">
2395 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/carnaval-de-lorrainville/">Carnaval de Lorrainville</a></p>
2396 + </div>
2397 +</div>
2398 + </div>
2399 + <div class="col-6 col-lg-3">
2400 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="300">
2401 + <div class="quanto-blog-thumb">
2402 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/danse-dla-quenouille/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/Affiche-Danse-de-la-Quenouille--2-.png?width=500');"></a>
2403 + <ul class="list-tags">
2404 + <li><span>Témiscamingue <svg
2405 + data-icon="MapPinSimpleLine"
2406 + viewBox="0 0 14 14"
2407 + width="14"
2408 + height="14"
2409 + class=""
2410 + fill="none" xmlns="http://www.w3.org/2000/svg">
2411 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2412 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2413 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2414 + </span></li>
2415 + </ul>
2416 + </div>
2417 + <div class="quanto-blog-content">
2418 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/danse-dla-quenouille/">Danse d&#8217;la quenouille</a></p>
2419 + </div>
2420 +</div>
2421 + </div>
2422 + <div class="col-6 col-lg-3">
2423 + <div class="quanto-blog-box repertoire-preview-trigger fade-anim" data-direction="right" data-post-id="279">
2424 + <div class="quanto-blog-thumb">
2425 + <a class="aspect-ratio" href="https://abitibi-temiscamingue.org/evenements/festival-dhumour-de-labitibi-temiscamingue/" style="background-image: url('https://mto.media.tourinsoft.eu/upload/FHAT-250704-481.jpg?width=500');"></a>
2426 + <ul class="list-tags">
2427 + <li><span>Vallée-de-l'Or <svg
2428 + data-icon="MapPinSimpleLine"
2429 + viewBox="0 0 14 14"
2430 + width="14"
2431 + height="14"
2432 + class=""
2433 + fill="none" xmlns="http://www.w3.org/2000/svg">
2434 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2435 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2436 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2437 + </span></li>
2438 + </ul>
2439 + </div>
2440 + <div class="quanto-blog-content">
2441 + <p class="h6"><a href="https://abitibi-temiscamingue.org/evenements/festival-dhumour-de-labitibi-temiscamingue/">Festival d&#8217;Humour de l&#8217;Abitibi-Témiscamingue</a></p>
2442 + </div>
2443 +</div>
2444 + </div>
2445 + </div>
2446 + <div class="row row-padding-top">
2447 + <div class="col-12">
2448 + <div class="blog-pagination">
2449 + <nav class="pagination">
2450 + <span aria-current="page" class="page-numbers current">1</span> <a class="page-numbers" href="https://abitibi-temiscamingue.org/festivals-et-evenements/page/2/">2</a> <a class="page-numbers" href="https://abitibi-temiscamingue.org/festivals-et-evenements/page/3/">3</a> <a class="next page-numbers" href="https://abitibi-temiscamingue.org/festivals-et-evenements/page/2/"> <svg
2451 + data-icon="CaretRight"
2452 + viewBox="0 0 32 32"
2453 + width="32"
2454 + height="32"
2455 + class=""
2456 + fill="none" xmlns="http://www.w3.org/2000/svg">
2457 + <path d="M23.0613 17.0612L13.0612 27.0612C12.7795 27.343 12.3973 27.5013 11.9987 27.5013C11.6002 27.5013 11.218 27.343 10.9362 27.0612C10.6545 26.7794 10.4961 26.3972 10.4961 25.9987C10.4961 25.6002 10.6545 25.218 10.9362 24.9362L19.875 15.9999L10.9387 7.0612C10.7992 6.92167 10.6885 6.75602 10.613 6.57372C10.5375 6.39141 10.4986 6.19602 10.4986 5.9987C10.4986 5.80137 10.5375 5.60598 10.613 5.42367C10.6885 5.24137 10.7992 5.07573 10.9387 4.9362C11.0783 4.79667 11.2439 4.68599 11.4262 4.61047C11.6085 4.53496 11.8039 4.49609 12.0012 4.49609C12.1986 4.49609 12.394 4.53496 12.5763 4.61047C12.7586 4.68599 12.9242 4.79667 13.0637 4.9362L23.0638 14.9362C23.2034 15.0757 23.3142 15.2414 23.3897 15.4239C23.4652 15.6063 23.5039 15.8018 23.5037 15.9992C23.5035 16.1966 23.4642 16.3921 23.3883 16.5743C23.3124 16.7565 23.2013 16.922 23.0613 17.0612Z" fill="currentColor" /> </svg>
2458 + </a> </nav>
2459 + </div>
2460 + </div>
2461 + </div>
2462 + <div id="map-info-liste-6a8e06e5468ca" class="map-info-liste">Google maps</div>
2463 + <script type="application/json" class="repertoire-map-data">{"addresses":[{"lat":48.2952921,"lng":-79.1984604,"postId":16522,"title":"Beltane &#8211; La Feste Pa\u00efenne par le Cirque Collini","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/beltane-la-feste-paienne-par-le-cirque-collini-2\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Beltane-La-Feste-Paienne.png?width=500","categories":["Concert, spectacle","Festivals et \u00e9v\u00e9nements"]},{"lat":48.23955,"lng":-79.015752,"postId":16624,"title":"Boomtown &#8211; Th\u00e9atre d\u00e9ambulatoire","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/boomtown-theatre-deambulatoire\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Boomtown-2021.jpg?width=500","categories":["Concert, spectacle","Festivals et \u00e9v\u00e9nements"]},{"lat":47.3548138,"lng":-79.349223,"postId":291,"title":"Carnaval de Lorrainville","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/carnaval-de-lorrainville\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Carnval-de-lorrainville.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements","Motoneige","Motoneige","Motoneige","Quoi faire","Sports d'hiver","Sports et plein air"]},{"lat":48.5607184,"lng":-78.106956,"postId":290,"title":"Circuit \u00e0 la d\u00e9couverte de l&#8217;eau ANISIPI","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/circuit-a-la-decouverte-de-leau-anisipi\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Anisipi-Moment-Factory-le-tipi-3.jpg?width=500","categories":["Exp\u00e9rience multim\u00e9dia \/ immersive","Festivals et \u00e9v\u00e9nements","Interpr\u00e9tation \/ observation de la nature","Quoi faire","Randonn\u00e9e","Sports et plein air","Tourisme autochtone"]},{"lat":47.4,"lng":-79,"postId":300,"title":"Danse d&#8217;la quenouille","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/danse-dla-quenouille\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Affiche-Danse-de-la-Quenouille--2-.png?width=500","categories":["Activit\u00e9s nautiques","Baignade","Festival","Festivals et \u00e9v\u00e9nements","Quoi faire","Sports et plein air"]},{"lat":48.094534,"lng":-77.76518,"postId":279,"title":"Festival d&#8217;Humour de l&#8217;Abitibi-T\u00e9miscamingue","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-dhumour-de-labitibi-temiscamingue\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/FHAT-250704-481.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.2394091,"lng":-79.020193,"postId":292,"title":"Festival d&#8217;humour \u00e9mergent en Abitibi-T\u00e9miscamingue","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-dhumour-emergent-en-abitibi-temiscamingue\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/audree-giroux-fhe-rouyn-noranda-19--3-.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.0948501,"lng":-77.765044,"postId":16646,"title":"Festival de contes et l\u00e9gendes en Abitibi-T\u00e9miscamingue","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-de-contes-et-legendes-en-abitibi-temiscamingue-2\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Trilogie.JPG?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements","Quoi faire","Randonn\u00e9e","Randonn\u00e9e p\u00e9destre","Sports et plein air"]},{"lat":48.094858,"lng":-77.765028,"postId":288,"title":"Festival de la rel\u00e8ve ind\u00e9pendante musicale en Abitibi-T\u00e9miscamingue (Frimat)","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-de-la-releve-independante-musicale-en-abitibi-temiscamingue-frimat\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/photo1-4.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.2342362,"lng":-79.01878359999,"postId":286,"title":"Festival de musique \u00e9mergente en Abitibi-T\u00e9miscamingue (FME)","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-de-musique-emergente-en-abitibi-temiscamingue-fme\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/A.FME-2024-Erika-Angell--Louis-Jalbert-2024-56.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.0940379,"lng":-77.78740739999,"postId":281,"title":"Festival de musique TRAD Val d&#8217;Or","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-de-musique-trad-val-dor\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/audree-giroux-festival-trad-val-dor-J2-10.jpg?width=500","categories":["Borne de recharge \u00e9lectrique","Festival","Festivals et \u00e9v\u00e9nements","Services","Voiture \u00e9lectrique"]},{"lat":48.246349,"lng":-79.016896,"postId":273,"title":"Festival des Guitares du Monde en Abitibi-T\u00e9miscamingue","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-des-guitares-du-monde-en-abitibi-temiscamingue\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/72dpi-Leduc-9110.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.237091,"lng":-79.024685,"postId":275,"title":"Festival du cin\u00e9ma international en Abitibi-T\u00e9miscamingue","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-du-cinema-international-en-abitibi-temiscamingue\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/FCIAT-7-Bloc-4-Theatre-du-Cuivre-0211-HDR.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.2370933,"lng":-79.0247682,"postId":275,"title":"Festival du cin\u00e9ma international en Abitibi-T\u00e9miscamingue","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-du-cinema-international-en-abitibi-temiscamingue\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/FCIAT-7-Bloc-4-Theatre-du-Cuivre-0211-HDR.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.810225,"lng":-79.202365,"postId":284,"title":"Festival \u00e9questre et Rod\u00e9o professionnel de La Sarre","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-equestre-et-rodeo-professionnel-de-la-sarre\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Festival-Equestre-Rodeo-LaSarre-MelindaTheberge-TAT--3-.JPEG?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.5969011,"lng":-78.1148898,"postId":301,"title":"Festival MAMA8I NIKAMO","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-mama8i-nikamo\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/GetAttachmentThumbnail.png?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.5971959,"lng":-78.12074609999,"postId":301,"title":"Festival MAMA8I NIKAMO","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-mama8i-nikamo\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/GetAttachmentThumbnail.png?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":47.4620837,"lng":-79.4354041,"postId":289,"title":"Festival western de Guigues","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-western-de-guigues\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/accueil-FWG-en-bois-wow.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.135947,"lng":-78.129408,"postId":285,"title":"Festival western de Malartic","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festival-western-de-malartic\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Course-baril.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.474178,"lng":-78.047432,"postId":297,"title":"Festivit\u00e9s Champ\u00eatres","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/festivites-champetres\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/IMG-9033.JPG?width=500","categories":["Borne de recharge \u00e9lectrique","Festival","Festivals et \u00e9v\u00e9nements","Services","Voiture \u00e9lectrique"]},{"lat":48.245935940746,"lng":-79.03417046547,"postId":277,"title":"F\u00eate d&#8217;hiver de Rouyn-Noranda","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/fete-dhiver-de-rouyn-noranda\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Audree.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements","Glissade sur neige","Luge d\u2019hiver","Patinage sur glace","Quoi faire","Sports d'hiver","Sports et plein air"]},{"lat":47.3320103,"lng":-79.4443583,"postId":278,"title":"Foire gourmande de l&#8217;Abitibi-T\u00e9miscamingue et du Nord-Est ontarien","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/foire-gourmande-de-labitibi-temiscamingue-et-du-nord-est-ontarien\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/CARO88-546-2.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.576466,"lng":-78.118663,"postId":287,"title":"H2O le festival","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/h2o-le-festival\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Mung--3579-reduite.jpg?width=500","categories":["Activit\u00e9s nautiques","Canot et kayak","Festival","Festivals et \u00e9v\u00e9nements","P\u00e9dalo","Planche \u00e0 pagaie (SUP)","Quoi faire","Sports et plein air"]},{"lat":46.7855296,"lng":-78.985067,"postId":293,"title":"Kipawa Countryfest","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/kipawa-countryfest\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/9e895e9c-d23a-42ac-8468-7668f6a53b47.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.44207,"lng":-77.636132,"postId":274,"title":"La foire du camionneur","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/la-foire-du-camionneur\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Foire-0617-September-03--2023.jpg?width=500","categories":["Borne de recharge \u00e9lectrique","Festival","Festivals et \u00e9v\u00e9nements","Services","Voiture \u00e9lectrique"]},{"lat":48.327807,"lng":-78.110275,"postId":296,"title":"La Route du terroir","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/la-route-du-terroir\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/417b3b428fc994d9ba3d1cddf84110d7.jpg?width=500","categories":["Festivals et \u00e9v\u00e9nements","Salon \/ foire"]},{"lat":48.793307,"lng":-79.2082535,"postId":7305,"title":"La Sarre en F\u00eate","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/la-sarre-en-fete\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/2I0A3064.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":47.390207,"lng":-78.698962,"postId":282,"title":"Music Fest de Belleterre","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/music-fest-de-belleterre\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/Music-Fest.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.245923,"lng":-79.008498,"postId":280,"title":"Osisko en lumi\u00e8re Westwood","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/osisko-en-lumiere-westwood\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/audree-giroux-167.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements"]},{"lat":48.597439581776,"lng":-78.11892690474,"postId":295,"title":"Pow-Wow de Pikogan","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/pow-wow-de-pikogan\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/448318879-122104906208354576-9035736681891654099-n.jpg?width=500","categories":["Festival","Festivals et \u00e9v\u00e9nements","Tourisme autochtone"]},{"lat":47.33272,"lng":-79.43724,"postId":298,"title":"Raid T\u00e9miscamingue","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/raid-temiscamingue\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/image23.jpg?width=500","categories":["Activit\u00e9s nautiques","Borne de recharge \u00e9lectrique","Canot et kayak","Festivals et \u00e9v\u00e9nements","Manifestation sportive","Natation","Quoi faire","Randonn\u00e9e","Randonn\u00e9e p\u00e9destre","Services","Sports et plein air","Tourisme autochtone","V\u00e9lo","V\u00e9lo","V\u00e9lo de montagne","Voiture \u00e9lectrique"]},{"lat":48.0958272,"lng":-77.78751319999,"postId":283,"title":"Salon du livre de l&#8217;Abitibi-T\u00e9miscamingue","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/salon-du-livre-de-labitibi-temiscamingue\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/SDL-2024--3-.JPG?width=500","categories":["Festivals et \u00e9v\u00e9nements","Salon \/ foire"]},{"lat":48.2369756,"lng":-79.0237681,"postId":276,"title":"Tour de l&#8217;Abitibi Glencore","permalink":"https:\/\/abitibi-temiscamingue.org\/evenements\/tour-de-labitibi-glencore\/","thumbnail":"https:\/\/mto.media.tourinsoft.eu\/upload\/vendredi-11-juillet-batch-1-3806.jpg?width=500","categories":["Festivals et \u00e9v\u00e9nements","Manifestation sportive","Quoi faire","Sports et plein air","V\u00e9lo","V\u00e9lo"]}],"mapStyles":[],"mapPin":"https:\/\/abitibi-temiscamingue.org\/wp-content\/themes\/lebleu\/images\/Map-pin.png"}</script>
2464 + </div>
2465 + </div>
2466 + <div class="offcanvas offcanvas-end desktop-sidemenu filter-repertoire" tabindex="-1" id="offcanvasrepertoire6a8e06e5468ca" aria-labelledby="offcanvasrepertoire6a8e06e5468caLabel">
2467 + <div class="offcanvas-header sidemenu-header">
2468 + <div class="filter-drawer-title h3">
2469 + <span class="filter-drawer-title__icon">
2470 + <svg
2471 + data-icon="Funnel"
2472 + viewBox="0 0 256 256"
2473 + width="256"
2474 + height="256"
2475 + class=""
2476 + fill="none" xmlns="http://www.w3.org/2000/svg">
2477 + <path d="M230.6,49.53A15.81,15.81,0,0,0,216,40H40A16,16,0,0,0,28.19,66.76l.08.09L96,139.17V216a16,16,0,0,0,24.87,13.32l32-21.34A16,16,0,0,0,160,194.66V139.17l67.74-72.32.08-.09A15.8,15.8,0,0,0,230.6,49.53ZM40,56h0Zm106.18,74.58A8,8,0,0,0,144,136v58.66L112,216V136a8,8,0,0,0-2.16-5.47L40,56H216Z" fill="currentColor"/> </svg>
2478 + </span>
2479 + <span>Filtrer</span>
2480 + </div>
2481 + <button type="button" class="btn-close btn-round-outline drawer-close" data-bs-dismiss="offcanvas" aria-label="Fermer">
2482 + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
2483 + <path d="M8.58981 10.0001L0.796875 2.20718L2.21109 0.792969L10.004 8.58582L17.7969 0.792969L19.2111 2.20718L11.4182 10.0001L19.2111 17.7929L17.7969 19.2072L10.004 11.4143L2.21109 19.2072L0.796875 17.7929L8.58981 10.0001Z" fill="currentColor"/>
2484 + </svg>
2485 + </button>
2486 + </div>
2487 + <div class="offcanvas-body sidemenu-body">
2488 + <div class="short-info">
2489 + <div class="filter-section js-repertoire-active-section" hidden>
2490 + <p class="h6">Mes filtres actifs</p>
2491 + <ul class="list-tags js-repertoire-active-filters"></ul>
2492 + </div>
2493 + <div class="filter-section">
2494 + <p class="h6">Saisons</p>
2495 + <div class="filter-pills" data-filter-group="saisons">
2496 + <input class="filter-pill__input" type="checkbox" id="saisons-tout-6a8e06e5468ca" checked>
2497 + <label class="filter-pill is-reset" for="saisons-tout-6a8e06e5468ca" data-reset="true">Tout</label>
2498 + <input class="filter-pill__input" type="checkbox" id="saisons-automne-6a8e06e5468ca" value="356" >
2499 + <label class="filter-pill" for="saisons-automne-6a8e06e5468ca">Automne</label>
2500 + <input class="filter-pill__input" type="checkbox" id="saisons-ete-6a8e06e5468ca" value="378" >
2501 + <label class="filter-pill" for="saisons-ete-6a8e06e5468ca">Été</label>
2502 + <input class="filter-pill__input" type="checkbox" id="saisons-hiver-6a8e06e5468ca" value="400" >
2503 + <label class="filter-pill" for="saisons-hiver-6a8e06e5468ca">Hiver</label>
2504 + <input class="filter-pill__input" type="checkbox" id="saisons-printemps-6a8e06e5468ca" value="432" >
2505 + <label class="filter-pill" for="saisons-printemps-6a8e06e5468ca">Printemps</label>
2506 + </div>
2507 + </div>
2508 + <div class="filter-section">
2509 + <p class="h6">Territoires</p>
2510 + <div class="filter-pills" data-filter-group="regions">
2511 + <input class="filter-pill__input" type="checkbox" id="regions-tout-6a8e06e5468ca" checked>
2512 + <label class="filter-pill is-reset" for="regions-tout-6a8e06e5468ca" data-reset="true">Tout</label>
2513 + <input class="filter-pill__input" type="checkbox" id="regions-abitibi-ouest-6a8e06e5468ca" value="257" >
2514 + <label class="filter-pill" for="regions-abitibi-ouest-6a8e06e5468ca">Abitibi-Ouest <svg
2515 + data-icon="MapPinSimpleLine"
2516 + viewBox="0 0 14 14"
2517 + width="14"
2518 + height="14"
2519 + class=""
2520 + fill="none" xmlns="http://www.w3.org/2000/svg">
2521 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2522 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2523 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2524 + </label>
2525 + <input class="filter-pill__input" type="checkbox" id="regions-amos-harricana-6a8e06e5468ca" value="259" >
2526 + <label class="filter-pill" for="regions-amos-harricana-6a8e06e5468ca">Amos-Harricana <svg
2527 + data-icon="MapPinSimpleLine"
2528 + viewBox="0 0 14 14"
2529 + width="14"
2530 + height="14"
2531 + class=""
2532 + fill="none" xmlns="http://www.w3.org/2000/svg">
2533 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2534 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2535 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2536 + </label>
2537 + <input class="filter-pill__input" type="checkbox" id="regions-rouyn-noranda-6a8e06e5468ca" value="258" >
2538 + <label class="filter-pill" for="regions-rouyn-noranda-6a8e06e5468ca">Rouyn-Noranda <svg
2539 + data-icon="MapPinSimpleLine"
2540 + viewBox="0 0 14 14"
2541 + width="14"
2542 + height="14"
2543 + class=""
2544 + fill="none" xmlns="http://www.w3.org/2000/svg">
2545 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2546 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2547 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2548 + </label>
2549 + <input class="filter-pill__input" type="checkbox" id="regions-temiscamingue-6a8e06e5468ca" value="261" >
2550 + <label class="filter-pill" for="regions-temiscamingue-6a8e06e5468ca">Témiscamingue <svg
2551 + data-icon="MapPinSimpleLine"
2552 + viewBox="0 0 14 14"
2553 + width="14"
2554 + height="14"
2555 + class=""
2556 + fill="none" xmlns="http://www.w3.org/2000/svg">
2557 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2558 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2559 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2560 + </label>
2561 + <input class="filter-pill__input" type="checkbox" id="regions-vallee-de-lor-6a8e06e5468ca" value="260" >
2562 + <label class="filter-pill" for="regions-vallee-de-lor-6a8e06e5468ca">Vallée-de-l&#039;Or <svg
2563 + data-icon="MapPinSimpleLine"
2564 + viewBox="0 0 14 14"
2565 + width="14"
2566 + height="14"
2567 + class=""
2568 + fill="none" xmlns="http://www.w3.org/2000/svg">
2569 + <path d="M7 7C8.44975 7 9.625 5.82475 9.625 4.375C9.625 2.92525 8.44975 1.75 7 1.75C5.55025 1.75 4.375 2.92525 4.375 4.375C4.375 5.82475 5.55025 7 7 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2570 + <path d="M7 11.8125V7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
2571 + <path d="M2.1875 11.8125H11.8125" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg>
2572 + </label>
2573 + </div>
2574 + </div>
2575 + <div class="filter-section">
2576 + <p class="h6">Thématiques</p>
2577 + <div class="js-repertoire-category-filter" data-filter-group="interets" data-locked-path="[384]">
2578 + <div class="js-repertoire-category-level mb-2 mt-2" data-category-level="1">
2579 + <select class="form-select js-repertoire-category-select" data-level="1" id="interets-level-1-6a8e06e5468ca">
2580 + <option value="">Tout</option>
2581 + <option value="384">Festivals et événements</option>
2582 + <option value="420">Où dormir</option>
2583 + <option value="418">Où manger</option>
2584 + <option value="434">Quoi faire</option>
2585 + <option value="448">Services</option>
2586 + </select>
2587 + </div>
2588 + <div class="js-repertoire-category-children"></div>
2589 + <script type="application/json" class="js-repertoire-category-tree">{"nodes":{"769":{"id":769,"name":"Activit\u00e9s et divertissement","parent":434},"763":{"id":763,"name":"Activit\u00e9s nautiques","parent":454},"759":{"id":759,"name":"Agrotourisme et terroir","parent":434},"813":{"id":813,"name":"Arboriculture \/ horticulture","parent":759},"352":{"id":352,"name":"Art, culture et patrimoine","parent":434},"923":{"id":923,"name":"Artisanat \/ m\u00e9tiers d'art","parent":352},"982":{"id":982,"name":"Baignade","parent":763},"943":{"id":943,"name":"Bar, bo\u00eete de nuit","parent":358},"945":{"id":945,"name":"Bar, bo\u00eete de nuit","parent":769},"951":{"id":951,"name":"Belv\u00e9d\u00e8re, tour d'observation","parent":769},"925":{"id":925,"name":"Biblioth\u00e8que \/ centre documentaire","parent":352},"452":{"id":452,"name":"Bien-\u00eatre et d\u00e9tente","parent":434},"1010":{"id":1010,"name":"BMX","parent":468},"1164":{"id":1164,"name":"Borne de recharge \u00e9lectrique","parent":787},"815":{"id":815,"name":"Boutique de produits du terroir","parent":759},"1128":{"id":1128,"name":"Bureau d'information touristique","parent":779},"362":{"id":362,"name":"Cabane \u00e0 sucre","parent":434},"1080":{"id":1080,"name":"Caf\u00e9 \/ salon de th\u00e9","parent":360},"360":{"id":360,"name":"Caf\u00e9s","parent":418},"1072":{"id":1072,"name":"Camion de cuisine de rue","parent":440},"1088":{"id":1088,"name":"Camping","parent":364},"364":{"id":364,"name":"Campings et pr\u00eats-\u00e0-camper","parent":420},"366":{"id":366,"name":"Canot et kayak","parent":763},"927":{"id":927,"name":"Centre \/ galerie d'art","parent":352},"867":{"id":867,"name":"Centre de ski alpin \/ planche \u00e0 neige","parent":344},"869":{"id":869,"name":"Centre de ski de fond \/ raquette","parent":344},"955":{"id":955,"name":"Centre r\u00e9cr\u00e9atif \/ sportif","parent":769},"1070":{"id":1070,"name":"Centre sant\u00e9, spa","parent":452},"1092":{"id":1092,"name":"Chalet, appartement, r\u00e9sidence de tourisme","parent":370},"370":{"id":370,"name":"Chalets, appartements et r\u00e9sidences de tourisme","parent":420},"372":{"id":372,"name":"Chasse","parent":765},"765":{"id":765,"name":"Chasse et p\u00eache","parent":434},"821":{"id":821,"name":"Chocolatier \/ confiserie","parent":759},"897":{"id":897,"name":"Concert, spectacle","parent":384},"823":{"id":823,"name":"Culture de petits fruits","parent":759},"825":{"id":825,"name":"Culture mara\u00eech\u00e8re","parent":759},"775":{"id":775,"name":"D\u00e9jeuner","parent":418},"1086":{"id":1086,"name":"D\u00e9jeuner","parent":775},"845":{"id":845,"name":"Distillerie","parent":358},"847":{"id":847,"name":"Distillerie","parent":759},"1194":{"id":1194,"name":"\u00c9cole","parent":799},"929":{"id":929,"name":"\u00c9difice \/ site religieux","parent":352},"799":{"id":799,"name":"\u00c9ducation","parent":448},"957":{"id":957,"name":"Entreprise industrielle","parent":769},"376":{"id":376,"name":"Escalade de glace","parent":344},"905":{"id":905,"name":"Exp\u00e9rience multim\u00e9dia \/ immersive","parent":384},"1016":{"id":1016,"name":"Fatbike","parent":468},"827":{"id":827,"name":"Ferme d'\u00e9levage animal","parent":759},"907":{"id":907,"name":"Festival","parent":384},"384":{"id":384,"name":"Festivals et \u00e9v\u00e9nements","parent":0},"829":{"id":829,"name":"Fromagerie (fabricant) \/ laiterie","parent":759},"1102":{"id":1102,"name":"G\u00eete touristique","parent":392},"392":{"id":392,"name":"G\u00eetes touristiques","parent":420},"1018":{"id":1018,"name":"Glissade sur neige","parent":344},"394":{"id":394,"name":"Glissoire d\u2019hiver","parent":344},"396":{"id":396,"name":"Golf","parent":454},"1042":{"id":1042,"name":"Golf","parent":396},"1040":{"id":1040,"name":"Golf miniature","parent":396},"1106":{"id":1106,"name":"H\u00f4tel","parent":402},"402":{"id":402,"name":"H\u00f4tels et motels","parent":420},"779":{"id":779,"name":"Information touristique","parent":448},"1036":{"id":1036,"name":"Interpr\u00e9tation \/ observation de la nature","parent":436},"1211":{"id":1211,"name":"Itin\u00e9raire touristique r\u00e9gional","parent":769},"959":{"id":959,"name":"Jardin botanique \/ horticole","parent":769},"961":{"id":961,"name":"Jardin zoologique","parent":769},"789":{"id":789,"name":"Location d'\u00e9quipement","parent":448},"1166":{"id":1166,"name":"Loueur d'\u00e9quipement de sport \/ plein air","parent":789},"1020":{"id":1020,"name":"Luge d\u2019hiver","parent":344},"971":{"id":971,"name":"Magasin, boutique","parent":769},"1068":{"id":1068,"name":"Magasin, boutique","parent":783},"783":{"id":783,"name":"Magasinage","parent":448},"913":{"id":913,"name":"Manifestation sportive","parent":384},"833":{"id":833,"name":"March\u00e9 public \/ fermier","parent":759},"974":{"id":974,"name":"Marina","parent":454},"976":{"id":976,"name":"Marina","parent":765},"978":{"id":978,"name":"Marina","parent":372},"980":{"id":980,"name":"Marina","parent":428},"795":{"id":795,"name":"M\u00e9dia d'information","parent":448},"1190":{"id":1190,"name":"M\u00e9dia d\u2019information","parent":795},"358":{"id":358,"name":"Microbrasseries, bars et distilleries","parent":418},"412":{"id":412,"name":"Miellerie \/ apiculture","parent":759},"414":{"id":414,"name":"Motoneige","parent":344},"1044":{"id":1044,"name":"Motoneige","parent":434},"1046":{"id":1046,"name":"Motoneige","parent":1044},"933":{"id":933,"name":"Mus\u00e9e, site historique \/ arch\u00e9ologique","parent":352},"986":{"id":986,"name":"Natation","parent":763},"793":{"id":793,"name":"Organisme","parent":448},"1176":{"id":1176,"name":"Organisme administratif","parent":793},"1182":{"id":1182,"name":"Organisme de d\u00e9veloppement \u00e9conomique","parent":793},"1188":{"id":1188,"name":"Organisme touristique","parent":793},"420":{"id":420,"name":"O\u00f9 dormir","parent":0},"418":{"id":418,"name":"O\u00f9 manger","parent":0},"965":{"id":965,"name":"Parc d'attractions","parent":769},"883":{"id":883,"name":"Parc municipal","parent":422},"885":{"id":885,"name":"Parc national du Qu\u00e9bec","parent":422},"422":{"id":422,"name":"Parcs et r\u00e9serves fauniques","parent":454},"1032":{"id":1032,"name":"Patinage sur glace","parent":344},"424":{"id":424,"name":"Patinoire","parent":344},"426":{"id":426,"name":"P\u00e2tisseries et boulangeries","parent":418},"947":{"id":947,"name":"P\u00e2tissier \/ boulanger","parent":759},"1082":{"id":1082,"name":"P\u00e2tissier \/ boulanger","parent":426},"937":{"id":937,"name":"Patrimoine architectural","parent":352},"428":{"id":428,"name":"P\u00eache","parent":765},"1026":{"id":1026,"name":"P\u00eache sur la glace","parent":344},"998":{"id":998,"name":"P\u00e9dalo","parent":763},"855":{"id":855,"name":"Piste \/ sentier","parent":454},"857":{"id":857,"name":"Plage","parent":763},"1008":{"id":1008,"name":"Planche \u00e0 pagaie (SUP)","parent":763},"996":{"id":996,"name":"Planche \u00e0 voile","parent":763},"1050":{"id":1050,"name":"Pourvoirie","parent":765},"1054":{"id":1054,"name":"Pourvoirie","parent":372},"1058":{"id":1058,"name":"Pourvoirie","parent":428},"1062":{"id":1062,"name":"Pourvoirie","parent":767},"1108":{"id":1108,"name":"Pourvoirie","parent":430},"430":{"id":430,"name":"Pourvoiries","parent":420},"434":{"id":434,"name":"Quoi faire","parent":0},"1004":{"id":1004,"name":"Rabaska","parent":763},"436":{"id":436,"name":"Randonn\u00e9e","parent":454},"1034":{"id":1034,"name":"Randonn\u00e9e p\u00e9destre","parent":436},"438":{"id":438,"name":"Raquette et ski","parent":344},"875":{"id":875,"name":"R\u00e9serve faunique","parent":422},"440":{"id":440,"name":"Restaurants","parent":418},"1076":{"id":1076,"name":"Restaurants","parent":440},"1174":{"id":1174,"name":"Salle de r\u00e9union \/ r\u00e9ception","parent":791},"939":{"id":939,"name":"Salle de spectacle \/ th\u00e9\u00e2tre","parent":352},"917":{"id":917,"name":"Salon \/ foire","parent":384},"767":{"id":767,"name":"S\u00e9jour en pourvoirie","parent":765},"448":{"id":448,"name":"Services","parent":0},"941":{"id":941,"name":"Site de reconstitution \/ interpr\u00e9tation historique","parent":352},"859":{"id":859,"name":"Sport d'aventure et de plein air","parent":454},"344":{"id":344,"name":"Sports d'hiver","parent":454},"454":{"id":454,"name":"Sports et plein air","parent":434},"797":{"id":797,"name":"Stationnement","parent":448},"1192":{"id":1192,"name":"Stationnement","parent":797},"460":{"id":460,"name":"Tourisme autochtone","parent":352},"791":{"id":791,"name":"Tourisme d'affaires \/ Salle de r\u00e9union","parent":448},"462":{"id":462,"name":"Traineau \u00e0 chiens","parent":344},"1084":{"id":1084,"name":"Traiteur","parent":773},"1168":{"id":1168,"name":"Traiteur","parent":791},"773":{"id":773,"name":"Traiteurs","parent":418},"785":{"id":785,"name":"Transport","parent":448},"1156":{"id":1156,"name":"Transporteur a\u00e9rien","parent":785},"1160":{"id":1160,"name":"Transporteur urbain \/ interurbain","parent":785},"468":{"id":468,"name":"V\u00e9lo","parent":454},"1012":{"id":1012,"name":"V\u00e9lo","parent":468},"1014":{"id":1014,"name":"V\u00e9lo de montagne","parent":468},"1002":{"id":1002,"name":"Voile","parent":763},"787":{"id":787,"name":"Voiture \u00e9lectrique","parent":448},"891":{"id":891,"name":"Zone d'exploitation contr\u00f4l\u00e9e (ZEC)","parent":422},"1052":{"id":1052,"name":"Zone d'exploitation contr\u00f4l\u00e9e (ZEC)","parent":765},"1056":{"id":1056,"name":"Zone d'exploitation contr\u00f4l\u00e9e (ZEC)","parent":372},"1060":{"id":1060,"name":"Zone d'exploitation contr\u00f4l\u00e9e (ZEC)","parent":428}},"children":{"434":[769,759,352,452,362,765,1044,454],"454":[763,396,974,422,855,436,859,344,468],"759":[813,815,821,823,825,847,827,829,833,412,947],"352":[923,925,927,929,933,937,939,941,460],"763":[982,366,986,998,857,1008,996,1004,1002],"358":[943,845],"769":[945,951,955,957,1211,959,961,971,965],"468":[1010,1016,1012,1014],"787":[1164],"779":[1128],"360":[1080],"418":[360,775,358,426,440,773],"440":[1072,1076],"364":[1088],"420":[364,370,392,402,430],"344":[867,869,376,1018,394,1020,414,1032,424,1026,438,462],"452":[1070],"370":[1092],"765":[372,976,428,1050,767,1052],"384":[897,905,907,913,917],"775":[1086],"799":[1194],"448":[799,779,789,783,795,793,797,791,785,787],"0":[384,420,418,434,448],"392":[1102],"396":[1042,1040],"402":[1106],"436":[1036,1034],"789":[1166],"783":[1068],"372":[978,1054,1056],"428":[980,1058,1060],"795":[1190],"1044":[1046],"793":[1176,1182,1188],"422":[883,885,875,891],"426":[1082],"767":[1062],"430":[1108],"791":[1174,1168],"797":[1192],"773":[1084],"785":[1156,1160]}}</script>
2590 + </div>
2591 + </div>
2592 + </div>
2593 + </div>
2594 + <div class="filter-drawer-footer">
2595 + <button class="quanto-link-btn btn btn-outline-light js-repertoire-reset btn-icon" type="button">
2596 + Réinitialiser <span class="icon">
2597 + <svg
2598 + data-icon="ArrowCounterClockwise"
2599 + viewBox="0 0 256 256"
2600 + width="256"
2601 + height="256"
2602 + class="arry1"
2603 + fill="none" xmlns="http://www.w3.org/2000/svg">
2604 + <path d="M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z" fill="currentColor" /> </svg>
2605 + <svg
2606 + data-icon="ArrowCounterClockwise"
2607 + viewBox="0 0 256 256"
2608 + width="256"
2609 + height="256"
2610 + class="arry2"
2611 + fill="none" xmlns="http://www.w3.org/2000/svg">
2612 + <path d="M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z" fill="currentColor" /> </svg>
2613 + </span>
2614 + </button>
2615 + <button class="quanto-link-btn btn btn-primary js-repertoire-apply btn-icon" type="button">
2616 + Appliquer <span class="icon">
2617 + <svg
2618 + data-icon="Check"
2619 + viewBox="0 0 256 256"
2620 + width="256"
2621 + height="256"
2622 + class="arry1"
2623 + fill="none" xmlns="http://www.w3.org/2000/svg">
2624 + <path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z" fill="currentColor"></path> </svg>
2625 + <svg
2626 + data-icon="Check"
2627 + viewBox="0 0 256 256"
2628 + width="256"
2629 + height="256"
2630 + class="arry2"
2631 + fill="none" xmlns="http://www.w3.org/2000/svg">
2632 + <path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z" fill="currentColor"></path> </svg>
2633 + </span>
2634 + </button>
2635 + </div>
2636 + </div>
2637 + <template class="repertoire-active-filter-template">
2638 + <li>
2639 + <span>
2640 + <a class="repertoire-active-filter-remove" href="javascript:void(0);" aria-label="Retirer">
2641 + <svg
2642 + data-icon="Close"
2643 + viewBox="0 0 20 20"
2644 + width="20"
2645 + height="20"
2646 + class=""
2647 + fill="none" xmlns="http://www.w3.org/2000/svg">
2648 + <path d="M8.58981 10.0001L0.796875 2.20718L2.21109 0.792969L10.004 8.58582L17.7969 0.792969L19.2111 2.20718L11.4182 10.0001L19.2111 17.7929L17.7969 19.2072L10.004 11.4143L2.21109 19.2072L0.796875 17.7929L8.58981 10.0001Z" fill="currentColor"></path> </svg>
2649 + </a>
2650 + <span class="repertoire-active-filter-label"></span>
2651 + </span>
2652 + </li>
2653 + </template>
2654 + </section>
2655 +<section class="espacement bg-color-primary" data-section="espacement"></section> <section class="quanto-project-section bg-color-primary section-padding-bottom" data-section="carousel_raccourcis">
2656 + <div class="bordure-top-shape">
2657 + <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 425 12">
2658 + <path class="st5" d="M-259,0h943v10.9s-147.1-5.4-239-5.4c-92.2,0-140.3,4.7-232.5,5.4C58.8,12.1,35.5,5.1-71.1,8.7c-30.5,1-187.9,2.2-187.9,2.2V0Z" fill="currentColor" />
2659 + <path class="st3" d="M-259,0h943v10.9s-147.1-5.4-239-5.4c-92.2,0-140.3,4.7-232.5,5.4C58.8,12.1,35.5,5.1-71.1,8.7c-30.5,1-187.9,2.2-187.9,2.2V0Z" fill="currentColor" />
2660 + </svg>
2661 +</div> <div class="container custom-container">
2662 + <div class="row gap-4">
2663 + <div class="col-lg-12 col-xxl-11 fade-anim" data-direction="left" >
2664 + <h2 class="h2 title fade-anim" data-direction="left">Besoin d’aide pour <br>faire un choix?</h2>
2665 + </div>
2666 + <div class="quanto_screenfix_right">
2667 + <div class="col-12 pe-0 fade-anim" data-direction="left" data-delay="0.10">
2668 + <div class="slider-raccourcis swiper lebleu-project__slider" data-loop="0" data-slide="auto">
2669 + <div class="slider-navigation d-flex align-items-center gap-3 ">
2670 + <div class="prev-btn">
2671 + <svg
2672 + data-icon="CaretLeft"
2673 + viewBox="0 0 32 32"
2674 + width="32"
2675 + height="32"
2676 + class=""
2677 + fill="none" xmlns="http://www.w3.org/2000/svg">
2678 + <path d="M21.0613 24.9381C21.343 25.2199 21.5014 25.6021 21.5014 26.0006C21.5014 26.3992 21.343 26.7814 21.0613 27.0631C20.7795 27.3449 20.3973 27.5033 19.9988 27.5033C19.6002 27.5033 19.218 27.3449 18.9363 27.0631L8.93625 17.0631C8.79641 16.9238 8.68545 16.7582 8.60975 16.5759C8.53404 16.3935 8.49507 16.1981 8.49507 16.0006C8.49507 15.8032 8.53404 15.6077 8.60975 15.4254C8.68545 15.2431 8.79641 15.0775 8.93625 14.9381L18.9363 4.93815C19.218 4.65636 19.6002 4.49805 19.9988 4.49805C20.3973 4.49805 20.7795 4.65636 21.0613 4.93815C21.343 5.21994 21.5014 5.60213 21.5014 6.00065C21.5014 6.39916 21.343 6.78136 21.0613 7.06315L12.125 15.9994L21.0613 24.9381Z" fill="currentColor" /> </svg>
2679 + </div>
2680 + <div class="next-btn">
2681 + <svg
2682 + data-icon="CaretRight"
2683 + viewBox="0 0 32 32"
2684 + width="32"
2685 + height="32"
2686 + class=""
2687 + fill="none" xmlns="http://www.w3.org/2000/svg">
2688 + <path d="M23.0613 17.0612L13.0612 27.0612C12.7795 27.343 12.3973 27.5013 11.9987 27.5013C11.6002 27.5013 11.218 27.343 10.9362 27.0612C10.6545 26.7794 10.4961 26.3972 10.4961 25.9987C10.4961 25.6002 10.6545 25.218 10.9362 24.9362L19.875 15.9999L10.9387 7.0612C10.7992 6.92167 10.6885 6.75602 10.613 6.57372C10.5375 6.39141 10.4986 6.19602 10.4986 5.9987C10.4986 5.80137 10.5375 5.60598 10.613 5.42367C10.6885 5.24137 10.7992 5.07573 10.9387 4.9362C11.0783 4.79667 11.2439 4.68599 11.4262 4.61047C11.6085 4.53496 11.8039 4.49609 12.0012 4.49609C12.1986 4.49609 12.394 4.53496 12.5763 4.61047C12.7586 4.68599 12.9242 4.79667 13.0637 4.9362L23.0638 14.9362C23.2034 15.0757 23.3142 15.2414 23.3897 15.4239C23.4652 15.6063 23.5039 15.8018 23.5037 15.9992C23.5035 16.1966 23.4642 16.3921 23.3883 16.5743C23.3124 16.7565 23.2013 16.922 23.0613 17.0612Z" fill="currentColor" /> </svg>
2689 + </div>
2690 +</div> <div class="swiper-wrapper">
2691 + <div class="swiper-slide slide-info">
2692 + <div class="slider-card box-info" data-bg="ete">
2693 + <div class="info">
2694 + <p class="h6">Laisse-nous t’inspirer. Tu es du genre à planifier chaque minute ou à y aller au feeling? il y a toujours un événement prêt à te surprendre dans la région. Clique, explore, puis laisse-toi embarquer</p>
2695 + </div>
2696 + <div class="footer">
2697 + <a
2698 + class="quanto-link-btn btn-pill btn-light btn-arrow-right btn-small"
2699 + href="https://abitibi-temiscamingue.org/blogue/"
2700 + target="_self"
2701 + >Explore tout</a>
2702 + </div>
2703 + </div>
2704 + </div>
2705 + <div class="swiper-slide">
2706 + <a class="slider-card"
2707 + href="https://abitibi-temiscamingue.org/festival-mama8i-nikamo/" target="_self">
2708 + <img class="bg" src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/03/artiste-FDA-band-homme-festival-mama8i-nikamo.avif" alt="Collectif musical FDA au Festival MAMA8i NIKAMO à Pikogan." /> <div class="info">
2709 + <p class="h3">MAMA8i NIKAMO à Pikogan: un festival pour célébrer la musique et la culture autochtone</p> </div>
2710 + </a>
2711 + </div>
2712 + <div class="swiper-slide">
2713 + <a class="slider-card"
2714 + href="https://abitibi-temiscamingue.org/le-festival-des-saines-habitudes-de-vie/" target="_self">
2715 + <img class="bg" src="https://abitibi-temiscamingue.org/wp-content/uploads/2024/06/Festival-saines-habitudes-de-vie_Velo_Lysandre-Rivard-768x503.png" alt="Festival des saines habitudes de vie volet vélo" /> <div class="info">
2716 + <p class="h3">Le Festival des saines habitudes de vie</p> </div>
2717 + </a>
2718 + </div>
2719 + <div class="swiper-slide">
2720 + <a class="slider-card"
2721 + href="https://abitibi-temiscamingue.org/152evenementjeu/" target="_self">
2722 + <img class="bg" src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/01/15-2-marie-eve-marois2-768x768.jpg" alt="" /> <div class="info">
2723 + <p class="h3">Événement 15-2 – Le festival ludique incontournable</p> </div>
2724 + </a>
2725 + </div>
2726 + </div>
2727 + </div>
2728 + </div>
2729 + </div>
2730 + </div>
2731 + </div>
2732 + </section>
2733 + </div> <footer class="footer-area overflow-hidden">
2734 + <a class="footer__cta" role="btn-newsletter" href="javascript:void(0);">
2735 + <div class="container custom-container">
2736 + <div class="row align-items-center">
2737 + <div class="col">
2738 + <p class="title">On reste&nbsp;<br>en contact?</p>
2739 + </div>
2740 + <div class="col-auto">
2741 + <div class="btn-round"> <svg
2742 + data-icon="EnvelopeOpen"
2743 + viewBox="0 0 48 48"
2744 + width="48"
2745 + height="48"
2746 + class=""
2747 + fill="none" xmlns="http://www.w3.org/2000/svg">
2748 + <path d="M42 18L42 37.5C42 37.8978 41.842 38.2794 41.5607 38.5607C41.2794 38.842 40.8978 39 40.5 39L7.5 39C7.10218 39 6.72065 38.842 6.43934 38.5607C6.15804 38.2794 6 37.8978 6 37.5L6.00001 18L24 6L42 18Z" stroke="currentColor" stroke-width="2"/>
2749 + <path d="M27.2719 28.5L41.5369 38.5763" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2750 + <path d="M6.46309 38.5763L20.7281 28.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2751 + <path d="M6.00001 18L20.7263 28.5L27.2719 28.5L42 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg>
2752 + </div>
2753 + </div>
2754 + </div>
2755 + </div>
2756 + </a>
2757 + <div class="footer__center">
2758 + <div class="container custom-container">
2759 + <div class="row">
2760 + <div class="col-12">
2761 + <div class="footer-widgets-wrapper">
2762 + <div class="footer-widgets order-0 order-lg-3 text-start text-lg-end">
2763 + <div class="social-links">
2764 + <ul class="custom-ul">
2765 + <li>
2766 + <a href="https://facebook.com/Abitibi.Temiscamingue" target="_blank">
2767 + <svg
2768 + data-icon="facebook-Circle"
2769 + viewBox="0 0 52 52"
2770 + width="52"
2771 + height="52"
2772 + class=""
2773 + fill="none" xmlns="http://www.w3.org/2000/svg">
2774 + <rect x="1.55" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8" />
2775 + <path d="M25.55 13.4004C23.147 13.4004 20.7979 14.113 18.7998 15.448C16.8018 16.7831 15.2445 18.6807 14.3249 20.9008C13.4053 23.1209 13.1647 25.5639 13.6335 27.9207C14.1023 30.2776 15.2594 32.4425 16.9587 34.1417C18.6579 35.8409 20.8228 36.9981 23.1797 37.4669C25.5365 37.9357 27.9795 37.6951 30.1996 36.7755C32.4197 35.8559 34.3173 34.2986 35.6524 32.3006C36.9874 30.3025 37.7 27.9534 37.7 25.5504C37.6964 22.3291 36.4152 19.2408 34.1374 16.963C31.8596 14.6852 28.7713 13.404 25.55 13.4004ZM26.9 34.9025V28.7004H29.15C29.508 28.7004 29.8514 28.5582 30.1046 28.305C30.3578 28.0518 30.5 27.7084 30.5 27.3504C30.5 26.9923 30.3578 26.649 30.1046 26.3958C29.8514 26.1426 29.508 26.0004 29.15 26.0004H26.9V23.7504C26.9 23.3923 27.0422 23.049 27.2954 22.7958C27.5486 22.5426 27.892 22.4004 28.25 22.4004H30.05C30.408 22.4004 30.7514 22.2582 31.0046 22.005C31.2578 21.7518 31.4 21.4084 31.4 21.0504C31.4 20.6923 31.2578 20.349 31.0046 20.0958C30.7514 19.8426 30.408 19.7004 30.05 19.7004H28.25C27.1759 19.7004 26.1457 20.1271 25.3862 20.8866C24.6267 21.6461 24.2 22.6763 24.2 23.7504V26.0004H21.95C21.592 26.0004 21.2486 26.1426 20.9954 26.3958C20.7422 26.649 20.6 26.9923 20.6 27.3504C20.6 27.7084 20.7422 28.0518 20.9954 28.305C21.2486 28.5582 21.592 28.7004 21.95 28.7004H24.2V34.9025C21.8354 34.5612 19.6878 33.3369 18.1894 31.476C16.6911 29.6151 15.9532 27.2557 16.1243 24.8727C16.2954 22.4897 17.3627 20.2599 19.1116 18.6322C20.8604 17.0044 23.1609 16.0994 25.55 16.0994C27.9391 16.0994 30.2396 17.0044 31.9885 18.6322C33.7373 20.2599 34.8046 22.4897 34.9757 24.8727C35.1468 27.2557 34.409 29.6151 32.9106 31.476C31.4122 33.3369 29.2646 34.5612 26.9 34.9025Z" fill="currentColor" /> </svg>
2776 + </a>
2777 + </li>
2778 + <li>
2779 + <a href="https://www.instagram.com/abitibi.temiscamingue/?hl=fr" target="_blank">
2780 + <svg
2781 + data-icon="instagram-Circle"
2782 + viewBox="0 0 52 52"
2783 + width="52"
2784 + height="52"
2785 + class=""
2786 + fill="none" xmlns="http://www.w3.org/2000/svg">
2787 + <rect x="1.55" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8" />
2788 + <path d="M25.55 20.1504C24.482 20.1504 23.438 20.4671 22.5499 21.0605C21.6619 21.6538 20.9698 22.4972 20.5611 23.4839C20.1523 24.4706 20.0454 25.5564 20.2538 26.6039C20.4621 27.6514 20.9764 28.6136 21.7316 29.3688C22.4868 30.124 23.449 30.6383 24.4965 30.8466C25.544 31.055 26.6298 30.9481 27.6165 30.5393C28.6032 30.1306 29.4466 29.4385 30.0399 28.5505C30.6333 27.6624 30.95 26.6184 30.95 25.5504C30.9485 24.1187 30.3791 22.746 29.3667 21.7337C28.3544 20.7213 26.9817 20.1519 25.55 20.1504ZM25.55 28.2504C25.016 28.2504 24.494 28.092 24.05 27.7954C23.606 27.4987 23.2599 27.077 23.0555 26.5836C22.8512 26.0903 22.7977 25.5474 22.9019 25.0236C23.0061 24.4999 23.2632 24.0188 23.6408 23.6412C24.0184 23.2636 24.4995 23.0064 25.0233 22.9023C25.547 22.7981 26.0899 22.8516 26.5833 23.0559C27.0766 23.2603 27.4983 23.6063 27.795 24.0504C28.0917 24.4944 28.25 25.0164 28.25 25.5504C28.25 26.2665 27.9655 26.9532 27.4592 27.4596C26.9528 27.9659 26.2661 28.2504 25.55 28.2504ZM30.95 13.4004H20.15C18.3604 13.4025 16.6448 14.1143 15.3793 15.3797C14.1139 16.6451 13.4021 18.3608 13.4 20.1504V30.9504C13.4021 32.74 14.1139 34.4556 15.3793 35.7211C16.6448 36.9865 18.3604 37.6983 20.15 37.7004H30.95C32.7396 37.6983 34.4553 36.9865 35.7207 35.7211C36.9861 34.4556 37.6979 32.74 37.7 30.9504V20.1504C37.6979 18.3608 36.9861 16.6451 35.7207 15.3797C34.4553 14.1143 32.7396 13.4025 30.95 13.4004ZM35 30.9504C35 32.0245 34.5733 33.0546 33.8138 33.8142C33.0543 34.5737 32.0241 35.0004 30.95 35.0004H20.15C19.0759 35.0004 18.0457 34.5737 17.2862 33.8142C16.5267 33.0546 16.1 32.0245 16.1 30.9504V20.1504C16.1 19.0763 16.5267 18.0461 17.2862 17.2866C18.0457 16.5271 19.0759 16.1004 20.15 16.1004H30.95C32.0241 16.1004 33.0543 16.5271 33.8138 17.2866C34.5733 18.0461 35 19.0763 35 20.1504V30.9504ZM33.2 19.7004C33.2 20.0564 33.0944 20.4044 32.8967 20.7004C32.6989 20.9964 32.4177 21.2271 32.0888 21.3634C31.7599 21.4996 31.398 21.5353 31.0488 21.4658C30.6997 21.3964 30.379 21.2249 30.1272 20.9732C29.8755 20.7214 29.704 20.4007 29.6346 20.0516C29.5651 19.7024 29.6008 19.3405 29.737 19.0116C29.8733 18.6827 30.104 18.4015 30.4 18.2037C30.696 18.006 31.044 17.9004 31.4 17.9004C31.8774 17.9004 32.3352 18.09 32.6728 18.4276C33.0104 18.7652 33.2 19.223 33.2 19.7004Z" fill="currentColor" /> </svg>
2789 + </a>
2790 + </li>
2791 + <li>
2792 + <a href="https://www.youtube.com/@abitibi-temiscamingue" target="_blank">
2793 + <svg
2794 + data-icon="youtube-Circle"
2795 + viewBox="0 0 52 52"
2796 + width="52"
2797 + height="52"
2798 + class=""
2799 + fill="none" xmlns="http://www.w3.org/2000/svg">
2800 + <rect x="1.55" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8" />
2801 + <path d="M30.3301 24.416L24.0301 20.366C23.8262 20.2349 23.5908 20.1612 23.3485 20.1524C23.1063 20.1437 22.8661 20.2004 22.6533 20.3165C22.4405 20.4327 22.2629 20.6039 22.1392 20.8124C22.0154 21.0208 21.9501 21.2588 21.95 21.5012V29.6012C21.9501 29.8436 22.0154 30.0815 22.1392 30.29C22.2629 30.4984 22.4405 30.6697 22.6533 30.7858C22.8661 30.9019 23.1063 30.9586 23.3485 30.9499C23.5908 30.9412 23.8262 30.8674 24.0301 30.7363L30.3301 26.6863C30.5201 26.564 30.6763 26.396 30.7845 26.1977C30.8927 25.9994 30.9494 25.7771 30.9494 25.5512C30.9494 25.3253 30.8927 25.103 30.7845 24.9046C30.6763 24.7063 30.5201 24.5383 30.3301 24.416ZM24.65 27.1262V23.9762L27.1036 25.5512L24.65 27.1262ZM37.9486 18.8574C37.8244 18.3733 37.5869 17.9257 37.2557 17.5513C36.9246 17.177 36.5093 16.8866 36.044 16.7042C32.12 15.191 25.9201 15.2012 25.55 15.2012C25.1799 15.2012 18.98 15.191 15.056 16.7064C14.5907 16.8889 14.1754 17.1792 13.8442 17.5536C13.5131 17.9279 13.2756 18.3756 13.1514 18.8597C12.8544 20.0072 12.5 22.0997 12.5 25.5512C12.5 29.0027 12.8544 31.0952 13.1514 32.2427C13.2756 32.7268 13.5131 33.1744 13.8442 33.5488C14.1754 33.9231 14.5907 34.2135 15.056 34.3959C18.8236 35.8505 24.6905 35.9012 25.4791 35.9012H25.6209C26.4084 35.9012 32.2764 35.8505 36.044 34.3959C36.5093 34.2135 36.9246 33.9231 37.2557 33.5488C37.5869 33.1744 37.8244 32.7268 37.9486 32.2427C38.2456 31.0952 38.6 29.0027 38.6 25.5512C38.6 22.0997 38.2456 20.0072 37.9486 18.8597V18.8574ZM35.3375 31.5654C35.3213 31.6344 35.289 31.6986 35.2432 31.7528C35.1975 31.8069 35.1396 31.8495 35.0742 31.877C31.7127 33.1742 26.0304 33.2012 25.586 33.2012H25.5421C25.4825 33.2012 19.5076 33.2203 16.0291 31.877C15.9632 31.8499 15.9046 31.8076 15.8582 31.7534C15.8119 31.6992 15.7791 31.6348 15.7625 31.5654C15.551 30.7509 15.2 28.8857 15.2 25.5512C15.2 22.2167 15.5544 20.3514 15.7625 19.5369C15.7787 19.4679 15.811 19.4037 15.8568 19.3496C15.9025 19.2954 15.9604 19.2528 16.0257 19.2253C19.5076 17.882 25.4814 17.9012 25.541 17.9012H25.5579C25.6186 17.9012 31.5924 17.882 35.0709 19.2253C35.1368 19.2524 35.1954 19.2948 35.2417 19.349C35.2881 19.4032 35.3209 19.4676 35.3375 19.5369C35.549 20.3514 35.9 22.2167 35.9 25.5512C35.9 28.8857 35.5456 30.7509 35.3375 31.5654Z" fill="currentColor" /> </svg>
2802 + </a>
2803 + </li>
2804 + <li>
2805 + <a href="https://www.pinterest.ca/AbitibiTemiscamingue/" target="_blank">
2806 + <svg
2807 + data-icon="pinterest-Circle"
2808 + viewBox="0 0 52 52"
2809 + width="52"
2810 + height="52"
2811 + class=""
2812 + fill="none" xmlns="http://www.w3.org/2000/svg">
2813 + <rect x="1.5498" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8"/>
2814 + <path d="M36.8001 23.7512C36.8001 26.4062 35.8675 28.8396 34.1732 30.6002C32.5836 32.2529 30.4247 33.2012 28.2501 33.2012C26.512 33.2012 25.2373 32.731 24.325 32.1539L23.2641 36.6606C23.225 36.8347 23.1517 36.9993 23.0486 37.145C22.9455 37.2906 22.8145 37.4143 22.6632 37.509C22.512 37.6037 22.3434 37.6674 22.1674 37.6966C21.9913 37.7257 21.8112 37.7197 21.6375 37.6788C21.4638 37.6379 21.3 37.5629 21.1554 37.4583C21.0109 37.3537 20.8885 37.2214 20.7953 37.0692C20.7022 36.917 20.6402 36.7478 20.6129 36.5715C20.5855 36.3951 20.5934 36.2151 20.6361 36.0419L24.2361 20.7419C24.2752 20.5678 24.3485 20.4031 24.4516 20.2575C24.5547 20.1119 24.6857 19.9882 24.837 19.8935C24.9882 19.7988 25.1568 19.735 25.3328 19.7059C25.5089 19.6768 25.689 19.6828 25.8627 19.7237C26.0364 19.7646 26.2002 19.8395 26.3448 19.9442C26.4893 20.0488 26.6117 20.181 26.7048 20.3333C26.798 20.4855 26.86 20.6546 26.8873 20.831C26.9147 21.0073 26.9068 21.1873 26.8641 21.3606L25.0135 29.2277C25.4061 29.698 26.3556 30.5012 28.2501 30.5012C31.1256 30.5012 34.1001 27.9756 34.1001 23.7512C34.0995 22.5782 33.8291 21.421 33.3099 20.3691C32.7907 19.3172 32.0365 18.3988 31.1057 17.6849C30.1749 16.971 29.0923 16.4808 27.9418 16.252C26.7913 16.0233 25.6035 16.0622 24.4704 16.3658C23.3373 16.6693 22.2892 17.2294 21.4071 18.0027C20.525 18.776 19.8326 19.7418 19.3834 20.8254C18.9341 21.909 18.7401 23.0815 18.8163 24.252C18.8924 25.4226 19.2368 26.56 19.8227 27.5762C19.9881 27.8852 20.0268 28.2464 19.9308 28.5834C19.8347 28.9204 19.6113 29.2069 19.3078 29.3822C19.0044 29.5575 18.6447 29.608 18.3047 29.5229C17.9647 29.4378 17.6712 29.2238 17.4861 28.9262C16.6925 27.5514 16.2259 26.0124 16.1222 24.4283C16.0185 22.8442 16.2806 21.2575 16.8882 19.7909C17.4958 18.3244 18.4326 17.0172 19.6262 15.9705C20.8197 14.9239 22.238 14.1658 23.7714 13.755C25.3048 13.3441 26.9121 13.2915 28.4691 13.6012C30.026 13.9108 31.4909 14.5745 32.7504 15.5408C34.0098 16.5072 35.0301 17.7502 35.7324 19.1739C36.4346 20.5976 36.7999 22.1638 36.8001 23.7512Z" fill="currentColor"/> </svg>
2815 + </a>
2816 + </li>
2817 + <li>
2818 + <a href="https://ca.linkedin.com/company/tourismeat" target="_blank">
2819 + <svg
2820 + data-icon="linkedin-Circle"
2821 + viewBox="0 0 52 52"
2822 + width="52"
2823 + height="52"
2824 + class=""
2825 + fill="none" xmlns="http://www.w3.org/2000/svg">
2826 + <rect x="1.55" y="1.55078" width="48" height="48" rx="20" stroke="currentColor" stroke-width="1.8" />
2827 + <path d="M35.45 13.4004H15.65C15.0533 13.4004 14.481 13.6374 14.059 14.0594C13.6371 14.4814 13.4 15.0537 13.4 15.6504V35.4504C13.4 36.0471 13.6371 36.6194 14.059 37.0414C14.481 37.4633 15.0533 37.7004 15.65 37.7004H35.45C36.0467 37.7004 36.619 37.4633 37.041 37.0414C37.4629 36.6194 37.7 36.0471 37.7 35.4504V15.6504C37.7 15.0537 37.4629 14.4814 37.041 14.0594C36.619 13.6374 36.0467 13.4004 35.45 13.4004ZM35 35.0004H16.1V16.1004H35V35.0004ZM23.75 30.9504V24.6504C23.7503 24.3698 23.8379 24.0963 24.0008 23.8679C24.1637 23.6395 24.3937 23.4675 24.6588 23.3758C24.924 23.2841 25.2111 23.2773 25.4803 23.3563C25.7495 23.4354 25.9874 23.5963 26.1609 23.8168C26.8466 23.4574 27.6134 23.2809 28.3872 23.3045C29.161 23.3281 29.9157 23.551 30.5782 23.9515C31.2407 24.352 31.7887 24.9167 32.1691 25.591C32.5496 26.2652 32.7496 27.0262 32.75 27.8004V30.9504C32.75 31.3084 32.6078 31.6518 32.3546 31.905C32.1014 32.1582 31.758 32.3004 31.4 32.3004C31.042 32.3004 30.6986 32.1582 30.4454 31.905C30.1922 31.6518 30.05 31.3084 30.05 30.9504V27.8004C30.05 27.323 29.8604 26.8652 29.5228 26.5276C29.1852 26.19 28.7274 26.0004 28.25 26.0004C27.7726 26.0004 27.3148 26.19 26.9772 26.5276C26.6396 26.8652 26.45 27.323 26.45 27.8004V30.9504C26.45 31.3084 26.3078 31.6518 26.0546 31.905C25.8014 32.1582 25.458 32.3004 25.1 32.3004C24.742 32.3004 24.3986 32.1582 24.1454 31.905C23.8922 31.6518 23.75 31.3084 23.75 30.9504ZM21.95 24.6504V30.9504C21.95 31.3084 21.8078 31.6518 21.5546 31.905C21.3014 32.1582 20.958 32.3004 20.6 32.3004C20.242 32.3004 19.8986 32.1582 19.6454 31.905C19.3922 31.6518 19.25 31.3084 19.25 30.9504V24.6504C19.25 24.2923 19.3922 23.949 19.6454 23.6958C19.8986 23.4426 20.242 23.3004 20.6 23.3004C20.958 23.3004 21.3014 23.4426 21.5546 23.6958C21.8078 23.949 21.95 24.2923 21.95 24.6504ZM18.8 20.1504C18.8 19.7944 18.9056 19.4464 19.1034 19.1504C19.3011 18.8544 19.5823 18.6236 19.9112 18.4874C20.2401 18.3512 20.602 18.3155 20.9512 18.385C21.3003 18.4544 21.6211 18.6259 21.8728 18.8776C22.1245 19.1293 22.296 19.4501 22.3654 19.7992C22.4349 20.1484 22.3992 20.5103 22.263 20.8392C22.1267 21.1681 21.896 21.4492 21.6 21.647C21.304 21.8448 20.956 21.9504 20.6 21.9504C20.1226 21.9504 19.6648 21.7607 19.3272 21.4232C18.9896 21.0856 18.8 20.6278 18.8 20.1504Z" fill="currentColor" /> </svg>
2828 + </a>
2829 + </li>
2830 + </ul>
2831 + </div>
2832 + <a class="quanto-link-btn btn-pill btn-light btn-arrow-right btn-small d-lg-inline-block mt-4"
2833 + href="https://vivreenabitibitemiscamingue.org"
2834 + target="_blank">
2835 + Attractivité AT </a>
2836 + </div>
2837 + <div class="footer-widgets order-1">
2838 + <div class="widget-links">
2839 + <ul id="menu-menu-footer-gauche" class="custom-ul"><li id="menu-item-7754" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7754"><a href="https://abitibi-temiscamingue.org/notre-organisme/">Notre organisme</a></li>
2840 +<li id="menu-item-13000" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-13000"><a href="https://abitibi-temiscamingue.org/contact/">Nous joindre</a></li>
2841 +<li id="menu-item-13001" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-13001"><a href="https://abitibi-temiscamingue.org/informations-pratiques/documents-brochures-liens-utiles/">Documents, brochures et liens utiles</a></li>
2842 +<li id="menu-item-13004" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-13004"><a href="https://abitibi-temiscamingue.org/espace-membres/">Espace membres</a></li>
2843 +<li id="menu-item-16528" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-16528"><a href="https://abitibi-temiscamingue.org/aide-financiere/">Aide financière</a></li>
2844 +</ul> </div>
2845 + </div>
2846 + <div class="footer-widgets order-1">
2847 + <div class="widget-links">
2848 + <ul id="menu-menu-footer-droite" class="custom-ul"><li id="menu-item-13164" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-13164"><a href="https://abitibi-temiscamingue.org/medias/">Zone médias</a></li>
2849 +<li id="menu-item-13165" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-13165"><a href="https://abitibi-temiscamingue.org/pros-du-voyage/">Pros du voyage</a></li>
2850 +<li id="menu-item-13166" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-13166"><a href="https://abitibi-temiscamingue.org/concours/">Concours</a></li>
2851 +<li id="menu-item-13167" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-13167"><a target="_blank" href="https://issuu.com/tourismeat/docs/guide_touristique_de_l_abitibi-t_miscamingue_2026-">Guide touristique</a></li>
2852 +</ul> </div>
2853 + </div>
2854 + <div class="footer-widgets order-2 order-lg-0 text-center text-lg-start">
2855 + <div class="footer-logo">
2856 + <a href="https://abitibi-temiscamingue.org">
2857 + <img src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/images/logo.svg" alt="Tourisme Abitibi-Témiscamingue" width="239" height="48"/>
2858 + </a>
2859 + </div>
2860 + </div>
2861 + </div>
2862 + </div>
2863 + </div>
2864 + </div>
2865 + </div>
2866 + <div class="footer__bottom">
2867 + <div class="container custom-container overflow-hidden">
2868 + <div class="row footer__bottom-content">
2869 + <div class="copyright-text col-auto col-lg-4">
2870 + Tourisme Abitibi-Témiscamingue — &copy; 2026
2871 + </div>
2872 + <div class="copyright-text col-auto col-lg-4 text-end text-lg-center btn-politique">
2873 + <a href="/politique-moderation/"
2874 + target="_self">
2875 + Politique de modération </a>
2876 + <a href="https://abitibi-temiscamingue.org/politique-de-confidentialite/">Politique de confidentialité</a>
2877 + </div>
2878 + <div class="copyright-text col-12 col-lg-4 text-center text-lg-end">
2879 + <span>Site Web : <a href="http://equipelebleu.com" target="_blank">LEBLEU</a></span>
2880 + </div>
2881 + </div>
2882 + </div>
2883 + </div>
2884 + </footer>
2885 + </div>
2886 + </div>
2887 +
2888 +
2889 +
2890 +
2891 +<!-- Newsletter Modal -->
2892 +<div class="modal fade" id="newsletterModal" tabindex="-1" aria-labelledby="newsletterModalLabel" aria-hidden="true">
2893 + <div class="modal-dialog modal-dialog-centered">
2894 + <div class="modal-content">
2895 + <div class="modal-header">
2896 + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
2897 + </div>
2898 + <div class="modal-body">
2899 + <div class="newsletter-container">
2900 + <div id="newsletterFormWrapper">
2901 + <span class="h5" id="newsletterModalLabel">
2902 + Inscris-toi à notre infolettre </span>
2903 + <p class="description">
2904 + Laisse nos suggestions chargées d'inspiration venir directement à toi! </p>
2905 + <div class="newsletter-form">
2906 + <lf-tag type="form" value="6982568e751352000735feb2" submit="Soumettre"></lf-tag>
2907 + </div>
2908 +
2909 + </div>
2910 +
2911 + <div id="newsletterSuccess" class="newsletter-success" style="display: none;">
2912 + <div class="success-icon">
2913 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
2914 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
2915 + </svg>
2916 + </div>
2917 + <p>Merci! Vous êtes maintenant inscrit à notre infolettre.</p>
2918 + </div>
2919 + </div>
2920 + </div>
2921 + </div>
2922 + </div>
2923 +</div>
2924 +
2925 +<script type="0316cf47fcdef64099276bd1-text/javascript">
2926 +(function() {
2927 + document.addEventListener('DOMContentLoaded', () => {
2928 + const modal = document.getElementById('newsletterModal');
2929 + let bsModal = null;
2930 +
2931 + // Initialize Bootstrap modal
2932 + if (modal && typeof bootstrap !== 'undefined') {
2933 + bsModal = new bootstrap.Modal(modal);
2934 + }
2935 +
2936 + // Handle click on any element with role="btn-newsletter"
2937 + document.addEventListener('click', (e) => {
2938 + const trigger = e.target.closest('[role="btn-newsletter"]');
2939 + if (trigger && bsModal) {
2940 + e.preventDefault();
2941 + bsModal.show();
2942 + }
2943 + });
2944 + });
2945 +})();
2946 +</script>
2947 +
2948 + <script type="speculationrules">
2949 +{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/wp-content/uploads/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/lebleu/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]}
2950 +</script>
2951 + <!-- Custom Button Color Styles -->
2952 +
2953 +
2954 + <!-- Floating Chat Widget -->
2955 + <div class="listeo-floating-chat-widget" id="listeo-floating-chat-widget">
2956 +
2957 +
2958 + <!-- Floating Button -->
2959 + <button
2960 + class="listeo-floating-chat-button has-custom-icon"
2961 + id="listeo-floating-chat-button"
2962 + aria-label="Open chat"
2963 + >
2964 + <img src="https://abitibi-temiscamingue.org/wp-content/uploads/2026/05/hand-chat.png" alt="Chat" class="listeo-floating-custom-icon listeo-floating-icon-open" />
2965 + <img src="https://abitibi-temiscamingue.org/wp-content/plugins/ai-chat-search/assets/icons/close.svg" alt="Close" class="listeo-floating-icon-close" style="display: none;" width="18" height="18" />
2966 + </button>
2967 +
2968 + <!-- Chat Popup (reuses exact shortcode HTML structure) -->
2969 + <div class="listeo-floating-chat-popup" id="listeo-floating-chat-popup" style="display: none; width: 390px; height: 600px;">
2970 + <div class="listeo-ai-chat-wrapper" id="listeo-floating-chat-instance" data-hide-images="0"> <div class="listeo-ai-chat-container">
2971 + <div class="listeo-ai-chat-header">
2972 + <div class="listeo-ai-chat-header-left">
2973 + <div class="listeo-ai-chat-title">Ton assistant de voyage</div>
2974 + </div>
2975 + <div class="listeo-ai-chat-menu">
2976 + <div class="listeo-ai-chat-menu-trigger" role="button" tabindex="0" aria-haspopup="menu" aria-expanded="false">
2977 + <svg width="18" height="18" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2978 + <path d="M3 6.5C2.17 6.5 1.5 7.17 1.5 8C1.5 8.83 2.17 9.5 3 9.5C3.83 9.5 4.5 8.83 4.5 8C4.5 7.17 3.83 6.5 3 6.5ZM8 6.5C7.17 6.5 6.5 7.17 6.5 8C6.5 8.83 7.17 9.5 8 9.5C8.83 9.5 9.5 8.83 9.5 8C9.5 7.17 8.83 6.5 8 6.5ZM13 6.5C12.17 6.5 11.5 7.17 11.5 8C11.5 8.83 12.17 9.5 13 9.5C13.83 9.5 14.5 8.83 14.5 8C14.5 7.17 13.83 6.5 13 6.5Z" fill="currentColor"/>
2979 + </svg>
2980 + </div>
2981 + <div class="listeo-ai-chat-menu-dropdown" role="menu" data-state="closed">
2982 + <div class="listeo-ai-chat-menu-item listeo-ai-chat-expand-btn" role="menuitem" tabindex="-1">
2983 + <svg class="icon-expand" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2984 + <polyline points="15 3 21 3 21 9"></polyline>
2985 + <polyline points="9 21 3 21 3 15"></polyline>
2986 + <line x1="21" y1="3" x2="14" y2="10"></line>
2987 + <line x1="3" y1="21" x2="10" y2="14"></line>
2988 + </svg>
2989 + <svg class="icon-collapse" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2990 + <polyline points="4 14 10 14 10 20"></polyline>
2991 + <polyline points="20 10 14 10 14 4"></polyline>
2992 + <line x1="14" y1="10" x2="21" y2="3"></line>
2993 + <line x1="3" y1="21" x2="10" y2="14"></line>
2994 + </svg>
2995 + <span class="text-expand">Expand chat</span>
2996 + <span class="text-collapse">Collapse chat</span>
2997 + </div>
2998 + <div class="listeo-ai-chat-menu-item listeo-ai-chat-clear-btn" role="menuitem" tabindex="-1">
2999 + <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3000 + <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
3001 + <line x1="12" y1="7" x2="12" y2="13"></line>
3002 + <line x1="9" y1="10" x2="15" y2="10"></line>
3003 + </svg>
3004 + Start a new chat </div>
3005 + </div>
3006 + </div>
3007 + </div>
3008 + <div class="listeo-ai-chat-messages" id="listeo-floating-chat-instance-messages">
3009 + <!-- Welcome message added by JavaScript -->
3010 + </div>
3011 +
3012 +
3013 + <div class="listeo-ai-chat-input-wrapper">
3014 + <textarea
3015 + id="listeo-floating-chat-instance-input"
3016 + class="listeo-ai-chat-input"
3017 + placeholder="Type a message"
3018 + rows="2"
3019 + maxlength="1000"
3020 + ></textarea>
3021 + <button
3022 + id="listeo-floating-chat-instance-send"
3023 + class="listeo-ai-chat-send-btn"
3024 + >
3025 + <img src="https://abitibi-temiscamingue.org/wp-content/plugins/ai-chat-search/assets/icons/arrow-up.svg" alt="Send" width="16" height="16" />
3026 + </button>
3027 + </div>
3028 +
3029 +
3030 +
3031 +
3032 +
3033 + </div>
3034 + </div>
3035 + </div>
3036 +
3037 + </div>
3038 +
3039 + <!-- This site is trusting LeadFox to convert visitors into customers - https://leadfox.co -->
3040 + <script async src="//app.leadfox.co/js/api/leadfox.js" data-key="59e001885a03eb16bd8a05dfe48dbf70" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3041 + <!-- LeadFox -->
3042 +
3043 +<!-- Consent Management powered by Complianz | GDPR/CCPA Cookie Consent https://wordpress.org/plugins/complianz-gdpr -->
3044 +<div id="cmplz-cookiebanner-container"><div class="cmplz-cookiebanner cmplz-hidden banner-1 banner-a optin cmplz-bottom-left cmplz-categories-type-view-preferences" aria-modal="true" data-nosnippet="true" role="dialog" aria-live="polite" aria-labelledby="cmplz-header-1-optin" aria-describedby="cmplz-message-1-optin">
3045 + <div class="cmplz-header">
3046 + <div class="cmplz-logo"></div>
3047 + <div class="cmplz-title" id="cmplz-header-1-optin">Gérer le consentement</div>
3048 + <div class="cmplz-close" tabindex="0" role="button" aria-label="Close dialog">
3049 + <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="times" class="svg-inline--fa fa-times fa-w-11" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
3050 + </div>
3051 + </div>
3052 +
3053 + <div class="cmplz-divider cmplz-divider-header"></div>
3054 + <div class="cmplz-body">
3055 + <div class="cmplz-message" id="cmplz-message-1-optin"><p>Pour offrir les meilleures expériences, nous utilisons des technologies telles que les cookies pour stocker et/ou accéder aux informations des appareils. Le fait de consentir à ces technologies nous permettra de traiter des données telles que le comportement de navigation ou les ID uniques sur ce site. Le fait de ne pas consentir ou de retirer son consentement peut avoir un effet négatif sur certaines caractéristiques et fonctions.</p></div>
3056 + <!-- categories start -->
3057 + <div class="cmplz-categories">
3058 + <details class="cmplz-category cmplz-functional" >
3059 + <summary>
3060 + <span class="cmplz-category-header">
3061 + <span class="cmplz-category-title">Fonctionnel</span>
3062 + <span class='cmplz-always-active'>
3063 + <span class="cmplz-banner-checkbox">
3064 + <input type="checkbox"
3065 + id="cmplz-functional-optin"
3066 + data-category="cmplz_functional"
3067 + class="cmplz-consent-checkbox cmplz-functional"
3068 + size="40"
3069 + value="1"/>
3070 + <label class="cmplz-label" for="cmplz-functional-optin"><span class="screen-reader-text">Fonctionnel</span></label>
3071 + </span>
3072 + Always active </span>
3073 + <span class="cmplz-icon cmplz-open">
3074 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" height="18" ><path d="M224 416c-8.188 0-16.38-3.125-22.62-9.375l-192-192c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L224 338.8l169.4-169.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-192 192C240.4 412.9 232.2 416 224 416z"/></svg>
3075 + </span>
3076 + </span>
3077 + </summary>
3078 + <div class="cmplz-description">
3079 + <span class="cmplz-description-functional">L’accès ou le stockage technique est strictement nécessaire dans la finalité d’intérêt légitime de permettre l’utilisation d’un service spécifique explicitement demandé par l’abonné ou l’utilisateur, ou dans le seul but d’effectuer la transmission d’une communication sur un réseau de communications électroniques.</span>
3080 + </div>
3081 + </details>
3082 +
3083 + <details class="cmplz-category cmplz-preferences" >
3084 + <summary>
3085 + <span class="cmplz-category-header">
3086 + <span class="cmplz-category-title">Preferences</span>
3087 + <span class="cmplz-banner-checkbox">
3088 + <input type="checkbox"
3089 + id="cmplz-preferences-optin"
3090 + data-category="cmplz_preferences"
3091 + class="cmplz-consent-checkbox cmplz-preferences"
3092 + size="40"
3093 + value="1"/>
3094 + <label class="cmplz-label" for="cmplz-preferences-optin"><span class="screen-reader-text">Preferences</span></label>
3095 + </span>
3096 + <span class="cmplz-icon cmplz-open">
3097 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" height="18" ><path d="M224 416c-8.188 0-16.38-3.125-22.62-9.375l-192-192c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L224 338.8l169.4-169.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-192 192C240.4 412.9 232.2 416 224 416z"/></svg>
3098 + </span>
3099 + </span>
3100 + </summary>
3101 + <div class="cmplz-description">
3102 + <span class="cmplz-description-preferences">The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.</span>
3103 + </div>
3104 + </details>
3105 +
3106 + <details class="cmplz-category cmplz-statistics" >
3107 + <summary>
3108 + <span class="cmplz-category-header">
3109 + <span class="cmplz-category-title">Statistiques</span>
3110 + <span class="cmplz-banner-checkbox">
3111 + <input type="checkbox"
3112 + id="cmplz-statistics-optin"
3113 + data-category="cmplz_statistics"
3114 + class="cmplz-consent-checkbox cmplz-statistics"
3115 + size="40"
3116 + value="1"/>
3117 + <label class="cmplz-label" for="cmplz-statistics-optin"><span class="screen-reader-text">Statistiques</span></label>
3118 + </span>
3119 + <span class="cmplz-icon cmplz-open">
3120 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" height="18" ><path d="M224 416c-8.188 0-16.38-3.125-22.62-9.375l-192-192c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L224 338.8l169.4-169.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-192 192C240.4 412.9 232.2 416 224 416z"/></svg>
3121 + </span>
3122 + </span>
3123 + </summary>
3124 + <div class="cmplz-description">
3125 + <span class="cmplz-description-statistics">Le stockage ou l’accès technique qui est utilisé exclusivement à des fins statistiques.</span>
3126 + <span class="cmplz-description-statistics-anonymous">The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.</span>
3127 + </div>
3128 + </details>
3129 + <details class="cmplz-category cmplz-marketing" >
3130 + <summary>
3131 + <span class="cmplz-category-header">
3132 + <span class="cmplz-category-title">Marketing</span>
3133 + <span class="cmplz-banner-checkbox">
3134 + <input type="checkbox"
3135 + id="cmplz-marketing-optin"
3136 + data-category="cmplz_marketing"
3137 + class="cmplz-consent-checkbox cmplz-marketing"
3138 + size="40"
3139 + value="1"/>
3140 + <label class="cmplz-label" for="cmplz-marketing-optin"><span class="screen-reader-text">Marketing</span></label>
3141 + </span>
3142 + <span class="cmplz-icon cmplz-open">
3143 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" height="18" ><path d="M224 416c-8.188 0-16.38-3.125-22.62-9.375l-192-192c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L224 338.8l169.4-169.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-192 192C240.4 412.9 232.2 416 224 416z"/></svg>
3144 + </span>
3145 + </span>
3146 + </summary>
3147 + <div class="cmplz-description">
3148 + <span class="cmplz-description-marketing">L’accès ou le stockage technique est nécessaire pour créer des profils d’internautes afin d’envoyer des publicités, ou pour suivre l’utilisateur sur un site web ou sur plusieurs sites web ayant des finalités marketing similaires.</span>
3149 + </div>
3150 + </details>
3151 + </div><!-- categories end -->
3152 + </div>
3153 +
3154 + <div class="cmplz-links cmplz-information">
3155 + <ul>
3156 + <li><a class="cmplz-link cmplz-manage-options cookie-statement" href="#" data-relative_url="#cmplz-manage-consent-container">Manage options</a></li>
3157 + <li><a class="cmplz-link cmplz-manage-third-parties cookie-statement" href="#" data-relative_url="#cmplz-cookies-overview">Manage services</a></li>
3158 + <li><a class="cmplz-link cmplz-manage-vendors tcf cookie-statement" href="#" data-relative_url="#cmplz-tcf-wrapper">Manage {vendor_count} vendors</a></li>
3159 + <li><a class="cmplz-link cmplz-external cmplz-read-more-purposes tcf" target="_blank" rel="noopener noreferrer nofollow" href="https://cookiedatabase.org/tcf/purposes/" aria-label="Read more about TCF purposes on Cookie Database">Read more about these purposes</a></li>
3160 + </ul>
3161 + </div>
3162 +
3163 + <div class="cmplz-divider cmplz-footer"></div>
3164 +
3165 + <div class="cmplz-buttons">
3166 + <button class="cmplz-btn cmplz-accept">Accepter</button>
3167 + <button class="cmplz-btn cmplz-deny">Refuser</button>
3168 + <button class="cmplz-btn cmplz-view-preferences">Voir les préférences</button>
3169 + <button class="cmplz-btn cmplz-save-preferences">Enregistrer les préférences</button>
3170 + <a class="cmplz-btn cmplz-manage-options tcf cookie-statement" href="#" data-relative_url="#cmplz-manage-consent-container">Voir les préférences</a>
3171 + </div>
3172 +
3173 +
3174 + <div class="cmplz-documents cmplz-links">
3175 + <ul>
3176 + <li><a class="cmplz-link cookie-statement" href="#" data-relative_url="">{title}</a></li>
3177 + <li><a class="cmplz-link privacy-statement" href="#" data-relative_url="">{title}</a></li>
3178 + <li><a class="cmplz-link impressum" href="#" data-relative_url="">{title}</a></li>
3179 + </ul>
3180 + </div>
3181 +</div>
3182 +</div>
3183 + <div id="cmplz-manage-consent" data-nosnippet="true"><button class="cmplz-btn cmplz-hidden cmplz-manage-consent manage-consent-1">Gérer le consentement</button>
3184 +
3185 +</div><script id="listeo-ai-chat-js-extra" type="0316cf47fcdef64099276bd1-text/javascript">
3186 +var listeoAiChatConfig = {"apiBase":"https://abitibi-temiscamingue.org/wp-json/listeo/v1","nonce":"388bd9fa2c","isLoggedIn":"","debugMode":"","placeholderImage":"","chatName":"Ton assistant de voyage","chatAvatarUrl":"","hideImages":"0","loadingStyle":"dots","typingAnimation":"1","colorScheme":"light","ragSourcesLimit":"5","imageInputEnabled":"","hasImageHeader":"","hasImageHeaderOverlay":"","hasAnimatedHeader":"","animatedBgColor":"#ff4800","wooCartEnabled":"","ajaxUrl":"","cartNonce":"","cartUrl":"","checkoutUrl":"","cartCount":"0","quickButtonsVisibility":"always","contextLength":"normal","rateLimits":{"tier1":10,"tier2":30,"tier3":100},"strings":{"placeholder":"Ask about listings...","sendButton":"Send","welcomeMessage":"Tu cherches des id\u00e9es pour ton prochain trip? Pose-moi tes questions, je suis l\u00e0 pour t'orienter avec la magie de l'IA","loading":"Thinking...","loadingConfig":"Please wait, loading configuration...","gettingDetails":"Getting listing details...","searchingDatabase":"Thinking...","analyzingResults":"Analyzing...","generatingAnswer":"Thinking...","analyzingListing":"Analyzing listing...","loadingButton":"Loading...","talkAboutListing":"Talk about this listing","listingNotFound":"Sorry, I couldn't find details for that listing.","errorGettingDetails":"Error getting listing details.","errorLoadingListing":"Error loading listing.","failedLoadDetails":"Failed to load listing details.","errorApiKey":"Please add API key in plugin settings.","apiNotConfigured":"\u26a0\ufe0f Hey, to start using the chatbot \u003Cstrong\u003Eplease add OpenAI or Gemini API key\u003C/strong\u003E in plugin settings!","errorConfig":"Failed to load chat configuration. Please try again later.","errorGeneral":"Sorry, an error occurred. Please try again.","chatDisabled":"AI Chat is currently disabled.","bestMatch":"Best Match","showMore":"Show more (%d)","listingContextLoaded":"Listing context loaded! You can now ask me anything about","talkAboutProduct":"Talk about this product","productContextLoaded":"Product context loaded! You can now ask me anything about","errorLoadingProduct":"Error loading product.","failedLoadProductDetails":"Failed to load product details.","searchingListings":"Searching listings...","searchingProducts":"Searching products...","searchingSiteContent":"Searching site content...","analyzingProducts":"Analyzing products...","gettingProductDetails":"Getting product details...","analyzingProduct":"Analyzing product...","comparingListings":"Comparing listings...","comparingProducts":"Comparing products...","analyzingContent":"Analyzing content...","checkingOrderStatus":"Checking order status...","analyzingOrderDetails":"Analyzing order details...","sendingMessage":"Sending message...","orderNotFound":"Order not found. Please check the order number and try again.","orderVerificationRequired":"Please provide your billing email to verify the order.","errorGettingOrder":"Unable to retrieve order status. Please try again later.","searchFailed":"Search failed. Please try again.","productSearchFailed":"Product search failed. Please try again.","unknownFunction":"Unknown function requested.","contentNotFound":"Content not found or not published.","errorGettingContent":"Error getting content details.","productNotFound":"Product not found or not published.","errorGettingProduct":"Error getting product details.","errorNetwork":"Network error - request could not be sent. Please check your connection.","errorConnection":"Connection was interrupted. Please try again.","errorTimeout":"Request timed out. Please try again.","errorRateLimit":"Too many requests. Please wait a moment and try again.","errorServer":"Server error occurred. Please try again.","thinkingAboutQuery":"Thinking...","noResultsGeneric":"I couldn't find results matching your search. Try different keywords or be more specific about what you're looking for.","rateLimitPrefix":"You've reached the limit of","rateLimitSuffix":"messages per","rateLimitWait":"Please wait","rateLimitBeforeTrying":"before trying again.","minute":"minute","minutes":"minutes","hour":"hour","hours":"hours","second":"second","seconds":"seconds","inStock":"In Stock","outOfStock":"Out of Stock","contactFormFillAll":"Please fill in all fields.","contactFormSent":"Message sent successfully!","contactFormError":"Failed to send message. Please try again.","micStartRecording":"Recording...","micStopRecording":"Processing...","micAccessDenied":"Microphone access denied. Please allow microphone access in your browser settings.","micNotSupported":"Speech-to-text is not supported in your browser.","micNoSSL":"Not available without SSL","audioTooLarge":"Recording is too long. Please try a shorter message.","transcriptionFailed":"Could not transcribe audio. Please try again.","speechNotAvailable":"Speech-to-text requires PRO version.","imageTooLarge":"Image is too large. Maximum size is 4MB.","imageResolutionTooLarge":"Image resolution is too large. Maximum is 3000x3000 pixels.","imageInvalidFormat":"Invalid image format. Allowed: JPEG, PNG, GIF, WebP.","imageAttached":"[Image attached]","analyzingImage":"Analyzing image...","preChatRequired":"Fill out required fields","addToCart":"Add to Cart","selectOptions":"Select Options","addingToCart":"Adding...","addedToCart":"Added!","cartErrorAdd":"Could not add to cart.","shoppingCart":"Shopping Cart","cartEmpty":"Your cart is empty.","cartSubtotal":"Subtotal","viewCart":"View Cart","checkout":"Checkout"},"chatConfig":{"enabled":"1","model":"gpt-5.1","max_tokens":3000,"temperature":0.6,"listeo_available":false,"woocommerce_available":false,"tools":[{"type":"function","function":{"name":"search_universal_content","description":"Search for general website content including blog posts, pages, documentation, policies, and guides. Use this for questions about: plugin features, how-to guides, documentation, policies, blog articles, site information. DO NOT use for searching listings/places or products.","parameters":{"type":"object","properties":{"query":{"type":"string","description":"The search query for general website content. Examples: \"how to install plugin\", \"refund policy\", \"pricing plans\", \"API documentation\""},"top_results":{"type":"integer","description":"Number of results to return (default: 5, max: 10)","default":5},"post_ids":{"type":"array","items":{"type":"integer"},"maxItems":2,"description":"Specific post/page IDs to search within. Use ONLY when custom instructions explicitly tell you to search specific post IDs for certain topics."}},"required":["query"]}}}],"api_configured":true}};
3187 +//# sourceURL=listeo-ai-chat-js-extra
3188 +</script>
3189 +<script src="https://abitibi-temiscamingue.org/wp-content/plugins/ai-chat-search/assets/js/ai-chatbot-core.js?ver=1.9.7" id="listeo-ai-chat-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3190 +<script id="listeo-ai-floating-chat-js-extra" type="0316cf47fcdef64099276bd1-text/javascript">
3191 +var listeoAiFloatingChatConfig = {"welcomeBubbleMessage":"","buttonIcon":"default","strings":{"openChat":"Open chat","closeChat":"Close chat"}};
3192 +//# sourceURL=listeo-ai-floating-chat-js-extra
3193 +</script>
3194 +<script src="https://abitibi-temiscamingue.org/wp-content/plugins/ai-chat-search/assets/js/ai-floating-widget.js?ver=1.9.7" id="listeo-ai-floating-chat-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3195 +<script id="ai-chat-search-js-extra" type="0316cf47fcdef64099276bd1-text/javascript">
3196 +var listeoAiSearch = {"ajax_url":"https://abitibi-temiscamingue.org/wp-admin/admin-ajax.php","nonce":"f5d8ff989e","debugMode":"","ai_enabled":"1","max_results":"10","search_url":"https://abitibi-temiscamingue.org","default_thumbnail":"","strings":{"searching":"Searching...","no_results":"No results found.","error":"Search error occurred.","best_match":"Best Match","type_keywords_first":"Type keywords first","top_listing_singular":"Top 1 listing matching","top_listings_plural":"Top %d listings matching","rateLimitError":"Too many searches. Please wait a moment and try again.","apiUnavailable":"AI search temporarily unavailable. Using basic search instead.","sessionExpired":"Session expired. Please refresh the page.","fallbackNotice":"Showing regular search results instead.","inStock":"In Stock","outOfStock":"Out of Stock"}};
3197 +//# sourceURL=ai-chat-search-js-extra
3198 +</script>
3199 +<script src="https://abitibi-temiscamingue.org/wp-content/plugins/ai-chat-search/assets/js/search.js?ver=1.9.7" id="ai-chat-search-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3200 +<script id="pll_cookie_script-js-after" type="0316cf47fcdef64099276bd1-text/javascript">
3201 +(function() {
3202 + var expirationDate = new Date();
3203 + expirationDate.setTime( expirationDate.getTime() + 31536000 * 1000 );
3204 + document.cookie = "pll_language=fr; expires=" + expirationDate.toUTCString() + "; path=/; secure; SameSite=Lax";
3205 + }());
3206 +
3207 +//# sourceURL=pll_cookie_script-js-after
3208 +</script>
3209 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/bootstrap.bundle.min.js?ver=6.9.7" id="bootstrap-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3210 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/jquery.mixitup.min.js?ver=6.9.7" id="mixitup-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3211 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/swiper-bundle.min.js?ver=6.9.7" id="swiper-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3212 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/jquery.magnific-popup.min.js?ver=6.9.7" id="magnific-popup-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3213 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/viewport.jquery.min.js?ver=6.9.7" id="viewport-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3214 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/gsap.min.js?ver=6.9.7" id="gsap-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3215 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/gsap-scroll-smoother.min.js?ver=6.9.7" id="gsap-scroll-smoother-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3216 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/gsap-scroll-to-plugin.min.js?ver=6.9.7" id="gsap-scroll-to-plugin-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3217 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/gsap-scroll-trigger.min.js?ver=6.9.7" id="gsap-scroll-trigge-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3218 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/gsap-split-text.min.js?ver=6.9.7" id="gsap-split-text-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3219 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/menu.min.js?ver=6.9.7" id="menu-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3220 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/assets/js/main.min.js?ver=6.9.7" id="main-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3221 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/js/header-drawer-menu.min.js?ver=1.0.02" id="header-drawer-menu-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3222 +<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyA-1mWbAJW2RTGCjoRmbVLRMTd5m6I0iUo&amp;libraries=places" id="google-maps-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3223 +<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js" id="google-maps-markerclusterer-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3224 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/js/lebleu.min.js?ver=1.0.0130" id="lebleu-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3225 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/js/lp.min.js?ver=1.0.0" id="lp-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3226 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/js/lebleu-timeline.min.js?ver=1.0.01" id="lebleu-timeline-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3227 +<script id="repertoire-script-js-extra" type="0316cf47fcdef64099276bd1-text/javascript">
3228 +var repertoirePreview = {"ajaxurl":"https://abitibi-temiscamingue.org/wp-admin/admin-ajax.php","modalAction":"lebleu_repertoire_preview_modal_html","previewAction":"lebleu_repertoire_preview_html","modalId":"repertoirePreviewModal","subcategoriesLabel":"Sous-cat\u00e9gories","allLabel":"Tout"};
3229 +//# sourceURL=repertoire-script-js-extra
3230 +</script>
3231 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/js/repertoire.min.js?ver=1.0.08" id="repertoire-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3232 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/js/drawer-filters.min.js?ver=1.0.05" id="drawer-filters-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3233 +<script id="lebleu-itineraire-js-extra" type="0316cf47fcdef64099276bd1-text/javascript">
3234 +var myAjax = {"ajaxurl":"https://abitibi-temiscamingue.org/wp-admin/admin-ajax.php","itineraireModalAction":"lebleu_itineraire_modal_html","itineraireExportModalAction":"lebleu_itineraire_export_modal_html","itineraireAddHeading":"Ajout\u00e9 avec succ\u00e8s","itineraireAddMessage":"%s a \u00e9t\u00e9 ajout\u00e9 \u00e0 votre itin\u00e9raire.","itineraireRemoveHeading":"Retir\u00e9 avec succ\u00e8s","itineraireRemoveMessage":"%s a \u00e9t\u00e9 retir\u00e9 de votre itin\u00e9raire.","closeLabel":"Fermer","closeIcon":" \u003Csvg \n data-icon=\"Close\"\n viewBox=\"0 0 20 20\"\n width=\"20\"\n height=\"20\"\n class=\"\"\n fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"\u003E\n \u003Cpath d=\"M8.58981 10.0001L0.796875 2.20718L2.21109 0.792969L10.004 8.58582L17.7969 0.792969L19.2111 2.20718L11.4182 10.0001L19.2111 17.7929L17.7969 19.2072L10.004 11.4143L2.21109 19.2072L0.796875 17.7929L8.58981 10.0001Z\" fill=\"currentColor\"\u003E\u003C/path\u003E \u003C/svg\u003E\n "};
3235 +//# sourceURL=lebleu-itineraire-js-extra
3236 +</script>
3237 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/js/lebleu-itineraire.min.js?ver=1.0.06" id="lebleu-itineraire-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3238 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/lib/slide-menu/slide-menu.modern.min.js?ver=1.0.21" id="slide-menu-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3239 +<script src="https://abitibi-temiscamingue.org/wp-content/themes/lebleu/lib/slide-menu/lebleu.min.js?ver=1.0.41" id="slide-menu-demo-script-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3240 +<script src="https://abitibi-temiscamingue.org/wp-content/plugins/google-site-kit/dist/assets/js/googlesitekit-events-provider-wpforms-08f4a4ca33532233b542.js" id="googlesitekit-events-provider-wpforms-js" defer type="0316cf47fcdef64099276bd1-text/javascript"></script>
3241 +<script id="cmplz-cookiebanner-js-extra" type="0316cf47fcdef64099276bd1-text/javascript">
3242 +var complianz = {"prefix":"cmplz_","user_banner_id":"1","set_cookies":[],"block_ajax_content":"","banner_version":"33806","version":"7.4.6","store_consent":"","do_not_track_enabled":"","consenttype":"optin","region":"ca","geoip":"","dismiss_timeout":"","disable_cookiebanner":"","soft_cookiewall":"","dismiss_on_scroll":"","cookie_expiry":"365","url":"https://abitibi-temiscamingue.org/wp-json/complianz/v1/","locale":"lang=fr&locale=fr_CA","set_cookies_on_root":"","cookie_domain":"","current_policy_id":"37","cookie_path":"/","categories":{"statistics":"statistics","marketing":"marketing"},"tcf_active":"","placeholdertext":"Click to accept {category} cookies and enable this content","css_file":"https://abitibi-temiscamingue.org/wp-content/uploads/complianz/css/banner-{banner_id}-{type}.css?v=33806","page_links":{"ca":{"cookie-statement":{"title":"Politique de confidentialit\u00e9 et de renseignements personnels","url":"https://abitibi-temiscamingue.org/politique-de-confidentialite/"}}},"tm_categories":"1","forceEnableStats":"","preview":"","clean_cookies":"","aria_label":"Click to accept {category} cookies and enable this content"};
3243 +//# sourceURL=cmplz-cookiebanner-js-extra
3244 +</script>
3245 +<script defer src="https://abitibi-temiscamingue.org/wp-content/plugins/complianz-gdpr/cookiebanner/js/complianz.min.js?ver=1778006123" id="cmplz-cookiebanner-js" type="0316cf47fcdef64099276bd1-text/javascript"></script>
3246 + <script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="0316cf47fcdef64099276bd1-|49" defer></script><script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4513226cdae34746b4dedf0b4dfa099e1781791509496" integrity="sha512-ZE9pZaUXND66v380QUtch/5sE9tPFh2zg45pR2PB0CVkCtOREv2AJKkSidISWkysEuQ0EH8faUU5du78bx87UQ==" data-cf-beacon='{"version":"2024.11.0","token":"9a9d605522ca40ffa2a8196e576e5fa1"}' crossorigin="anonymous"></script>
3247 +<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a30dcd674a3c2134',t:'MTc4NzY5NDUyMQ=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
3248 +</html>
3249 +
3250 +
3251 +<!-- Page cached by LiteSpeed Cache 7.9 on 2026-08-25 17:19:33 -->
\ No newline at end of file
added tests/fixtures/atuvu_fiche_multiseances.html +1583 −0
@@ -0,0 +1,1583 @@
1 +<!DOCTYPE html>
2 +<html lang="fr" dir="ltr">
3 +
4 +<head>
5 + <title>Ballet of Lights: La Belle au bois dormant dans un spectacle étincelant: tarifs réduits et billets gratuits | atuvu.ca</title>
6 +<meta name="title" content="Ballet of Lights: La Belle au bois dormant dans un spectacle étincelant: tarifs réduits et billets gratuits | atuvu.ca" />
7 +<meta name="description" content="Ballet of Lights: La Belle au bois dormant dans un spectacle étincelant: trouvez des billets à tarif réduit et des billets gratuits. Jusqu'à 80% de rabais. Devenez membre d'atuvu.ca et profitez des offres exclusives." />
8 +<meta property="keywords" content="atuvu.ca, Danse, Ballet, , " />
9 +
10 +
11 +<meta property="og:title" content="Ballet of Lights: La Belle au bois dormant dans un spectacle étincelant: tarifs réduits et billets gratuits | atuvu.ca" />
12 +<meta property="og:description" content="Ballet of Lights: La Belle au bois dormant dans un spectacle étincelant: trouvez des billets à tarif réduit et des billets gratuits. Jusqu'à 80% de rabais. Devenez membre d'atuvu.ca et profitez des offres exclusives." />
13 +<meta property="og:image" content="https://atuvu.ca/uploads/images/2026/07/we-call-it-ballet-belle-au-bois-dormant-danse-spectacle.jpg" />
14 +<meta property="og:url" content="https://atuvu.ca/billets-spectacle/we-call-it-ballet-la-belle-au-bois-dormant-danse-et-spectacle-de-lumiere/" />
15 + <meta property="og:type" content="Article" />
16 +
17 +<meta charset="utf-8">
18 +<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1">
19 +
20 +
21 +<link rel="home" href="https://atuvu.ca">
22 +<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
23 +<link rel="icon" type="image/png"
24 + href="https://atuvu.ca/img/atuvu/favicon.png">
25 +
26 +
27 +<link rel="manifest" href="https://atuvu.ca/manifest.json">
28 +<meta name="mobile-web-app-capable" content="yes">
29 +<meta name="theme-color" content="#ffffff">
30 +
31 +
32 +<meta name="apple-mobile-web-app-capable" content="yes">
33 +<meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
34 +<meta name="apple-mobile-web-app-title" content="atuvu.ca">
35 +<link rel="apple-touch-icon" sizes="180x180" href="https://atuvu.ca/img/atuvu/icon180.png">
36 +
37 +
38 +<meta name="msapplication-TileImage" content="https://atuvu.ca/img/atuvu/icon512.png">
39 +<meta name="msapplication-TileColor" content="#ffffff">
40 +
41 +
42 +<meta property="fb:app_id" content="178489298841730" />
43 +<meta property="og:locale" content="fr_CA" />
44 +
45 +
46 +
47 +<link rel="alternate" type="application/rss+xml" title="Magazine Culturel atuvu.ca" href="https://atuvu.ca/rss.xml" />
48 +
49 +
50 +<link rel="preconnect" href="https://www.googletagmanager.com">
51 +<link rel="preconnect" href="https://connect.facebook.net">
52 +<link rel="preconnect" href="https://www.google-analytics.com">
53 +
54 +
55 +<link rel="preconnect" href="https://fonts.googleapis.com">
56 +<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
57 +<link href="https://fonts.googleapis.com/css2?family=Muli:wght@400;600;700;800&display=swap" rel="stylesheet">
58 +
59 +
60 +
61 +
62 +
63 +
64 +
65 +<link rel="preload" as="style" href="https://atuvu.ca/build/assets/fr-DJAvaHnz.css" /><link rel="preload" as="style" href="https://atuvu.ca/build/assets/main-BppCHRAA.css" /><link rel="modulepreload" href="https://atuvu.ca/build/assets/main-DhUaDXjD.js" /><link rel="modulepreload" href="https://atuvu.ca/build/assets/fr-D79UFmCT.js" /><link rel="stylesheet" href="https://atuvu.ca/build/assets/fr-DJAvaHnz.css" /><link rel="stylesheet" href="https://atuvu.ca/build/assets/main-BppCHRAA.css" /><script type="module" src="https://atuvu.ca/build/assets/main-DhUaDXjD.js"></script>
66 +
67 +
68 +
69 +<script>
70 + (function(w, d, s, l, i) {
71 + w[l] = w[l] || [];
72 + w[l].push({
73 + 'gtm.start': new Date().getTime(),
74 + event: 'gtm.js'
75 + });
76 + var f = d.getElementsByTagName(s)[0],
77 + j = d.createElement(s),
78 + dl = l != 'dataLayer' ? '&l=' + l : '';
79 + j.async = true;
80 + j.src =
81 + 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
82 + f.parentNode.insertBefore(j, f);
83 + })(window, document, 'script', 'dataLayer', 'GTM-5892GNR');
84 +</script>
85 +
86 +<script src="https://storage.googleapis.com/fuel_modules/modules/FD_HB/266033/Fuel_id_266033.js"  async></script>
87 +</head>
88 +
89 +<body class="">
90 + <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5892GNR" height="0" width="0"
91 + style="display:none;visibility:hidden"></iframe></noscript>
92 +
93 +
94 +
95 +
96 + <div class="pop-up">
97 + <div class="popuptotal">
98 + <div class="pop-up--content">
99 + <!-- Bouton de fermeture -->
100 + <button class="pop-up__close button-rounded js-close-button" data-close="menu">
101 + <img src="https://atuvu.ca/img/icons/cross-nav-bar.svg" alt="Fermer">
102 + </button>
103 +
104 + <!-- Bloc gris pour le logo -->
105 + <div class="logo-box">
106 + <img class="imglogopopup" src="/img/atuvu/atuvu.svg" alt="atuvu.ca">
107 + </div>
108 +
109 + <!-- Texte (titre + sous-titre) -->
110 + <div class="text">
111 + <h2>Inscrivez-vous</h2>
112 + <p>Des offres exclusives et événements gratuits</p>
113 + </div>
114 +
115 + <!-- Boutons (nouveau design) -->
116 + <div class="popup-btn">
117 + <a href="https://atuvu.ca/inscription" class="btn-outline">Inscription</a>
118 + <a href="https://atuvu.ca/devenir-membre" class="btn-filled">En savoir plus</a>
119 + </div>
120 + </div>
121 + </div>
122 +</div>
123 +
124 +<div id="adblock-popup" class="atuvu-popup atuvux-adblock-modal" style="display: none;">
125 + <div class="atuvu-popup__overlay"></div>
126 + <div class="atuvu-popup__content">
127 + <button class="pop-up__close button-rounded js-adblock-close" aria-label="Fermer">
128 + <img src="https://atuvu.ca/img/icons/cross-nav-bar.svg" alt="Fermer">
129 + </button>
130 +
131 + <!-- STEP 1 -->
132 + <div id="adblock-step-1">
133 + <div class="atuvu-popup__header">
134 + <img class="atuvu-popup__logo" src="/img/atuvu/atuvu.svg" alt="atuvu.ca">
135 + </div>
136 +
137 + <h2 class="atuvu-popup__title">Bloqueur de publicité détecté !</h2>
138 +
139 + <div class="atuvu-popup__body">
140 + <p>Les publicités soutiennent notre média culturel indépendant. Elles nous aident à :</p>
141 + <ul>
142 + <li>faire rayonner la culture d’ici</li>
143 + <li>financer du contenu original de qualité</li>
144 + <li>proposer des offres exclusives à nos membres</li>
145 + </ul>
146 + <p>En désactivant votre bloqueur pour <a href="/">atuvu.ca</a>, vous contribuez à la vitalité de notre média et de notre communauté.</p>
147 + <p>Merci pour votre soutien !</p>
148 + </div>
149 +
150 + <div class="atuvu-popup__actions">
151 + <button class="btn-filled js-adblock-next" id="btn-adblock-next">Désactiver mon bloqueur pour soutenir atuvu.ca</button>
152 + </div>
153 + </div>
154 +
155 + <!-- STEP 2 -->
156 + <div id="adblock-step-2" style="display: none;">
157 + <div class="atuvu-popup__header">
158 + <img class="atuvu-popup__logo" src="/img/atuvu/atuvu.svg" alt="atuvu.ca">
159 + </div>
160 +
161 + <h2 class="atuvu-popup__title">Comment désactiver votre bloqueur de publicité pour atuvu.ca ?</h2>
162 +
163 + <div class="atuvu-popup__body instruction-body">
164 + <p><strong>1. Repérez l'icône de votre bloqueur</strong> de publicité en haut à droite de votre navigateur.</p>
165 + <div class="adblock-icons">
166 + <div class="adblock-icon"><img src="https://atuvu.ca/img/adblock/adblock-plus.png" alt="Adblock Plus"></div>
167 + <div class="adblock-icon"><img src="https://atuvu.ca/img/adblock/adblock.png" alt="Adblock"></div>
168 + <div class="adblock-icon"><img src="https://atuvu.ca/img/adblock/ublock-origin.png" alt="uBlock Origin"></div>
169 + <div class="adblock-icon"><img src="https://atuvu.ca/img/adblock/ublock.png" alt="uBlock"></div>
170 + <div class="adblock-icon"><img src="https://atuvu.ca/img/adblock/ghostery.png" alt="Ghostery"></div>
171 + </div>
172 + <p><strong>2. Cliquez sur l'icône, puis désactivez le blocage</strong> pour le site atuvu.ca.</p>
173 + <p><strong>3. Actualisez la page.</strong></p>
174 +
175 + <h3 class="instruction-subtitle">Vous ne voyez pas votre bloqueur ?</h3>
176 + <p><strong>Ouvrez le menu de votre navigateur</strong> (souvent trois points, en haut à droite), puis allez dans Extensions ou Modules complémentaires pour gérer vos bloqueurs de publicité.</p>
177 +
178 + <p class="atuvu-popup__footer-text">Merci de contribuer à faire rayonner la culture d’ici.</p>
179 + </div>
180 +
181 + <div class="atuvu-popup__actions instruction-actions">
182 + <button class="btn-filled js-adblock-reload">Actualiser la page</button>
183 + </div>
184 + </div>
185 + </div>
186 +</div>
187 +
188 +<header class="totalheader">
189 + <div class="nav-overlay js-close-button" data-close="menu"></div>
190 + <div class="top-red-bar">Passionnés de spectacles et de culture</div>
191 + <div class="nav-sidebar">
192 + <div class="nav-sidebar__content">
193 + <!-- Bouton burger -->
194 + <button class="nav-sidebar__toggle">
195 + <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
196 + <path d="M3 6H21M3 12H21M3 18H21" stroke="white" stroke-width="2" stroke-linecap="round" />
197 + </svg>
198 + </button>
199 +
200 + <!-- Icônes de la barre rouge -->
201 + <ul class="nav-sidebar__icons">
202 + <li class="icon-home">
203 + <a href="https://atuvu.ca" class="tooltip" data-tooltip="Accueil">
204 + <img src="https://atuvu.ca/img/icons/home.svg" alt="Accueil">
205 + </a>
206 + </li>
207 + <li class="icon-articles">
208 + <a href="https://atuvu.ca/magazine-culturel" class="tooltip" data-tooltip="Magazine">
209 + <img src="https://atuvu.ca/img/icons/magazine.svg" alt="magazine">
210 + </a>
211 + </li>
212 + <li class="icon-Offres">
213 + <a href="https://atuvu.ca/programmation-culturelle" class="tooltip" data-tooltip="Offres">
214 + <img src="https://atuvu.ca/img/icons/offres.svg" alt="Offres">
215 + </a>
216 + </li>
217 + <li class="icon-calendar">
218 + <a data-close="calendar" href="#" class="tooltip" data-tooltip="Calendrier">
219 + <img src="https://atuvu.ca/img/icons/calendar-navbar.svg" alt="Calendrier">
220 + </a>
221 + </li>
222 + <li class="user-connection">
223 + <a href="https://atuvu.ca/connexion" class="user-menu">
224 + <img src="https://atuvu.ca/img/icons/connection-button-mobile.svg" alt="">
225 + </a>
226 + </li>
227 + </ul>
228 + </div>
229 +</div>
230 +
231 +<!-- Nouvelle Navbar -->
232 +<div class="nav-sidebar__menu">
233 + <!-- Bouton de fermeture -->
234 + <button class="nav-sidebar__menu__close button-rounded js-close-button" data-close="menu">
235 + <img src="https://atuvu.ca/img/icons/cross-nav-bar.svg" alt="Flèche">
236 + </button>
237 +
238 + <!-- Logo -->
239 + <a href="https://atuvu.ca" class="nav-sidebar__logo">
240 + <img src="/img/atuvu/atuvu.svg" alt="atuvu.ca">
241 + </a>
242 +
243 + <!-- Liens du menu -->
244 + <ul class="nav-sidebar__links">
245 +
246 +
247 + <li>
248 + <a href="https://atuvu.ca">
249 + Accueil
250 + <span><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
251 + </a>
252 + </li>
253 +
254 + <li class="nav-sidebar__parent">
255 + <a href="https://atuvu.ca/magazine-culturel">
256 + Magazine
257 + <span class="nav-arrow"><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
258 + </a>
259 + <ul class="nav-sidebar__sub-links">
260 + <!-- Lien vers toutes les rubriques -->
261 + <li>
262 + <a href="https://atuvu.ca/magazine-culturel">Toutes les rubriques</a>
263 + </li>
264 +
265 + <!-- Rubriques dynamiques : $rubricService->withRecentArticles() -->
266 + <li>
267 + <a href="https://atuvu.ca/magazine-culturel/nouvelles-culturelles">
268 + Nouvelles
269 + </a>
270 + </li>
271 + <li>
272 + <a href="https://atuvu.ca/magazine-culturel/zoom-photo">
273 + Zoom photo
274 + </a>
275 + </li>
276 + <li>
277 + <a href="https://atuvu.ca/magazine-culturel/suggestion-sortie">
278 + Suggestions de sorties
279 + </a>
280 + </li>
281 + <li>
282 + <a href="https://atuvu.ca/magazine-culturel/critiques">
283 + Critiques
284 + </a>
285 + </li>
286 + <li>
287 + <a href="https://atuvu.ca/magazine-culturel/entrevue">
288 + Entrevue
289 + </a>
290 + </li>
291 + <li>
292 + <a href="https://atuvu.ca/magazine-culturel/suggestions-lecture">
293 + Suggestions de lecture
294 + </a>
295 + </li>
296 + <li>
297 + <a href="https://atuvu.ca/magazine-culturel/concours-prix-a-gagner">
298 + Concours
299 + </a>
300 + </li>
301 + <li>
302 + <a href="https://atuvu.ca/magazine-culturel/actualite-musique-concert">
303 + Musique
304 + </a>
305 + </li>
306 + <li>
307 + <a href="https://atuvu.ca/magazine-culturel/actu-musicale">
308 + Actu Musicale
309 + </a>
310 + </li>
311 + <li>
312 + <a href="https://atuvu.ca/magazine-culturel/actualite-cinema">
313 + Cinéma
314 + </a>
315 + </li>
316 + </ul>
317 + </li>
318 +
319 +
320 +
321 + <li class="nav-sidebar__parent">
322 + <a href="https://atuvu.ca/programmation-culturelle">
323 + Nos offres
324 + <span class="nav-arrow"><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
325 + </a>
326 + <ul class="nav-sidebar__sub-links">
327 +
328 +
329 + <li><a href="https://atuvu.ca/liste-des-evenements-en-vedette">En vedette</a></li>
330 + <li><a href="https://atuvu.ca/activite-gratuite">Gratuit</a></li>
331 + <li><a href="https://atuvu.ca/billet-de-spectacle-vip">Offres VIP</a></li>
332 + <li><a href="https://atuvu.ca/offres-privees">Offres privées: connectez-vous!</a></li>
333 + <li><a href="https://atuvu.ca/nouvelles-offres-promo">Nouvelles offres</a></li>
334 +
335 +
336 +
337 +
338 + <li>
339 + <a href="https://atuvu.ca/billets/actualite-cinema">Cinéma</a>
340 + </li>
341 + <li>
342 + <a href="https://atuvu.ca/billets/spectacle-cirque">Cirque</a>
343 + </li>
344 + <li>
345 + <a href="https://atuvu.ca/billets/spectacle-de-conte">Conte</a>
346 + </li>
347 + <li>
348 + <a href="https://atuvu.ca/billets/spectacle-de-danse">Danse</a>
349 + </li>
350 + <li>
351 + <a href="https://atuvu.ca/billets/activites-a-faire">Divers</a>
352 + </li>
353 + <li>
354 + <a href="https://atuvu.ca/billets/exposition-musee">Exposition</a>
355 + </li>
356 + <li>
357 + <a href="https://atuvu.ca/billets/spectacle-humour">Humour</a>
358 + </li>
359 + <li>
360 + <a href="https://atuvu.ca/billets/spectacle-improvisation">Improvisation</a>
361 + </li>
362 + <li>
363 + <a href="https://atuvu.ca/billets/jeux">Jeux</a>
364 + </li>
365 + <li>
366 + <a href="https://atuvu.ca/billets/concert-de-musique">Musique</a>
367 + </li>
368 + <li>
369 + <a href="https://atuvu.ca/billets/piece-de-theatre">Théâtre</a>
370 + </li>
371 +
372 +
373 + </ul>
374 + </li>
375 +
376 +
377 + <li class="nav-sidebar__parent">
378 + <li class="nav-sidebar__parent">
379 + <a href="#" onclick="event.preventDefault(); document.getElementById('calendarModal').classList.add('open');">
380 + Calendrier
381 + <span>
382 + <img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche">
383 + </span>
384 + </a>
385 +
386 + <!-- Sous-menu filtrant : Aujourd'hui, Demain, etc. -->
387 + <ul class="nav-sidebar__sub-links sub-calendar">
388 +
389 + <li>
390 + <a href="#" onclick="event.preventDefault(); document.getElementById('todayForm').submit();">
391 + Aujourd'hui
392 + </a>
393 + <form id="todayForm" action="https://atuvu.ca/calendrier-des-evenements" method="get" style="display:none;">
394 + <input type="hidden" name="start_date" value="1787630400">
395 + <input type="hidden" name="end_date" value="1787716800">
396 + </form>
397 + </li>
398 +
399 +
400 + <li>
401 + <a href="#" onclick="event.preventDefault(); document.getElementById('tomorrowForm').submit();">
402 + Demain
403 + </a>
404 + <form id="tomorrowForm" action="https://atuvu.ca/calendrier-des-evenements" method="get" style="display:none;">
405 + <input type="hidden" name="start_date" value="1787716800">
406 + <input type="hidden" name="end_date" value="1787803200">
407 + </form>
408 + </li>
409 +
410 +
411 + <li>
412 + <a href="#" onclick="event.preventDefault(); document.getElementById('weekForm').submit();">
413 + Cette semaine
414 + </a>
415 + <form id="weekForm" action="https://atuvu.ca/calendrier-des-evenements" method="get" style="display:none;">
416 + <input type="hidden" name="start_date" value="1787630400">
417 + <input type="hidden" name="end_date" value="1788148800">
418 + </form>
419 + </li>
420 +
421 +
422 + <li>
423 + <a href="#" onclick="event.preventDefault(); document.getElementById('weekendForm').submit();">
424 + Ce weekend
425 + </a>
426 + <form id="weekendForm" action="https://atuvu.ca/calendrier-des-evenements" method="get" style="display:none;">
427 + <input type="hidden" name="start_date" value="1787976000">
428 + <input type="hidden" name="end_date" value="1788148800">
429 + </form>
430 + </li>
431 +
432 +
433 + <li>
434 + <a href="#" onclick="event.preventDefault(); document.getElementById('monthForm').submit();">
435 + Ce mois-ci
436 + </a>
437 + <form id="monthForm" action="https://atuvu.ca/calendrier-des-evenements" method="get" style="display:none;">
438 + <input type="hidden" name="start_date" value="1787630400">
439 + <input type="hidden" name="end_date" value="1788235200">
440 + </form>
441 + </li>
442 + </ul>
443 +</li>
444 +<!-- MODAL Calendrier (en dehors du <li>) -->
445 +<div class="calendar-modal" id="calendarModal">
446 + <!-- Bouton de fermeture du calendrier -->
447 + <button class="calendar-modal__close js-close-button button-rounded" data-close="calendar">
448 + <img src="https://atuvu.ca/img/icons/cross-nav-bar.svg" alt="Fermer le calendrier">
449 + </button>
450 +
451 + <a href="https://atuvu.ca" class="nav-sidebar__logo">
452 + <img src="/img/atuvu/atuvu.svg" alt="atuvu.ca">
453 + </a>
454 +
455 +
456 +
457 + <!-- Conteneur de navigation du mois -->
458 + <div class="calendar-modal__month-nav">
459 + <!-- Bouton flèche gauche -->
460 + <button class="calendar-modal__nav-btn flatpickr-prev-month">
461 + <img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche gauche">
462 + </button>
463 +
464 + <!-- Titre du mois -->
465 + <span id="flatpickrMonthTitle" class="calendar-modal__month-name"></span>
466 +
467 + <!-- Bouton flèche droite -->
468 + <button class="calendar-modal__nav-btn flatpickr-next-month">
469 + <img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche droite">
470 + </button>
471 + </div>
472 +
473 +
474 + <!-- Conteneur Flatpickr -->
475 + <div id="flatpickrContainer"></div>
476 +
477 + <!-- Form caché pour soumettre l’intervalle -->
478 + <form id="flatpickrRangeForm" action="https://atuvu.ca/calendrier-des-evenements" method="get" style="display: none;">
479 + <input type="hidden" name="start_date" id="rangeStart">
480 + <input type="hidden" name="end_date" id="rangeEnd">
481 + </form>
482 +
483 + <!-- Boutons de raccourci -->
484 + <div class="calendar-modal__footer-btns">
485 + <button class="calendar-modal__footer-btn" data-range="week"
486 + onclick="event.preventDefault(); document.getElementById('tomorrowForm').submit();">
487 + Cette semaine
488 + </button>
489 + <button class="calendar-modal__footer-btn" data-range="weekend"
490 + onclick="event.preventDefault(); document.getElementById('weekendForm').submit();">
491 + Ce weekend
492 + </button>
493 + <button class="calendar-modal__footer-btn" data-range="month"
494 + onclick="event.preventDefault(); document.getElementById('monthForm').submit();">
495 + Ce mois-ci
496 + </button>
497 + </div>
498 +</div>
499 + </li>
500 +
501 +
502 + <li class="nav-sidebar__parent">
503 + <a href="https://atuvu.ca/boutique">
504 + Boutique
505 + <span class="nav-arrow"><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
506 + </a>
507 +
508 + <ul class="nav-sidebar__sub-links">
509 +
510 + <li><a href="https://atuvu.ca/produits-vedette">En vedette</a></li>
511 + <li><a href="https://atuvu.ca/produits-nouveaute">Nouveautés</a></li>
512 + <li><a href="https://atuvu.ca/produits-promo">Promotions</a></li>
513 + <li><a href="https://atuvu.ca/boutique">Tous les produits</a></li>
514 +
515 +
516 +
517 +
518 +
519 + <li>
520 + <a href="https://atuvu.ca/boutique/actualite-cinema">
521 + Cinéma
522 + </a>
523 + </li>
524 +
525 + <li>
526 + <a href="https://atuvu.ca/boutique/jeux">
527 + Jeux
528 + </a>
529 + </li>
530 +
531 +
532 + <li>
533 + <a href="https://atuvu.ca/boutique/piece-de-theatre">
534 + Théâtre
535 + </a>
536 + </li>
537 + </ul>
538 + </li>
539 +
540 +
541 +
542 + <li class="nav-sidebar__parent">
543 + <a href="https://atuvu.ca/villes">
544 + Villes
545 + <span class="nav-arrow"><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
546 + </a>
547 + <ul class="nav-sidebar__sub-links">
548 + <li>
549 + <a href="https://atuvu.ca/ville/montreal">
550 + Montréal
551 + </a>
552 + </li>
553 + <li>
554 + <a href="https://atuvu.ca/ville/saint-jean-sur-richelieu">
555 + Saint-Jean-sur-Richelieu
556 + </a>
557 + </li>
558 + <li>
559 + <a href="https://atuvu.ca/ville/sherbrooke">
560 + Sherbrooke
561 + </a>
562 + </li>
563 + <li>
564 + <a href="https://atuvu.ca/ville/repentigny">
565 + Repentigny
566 + </a>
567 + </li>
568 + <li>
569 + <a href="https://atuvu.ca/ville/laval">
570 + Laval
571 + </a>
572 + </li>
573 + <li><a href="https://atuvu.ca/villes">Toutes les villes</a></li>
574 + </ul>
575 + </li>
576 +
577 +
578 + <li class="nav-sidebar__parent">
579 + <a href="https://atuvu.ca/festivals">
580 + Festivals
581 + <span class="nav-arrow"><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
582 + </a>
583 + <ul class="nav-sidebar__sub-links">
584 + <li>
585 + <a href="https://atuvu.ca/festival/les-dimanches-du-conte">
586 + Les Dimanches du conte
587 + </a>
588 + </li>
589 + <li>
590 + <a href="https://atuvu.ca/festival/festival-martinique-gourmande">
591 + Festival Martinique Gourmande
592 + </a>
593 + </li>
594 + <li>
595 + <a href="https://atuvu.ca/festival/festisable-st-come">
596 + FESTISABLE St-Côme
597 + </a>
598 + </li>
599 + <li>
600 + <a href="https://atuvu.ca/festival/festival-de-montgolfi%C3%A8res-de-gatineau">
601 + Festival de montgolfières de Gatineau (FMG)
602 + </a>
603 + </li>
604 + <li>
605 + <a href="https://atuvu.ca/festival/festival-de-musique-emergente">
606 + Festival de musique émergente (FME)
607 + </a>
608 + </li>
609 + <li><a href="https://atuvu.ca/festivals">Tous les festivals</a></li>
610 + </ul>
611 + </li>
612 +
613 +
614 + <li>
615 + <a href="https://atuvu.ca/communaute-atuvu">
616 + Communauté
617 + <span><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
618 + </a>
619 + </li>
620 +
621 +
622 + <li>
623 + <a href="https://atuvu.ca/inscription">
624 + Inscription
625 + <span><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
626 + </a>
627 + </li>
628 +
629 +
630 +
631 +
632 +
633 +
634 +
635 +
636 + <li class="nav-sidebar__parent">
637 + <!-- Pour les non-connectés, on peut afficher deux items "Connexion" et "Devenir Membre" comme les autres liens. -->
638 + <li class="nav-sidebar__parent">
639 + <a href="https://atuvu.ca/connexion">
640 + Connexion
641 + <span class="nav-arrow"><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
642 + </a>
643 + </li>
644 + </li>
645 +
646 +
647 + <li>
648 + <a href="https://atuvu.ca/choisir-un-abonnement">
649 + Abonnement VIP
650 + <span><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
651 + </a>
652 + </li>
653 +
654 +
655 +
656 +
657 +
658 +
659 +
660 + <li class="nav-sidebar__parent">
661 + <!-- Si le producteur n'est pas connecté, affiche le lien par défaut -->
662 + <li class="nav-sidebar__parent">
663 + <a href="https://atuvu.ca/producteur/ajouter-un-evenement">
664 + Promouvoir
665 + <span class="nav-arrow"><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
666 + </a>
667 + <ul class="nav-sidebar__sub-links">
668 + <li><a href="https://atuvu.ca/producteur/ajouter-un-evenement">Inscrire un événement</a></li>
669 + <li><a href="https://atuvu.ca/media-kit">Annoncer avec nous</a></li>
670 + </ul>
671 + </li>
672 + </li>
673 +
674 +
675 + <li>
676 + <a href="https://atuvu.ca/contact">
677 + Nous contacter
678 + <span><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
679 + </a>
680 + </li>
681 +
682 +
683 + <li>
684 + <a href="https://atuvu.ca/a-propos">
685 + À propos
686 + <span><img src="https://atuvu.ca/img/icons/arrow-nav-bar.svg" alt="Flèche"></span>
687 + </a>
688 + </li>
689 +
690 + </ul>
691 +</div>
692 +
693 + <!-- GPT AdSlot 8 for Ad unit 'cc_atuvu_970x_1' ### Size: [[970,90],[970,250],[970,500]] -->
694 +<!-- Adslot's refresh function: googletag.pubads().refresh([gptadslots[7]]) -->
695 +<div id='div-gpt-ad-1026097-8' class="ad-mast">
696 +</div>
697 +
698 +<!-- End AdSlot 8 -->
699 +
700 + <section class="main-navbar">
701 + <div class="wrapper">
702 + <div class="left">
703 + <a href="https://atuvu.ca" class="main-logo">
704 + <img src="/img/atuvu/atuvu.svg" alt="atuvu.ca">
705 + </a>
706 + <span class="phraselogo">Passionnés de spectacles et de culture</span>
707 + </div>
708 + <div class="right">
709 + <ul>
710 + <li class="promouvoir tooltip tooltip-bottom" data-tooltip="Inscrivez votre événement">
711 + <a href="https://atuvu.ca/producteur/ajouter-un-evenement">
712 + Promouvoir
713 + </a>
714 + </li>
715 + <li class="inscription-btn tooltip tooltip-bottom" data-tooltip="Devenez membre">
716 + <a href="https://atuvu.ca/inscription" class="btn inscription">Inscription</a>
717 + </li>
718 +
719 +
720 + <li class="user-connection tooltip tooltip-bottom" data-tooltip="Connexion membre">
721 + <a href="https://atuvu.ca/connexion" class="user-menu">
722 + <img src="https://atuvu.ca/img/icons/connection-button.svg" alt="">
723 + </a>
724 + <li class="search-icon ">
725 + <button type="button" class="search-toggle button-rounded">
726 + <img src="https://atuvu.ca/img/icons/search-black.svg" alt="">
727 + </button>
728 + </li>
729 + </li>
730 + </ul>
731 + </div>
732 + </div>
733 +</section>
734 +
735 + <section class="middle">
736 + <div class="wrapper">
737 + <div class="row">
738 + <div id="logoPubContainer" class="right">
739 + <a href="https://atuvu.ca" class="main-logo-desktop">
740 + <img src="/img/atuvu/atuvu.svg" alt="atuvu.ca">
741 + </a>
742 +
743 + </div>
744 + </div>
745 + </div>
746 + </section>
747 +
748 + <div class="white-navbar-search">
749 + <form class="search-form" action="https://atuvu.ca/recherche" method="get">
750 + <input class="white-navbar-search-input" type="search" name="search" placeholder="Recherche..."
751 + autocomplete="off">
752 + <button type="submit"></button>
753 + </form>
754 +
755 +
756 +
757 +
758 +</header>
759 + <!-- GPT AdSlot 6 for Ad unit 'cc_atuvu_pixel_1' ### Size: [[1,1]] -->
760 +<!-- Adslot's refresh function: googletag.pubads().refresh([gptadslots[5]]) -->
761 +<div id='div-gpt-ad-1026097-6'></div>
762 +
763 +<!-- End AdSlot 6 -->
764 + <!-- GPT AdSlot 7 for Ad unit 'cc_atuvu_pixel_2' ### Size: [[1,1]] -->
765 +<!-- Adslot's refresh function: googletag.pubads().refresh([gptadslots[6]]) -->
766 +<div id='div-gpt-ad-1026097-7'>
767 +</div>
768 +
769 +<!-- End AdSlot 7 -->
770 + <main>
771 + <div class="wrapper">
772 + <!-- LeaderboardMobileOne for Ad unit 'cc_atuvu_mobile_320x50' ### Size: [320,100] -->
773 +<div id='div-gpt-ad-1026097-9' class="leaderboard-mobile"></div>
774 +<!-- End LeaderboardMobileOne -->
775 +
776 + <div class="uniquePageId" data-keyname = "atuvu_event" data-id="31878" data-keyname2="atuvu_category"
777 + data-id2="Danse"></div>
778 +
779 + <div class="content-sidebar">
780 +
781 + <div class="left">
782 +
783 + <div class="event">
784 +
785 + <div class="event-heading">
786 + <div class="row">
787 + <div class="left">
788 + <div class="square-slider">
789 + <img src="/uploads/images/2026/07/we-call-it-ballet-belle-au-bois-dormant-danse-spectacle-cropped.jpg?cropped_at=1783020251">
790 + </div>
791 + </div>
792 +
793 + <div class="right">
794 + <div class="content">
795 +
796 + <div class="label-share">
797 + <a class="red-label"
798 + href="https://atuvu.ca/billets/spectacle-de-danse">Danse</a>
799 + <div class="red-label">Ballet</div>
800 + </div>
801 +
802 + <h1 class="name">Ballet of Lights: La Belle au bois dormant dans un spectacle étincelant</h1>
803 +
804 +
805 + </div>
806 + </div>
807 + </div>
808 + </div>
809 +
810 + <div class="event-footing">
811 + <span class="anchor" id="dates"></span>
812 + <div class="event-plays">
813 +
814 + <h3></h3>
815 + <div class="firsts">
816 + <a href="https://atuvu.ca/billets/spectacle-de-danse/ballet-of-lights-la-belle-au-bois-dormant-dans-un-spectacle-etincelant-theatre-rialto-12-septembre-2026-billets-a-prix-reguliers" class="event-play">
817 + <div class="event-play__content">
818 + <div class="event-play__date-place">
819 + <div class="date">12 septembre 2026 - 16h00
820 + </div>
821 + <div class="place">
822 + <span>Théâtre Rialto</span><br />
823 + <span>Montréal</span>
824 + </div>
825 + </div>
826 + <div class="event-play__deal">
827 + <div class="price eventprice eventprice--event-play-table">
828 + <div class='price-details'>
829 + <div class="pricetype">43.20 $</div>
830 + </div>
831 + </div>
832 + </div>
833 + </div>
834 + </a>
835 + <a href="https://atuvu.ca/billets/spectacle-de-danse/ballet-of-lights-la-belle-au-bois-dormant-dans-un-spectacle-etincelant-theatre-rialto-12-septembre-2026-billets-a-prix-reguliers-1783019231" class="event-play">
836 + <div class="event-play__content">
837 + <div class="event-play__date-place">
838 + <div class="date">12 septembre 2026 - 18h30
839 + </div>
840 + <div class="place">
841 + <span>Théâtre Rialto</span><br />
842 + <span>Montréal</span>
843 + </div>
844 + </div>
845 + <div class="event-play__deal">
846 + <div class="price eventprice eventprice--event-play-table">
847 + <div class='price-details'>
848 + <div class="pricetype">43.20 $</div>
849 + </div>
850 + </div>
851 + </div>
852 + </div>
853 + </a>
854 + <a href="https://atuvu.ca/billets/spectacle-de-danse/ballet-of-lights-la-belle-au-bois-dormant-dans-un-spectacle-etincelant-theatre-rialto-8-novembre-2026-billets-a-prix-reguliers" class="event-play">
855 + <div class="event-play__content">
856 + <div class="event-play__date-place">
857 + <div class="date"> 8 novembre 2026 - 16h00
858 + </div>
859 + <div class="place">
860 + <span>Théâtre Rialto</span><br />
861 + <span>Montréal</span>
862 + </div>
863 + </div>
864 + <div class="event-play__deal">
865 + <div class="price eventprice eventprice--event-play-table">
866 + <div class='price-details'>
867 + <div class="pricetype">43.20 $</div>
868 + </div>
869 + </div>
870 + </div>
871 + </div>
872 + </a>
873 + </div>
874 + <div class="extras">
875 + <a href="https://atuvu.ca/billets/spectacle-de-danse/ballet-of-lights-la-belle-au-bois-dormant-dans-un-spectacle-etincelant-theatre-rialto-8-novembre-2026-billets-a-prix-reguliers-1783019232" class="event-play">
876 + <div class="event-play__content">
877 + <div class="event-play__date-place">
878 + <div class="date"> 8 novembre 2026 - 18h30
879 + </div>
880 + <div class="place">
881 + <span>Théâtre Rialto</span><br />
882 + <span>Montréal</span>
883 + </div>
884 + </div>
885 + <div class="event-play__deal">
886 + <div class="price eventprice eventprice--event-play-table">
887 + <div class='price-details'>
888 + <div class="pricetype">43.20 $</div>
889 + </div>
890 + </div>
891 + </div>
892 + </div>
893 + </a>
894 + <a href="https://atuvu.ca/billets/spectacle-de-danse/ballet-of-lights-la-belle-au-bois-dormant-dans-un-spectacle-etincelant-theatre-rialto-31-janvier-2027-billets-a-prix-reguliers" class="event-play">
895 + <div class="event-play__content">
896 + <div class="event-play__date-place">
897 + <div class="date">31 janvier 2027 - 16h00
898 + </div>
899 + <div class="place">
900 + <span>Théâtre Rialto</span><br />
901 + <span>Montréal</span>
902 + </div>
903 + </div>
904 + <div class="event-play__deal">
905 + <div class="price eventprice eventprice--event-play-table">
906 + <div class='price-details'>
907 + <div class="pricetype">43.20 $</div>
908 + </div>
909 + </div>
910 + </div>
911 + </div>
912 + </a>
913 + <a href="https://atuvu.ca/billets/spectacle-de-danse/ballet-of-lights-la-belle-au-bois-dormant-dans-un-spectacle-etincelant-theatre-rialto-31-janvier-2027-billets-a-prix-reguliers-1783019233" class="event-play">
914 + <div class="event-play__content">
915 + <div class="event-play__date-place">
916 + <div class="date">31 janvier 2027 - 18h30
917 + </div>
918 + <div class="place">
919 + <span>Théâtre Rialto</span><br />
920 + <span>Montréal</span>
921 + </div>
922 + </div>
923 + <div class="event-play__deal">
924 + <div class="price eventprice eventprice--event-play-table">
925 + <div class='price-details'>
926 + <div class="pricetype">43.20 $</div>
927 + </div>
928 + </div>
929 + </div>
930 + </div>
931 + </a>
932 + </div>
933 + <div class="see-more">Plus de dates</div>
934 + </div>
935 +</div>
936 +
937 +
938 +
939 +
940 + <div class="event-content"><h2>We Call It Ballet : La Belle au bois dormant, danse et spectacle de lumière</h2>
941 +<p class="isSelectedEnd">✨ <strong>Redécouvrez le célèbre ballet </strong><em><strong>La Belle au bois dormant</strong></em><strong> dans une expérience visuelle où la danse classique s'illumine de mille feux.</strong></p>
942 +<p class="isSelectedEnd">Pendant environ une heure, des danseurs professionnels donnent vie au chef-d'œuvre inspiré de la musique de <strong>Tchaïkovski</strong>, dans une mise en scène qui marie élégance, technologie et poésie.</p>
943 +<h3>Une expérience immersive</h3>
944 +<p class="isSelectedEnd">Les costumes des interprètes sont ornés de milliers de points lumineux qui réagissent aux mouvements des danseurs. Chaque pirouette, chaque porté et chaque saut créent un jeu de lumière fascinant qui transforme la scène en un véritable tableau vivant.</p>
945 +<p class="isSelectedEnd">Le spectacle revisite l'histoire intemporelle de la princesse Aurore, de la malédiction de son enfance jusqu'à son réveil, dans une interprétation à la fois fidèle à l'esprit du ballet classique et résolument contemporaine.</p>
946 +<h3>Pourquoi assister à <em>We Call It Ballet</em>?</h3>
947 +<p class="isSelectedEnd">🩰 Une relecture originale d'un grand classique du ballet.</p>
948 +<p class="isSelectedEnd">💡 Des costumes lumineux qui subliment chaque mouvement des danseurs.</p>
949 +<p class="isSelectedEnd">🎼 Une mise en scène inspirée de la musique emblématique de Tchaïkovski.</p>
950 +<p class="isSelectedEnd">✨ Une expérience accessible autant aux passionnés de danse qu'aux spectateurs qui découvrent le ballet pour la première fois.</p>
951 +<h3>À savoir</h3>
952 +<ul data-spread="false">
953 +<li>Durée approximative : <strong>60 minutes</strong></li>
954 +<li>Interprété par des danseurs professionnels.</li>
955 +<li>Une production qui allie danse classique, effets lumineux et scénographie immersive.</li>
956 +</ul>
957 +<p>Laissez-vous transporter dans un univers où la grâce du ballet rencontre la magie de la lumière, pour une soirée aussi émouvante que spectaculaire.</p>
958 +<p><br /><div><a href="//fever.pxf.io/POK6qX" target="_blank">fever.pxf.io/POK6qX</a></div></p>
959 + </div>
960 +
961 + <div class="sub-details">
962 +
963 + </div>
964 +
965 +
966 +
967 + <div class="comment-section" id="comment-section">
968 +
969 + <!-- Heading -->
970 +
971 + <div class="red-tab">
972 + <div class="tab">Aucun commentaire</div>
973 + </div>
974 +
975 +
976 + <!-- Commentaires -->
977 + <div class="rows-layout">
978 +
979 + </div>
980 +
981 + <div class="pagination-container">
982 +
983 + </div>
984 +
985 +
986 +
987 +
988 + <h2>Vous devez être connecté pour donner un avis.</h2>
989 + <a href="https://atuvu.ca/connexion" class="btn-rect">Connectez-vous ici.</a>
990 +
991 +
992 +
993 +</div>
994 +
995 +
996 + <div id="allOffers">
997 + <div class="allOffers">
998 + <div class="red-tab arrow-space withlink">
999 + <div class="tab">
1000 + <a href="https://atuvu.ca/programmation-culturelle">
1001 + Toutes les offres
1002 + </a>
1003 + </div>
1004 + <a href="https://atuvu.ca/programmation-culturelle" class="arrow"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25">
1005 + <path d="m17.5 5.999-.707.707 5.293 5.293H1v1h21.086l-5.294 5.295.707.707L24 12.499l-6.5-6.5z"
1006 + data-name="Right" />
1007 + </svg></a>
1008 + </div>
1009 + <div class="allOffersData">
1010 + <div class="slider-layout new-carousel-slider">
1011 + <ul class="slider rows-layout new-carousel-slide" data-custom="true">
1012 + <li class="carousel-cell slide home-thematique-slides">
1013 + <div class="slider-event slider-item">
1014 + <a href="https://atuvu.ca/billets-spectacle/garden-of-roses" class="url">
1015 + <div class="img" style="background-image:url(/uploads/images/2026/08/garden-of-roses.jpg)">
1016 + </div>
1017 + <div class="slider-content">
1018 + <div class="cats">
1019 + <div class="main_cat red-label">Musique</div>
1020 + <div class="sub_cat red-label">Rock</div>
1021 + </div>
1022 + <div class="title">Garden of Roses</div>
1023 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Lavaltrie</div>
1024 + <div class="offer">Invitations gratuites</div>
1025 + </div>
1026 + </a>
1027 +</div>
1028 + </li>
1029 + <li class="carousel-cell slide home-thematique-slides">
1030 + <div class="slider-event slider-item">
1031 + <a href="https://atuvu.ca/billets-spectacle/vincent-et-la-prophetie-des-mers-premiere-montrealaise" class="url">
1032 + <div class="img" style="background-image:url(/uploads/images/2026/08/vincent-prophetie-mers-premiere-montrealaise.jpg)">
1033 + </div>
1034 + <div class="slider-content">
1035 + <div class="cats">
1036 + <div class="main_cat red-label">Cinéma</div>
1037 + <div class="sub_cat red-label">Animation</div>
1038 + <div class="sub_cat red-label">Familial</div>
1039 + <div class="sub_cat red-label">Aventure</div>
1040 + </div>
1041 + <div class="title">Vincent et la prophétie des mers - Première montréalaise</div>
1042 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Montréal</div>
1043 + <div class="offer">Invitations gratuites</div>
1044 + </div>
1045 + </a>
1046 +</div>
1047 + </li>
1048 + <li class="carousel-cell slide home-thematique-slides">
1049 + <div class="slider-event slider-item">
1050 + <a href="https://atuvu.ca/billets-spectacle/manouchka-en-spectacle" class="url">
1051 + <div class="img" style="background-image:url(/uploads/images/2026/08/manouchka-en-spectacle-1782000000-1782076838-1786634681.jpg)">
1052 + </div>
1053 + <div class="slider-content">
1054 + <div class="cats">
1055 + <div class="main_cat red-label">Musique</div>
1056 + <div class="sub_cat red-label">Chanson franco</div>
1057 + <div class="sub_cat red-label">Pop anglo</div>
1058 + </div>
1059 + <div class="title">Manouchka en spectacle</div>
1060 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Montréal</div>
1061 + <div class="offer">Invitations gratuites</div>
1062 + </div>
1063 + </a>
1064 +</div>
1065 + </li>
1066 + <li class="carousel-cell slide home-thematique-slides">
1067 + <div class="slider-event slider-item">
1068 + <a href="https://atuvu.ca/billets-spectacle/cest-la-crise" class="url">
1069 + <div class="img" style="background-image:url(/uploads/images/2026/06/cest-crise.jpg)">
1070 + </div>
1071 + <div class="slider-content">
1072 + <div class="cats">
1073 + <div class="main_cat red-label">Théâtre</div>
1074 + <div class="sub_cat red-label">Boulevard</div>
1075 + <div class="sub_cat red-label">Vaudeville</div>
1076 + <div class="sub_cat red-label">Comédie</div>
1077 + </div>
1078 + <div class="title">C’est la crise !</div>
1079 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Montréal</div>
1080 + <div class="offer">2 pour 1</div>
1081 + </div>
1082 + </a>
1083 +</div>
1084 + </li>
1085 + <li class="carousel-cell slide home-thematique-slides">
1086 + <div class="slider-event slider-item">
1087 + <a href="https://atuvu.ca/billets-spectacle/mon-jour-de-chance" class="url">
1088 + <div class="img" style="background-image:url(/uploads/images/2026/08/mon-jour-chance-image-titre-1200x875-1785763405.jpg)">
1089 + </div>
1090 + <div class="slider-content">
1091 + <div class="cats">
1092 + <div class="main_cat red-label">Théâtre</div>
1093 + <div class="sub_cat red-label">Comédie</div>
1094 + </div>
1095 + <div class="title">Mon jour de chance</div>
1096 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Montréal</div>
1097 + <div class="offer">Plusieurs offres promo</div>
1098 + </div>
1099 + </a>
1100 +</div>
1101 + </li>
1102 + <li class="carousel-cell slide home-thematique-slides">
1103 + <div class="slider-event slider-item">
1104 + <a href="https://atuvu.ca/billets-spectacle/the-one-man-show-presente-par-le-cabaret-new-yorkais" class="url">
1105 + <div class="img" style="background-image:url(/uploads/images/2026/08/the-one-man-show-presente-par-cabaret-new-yorkais.jpg)">
1106 + </div>
1107 + <div class="slider-content">
1108 + <div class="cats">
1109 + <div class="main_cat red-label">Musique</div>
1110 + <div class="sub_cat red-label">Variété</div>
1111 + <div class="sub_cat red-label">Rock</div>
1112 + <div class="sub_cat red-label">Pop</div>
1113 + </div>
1114 + <div class="title">The One-Man Show | Présenté par Le Cabaret New-Yorkais</div>
1115 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Laval</div>
1116 + <div class="offer">2 pour 1</div>
1117 + </div>
1118 + </a>
1119 +</div>
1120 + </li>
1121 + <li class="carousel-cell slide home-thematique-slides">
1122 + <div class="slider-event slider-item">
1123 + <a href="https://atuvu.ca/billets-spectacle/angelo-schiraldi-lelu" class="url">
1124 + <div class="img" style="background-image:url(/uploads/images/2026/07/angelo-schiraldi-lelu.jpg)">
1125 + </div>
1126 + <div class="slider-content">
1127 + <div class="cats">
1128 + <div class="main_cat red-label">Humour</div>
1129 + <div class="sub_cat red-label">Engagé</div>
1130 + <div class="sub_cat red-label">Général</div>
1131 + <div class="sub_cat red-label">Stand-up</div>
1132 + </div>
1133 + <div class="title">Angelo Schiraldi – L’Élu</div>
1134 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Montréal</div>
1135 + <div class="offer">2 pour 1</div>
1136 + </div>
1137 + </a>
1138 +</div>
1139 + </li>
1140 + <li class="carousel-cell slide home-thematique-slides">
1141 + <div class="slider-event slider-item">
1142 + <a href="https://atuvu.ca/billets-spectacle/mendelssohn-voyage-1786848287" class="url">
1143 + <div class="img" style="background-image:url(/uploads/images/2026/08/mendelssohn-voyage-1786848287.jpg)">
1144 + </div>
1145 + <div class="slider-content">
1146 + <div class="cats">
1147 + <div class="main_cat red-label">Musique</div>
1148 + <div class="sub_cat red-label">Classique</div>
1149 + </div>
1150 + <div class="title">Mendelssohn voyage!</div>
1151 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Laval</div>
1152 + <div class="offer">Tarif réduit -15%</div>
1153 + </div>
1154 + </a>
1155 +</div>
1156 + </li>
1157 + <li class="carousel-cell slide home-thematique-slides">
1158 + <div class="slider-event slider-item">
1159 + <a href="https://atuvu.ca/billets-spectacle/activemirror-the-satisfactory-johnny-turbo-et-bebe-and-oona" class="url">
1160 + <div class="img" style="background-image:url(/uploads/images/2026/08/activemirror-the-satisfactory-johnny-turbo-bebe-and-oona.jpg)">
1161 + </div>
1162 + <div class="slider-content">
1163 + <div class="cats">
1164 + <div class="main_cat red-label">Musique</div>
1165 + <div class="sub_cat red-label">Indie</div>
1166 + <div class="sub_cat red-label">Rock</div>
1167 + </div>
1168 + <div class="title">Activemirror, The Satisfactory, Johnny Turbo et Bebe and Oona</div>
1169 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Montréal</div>
1170 + <div class="offer">Tarif réduit -15%</div>
1171 + </div>
1172 + </a>
1173 +</div>
1174 + </li>
1175 + <li class="carousel-cell slide home-thematique-slides">
1176 + <div class="slider-event slider-item">
1177 + <a href="https://atuvu.ca/billets-spectacle/plaisirs-interdits" class="url">
1178 + <div class="img" style="background-image:url(/uploads/images/2026/03/plaisirs-interdits.jpg)">
1179 + </div>
1180 + <div class="slider-content">
1181 + <div class="cats">
1182 + <div class="main_cat red-label">Théâtre</div>
1183 + <div class="sub_cat red-label">Comédie</div>
1184 + <div class="sub_cat red-label">Musical</div>
1185 + <div class="sub_cat red-label">Opéra</div>
1186 + </div>
1187 + <div class="title">Plaisirs Interdits</div>
1188 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Montréal</div>
1189 + <div class="offer">2 pour 1</div>
1190 + </div>
1191 + </a>
1192 +</div>
1193 + </li>
1194 + <li class="carousel-cell slide home-thematique-slides">
1195 + <div class="slider-event slider-item">
1196 + <a href="https://atuvu.ca/billets-spectacle/festival-de-montgolfieres-de-gatineau" class="url">
1197 + <div class="img" style="background-image:url(/uploads/images/2026/06/festival-montgolfieres-gatineau.jpg)">
1198 + <div class="festival_title">Festival de montgolfières de Gatineau (FMG)</div>
1199 + </div>
1200 + <div class="slider-content">
1201 + <div class="cats">
1202 + <div class="main_cat red-label">Musique</div>
1203 + <div class="sub_cat red-label">Pop</div>
1204 + <div class="sub_cat red-label">Rap / Hip-Hop</div>
1205 + </div>
1206 + <div class="title">Festival de montgolfières de Gatineau</div>
1207 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Gatineau</div>
1208 + <div class="offer">Plusieurs offres promo</div>
1209 + </div>
1210 + </a>
1211 +</div>
1212 + </li>
1213 + <li class="carousel-cell slide home-thematique-slides">
1214 + <div class="slider-event slider-item">
1215 + <a href="https://atuvu.ca/billets-spectacle/si-dieu-le-veut" class="url">
1216 + <div class="img" style="background-image:url(/uploads/images/2026/01/1000x1000-1768572426.jpg)">
1217 + </div>
1218 + <div class="slider-content">
1219 + <div class="cats">
1220 + <div class="main_cat red-label">Théâtre</div>
1221 + <div class="sub_cat red-label">Théâtre documentaire</div>
1222 + </div>
1223 + <div class="title">Si Dieu le veut!</div>
1224 + <div class="city"><img src="/img/icons/map-icon.png" alt="atuvu">Montréal</div>
1225 + <div class="offer">Invitations gratuites</div>
1226 + </div>
1227 + </a>
1228 +</div>
1229 + </li>
1230 + </ul>
1231 +</div>
1232 + </div>
1233 + </div>
1234 + </div>
1235 +
1236 + </div>
1237 +
1238 + </div>
1239 +
1240 + <div class="right sidebar mobile-sidebar">
1241 + <a href="https://atuvu.ca/inscription" class="autopromo-ad autopromo-subscribe-ad">
1242 + <img src="https://atuvu.ca/img/autopromo-subscribe.jpg" alt="">
1243 +</a>
1244 +
1245 +
1246 + <div class="mobile-only-ad-zone">
1247 +
1248 + <!-- BigBoxOneMobile 'cc_atuvu_mobile_320x250_1' ### Size: [[300, 600], [300, 250]] -->
1249 +<div id='div-gpt-ad-1608743744528-0' class="padded-ad padded-ad-mobile">
1250 +
1251 +</div>
1252 +
1253 + <!-- End BigBoxOneMobile -->
1254 +
1255 + </div>
1256 +
1257 +<!-- BigBoxOneDesktop for Ad unit 'cc_atuvu_300x_1' ### Size: [300, 250],[300, 600], -->
1258 +<div id='div-gpt-ad-1026097-3' class="padded-ad">
1259 +
1260 +</div>
1261 +
1262 +<!-- End BigBoxOneDesktop -->
1263 +
1264 +
1265 +
1266 +<div class="plays-guichet-sidebar-articles">
1267 + <div>
1268 + <a class="article-link" href="https://atuvu.ca/index.php/magazine-culturel/suggestion-sortie/le-royal-winnipeg-ballet-sapprete-a-faire-vibrer-le-festival-des-arts-de-saint-sauveur"></a>
1269 + <article class="featured-article" style="background-image: url('/uploads/images/2026/07/royal-winnipeg-ballet-sapprete-a-faire-vibrer-festival-arts-cropped.jpg?cropped_at=1784317921')">
1270 + <div class="content">
1271 + <a class="red-label" href="https://atuvu.ca/index.php/magazine-culturel/suggestion-sortie">Suggestions de sorties</a>
1272 + <h3>Le « Royal Winnipeg Ballet » s’apprête à faire vibrer le Festival des Arts de Saint-Sauveur</h3>
1273 + <span class="info">Par Ève Christian |
1274 + 17 juillet 2026</span>
1275 + </div>
1276 + </article>
1277 + </div>
1278 +<div>
1279 + <a class="article-link" href="https://atuvu.ca/index.php/magazine-culturel/nouvelles-culturelles/festival-quartiers-danses-2026-la-programmation-en-salle-de-la-24e-edition-devoilee"></a>
1280 + <article class="featured-article" style="background-image: url('/uploads/images/2026/06/festival-quartiers-danses-2026-programmation-en-salle-24e-cropped.jpg?cropped_at=1781186358')">
1281 + <div class="content">
1282 + <a class="red-label" href="https://atuvu.ca/index.php/magazine-culturel/nouvelles-culturelles">Nouvelles</a>
1283 + <h3>Festival Quartiers Danses 2026 : la programmation en salle de la 24e édition dévoilée</h3>
1284 + <span class="info">Par Théa Paradis |
1285 + 11 juin 2026</span>
1286 + </div>
1287 + </article>
1288 + </div>
1289 + </div>
1290 +
1291 + <div class="mobile-only-ad-zone">
1292 +
1293 + <!-- BigBoxTwoMobile 'cc_atuvu_mobile_320x250_2' ### Size: [[300, 600], [300, 250]] -->
1294 +<div id='div-gpt-ad-1608743744528-1' class="padded-ad padded-ad-mobile">
1295 +
1296 +</div>
1297 +
1298 + <!-- End BigBoxTwoMobile -->
1299 +
1300 + </div>
1301 +<!-- BigBoxTwoDesktop for Ad unit 'cc_atuvu_300x_2' ### Size: [300, 250],[300, 600], -->
1302 +<div id='div-gpt-ad-1026097-4' class="padded-ad">
1303 +
1304 +</div>
1305 +<!-- End BigBoxTwoDesktop -->
1306 +
1307 +
1308 +
1309 +
1310 +
1311 +<div class="slider-layout recommended-events">
1312 + <div class="red-tab">
1313 + <div class="tab">Nos recommandations</div>
1314 + </div>
1315 + <ul class="slider rows-layout recommended-events-slide" data-slidername="recommendedSlider"
1316 + data-slidersource="/api/site/events?scope=RecommendedEventSlider" data-perpage="2">
1317 + <li class="">
1318 + <article class="event-preview-small">
1319 + <div class="left">
1320 + <a href="https://atuvu.ca/billets-spectacle/une-femme-denfer">
1321 + <img src="/uploads/images/2026/06/femme-denfer-square.jpg?cropped_at=1780666978" alt="Une femme d&#039;enfer">
1322 + </a>
1323 + </div>
1324 + <div class="right">
1325 + <a href="https://atuvu.ca/billets-spectacle/une-femme-denfer" class="title">Une femme d&#039;enfer</a>
1326 + <a href="https://atuvu.ca/billets-spectacle/une-femme-denfer" class="link">En savoir plus <span
1327 + class="red-arrow">></span></a>
1328 + </div>
1329 + </article>
1330 + <article class="event-preview-small">
1331 + <div class="left">
1332 + <a href="https://atuvu.ca/billets-spectacle/festisable-st-come-1786813137">
1333 + <img src="/uploads/images/2026/08/festisable-st-come-1786813137-square.jpg" alt="FESTISABLE St-Côme">
1334 + </a>
1335 + </div>
1336 + <div class="right">
1337 + <a href="https://atuvu.ca/billets-spectacle/festisable-st-come-1786813137" class="title">FESTISABLE St-Côme</a>
1338 + <a href="https://atuvu.ca/billets-spectacle/festisable-st-come-1786813137" class="link">En savoir plus <span
1339 + class="red-arrow">></span></a>
1340 + </div>
1341 + </article>
1342 + </li>
1343 + <li class="sliderDisplayHidden">
1344 + <article class="event-preview-small">
1345 + <div class="left">
1346 + <a href="https://atuvu.ca/billets-spectacle/plaisirs-interdits">
1347 + <img src="/uploads/images/2026/03/plaisirs-interdits-square.jpg" alt="Plaisirs Interdits">
1348 + </a>
1349 + </div>
1350 + <div class="right">
1351 + <a href="https://atuvu.ca/billets-spectacle/plaisirs-interdits" class="title">Plaisirs Interdits</a>
1352 + <a href="https://atuvu.ca/billets-spectacle/plaisirs-interdits" class="link">En savoir plus <span
1353 + class="red-arrow">></span></a>
1354 + </div>
1355 + </article>
1356 + </li>
1357 + </ul>
1358 + </div>
1359 +
1360 +<div class="red-tab social-links">
1361 + <div class="tab">
1362 + Suivez-nous
1363 + </div>
1364 + <div class="red-logos">
1365 + <a href="https://www.facebook.com/pages/atuvuca/129197624972" target="_blank">
1366 + <object class="facebook" type="image/svg+xml" data="/img/icons/facebook-icon-red.svg"></object>
1367 +
1368 + </a>
1369 + <a href="https://x.com/atuvu_ca" target="_blank">
1370 + <object class="x" type="image/svg+xml" data="/img/icons/x-icon-red.svg"></object>
1371 +
1372 + </a>
1373 + <a href="https://www.instagram.com/atuvu.ca" target="_blank">
1374 + <object class="instagram" type="image/svg+xml" data="/img/icons/instagram-icon-red.svg"></object>
1375 +
1376 + </a>
1377 + </div>
1378 +</div>
1379 +
1380 + <div class="mobile-only-ad-zone">
1381 +
1382 + <!-- BigBoxThreeMobile 'cc_atuvu_mobile_320x250_3' ### Size: [[300, 600], [300, 250]] -->
1383 +<div id='div-gpt-ad-1608743744528-2' class="padded-ad padded-ad-mobile">
1384 +
1385 +</div>
1386 +
1387 + <!-- End BigBoxThreeMobile -->
1388 +
1389 + </div>
1390 +
1391 +<!-- BigBoxThreeDesktop for Ad unit 'cc_atuvu_300x_3' ### Size: [300, 250],[300, 600], -->
1392 +<div id='div-gpt-ad-1026097-5' class="padded-ad">
1393 +
1394 +</div>
1395 +<!-- End BigBoxThreeDesktop -->
1396 + </div>
1397 +
1398 + </div>
1399 +
1400 +
1401 +
1402 + <div class="row">
1403 +
1404 + <div class="credit-full-width">
1405 + <!-- GPT AdSlot 2 for Ad unit 'cc_atuvu_728x90_2' ### Size: [[728,90],[320,50]] -->
1406 + <!-- Adslot's refresh function: googletag.pubads().refresh([gptadslots[1]]) -->
1407 + <div id='div-gpt-ad-1026097-2' class="leaderboard-two">
1408 + </div>
1409 +
1410 + <!-- End AdSlot 2 -->
1411 +
1412 + <!-- LeaderboardMobileTwo for Ad unit 'cc_atuvu_mobile_320x50_2' ### Size: [320,100] -->
1413 +<div id='div-gpt-ad-1026097-10' class="leaderboard-mobile"></div>
1414 +<!-- End LeaderboardMobileTwo -->
1415 + <a href="https://www.culturecible.ca/" target="_blank"><img class="desktop" src="/img/cultureCible.jpg"
1416 + alt="img-credit"></a>
1417 + <a href="https://www.culturecible.ca/" target="_blank"><img class="mobile" src="/img/cultureCible-petit.jpg"
1418 + alt="img-credit"></a>
1419 +
1420 + </div>
1421 +</div>
1422 + </div>
1423 + </main>
1424 +
1425 + <footer>
1426 + <section class="grey">
1427 + <div class="wrapper">
1428 + <div class = "topHidden">
1429 + <p>Suivez-nous</p>
1430 + <a href="https://www.facebook.com/pages/atuvuca/129197624972" target="_blank">
1431 + <object class="facebook" type="image/svg+xml" data="/img/icons/facebook-icon.svg"></object>
1432 + </a>
1433 + <a href="https://x.com/atuvu_ca" target="_blank">
1434 + <object class="x" type="image/svg+xml" data="/img/icons/x-icon.svg"></object>
1435 + </a>
1436 + <a href="https://www.instagram.com/atuvu.ca" target="_blank">
1437 + <object class="instagram" type="image/svg+xml" data="/img/icons/instagram-icon.svg"></object>
1438 + </a>
1439 + </div>
1440 + <div class="row middle">
1441 + <ul>
1442 + <li><a href="https://atuvu.ca/a-propos">À propos d'atuvu.ca</a></li>
1443 + <li><a href="https://atuvu.ca/producteur/ajouter-un-evenement">Inscrire un événement</a></li>
1444 + <li><a href="https://atuvu.ca/media-kit">Annoncer avec nous</a></li>
1445 + <li><a href="https://atuvu.ca/devenir-membre">Devenir membre</a></li>
1446 + <li><a href="https://atuvu.ca/charte">Charte du membre</a></li>
1447 + </ul>
1448 + <ul class="hide-mobile">
1449 + <li><a href="https://atuvu.ca/billets/concert-de-musique">Musique</a>
1450 + </li>
1451 + <li><a href="https://atuvu.ca/billets/piece-de-theatre">Théâtre</a></li>
1452 + <li><a href="https://atuvu.ca/billets/spectacle-de-danse">Danse</a></li>
1453 + <li><a href="https://atuvu.ca/billets/spectacle-humour">Humour</a></li>
1454 + <li><a href="https://atuvu.ca/billets/spectacle-cirque">Cirque</a></li>
1455 + </ul>
1456 + <ul class="hide-mobile">
1457 + <li><a href="https://atuvu.ca/billets/spectacle-pour-enfant">Conte</a>
1458 + </li>
1459 + <li><a
1460 + href="https://atuvu.ca/billets/spectacle-improvisation">Improvisation</a>
1461 + </li>
1462 + <li><a href="https://atuvu.ca/billets/spectacle-de-magie">Magie</a></li>
1463 + <li><a href="https://atuvu.ca/billets/actualite-cinema">Cinéma</a></li>
1464 + <li><a href="https://atuvu.ca/billets/activites-a-faire">Divers</a></li>
1465 + </ul>
1466 + <ul>
1467 + <li><a href="https://atuvu.ca/magazine-culturel">Magazine</a></li>
1468 + <li><a href="https://atuvu.ca/choisir-un-abonnement">Abonnement VIP</a></li>
1469 + <li><a href="https://atuvu.ca/archives">Archives</a></li>
1470 + </ul>
1471 + <ul>
1472 + <li><a href="https://atuvu.ca/conditions">Conditions d'utilisation</a></li>
1473 + <li><a href="https://atuvu.ca/privacy">Politique de confidentialité</a></li>
1474 +
1475 + <li><a href="https://atuvu.ca/contact">Nous contacter</a></li>
1476 + </ul>
1477 + <ul class="amis">
1478 + <li>Sites amis:</li>
1479 + <li><a href="https://www.baronmag.com/" target="_blank">Baron MAG</a></li>
1480 + <li><a href="https://www.labibleurbaine.com/" target="_blank">Bible Urbaine</a></li>
1481 + <li><a href="https://lecanalauditif.ca/" target="_blank">Le Canal Auditif</a></li>
1482 + <li><a href="http://www.sorstu.ca/" target="_blank">Sors-tu.ca</a></li>
1483 + </ul>
1484 + </div>
1485 +
1486 + <div class="row bottom">
1487 + <div class = "left">
1488 + <p>4521 Boul. Saint-Laurent, Montréal, QC H2T 1R2, Canada</p>
1489 +
1490 + <p>&copy; Copyright ATUVU.CA Tous droits réservés</p>
1491 + </div>
1492 + <div class = "right">
1493 + <p>Suivez-nous</p>
1494 + <a href="https://www.facebook.com/pages/atuvuca/129197624972" target="_blank">
1495 + <object class="facebook" type="image/svg+xml" data="/img/icons/facebook-icon.svg"></object>
1496 + </a>
1497 + <a href="https://x.com/atuvu_ca" target="_blank">
1498 + <object class="x" type="image/svg+xml" data="/img/icons/x-icon.svg"></object>
1499 + </a>
1500 +
1501 + <a href="https://www.instagram.com/atuvu.ca" target="_blank">
1502 + <object class="instagram" type="image/svg+xml" data="/img/icons/instagram-icon.svg"></object>
1503 + </a>
1504 + </div>
1505 + </div>
1506 + <div class="row bottom">
1507 + <p>Le nouveau site atuvu.ca a reçu le soutien du Fonds du Canada pour les périodiques</p>
1508 + <img class="finance_canada" src="/img/finance-par-canada.png" alt="">
1509 + </div>
1510 + </div>
1511 + </section>
1512 +</footer>
1513 +</div>
1514 +
1515 + <script>
1516 + console.log('%cLogged out as Member', 'font-size:14px; color:red;');
1517 +
1518 + console.log('%cLogged out as Admin', 'font-size:14px; color:red;');
1519 +
1520 + console.log('%cLogged out as Producer', 'font-size:14px; color:red;');
1521 + </script>
1522 +
1523 +
1524 + <script>
1525 + function isIE() {
1526 + ua = navigator.userAgent;
1527 + /* MSIE used to detect old browsers and Trident used to newer ones*/
1528 + var is_ie = ua.indexOf("MSIE ") > -1 || ua.indexOf("Trident/") > -1;
1529 +
1530 + return is_ie;
1531 + }
1532 + /* Create an alert to show if the browser is IE or not */
1533 + if (isIE()) {
1534 +
1535 + alert(
1536 + 'Vous utilisez un ancien navigateur. Veuillez changer de navigateur pour accéder à tout le contenu de atuvu.ca');
1537 +
1538 + var warningDiv = document.getElementById("warningBox");
1539 + warningDiv.style.display = "block";
1540 +
1541 + }
1542 +</script>
1543 +
1544 +<script type="application/ld+json">
1545 + {
1546 + "@context": "https://schema.org",
1547 + "@type": "Organization",
1548 + "name": "atuvu.ca",
1549 + "url": "https://atuvu.ca",
1550 + "sameAs":[
1551 + "https://www.facebook.com/atuvuca/",
1552 + "https://www.instagram.com/atuvu.ca/",
1553 + "https://www.linkedin.com/company/atuvu-ca/",
1554 + "https://X.com/atuvu_ca"
1555 + ]
1556 + }
1557 + </script>
1558 +
1559 +<script type="application/ld+json">
1560 + {
1561 + "@context": "https://schema.org",
1562 + "@type": "Organization",
1563 + "url": "https://atuvu.ca",
1564 + "contactPoint": [
1565 + { "@type": "ContactPoint",
1566 + "telephone": "+1-514-416-6114",
1567 + "contactType": "customer service"
1568 + }
1569 + ]
1570 + }
1571 + </script>
1572 +
1573 +<script type="application/ld+json">
1574 + {
1575 + "@context": "https://schema.org",
1576 + "@type": "Organization",
1577 + "url": "https://atuvu.ca",
1578 + "logo": "https://atuvu.ca/img/atuvu/atuvu.png"
1579 + }
1580 + </script>
1581 +<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a30d9040ac34fbd3',t:'MTc4NzY5MjAxNg=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
1582 +
1583 +</html>
added tests/fixtures/banq_fiche.html +691 −0
@@ -0,0 +1,1294 @@
1 +<!DOCTYPE html>
2 +<html lang="fr" dir="ltr" prefix="content: http://purl.org/rss/1.0/modules/content/ dc: http://purl.org/dc/terms/ foaf: http://xmlns.com/foaf/0.1/ og: http://ogp.me/ns# rdfs: http://www.w3.org/2000/01/rdf-schema# schema: http://schema.org/ sioc: http://rdfs.org/sioc/ns# sioct: http://rdfs.org/sioc/types# skos: http://www.w3.org/2004/02/skos/core# xsd: http://www.w3.org/2001/XMLSchema# ">
3 + <head>
4 + <script type="text/javascript">!function(){if("prod-portail-origin.banq.qc.ca"===window.location.hostname){var a="https://www.banq.qc.ca"+window.location.pathname+window.location.search+window.location.hash;window.location.replace(a)}}();</script>
5 + <script type="text/javascript">(function(){(function(e,r){var t=document.createElement("link");t.rel="preconnect";t.as="script";var n=document.createElement("link");n.rel="dns-prefetch";n.as="script";var i=document.createElement("script");i.id="spcloader";i.type="text/javascript";i["async"]=true;i.charset="utf-8";var o="https://sdk.privacy-center.org/"+e+"/loader.js?target_type=notice&target="+r;if(window.didomiConfig&&window.didomiConfig.user){var a=window.didomiConfig.user;var c=a.country;var d=a.region;if(c){o=o+"&country="+c;if(d){o=o+"&region="+d}}}t.href="https://sdk.privacy-center.org/";n.href="https://sdk.privacy-center.org/";i.src=o;var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(t,s);s.parentNode.insertBefore(n,s);s.parentNode.insertBefore(i,s)})("e2436f9f-3c99-4a96-8aa6-856674554710","RTQWWFfj")})();</script>
6 +
7 + <meta charset="utf-8" /><script type="text/javascript">(window.NREUM||(NREUM={})).init={privacy:{cookies_enabled:true},ajax:{deny_list:["bam.nr-data.net"]},feature_flags:["soft_nav"],distributed_tracing:{enabled:true}};(window.NREUM||(NREUM={})).loader_config={agentID:"1589031977",accountID:"6264512",trustKey:"6264512",xpid:"UgQBVVNSChABUVZSAwAHUFcA",licenseKey:"NRJS-e7833320edf79ac1a05",applicationID:"1570410631",browserID:"1589031977"};;/*! For license information please see nr-loader-spa-1.320.1.min.js.LICENSE.txt */
8 +(()=>{var e={8122(e,t,r){"use strict";r.d(t,{a:()=>s});var n=r(944);function s(e,t){try{if(!e||"object"!=typeof e)return(0,n.R)(3);if(!t||"object"!=typeof t)return(0,n.R)(4);const r=Object.create(Object.getPrototypeOf(t),Object.getOwnPropertyDescriptors(t)),i=0===Object.keys(r).length?e:r;for(let a in i)if(void 0!==e[a])try{if(null===e[a]){r[a]=null;continue}Array.isArray(e[a])&&Array.isArray(t[a])?r[a]=Array.from(new Set([...e[a],...t[a]])):e[a]instanceof Map||e[a]instanceof Set||e[a]instanceof Date||e[a]instanceof RegExp?r[a]=e[a]:"object"==typeof e[a]&&null!==t[a]&&"object"==typeof t[a]?r[a]=s(e[a],t[a]):r[a]=e[a]}catch(e){r[a]||(0,n.R)(1,e)}return r}catch(e){(0,n.R)(2,e)}}},2555(e,t,r){"use strict";r.d(t,{f:()=>a});var n=r(384),s=r(8122);const i={beacon:n.NT.beacon,errorBeacon:n.NT.errorBeacon,licenseKey:void 0,applicationID:void 0,sa:void 0,queueTime:void 0,applicationTime:void 0,ttGuid:void 0,user:void 0,account:void 0,product:void 0,extra:void 0,jsAttributes:{},userAttributes:void 0,atts:void 0,transactionName:void 0,tNamePlain:void 0};function a(e){try{return!!e.licenseKey&&!!e.errorBeacon&&!!e.applicationID}catch(e){return!1}}r.d(t,["D",0,e=>(0,s.a)(e,i)])},7699(e,t,r){"use strict";var n=r(860);const s={[n.K7.logging]:!0,[n.K7.genericEvents]:!0,[n.K7.jserrors]:!0,[n.K7.ajax]:!0};r.d(t,["It",0,1e6,"KC",0,s,"qh",0,"SESSION_ERROR"])},9324(e,t,r){"use strict";r.d(t,["A",0,"@newrelic/rrweb","x",0,"1.320.1"])},981(e,t,r){"use strict";r.d(t,["R",0,{AJAX:"AjaxRequest",PA:"PageAction",UA:"UserAction",BP:"BrowserPerformance",WS:"WebSocket",SPV:"SecurityPolicyViolation",JSE:"JavaScriptError",LOG:"Log",PVE:"PageView",PVT:"PageViewTiming",SR:"SessionReplay",ST:"SessionTrace",BI:"BrowserInteraction"}])},9752(e,t,r){"use strict";const n="newrelic-iframe-",s=n+"timing-update",i=n+"api",a=n+"api-response",o=n+"vitals-update",c=n+"ajax";r.d(t,["$8",0,o,"Gi",0,a,"KU",0,c,"Pl",0,n,"cS",0,i,"cr",0,s])},6154(e,t,r){"use strict";var n=r(1863);const s="undefined"!=typeof window&&!!window.document,i="undefined"!=typeof WorkerGlobalScope&&("undefined"!=typeof self&&self instanceof WorkerGlobalScope&&self.navigator instanceof WorkerNavigator||"undefined"!=typeof globalThis&&globalThis instanceof WorkerGlobalScope&&globalThis.navigator instanceof WorkerNavigator),a=s?window:"undefined"!=typeof WorkerGlobalScope&&("undefined"!=typeof self&&self instanceof WorkerGlobalScope&&self||"undefined"!=typeof globalThis&&globalThis instanceof WorkerGlobalScope&&globalThis),o=Boolean("hidden"===a?.document?.visibilityState),c=""+a?.location,d=/iPad|iPhone|iPod/.test(a.navigator?.userAgent),u=d&&"undefined"==typeof SharedWorker,l=(()=>{const e=a.navigator?.userAgent?.match(/Firefox[/\s](\d+\.\d+)/);return Array.isArray(e)&&e.length>=2?+e[1]:0})(),f=Date.now()-(0,n.t)();r.d(t,["OF",0,d,"RI",0,s,"WN",0,f,"bv",0,i,"gm",0,a,"lR",0,l,"m",0,c,"mw",0,o,"sb",0,u,"zk",0,()=>{const e=a?.performance?.getEntriesByType?.("navigation")?.[0];if(e&&e.responseStart>0&&e.responseStart<a.performance.now())return e}])},7295(e,t,r){"use strict";r.d(t,{Xv:()=>a,gX:()=>s,iW:()=>i});var n=[];function s(e){if(!e||i(e))return!1;if(0===n.length)return!0;if("*"===n[0].hostname)return!1;for(var t=0;t<n.length;t++){var r=n[t];if(r.hostname.test(e.hostname)&&r.pathname.test(e.pathname))return!1}return!0}function i(e){return void 0===e.hostname}function a(e){if(n=[],e&&e.length)for(var t=0;t<e.length;t++){let r=e[t];if(!r)continue;if("*"===r)return void(n=[{hostname:"*"}]);0===r.indexOf("http://")?r=r.substring(7):0===r.indexOf("https://")&&(r=r.substring(8));const s=r.indexOf("/");let i,a;s>0?(i=r.substring(0,s),a=r.substring(s)):(i=r,a="*");let[c]=i.split(":");n.push({hostname:o(c),pathname:o(a,!0)})}}function o(e,t=!1){const r=e.replace(/[.+?^${}()|[\]\\]/g,e=>"\\"+e).replace(/\*/g,".*?");return new RegExp((t?"^":"")+r+"$")}},3241(e,t,r){"use strict";r.d(t,{W:()=>s});var n=r(6154);function s(e={}){try{n.gm.dispatchEvent(new CustomEvent("newrelic",{detail:e}))}catch(e){}}},1687(e,t,r){"use strict";r.d(t,{Ak:()=>o,Ze:()=>d,x3:()=>c});var n=r(3241),s=r(3606),i=r(860),a=r(2646);function o(e,t){if(!e)return;const r={staged:!1,priority:i.P3[t]||0};e.runtime.drainRegistry.get(t)||e.runtime.drainRegistry.set(t,r)}function c(e,t){if(!e)return;const r=e.runtime.drainRegistry;r&&(r.get(t)&&r.delete(t),l(e,t,!1),r.size&&u(e))}function d(e,t="feature",r=!1){if(e){if(!e.runtime.drainRegistry.get(t)||r)return l(e,t);e.runtime.drainRegistry.get(t).staged=!0,u(e)}}function u(e){if(!e)return;const t=Array.from(e.runtime.drainRegistry);t.every(([e,t])=>t.staged)&&(t.sort((e,t)=>e[1].priority-t[1].priority),t.forEach(([t])=>{e.runtime.drainRegistry.delete(t),l(e,t)}))}function l(e,t,r=!0){if(!e)return;const i=e.ee,o=s.i.handlers;if(i&&!i.aborted&&i.backlog&&o){if((0,n.W)({type:"lifecycle",name:"drain",feature:t}),r){const e=i.backlog[t],r=o[t];if(r){for(let t=0;e&&t<e.length;++t)f(e[t],r);Object.entries(r).forEach(([e,t])=>{Object.values(t||{}).forEach(t=>{t[0]?.on&&t[0].context()instanceof a.y&&!t[0].listeners(e).includes(t[1])&&t[0].on(e,t[1])})})}}i.isolatedBacklog||delete o[t],i.backlog[t]=null,i.emit("drain-"+t,[])}}function f(e,t){var r=e[1];Object.values(t[r]||{}).forEach(t=>{var r=e[0];if(t[0]===r){var n=t[1],s=e[3],i=e[2];n.apply(s,i)}})}},7836(e,t,r){"use strict";var n=r(384),s=r(8990),i=r(2646),a=r(5607);const o="nr@context:".concat(a.W),c=function e(t,r){var n={},a={},u={},l=!1;try{l=16===r.length&&d.initializedAgents?.[r]?.runtime.isolatedBacklog}catch(e){}var f={on:p,addEventListener:p,removeEventListener:function(e,t){var r=n[e];if(!r)return;for(var s=0;s<r.length;s++)r[s]===t&&r.splice(s,1)},emit:function(e,r,n,s,i){!1!==i&&(i=!0);if(c.aborted&&!s)return;t&&i&&t.emit(e,r,n);var o=h(n);g(e).forEach(e=>{e.apply(o,r)});var d=v()[a[e]];d&&d.push([f,e,r,o]);return o},get:m,listeners:g,context:h,buffer:function(e,t){const r=v();if(t=t||"feature",f.aborted)return;Object.entries(e||{}).forEach(([e,n])=>{a[n]=t,t in r||(r[t]=[])})},abort:function(){f._aborted=!0,Object.keys(f.backlog).forEach(e=>{delete f.backlog[e]})},isBuffering:function(e){return!!v()[a[e]]},debugId:r,backlog:l?{}:t&&"object"==typeof t.backlog?t.backlog:{},isolatedBacklog:l};return Object.defineProperty(f,"aborted",{get:()=>{let e=f._aborted||!1;return e||(t&&(e=t.aborted),e)}}),f;function h(e){return e&&e instanceof i.y?e:e?(0,s.I)(e,o,()=>new i.y(o)):new i.y(o)}function p(e,t){n[e]=g(e).concat(t)}function g(e){return n[e]||[]}function m(t){return u[t]=u[t]||e(f,t)}function v(){return f.backlog}}(void 0,"globalEE"),d=(0,n.Zm)();d.ee||(d.ee=c),r.d(t,["P",0,o,"ee",0,c])},2646(e,t,r){"use strict";r.d(t,{y:()=>n});class n{constructor(e){this.contextId=e}}},9908(e,t,r){"use strict";r.d(t,{d:()=>n,p:()=>s});var n=r(7836).ee.get("handle");function s(e,t,r,s,i){i?(i.buffer([e],s),i.emit(e,t,r)):(n.buffer([e],s),n.emit(e,t,r))}},3606(e,t,r){"use strict";r.d(t,{i:()=>i});var n=r(9908);i.on=a;var s=i.handlers={};function i(e,t,r,i){a(i||n.d,s,e,t,r)}function a(e,t,r,s,i){i||(i="feature"),e||(e=n.d);var a=t[i]=t[i]||{};(a[r]=a[r]||[]).push([e,s])}},3878(e,t,r){"use strict";function n(e,t){return{capture:e,passive:!1,signal:t}}function s(e,t,r=!1,s){window.addEventListener(e,t,n(r,s))}function i(e,t,r=!1,s){document.addEventListener(e,t,n(r,s))}r.d(t,{DD:()=>i,jT:()=>n,sp:()=>s})},5607(e,t,r){"use strict";const n=(0,r(9566).bz)();r.d(t,["W",0,n])},9566(e,t,r){"use strict";r.d(t,{LA:()=>a,ZF:()=>o,bz:()=>i,el:()=>c});var n=r(6154);function s(e,t){return e?15&e[t]:16*Math.random()|0}function i(){const e=n.gm?.crypto||n.gm?.msCrypto;let t,r=0;return e&&e.getRandomValues&&(t=e.getRandomValues(new Uint8Array(30))),"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".split("").map(e=>"x"===e?s(t,r++).toString(16):"y"===e?(3&s()|8).toString(16):e).join("")}function a(e){const t=n.gm?.crypto||n.gm?.msCrypto;let r,i=0;t&&t.getRandomValues&&(r=t.getRandomValues(new Uint8Array(e)));const a=[];for(var o=0;o<e;o++)a.push(s(r,i++).toString(16));return a.join("")}function o(){return a(16)}function c(){return a(32)}},2614(e,t,r){"use strict";r.d(t,["BB",0,18e5,"Wt",0,"NRBA_SESSION::","g",0,{OFF:0,FULL:1,ERROR:2},"iL",0,{SAME_TAB:"same-tab",CROSS_TAB:"cross-tab"},"tS",0,{STARTED:"session-started",PAUSE:"session-pause",RESET:"session-reset",RESUME:"session-resume",UPDATE:"session-update"},"wk",0,144e5])},733(e,t,r){"use strict";function n(e,t){return function(e){let t=2166136261;for(let r=0;r<e.length;r++)t^=e.charCodeAt(r),t=Math.imul(t,16777619);return(t>>>0).toString(16).padStart(8,"0")}("".concat(String(e),":").concat(String(t)))}r.d(t,{Y:()=>n})},1863(e,t,r){"use strict";function n(){return Math.floor(performance.now())}r.d(t,{t:()=>n})},9119(e,t,r){"use strict";r.d(t,{L:()=>i});var n=/([^?#]*)[^#]*(#[^?]*|$).*/,s=/([^?#]*)().*/;function i(e,t){return e?e.replace(t?n:s,"$1$2"):e}},7485(e,t,r){"use strict";r.d(t,{D:()=>s});var n=r(6154);function s(e){if(0===(e||"").indexOf("data:"))return{protocol:"data"};try{const t=new URL(e,location.href),r={port:t.port,hostname:t.hostname,pathname:t.pathname,search:t.search,protocol:t.protocol.slice(0,t.protocol.indexOf(":")),sameOrigin:t.protocol===n.gm?.location?.protocol&&t.host===n.gm?.location?.host};return r.port&&""!==r.port||("http:"===t.protocol&&(r.port="80"),"https:"===t.protocol&&(r.port="443")),r.pathname&&""!==r.pathname?r.pathname.startsWith("/")||(r.pathname="/".concat(r.pathname)):r.pathname="/",r}catch(e){return{}}}},7866(e,t,r){"use strict";r.d(t,["Nc",0,/^\s*at Function code \(Function code:\d+:\d+\)\s*/i,"cn",0,/^\s*(?:([^@]*)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i,"fL",0,/^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i,"h3",0,/function (.+?)\s*\(/,"hB",0,/^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i])},944(e,t,r){"use strict";r.d(t,{R:()=>s});var n=r(3241);function s(e,t){"function"==typeof console.debug&&(console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(e),t),(0,n.W)({drained:null,type:"data",name:"warn",feature:"warn",data:{code:e,secondary:t}}))}},8990(e,t,r){"use strict";r.d(t,{I:()=>s});var n=Object.prototype.hasOwnProperty;function s(e,t,r){if(n.call(e,t))return e[t];var s=r();if(Object.defineProperty&&Object.keys)try{return Object.defineProperty(e,t,{value:s,writable:!0,enumerable:!1}),s}catch(e){}return e[t]=s,s}},6389(e,t,r){"use strict";function n(e,t=500,r={}){const n=r?.leading||!1;let s;return(...r)=>{n&&void 0===s&&(e.apply(this,r),s=setTimeout(()=>{s=clearTimeout(s)},t)),n||(clearTimeout(s),s=setTimeout(()=>{e.apply(this,r)},t))}}function s(e){let t=!1;return(...r)=>{t||(t=!0,e.apply(this,r))}}r.d(t,{J:()=>s,s:()=>n})},1910(e,t,r){"use strict";r.d(t,{i:()=>i});var n=r(944);const s=new Map;function i(...e){return e.every(e=>{if(s.has(e))return s.get(e);const t="function"==typeof e?e.toString():"",r=t.includes("[native code]"),i=t.includes("nrWrapper");return r||i||(0,n.R)(64,e?.name||t),s.set(e,r),r})}},3304(e,t,r){"use strict";r.d(t,{A:()=>s});var n=r(7836);function s(e){try{return JSON.stringify(e,(()=>{const e=new WeakSet;return(t,r)=>{if("object"==typeof r&&null!==r){if(e.has(r))return;e.add(r)}return r}})())??""}catch(e){try{n.ee.emit("internal-error",[e])}catch(e){}return""}}},5718(e,t,r){"use strict";r.d(t,{AZ:()=>m,Qr:()=>E,QL:()=>v});var n=r(6154),s=r(1863),i=r(9119),a=r(7866);class o{dom=new c;performance=new c;constructor(e){this.url=e,this.claimedBy=new Set}get script(){const e=Math.max(this.dom.start,this.performance.end);return{start:e,end:Math.max(this.dom.end,this.performance.end,e)}}}class c{start=0;end=0;value=void 0}function d(e=()=>{},t){return{get:()=>void 0!==t?t:e(),set:e=>{t=e}}}let u;try{u=m(v())[0]}catch(e){u=m(e)[0]}const l=e=>"script"===e.initiatorType||["link","fetch"].includes(e.initiatorType)&&(0,i.L)(e.name).endsWith(".js"),f=new Map;let h=[];function p(e){return f.get(e)}function g(e){const t=p(e);if(t)return t;const r=new o(e);if(f.set(e,r),f.size>1e3){const e=f.keys().next().value;f.delete(e)}return r}if(n.gm.MutationObserver&&n.gm.document){new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{if("SCRIPT"===e.nodeName&&e.src){const t=g((0,i.L)(e.src));t.dom.start=(0,s.t)(),t.dom.value=e;const r=()=>{t.dom.end=(0,s.t)()};["load","error"].forEach(t=>e.addEventListener(t,r,{once:!0}))}})})}).observe(n.gm.document,{childList:!0,subtree:!0})}if(n.gm.PerformanceObserver?.supportedEntryTypes.includes("resource")){new PerformanceObserver(e=>{e.getEntries().filter(l).forEach(e=>{const t=g((0,i.L)(e.name));t.performance.start=Math.floor(e.startTime),t.performance.end=Math.floor(e.responseEnd),t.performance.value=e;const r=[];h.forEach(({test:t,addedAt:n},i)=>{(t(e)||(0,s.t)()-n>1e4)&&r.push(i)}),h=h.filter((e,t)=>!r.includes(t))})}).observe({type:"resource",buffered:!0})}function m(e){if(!e||"string"!=typeof e)return[];const t=new Set,r=e.split("\n");for(const e of r){const r=e.match(a.cn)||e.match(a.hB)||e.match(a.fL);if(r&&r[2])t.add((0,i.L)(r[2]));else{const r=e.match(/\(([^)]+\.js):\d+:\d+\)/)||e.match(/^\s+at\s+([^\s(]+\.js):\d+:\d+/);r&&r[1]&&t.add((0,i.L)(r[1]))}}return[...t]}function v(){let e;try{const t=Error.stackTraceLimit;Error.stackTraceLimit=50,e=(new Error).stack,Error.stackTraceLimit=t}catch(t){e=(new Error).stack}return e}function y(e,t){return(0,i.L)(e.name)===t}function b(e,t){e.fetchStart=Math.floor(t.startTime),e.fetchEnd=Math.floor(t.responseEnd),e.asset=t.name,e.type=t.initiatorType}function E(e){const t=e?.id,r={registeredAt:(0,s.t)(),reportedAt:void 0,fetchStart:0,fetchEnd:0,scriptStart:0,scriptEnd:0,asset:void 0,type:"unknown"},a=v();if(!a)return r;const o=n.gm.performance?.getEntriesByType("navigation")?.[0]?.name||"";try{const e=m(a),c=(e.length>1?e.filter(e=>u!==e):e)[0];if(!c)return r;if(o.includes(c))return r.asset=(0,i.L)(o),r.type="inline",r;r.correlation=p(c);const l=r.correlation?.performance.value||n.gm.performance?.getEntriesByType("resource")?.find(e=>y(e,c));if(l)b(r,l);else{(function(e){if(!e||!n.gm.document)return!1;try{const t=n.gm.document.querySelectorAll('link[rel="preload"][as="script"]');for(const r of t)if((0,i.L)(r.href)===e)return!0}catch(e){}return!1})(c)&&(r.asset=c,r.type="preload"),function(e,t){n.gm.PerformanceObserver?.supportedEntryTypes?.includes("resource")&&h.push({addedAt:(0,s.t)(),test:r=>!!y(r,t)&&(b(e,r),!0)})}(r,c)}const f=r.correlation,g=!!t&&!!f?.claimedBy.has(t);f&&t&&f.claimedBy.add(t);const v=()=>{const e=f?.script.start;if(!g||!e)return!1;return r.registeredAt-e>1e4};Object.defineProperty(r,"scriptStart",d(()=>v()?r.registeredAt:f?.script.start??r.fetchEnd)),Object.defineProperty(r,"scriptEnd",d(()=>v()?r.registeredAt:f?.script.end??r.registeredAt))}catch(e){}return r}},5732(e,t,r){"use strict";r.d(t,{$5:()=>u,B5:()=>d,Ms:()=>i,NG:()=>s,Ux:()=>o,YA:()=>c,yx:()=>a});var n=r(5718);function s(e,t){if(!f(e,t))return;const r=t.runtime.registeredEntities;return r?.find(t=>t.metadata.target.iframeInterfaceId===e)}function i(e,t){if(!f(e,t))return[];const r=t.runtime.registeredEntities;return r?.filter(t=>String(t.metadata.target.id)===String(e)).map(e=>e.metadata.target)||[]}function a(e,t){if(!f(e,t))return[];const r=t.runtime.registeredEntities,n=r?.filter(t=>t.metadata.timings?.asset?.endsWith(e));return function(e){if(!e?.length)return e||[];const t=new Map,r=[];for(const n of e){const e=n.metadata?.timings?.asset;if(!e){r.push(n);continue}const s="".concat(e,"::").concat(n.metadata?.target?.id),i=t.get(s);if(i){if(i.metadata.target?.blocked&&!n.metadata.target?.blocked){const e=r.indexOf(i);-1!==e&&(r[e]=n),t.set(s,n)}}else t.set(s,n),r.push(n)}return r}(n).map(e=>e.metadata.target)}function o(e,t){if(!l(t))return{};const r=t.agentRef.runtime.appMetadata.agents[0].entityGuid;return e?e.attributes:{"entity.guid":r,appId:t.agentRef.info.applicationID}}function c(e,t){return d(e,t)?{"child.id":e.id,"child.type":e.type,...o(void 0,t)}:{}}function d(e,t){return!!e&&!!l(t)&&t.agentRef.init.api.register.duplicate_data_to_container}function u(e){if(!f(!0,e)||!e?.runtime?.registeredEntities?.length)return[void 0];const t=[];try{var r=(0,n.AZ)((0,n.QL)());let s=r.length-1;for(;r[s];)t.push(...a(r[s--],e))}catch(e){}const s=function(e){const t=new Set,r=[];for(const n of e){const e=n?.instance;t.has(e)||(t.add(e),r.push(n))}return r}(t);return s.length||s.push(void 0),s}function l(e){return 2===e?.harvestEndpointVersion}function f(e,t){return!!e&&!!t?.init.api.register.enabled}r.d(t,["fQ",0,{MFE:"MFE",BA:"BA"}])},5289(e,t,r){"use strict";r.d(t,{GG:()=>a,Qr:()=>c,sB:()=>o});var n=r(3878),s=r(6389);function i(){return"undefined"==typeof document||"complete"===document.readyState}function a(e,t){if(i())return e();const r=(0,s.J)(e),a=setInterval(()=>{i()&&(clearInterval(a),r())},500);(0,n.sp)("load",r,t)}function o(e){if(i())return e();(0,n.DD)("DOMContentLoaded",e)}function c(e){if(i())return e();(0,n.sp)("popstate",e)}},384(e,t,r){"use strict";r.d(t,{Zm:()=>c,bQ:()=>u,dV:()=>d,pV:()=>l});var n=r(6154),s=r(1863),i=r(944),a=r(1910);const o={beacon:"bam.nr-data.net",errorBeacon:"bam.nr-data.net"};function c(){return n.gm.NREUM||(n.gm.NREUM={}),void 0===n.gm.newrelic&&(n.gm.newrelic=n.gm.NREUM),n.gm.NREUM}function d(){let e=c();return e.o||(e.o={ST:n.gm.setTimeout,SI:n.gm.setImmediate||n.gm.setInterval,CT:n.gm.clearTimeout,XHR:n.gm.XMLHttpRequest,REQ:n.gm.Request,EV:n.gm.Event,PR:n.gm.Promise,MO:n.gm.MutationObserver,FETCH:n.gm.fetch,WS:n.gm.WebSocket},(0,a.i)(...Object.values(e.o))),e}function u(e,t){let r=c();r.initializedAgents??={},t.initializedAt={ms:(0,s.t)(),date:new Date},r.initializedAgents[e]=t,2===Object.keys(r.initializedAgents).length&&(0,i.R)(69)}function l(){return function(){let e=c();const t=e.info||{};e.info={beacon:o.beacon,errorBeacon:o.errorBeacon,...t}}(),function(){let e=c();const t=e.init||{};e.init={...t}}(),d(),function(){let e=c();const t=e.loader_config||{};e.loader_config={...t}}(),c()}r.d(t,["NT",0,o])},2843(e,t,r){"use strict";r.d(t,{G:()=>i,u:()=>s});var n=r(3878);function s(e,t=!1,r,s){(0,n.DD)("visibilitychange",function(){if(t)return void("hidden"===document.visibilityState&&e());e(document.visibilityState)},r,s)}function i(e,t,r){(0,n.sp)("pagehide",e,t,r)}},8139(e,t,r){"use strict";r.d(t,{u:()=>f});var n=r(7836),s=r(3434),i=r(8990),a=r(6154);const o={},c=a.gm.XMLHttpRequest,d="addEventListener",u="removeEventListener",l="nr@wrapped:".concat(n.P);function f(e){var t=function(e){return(e||n.ee).get("events")}(e);if(o[t.debugId]++)return t;o[t.debugId]=1;var r=(0,s.YM)(t,!0);function f(e){r.inPlace(e,[d,u],"-",p)}function p(e,t){return e[1]}return"getPrototypeOf"in Object&&(a.RI&&h(document,f),c&&h(c.prototype,f),h(a.gm,f)),t.on(d+"-start",function(e,t){var n=e[1];if(null!==n&&("function"==typeof n||"object"==typeof n)&&"newrelic"!==e[0]){var s=(0,i.I)(n,l,function(){var e={object:function(){if("function"!=typeof n.handleEvent)return;return n.handleEvent.apply(n,arguments)},function:n}[typeof n];return e?r(e,"fn-",null,e.name||"anonymous"):n});this.wrapped=e[1]=s}}),t.on(u+"-start",function(e){e[1]=this.wrapped||e[1]}),t}function h(e,t,...r){let n=e;for(;"object"==typeof n&&!Object.prototype.hasOwnProperty.call(n,d);)n=Object.getPrototypeOf(n);n&&t(n,...r)}},3434(e,t,r){"use strict";r.d(t,{YM:()=>d});var n=r(7836),s=r(5607),i=r(5732);const a="nr@original:".concat(s.W);var o=Object.prototype.hasOwnProperty,c=!1;function d(e,t,r){return e||(e=n.ee),s.inPlace=function(e,t,r,n,i,a){r||(r="");const o="-"===r.charAt(0);for(let c=0;c<t.length;c++){const d=t[c],u=e[d];l(u)||(e[d]=s(u,o?d+r:r,n,d,i,a))}},s.flag=a,s;function s(t,n,s,c,f,h){return l(t)?t:(n||(n=""),nrWrapper[a]=t,function(e,t,r){if(Object.defineProperty&&Object.keys)try{return Object.keys(e).forEach(function(r){Object.defineProperty(t,r,{get:function(){return e[r]},set:function(t){return e[r]=t,t}})}),t}catch(e){u([e],r)}for(var n in e)o.call(e,n)&&(t[n]=e[n])}(t,nrWrapper,e),nrWrapper);function nrWrapper(){var a,o,l,p;let g,m;try{o=this,a=[...arguments],m=h?(0,i.$5)(r):[void 0],l="function"==typeof s?s(a,o):s||{}}catch(t){u([t,"",[a,o,c],l],e)}d(n+"start",[a,o,c,m],l,f);const v=performance.now();let y;try{return p=t.apply(o,a),y=performance.now(),p}catch(e){throw y=performance.now(),d(n+"err",[a,o,e,m],l,f),g=e,g}finally{const e=y-v,t={start:v,end:y,duration:e,isLongTask:e>=50,methodName:c,thrownError:g};t.isLongTask&&d("long-task",[t,o,m],l,f),d(n+"end",[a,o,p,m],l,f)}}}function d(r,n,s,i){if(!c||t){var a=c;c=!0;try{e.emit(r,n,s,t,i)}catch(t){u([t,r,n,s],e)}c=a}}}function u(e,t){t||(t=n.ee);try{t.emit("internal-error",e)}catch(e){}}function l(e){return!(e&&"function"==typeof e&&e.apply&&!e[a])}r.d(t,["Jt",0,a])},9300(e,t,r){"use strict";const n=r(860).K7.ajax;r.d(t,["TZ",0,n,"f5",0,"ajaxRequest.id","mo",0,{NONE:"none",FAILURES:"failures",ALL:"all"}])},3333(e,t,r){"use strict";var n=r(981);const s=r(860).K7.genericEvents,i=[n.R.PA,n.R.UA,n.R.BP];r.d(t,["$v",0,{RESOURCES:"experimental.resources",REGISTER:"register"},"TZ",0,s,"Zp",0,["auxclick","click","copy","keydown","paste","scrollend"],"kd",0,i,"qN",0,["focus","blur"]])},6774(e,t,r){"use strict";const n=r(860).K7.jserrors;r.d(t,["T",0,n])},993(e,t,r){"use strict";const n=r(860).K7.logging;r.d(t,["A$",0,{OFF:0,ERROR:1,WARN:2,INFO:3,DEBUG:4,TRACE:5},"TZ",0,n,"p_",0,{ERROR:"ERROR",WARN:"WARN",INFO:"INFO",DEBUG:"DEBUG",TRACE:"TRACE"}])},3785(e,t,r){"use strict";r.d(t,{R:()=>c,b:()=>d});var n=r(9908),s=r(1863),i=r(860),a=r(3969),o=r(993);function c(e,t,r={},c=o.p_.INFO,d=!0,u,l=(0,s.t)()){(0,n.p)(a.xV,["API/logging/".concat(c.toLowerCase(),"/called")],void 0,i.K7.metrics,e),(0,n.p)("log",[l,t,r,c,d,u],void 0,i.K7.logging,e)}function d(e){return"string"==typeof e&&Object.values(o.p_).some(t=>t===e.toUpperCase().trim())}},3969(e,t,r){"use strict";const n=r(860).K7.metrics;r.d(t,["TZ",0,n,"XG",0,"storeEventMetrics","xV",0,"storeSupportabilityMetrics"])},6630(e,t,r){"use strict";const n=r(860).K7.pageViewEvent;r.d(t,["T",0,n])},782(e,t,r){"use strict";const n=r(860).K7.pageViewTiming;r.d(t,["T",0,n])},6344(e,t,r){"use strict";var n=r(2614);const s=r(860).K7.sessionReplay,i={[n.g.ERROR]:15e3,[n.g.FULL]:3e5,[n.g.OFF]:0};r.d(t,["Qb",0,{API:"api",RESUME:"resume",SWITCH_TO_FULL:"switchToFull",INITIALIZE:"initialize",PRELOAD:"preload"},"TZ",0,s,"Vh",0,"errorDuringReplay","_s",0,{DomContentLoaded:0,Load:1,FullSnapshot:2,IncrementalSnapshot:3,Meta:4,Custom:5},"bc",0,{RESET:{message:"Session was reset",sm:"Reset"},IMPORT:{message:"Recorder failed to import",sm:"Import"},TOO_MANY:{message:"429: Too Many Requests",sm:"Too-Many"},TOO_BIG:{message:"Payload was too large",sm:"Too-Big"},CROSS_TAB:{message:"Session Entity was set to OFF on another tab",sm:"Cross-Tab"},ENTITLEMENTS:{message:"Session Replay is not allowed and will not be started",sm:"Entitlement"}},"yP",0,i])},5270(e,t,r){"use strict";r.d(t,{Aw:()=>a,SR:()=>i,rF:()=>o});var n=r(384),s=r(7767);function i(e){return!!(0,n.dV)().o.MO&&(0,s.V)(e)&&!0===e?.session_trace.enabled}function a(e){return!0===e?.session_replay.preload&&i(e)}function o(e,t){try{if("string"==typeof t?.type){if("password"===t.type.toLowerCase())return"*".repeat(e?.length||0);if(void 0!==t?.dataset?.nrUnmask||t?.classList?.contains("nr-unmask"))return e}}catch(e){}return"string"==typeof e?e.replace(/[\S]/g,"*"):"*".repeat(e?.length||0)}},3738(e,t,r){"use strict";const n=r(860).K7.sessionTrace,s="-start",i="-end",a="fn"+s,o="fn"+i;r.d(t,["He",0,"bstResource","Kp",0,i,"Lc",0,o,"Rz",0,"pushState","TZ",0,n,"bD",0,"resource","bc",0,{CROSS_TAB:{message:"Session Entity was set to OFF on another tab"},ENTITLEMENTS:{message:"Session Trace is not allowed and will not be started"},RESET:{message:"Session was reset"},SESSION_CHANGED:{message:"Session identifier changed unexpectedly"}},"d3",0,s,"uP",0,a])},3962(e,t,r){"use strict";const n=r(860).K7.softNav;r.d(t,["O2",0,{INITIAL_PAGE_LOAD:"",ROUTE_CHANGE:1,UNSPECIFIED:2},"OV",0,"popstate","Qu",0,{INTERACTION:1,AJAX:2,CUSTOM_END:3,CUSTOM_TRACER:4},"TZ",0,n,"ih",0,{IP:"in progress",PF:"pending finish",FIN:"finished",CAN:"cancelled"},"pP",0,"initialPageLoad","tC",0,["click","keydown","submit"]])},4234(e,t,r){"use strict";r.d(t,{W:()=>s});var n=r(1687);class s{constructor(e,t){this.agentRef=e,this.ee=e?.ee,this.featureName=t,this.blocked=!1}deregisterDrain(){(0,n.x3)(this.agentRef,this.featureName)}}},7767(e,t,r){"use strict";var n=r(6154);r.d(t,["V",0,e=>n.RI&&!0===e?.privacy.cookies_enabled])},8362(e,t,r){"use strict";r.d(t,{d:()=>i});var n=r(9566),s=r(1741);class i extends s.W{agentIdentifier=(0,n.LA)(16)}},1741(e,t,r){"use strict";r.d(t,{W:()=>i});var n=r(944),s=r(4261);class i{#e(e,...t){if(this[e]!==i.prototype[e])return this[e](...t);(0,n.R)(35,e)}addPageAction(e,t){return this.#e(s.hG,e,t)}register(e){return this.#e(s.eY,e)}recordCustomEvent(e,t){return this.#e(s.fF,e,t)}setPageViewName(e,t){return this.#e(s.Fw,e,t)}setCustomAttribute(e,t,r){return this.#e(s.cD,e,t,r)}noticeError(e,t){return this.#e(s.o5,e,t)}setUserId(e,t=!1){return this.#e(s.Dl,e,t)}setApplicationVersion(e){return this.#e(s.nb,e)}setErrorHandler(e){return this.#e(s.bt,e)}addRelease(e,t){return this.#e(s.k6,e,t)}log(e,t){return this.#e("log",e,t)}start(){return this.#e("start")}finished(e){return this.#e(s.BL,e)}recordReplay(){return this.#e(s.CH)}pauseReplay(){return this.#e(s.Tb)}addToTrace(e){return this.#e(s.U2,e)}setCurrentRouteName(e){return this.#e(s.PA,e)}interaction(e){return this.#e(s.dT,e)}wrapLogger(e,t,r){return this.#e(s.Wb,e,t,r)}measure(e,t){return this.#e(s.V1,e,t)}consent(e){return this.#e(s.Pv,e)}}},4261(e,t,r){"use strict";r.d(t,["BL",0,"finished","CH",0,"recordReplay","Dl",0,"setUserId","Fw",0,"setPageViewName","PA",0,"setCurrentRouteName","Pv",0,"consent","Tb",0,"pauseReplay","U2",0,"addToTrace","V1",0,"measure","Wb",0,"wrapLogger","bt",0,"setErrorHandler","cD",0,"setCustomAttribute","dT",0,"interaction","eY",0,"register","fF",0,"recordCustomEvent","hG",0,"addPageAction","hw",0,"api-ixn-","k6",0,"addRelease","nb",0,"setApplicationVersion","o5",0,"noticeError"])},1738(e,t,r){"use strict";r.d(t,{U:()=>l,Y:()=>u});var n=r(3241),s=r(9908),i=r(1863),a=r(944),o=r(3969),c=r(8362),d=r(860);function u(e,t,r,i){const u=i||r;!u||u[e]&&u[e]!==c.d.prototype[e]||(u[e]=function(){(0,s.p)(o.xV,["API/"+e+"/called"],void 0,d.K7.metrics,r.ee),(0,n.W)({drained:!!r.runtime?.activatedFeatures,type:"data",name:"api",feature:"api-"+e,data:{}});try{return t.apply(this,arguments)}catch(e){(0,a.R)(23,e)}})}function l(e,t,r,n,a){const o=e.info;null===r?delete o.jsAttributes[t]:o.jsAttributes[t]=r,(a||null===r)&&(0,s.p)("api-"+n,[(0,i.t)(),t,r],void 0,"session",e.ee)}},8374(e,t,r){r.nc=(()=>{try{return document?.currentScript?.nonce}catch(e){}return""})()},860(e,t,r){"use strict";const n="events",s="jserrors",i="browser/blobs",a="browser/logs",o={ajax:"ajax",genericEvents:"generic_events",jserrors:s,logging:"logging",metrics:"metrics",pageAction:"page_action",pageViewEvent:"page_view_event",pageViewTiming:"page_view_timing",sessionReplay:"session_replay",sessionTrace:"session_trace",softNav:"soft_navigations"},c={[o.pageViewEvent]:1,[o.pageViewTiming]:2,[o.metrics]:3,[o.jserrors]:4,[o.softNav]:5,[o.ajax]:6,[o.sessionTrace]:7,[o.sessionReplay]:8,[o.logging]:9,[o.genericEvents]:10},d={[o.pageViewEvent]:"rum",[o.pageViewTiming]:n,[o.ajax]:n,[o.softNav]:n,[o.metrics]:s,[o.jserrors]:s,[o.sessionTrace]:i,[o.sessionReplay]:i,[o.logging]:a,[o.genericEvents]:"ins"};r.d(t,["$J",0,d,"K7",0,o,"P3",0,c,"XX",0,s,"Yy",0,a,"df",0,i])}};const t={};function r(n){const s=t[n];if(void 0!==s)return s.exports;const i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}r.m=e,r.d=(e,t)=>{if(Array.isArray(t))for(var n=0;n<t.length;){var s=t[n++],i=t[n++];r.o(e,s)?0===i&&n++:0===i?Object.defineProperty(e,s,{enumerable:!0,value:t[n++]}):Object.defineProperty(e,s,{enumerable:!0,get:i})}else for(var s in t)r.o(t,s)&&!r.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:t[s]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,n)=>(r.f[n](e,t),t),[])),r.u=e=>({212:"nr-spa-compressor",238:"nr-spa-iframe-message-handler",249:"nr-spa-recorder",478:"nr-spa"}[e]+"-1.320.1.min.js"),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{const e={},t="NRBA-1.320.1.PROD:";r.l=(n,s,i,a)=>{if(e[n])return void e[n].push(s);let o,c;if(void 0!==i){const e=document.getElementsByTagName("script");for(var d=0;d<e.length;d++){const r=e[d];if(r.getAttribute("src")==n||r.getAttribute("data-webpack")==t+i){o=r;break}}}if(!o){c=!0;var u={478:"sha512-YoskTYHPIrWG98sE4VYEwW1X1hRU7SYQ5gUkuum+Vnk9YpVQMgMbgG8lYKZpEY1t/BlMbhj5TcDkm4hd+iAoFg==",249:"sha512-J9cfahQHdbmusKS+pYtR4jLIuZaPm3/z+WR3hPZ0Kr4uam5dSP02IuHD0q2H6HdFn0nSXbH+NgataXtpXuefmw==",212:"sha512-qm5xRUse5eEodOYrSB0RiaDiJVRk6acEocDkpn89y/Sta3H5hsn8tkdcuxKplkKTvV8PR9RYjOr1XTPyeef6OA==",238:"sha512-8LyhnCBHH3+KEvagggDkCeTyGbF4Oh0ALm0Mqe3de/rRY54KvHpDHI+S7ELikMAH5K1tQsIj6t6jF+ul22D/yA=="};o=document.createElement("script"),o.charset="utf-8",r.nc&&o.setAttribute("nonce",r.nc),o.setAttribute("data-webpack",t+i),o.src=n,0!==o.src.indexOf(window.location.origin+"/")&&(o.crossOrigin="anonymous"),u[a]&&(o.integrity=u[a])}e[n]=[s];const l=(t,r)=>{o.onerror=o.onload=null,clearTimeout(f);const s=e[n];if(delete e[n],o.parentNode?.removeChild(o),s?.forEach(e=>e(r)),t)return t(r)},f=setTimeout(l.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=l.bind(null,o.onerror),o.onload=l.bind(null,o.onload),c&&document.head.appendChild(o)}})(),r.r=e=>{Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="https://js-agent.newrelic.com/",(()=>{const e={38:0,788:0};r.f.j=(t,n)=>{let s=r.o(e,t)?e[t]:void 0;if(0!==s)if(s)n.push(s[2]);else{const i=new Promise((r,n)=>s=e[t]=[r,n]);n.push(s[2]=i);const a=r.p+r.u(t),o=new Error,c=n=>{if(r.o(e,t)&&(s=e[t],0!==s&&(e[t]=void 0),s)){const e=n&&("load"===n.type?"missing":n.type),r=n&&n.target&&n.target.src;o.message="Loading chunk "+t+" failed: ("+e+": "+r+")",o.name="ChunkLoadError",o.type=e,o.request=r,s[1](o)}};r.l(a,c,"chunk-"+t,t)}};const t=(t,n)=>{let[s,i,a]=n;var o,c,d=0;if(s.some(t=>0!==e[t])){for(o in i)r.o(i,o)&&(r.m[o]=i[o]);if(a)a(r)}for(t&&t(n);d<s.length;d++)c=s[d],r.o(e,c)&&e[c]&&e[c][0](),e[c]=0},n=self["webpackChunk:NRBA-1.320.1.PROD"]=self["webpackChunk:NRBA-1.320.1.PROD"]||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))})(),(()=>{"use strict";r(8374);var e=r(8362),t=r(860);const n=Object.values(t.K7);var s=r(384),i=r(1741);var a=r(2555),o=r(3333),c=r(9300);const d=e=>{if(!e||"string"!=typeof e)return!1;try{document.createDocumentFragment().querySelector(e)}catch{return!1}return!0};var u=r(2614),l=r(944),f=r(8122);const h="[data-nr-mask]",p=e=>(0,f.a)(e,(()=>{const e={feature_flags:[],experimental:{register:!1,resources:!1,iframe_bridge:!1,iframe_domains:[]},mask_selector:"*",block_selector:"[data-nr-block]",mask_input_options:{color:!1,date:!1,"datetime-local":!1,email:!1,month:!1,number:!1,range:!1,search:!1,tel:!1,text:!1,time:!1,url:!1,week:!1,textarea:!1,select:!1,password:!0}};return{ajax:{deny_list:void 0,block_internal:!0,enabled:!0,autoStart:!0,capture_payloads:c.mo.NONE},api:{register:{get enabled(){return e.feature_flags.includes(o.$v.REGISTER)||e.experimental.register},set enabled(t){e.experimental.register=t},duplicate_data_to_container:!1,get allow_iframe_bridge(){return e.feature_flags.includes(o.$v.IFRAME_BRIDGE)||e.experimental.iframe_bridge},set allow_iframe_bridge(t){e.experimental.iframe_bridge=t},get iframe_domains(){return e.experimental.iframe_domains},set iframe_domains(t){Array.isArray(t)?e.experimental.iframe_domains=t:(0,l.R)(1,t)}}},browser_consent_mode:{enabled:!1},distributed_tracing:{enabled:void 0,exclude_newrelic_header:void 0,cors_use_newrelic_header:void 0,cors_use_tracecontext_headers:void 0,allowed_origins:void 0},get feature_flags(){return e.feature_flags},set feature_flags(t){e.feature_flags=t},generic_events:{enabled:!0,autoStart:!0},harvest:{interval:30},jserrors:{enabled:!0,autoStart:!0},logging:{enabled:!0,autoStart:!0},metrics:{enabled:!0,autoStart:!0},obfuscate:void 0,page_action:{enabled:!0},page_view_event:{enabled:!0,autoStart:!0},page_view_timing:{enabled:!0,autoStart:!0},performance:{capture_marks:!1,capture_measures:!1,capture_detail:!0,resources:{get enabled(){return e.feature_flags.includes(o.$v.RESOURCES)||e.experimental.resources},set enabled(t){e.experimental.resources=t},asset_types:[],first_party_domains:[],ignore_newrelic:!0}},privacy:{cookies_enabled:!0},proxy:{assets:void 0,beacon:void 0},session:{expiresMs:u.wk,inactiveMs:u.BB},session_replay:{autoStart:!0,enabled:!1,preload:!1,sampling_rate:10,error_sampling_rate:100,collect_fonts:!1,inline_images:!1,fix_stylesheets:!0,mask_all_inputs:!0,get mask_text_selector(){return e.mask_selector},set mask_text_selector(t){d(t)?e.mask_selector="".concat(t,",").concat(h):""===t||null===t?e.mask_selector=h:(0,l.R)(5,t)},get block_class(){return"nr-block"},get ignore_class(){return"nr-ignore"},get mask_text_class(){return"nr-mask"},get block_selector(){return e.block_selector},set block_selector(t){d(t)?e.block_selector+=",".concat(t):""!==t&&(0,l.R)(6,t)},get mask_input_options(){return e.mask_input_options},set mask_input_options(t){t&&"object"==typeof t?e.mask_input_options={...t,password:!0}:(0,l.R)(7,t)}},session_trace:{enabled:!0,autoStart:!0},soft_navigations:{enabled:!0,autoStart:!0},ssl:void 0,user_actions:{enabled:!0,elementAttributes:["id","className","tagName","type"]},web_sockets:{enabled:!1}}})());var g=r(6154),m=r(9324);let v=0;const y={buildEnv:"PROD",distMethod:"CDN",version:m.x,originTime:g.WN},b={consented:!1},E={activatedFeatures:void 0,appMetadata:{},configured:!1,get consented(){return this.session?.state?.consent||b.consented},set consented(e){b.consented=e},customTransaction:void 0,denyList:[],disabled:!1,drainRegistry:new Map,harvester:void 0,isolatedBacklog:!1,isRecording:!1,loaderType:void 0,maxBytes:3e4,obfuscator:void 0,onerror:void 0,ptid:void 0,releaseIds:{},session:void 0,timeKeeper:void 0,registeredEntities:[],jsAttributesMetadata:{bytes:0},get harvestCount(){return++v},listeningForIframeMessages:!1};var w=r(7836),R=r(3241);const A={accountID:void 0,trustKey:void 0,agentID:void 0,licenseKey:void 0,applicationID:void 0,xpid:void 0};var T=r(9908),x=r(9752);function S(e,t={},n,o){let{init:c,info:d,loader_config:u,runtime:l={},exposed:h=!0}=t;if(!d){const e=(0,s.pV)();c=e.init,d=e.info,u=e.loader_config}var m;e.init=p(c||{}),e.loader_config=(m=u||{},(0,f.a)(m,A)),d.jsAttributes??={},g.bv&&(d.jsAttributes.isWorker=!0),e.info=(0,a.D)(d);const v=e.init;e.runtime??=(e=>{const t=(0,f.a)(e,E),r=Object.keys(y).reduce((e,t)=>(e[t]={value:y[t],writable:!1,configurable:!0,enumerable:!0},e),{});return Object.defineProperties(t,r)})(l),v.proxy.assets&&(e=>{const t=e.startsWith("http");e+="/",r.p=t?e:"https://"+e})(v.proxy.assets),e.runtime.configured||(Object.defineProperty(e,"beacons",{get:()=>[e.info.beacon,e.info.errorBeacon,e.init.proxy.assets,e.init.proxy.beacon].filter(Boolean)}),Object.defineProperty(e.runtime,"denyList",{get:()=>[...e.init.ajax.deny_list||[],...e.init.ajax.block_internal?e.beacons:[]]}),e.runtime.ptid=e.agentIdentifier,function(e){const t=(0,s.pV)();Object.getOwnPropertyNames(i.W.prototype).forEach(r=>{const n=i.W.prototype[r];if("function"!=typeof n||"constructor"===n)return;let s=t[r];e[r]&&!1!==e.exposed&&"micro-agent"!==e.runtime?.loaderType&&(t[r]=(...t)=>{const n=e[r](...t);return s?s(...t):n})})}(e),e.runtime.loaderType=n,e.ee=w.ee.get(e.agentIdentifier),e.exposed=h,(0,R.W)({drained:!!e.runtime.activatedFeatures,type:"lifecycle",name:"initialize",feature:void 0,data:e.config}),e.init.api.register.allow_iframe_bridge&&g.gm.addEventListener("message",t=>{"string"==typeof t.data?.type&&t.data.type.startsWith(x.Pl)&&(0,T.p)("iframe-message",[t],void 0,"IFRAME",e.ee)}),e.runtime.configured=!0)}var _=r(1863),O=r(4261),N=r(1738);var P=r(1687),L=r(4234),k=r(5289),C=r(5270),I=r(7767),j=r(6389),M=r(7699);const D=new WeakSet;class B extends L.W{constructor(e,t){super(e,t),this.abortHandler=void 0,this.featAggregate=void 0,this.loadedSuccessfully=void 0,this.onAggregateImported=new Promise(e=>{this.loadedSuccessfully=e}),this.deferred=Promise.resolve(),!1===e.init[this.featureName].autoStart?this.deferred=new Promise((t,r)=>{this.ee.on("manual-start-all",(0,j.J)(()=>{(0,P.Ak)(e,this.featureName),t()}))}):(0,P.Ak)(e,t)}importAggregator(e,t,n={}){if(this.featAggregate)return;const s=async()=>{if(await this.deferred,this.#t(e),!(0,a.f)(e.info))return(0,l.R)(43),e.ee.abort(),void this.loadedSuccessfully(!1);let s;try{if((0,I.V)(e.init)){const{setupAgentSession:t}=await r.e(478).then(r.bind(r,8766));s=t(e)}}catch(e){(0,l.R)(20,e),this.ee.emit("internal-error",[e]),(0,T.p)(M.qh,[e],void 0,this.featureName,this.ee)}if(e.init.api.register.allow_iframe_bridge)try{const{setupIframeMFEMessageListener:t}=await r.e(238).then(r.bind(r,6417));t(e)}catch(e){(0,l.R)(23,e)}try{if(!this.#r(this.featureName,s,e.init))return(0,P.Ze)(this.agentRef,this.featureName),void this.loadedSuccessfully(!1);const{Aggregate:r}=await t();this.featAggregate=new r(e,n),e.runtime.harvester.initializedAggregates.push(this.featAggregate),this.loadedSuccessfully(!0)}catch(e){(0,l.R)(34,e),this.abortHandler?.(),(0,P.Ze)(this.agentRef,this.featureName,!0),this.loadedSuccessfully(!1),this.ee&&this.ee.abort()}};g.RI?(0,k.GG)(()=>s(),!0):s()}#r(e,r,n){if(this.blocked)return!1;switch(e){case t.K7.sessionReplay:return(0,C.SR)(n)&&!!r;case t.K7.sessionTrace:return!!r;default:return!0}}#t(e){if(!D.has(e)&&(D.add(e),!(0,a.f)(e.info))){const t=(0,s.pV)();let r={...t.info?.jsAttributes};try{r={...r,...e.info?.jsAttributes}}catch(e){}S(e,{...t,info:{...t.info,jsAttributes:r},runtime:e.runtime},e.runtime.loaderType)}}}var H=r(6630);class K extends B{static featureName=H.T;constructor(e){var t;super(e,H.T),this.setupInspectionEvents(),t=e,(0,N.Y)(O.Fw,function(e,r){"string"==typeof e&&("/"!==e.charAt(0)&&(e="/"+e),t.runtime.customTransaction=(r||"http://custom.transaction")+e,(0,T.p)("api-"+O.Fw,[(0,_.t)()],void 0,void 0,t.ee))},t),this.importAggregator(e,()=>r.e(478).then(r.bind(r,5839)))}setupInspectionEvents(){const e=(e,t)=>{e&&(0,R.W)({timeStamp:e.timeStamp,loaded:"complete"===e.target.readyState,type:"window",name:t,data:e.target.location+""})};(0,k.sB)(t=>{e(t,"DOMContentLoaded")}),(0,k.GG)(t=>{e(t,"load")}),(0,k.Qr)(t=>{e(t,"navigate")}),this.ee.on(u.tS.UPDATE,(e,t)=>{(0,R.W)({type:"lifecycle",name:"session",data:t})})}}class W extends e.d{constructor(e){var t;(super(),g.gm)?(this.features={},(0,s.bQ)(this.agentIdentifier,this),this.desiredFeatures=new Set(e.features||[]),this.desiredFeatures.add(K),S(this,e,e.loaderType||"agent"),t=this,(0,N.Y)(O.cD,function(e,r,n=!1){if("string"==typeof e){if(["string","number","boolean"].includes(typeof r)||null===r)return(0,N.U)(t,e,r,O.cD,n);(0,l.R)(40,typeof r)}else(0,l.R)(39,typeof e)},t),function(e){(0,N.Y)(O.Dl,function(t,r=!1){if("string"!=typeof t&&null!==t)return void(0,l.R)(41,typeof t);const n=e.info.jsAttributes["enduser.id"];r&&null!=n&&n!==t?(0,T.p)("api-setUserIdAndResetSession",[t],void 0,"session",e.ee):(0,N.U)(e,"enduser.id",t,O.Dl,!0)},e)}(this),function(e){(0,N.Y)(O.nb,function(t){if("string"==typeof t||null===t)return(0,N.U)(e,"application.version",t,O.nb,!1);(0,l.R)(42,typeof t)},e)}(this),function(e){(0,N.Y)("start",function(){e.ee.emit("manual-start-all")},e)}(this),function(e){(0,N.Y)(O.Pv,function(t=!0){if("boolean"==typeof t){if((0,T.p)("api-"+O.Pv,[t],void 0,"session",e.ee),e.runtime.consented=t,t){const t=e.features.page_view_event;t.onAggregateImported.then(e=>{const r=t.featAggregate;e&&!r.sentRum&&r.sendRum()})}}else(0,l.R)(65,typeof t)},e)}(this),this.run()):(0,l.R)(21)}get config(){return{info:this.info,init:this.init,loader_config:this.loader_config,runtime:this.runtime}}get api(){return this}run(){try{const e=function(e){const t={};return n.forEach(r=>{t[r]=!!e[r]?.enabled}),t}(this.init),r=[...this.desiredFeatures];r.sort((e,r)=>t.P3[e.featureName]-t.P3[r.featureName]),r.forEach(r=>{if(!e[r.featureName]&&r.featureName!==t.K7.pageViewEvent)return;const n=function(e){switch(e){case t.K7.ajax:return[t.K7.jserrors];case t.K7.sessionTrace:return[t.K7.ajax,t.K7.pageViewEvent];case t.K7.sessionReplay:return[t.K7.sessionTrace];case t.K7.pageViewTiming:return[t.K7.pageViewEvent];default:return[]}}(r.featureName).filter(e=>!(e in this.features));n.length>0&&(0,l.R)(36,{targetFeature:r.featureName,missingDependencies:n}),this.features[r.featureName]=new r(this)})}catch(e){(0,l.R)(22,e);for(const e in this.features)this.features[e].abortHandler?.();const t=(0,s.Zm)();delete t.initializedAgents[this.agentIdentifier]?.features,delete this.sharedAggregator;return t.ee.get(this.agentIdentifier).abort(),!1}}}var F=r(2843),U=r(782);class V extends B{static featureName=U.T;constructor(e){super(e,U.T),g.RI&&((0,F.u)(()=>(0,T.p)("docHidden",[(0,_.t)()],void 0,U.T,this.ee),!0),(0,F.G)(()=>(0,T.p)("winPagehide",[(0,_.t)()],void 0,U.T,this.ee)),this.importAggregator(e,()=>r.e(478).then(r.bind(r,9917))))}}var z=r(3969);class G extends B{static featureName=z.TZ;constructor(e){super(e,z.TZ),this.importAggregator(e,()=>r.e(478).then(r.bind(r,6555)))}}var q=r(6774),Y=r(3878),Z=r(3304);class Q{constructor(e,t,r,n,s){this.name="UncaughtError",this.message="string"==typeof e?e:(0,Z.A)(e),this.sourceURL=t,this.line=r,this.column=n,this.__newrelic=s}}function X(e){return te(e)?e:new Q(void 0!==e?.message?e.message:e,e?.filename||e?.sourceURL,e?.lineno||e?.line,e?.colno||e?.col,e?.__newrelic,e?.cause)}function J(e){const t="Unhandled Promise Rejection: ";if(!e?.reason)return;if(te(e.reason)){try{e.reason.message.startsWith(t)||(e.reason.message=t+e.reason.message)}catch(e){}return X(e.reason)}const r=X(e.reason);return(r.message||"").startsWith(t)||(r.message=t+r.message),r}function ee(e){if(e.error instanceof SyntaxError&&!/:\d+$/.test(e.error.stack?.trim())){const t=new Q(e.message,e.filename,e.lineno,e.colno,e.error.__newrelic,e.cause);return t.name=SyntaxError.name,t}return te(e.error)?e.error:X(e)}function te(e){return e instanceof Error&&!!e.stack}function re(e,r,n,s,i=(0,_.t)()){"string"==typeof e&&(e=new Error(e)),(0,T.p)("err",[e,i,!1,r,n.runtime.isRecording,void 0,s],void 0,t.K7.jserrors,n.ee),(0,T.p)("uaErr",[],void 0,t.K7.genericEvents,n.ee)}var ne=r(5732),se=r(993),ie=r(3785);function ae(e,{customAttributes:t={},level:r=se.p_.INFO}={},n,s,i=(0,_.t)()){(0,ie.R)(n.ee,e,t,r,!1,s,i)}function oe(e,r,n,s,i=(0,_.t)()){(0,T.p)("api-"+O.hG,[i,e,r,s],void 0,t.K7.genericEvents,n.ee)}function ce(e,r,n,s,i=(0,_.t)()){const{start:a,end:o,customAttributes:c}=r||{},d={customAttributes:c||{}};if("object"!=typeof d.customAttributes||"string"!=typeof e||0===e.length)return void(0,l.R)(57);const u=(e,t)=>null==e?t:"number"==typeof e?e:e instanceof PerformanceMark?e.startTime:Number.NaN;if(d.start=u(a,0),d.end=u(o,i),Number.isNaN(d.start)||Number.isNaN(d.end))(0,l.R)(57);else{if(d.duration=d.end-d.start,!(d.duration<0))return(0,T.p)("api-"+O.V1,[d,e,s],void 0,t.K7.genericEvents,n.ee),d;(0,l.R)(58)}}function de(e,r={},n,s,i=(0,_.t)()){(0,T.p)("api-"+O.fF,[i,e,r,s],void 0,t.K7.genericEvents,n.ee)}var ue=r(5718);const le=(e,t)=>e?.nrMfeId===t.id&&(!e?.nrMfeObserved||e?.nrMfeObserved===t.instance),fe=e=>{try{return g.gm.CSS.escape(e)}catch(t){return e}},he=(e,t={})=>{const r=t.id,n=t.instance;if(!e||!r||!n)return!1;try{let r=1===e.nodeType?e:e.parentElement;for(;r?.tagName;){if(le(r.dataset,t))return r.dataset.nrMfeObserved=n,!0;r=r.parentNode}}catch(e){}return!1},pe=(e,t)=>{const r=g.gm.document?.querySelectorAll('[data-nr-mfe-id="'.concat(fe(e.id),'"]')),n=(Array.from(r)||[]).find(t=>le(t.dataset,e));let s=!!n;s&&(n.dataset.nrMfeObserved??=e.instance);const i=new g.gm.MutationObserver(r=>{r.forEach(r=>{r.addedNodes.forEach(r=>{const n=1===r.nodeType?r:null;!s&&le(n?.dataset,e)&&(i.disconnect(),i.observe(n,{childList:!0,subtree:!0}),s=!0,n.dataset.nrMfeObserved=e.instance),(e=>{try{return e?.textContent?.trim()||["img","video","canvas","svg"].includes(e?.nodeName?.toLowerCase())}catch(e){return!1}})(r)&&(s||he(r,e))&&t(r,i)})})});return i.observe(n||g.gm.document,{childList:!0,subtree:!0}),i},ge=(e,t,r)=>{try{const n=new g.gm.PerformanceObserver(e=>{e.getEntries().forEach(r)});return n.observe(t),e.push(n),n}catch(e){return null}};function me(e,t){let r,n,s=!1;const i=e=>null==e?e:e-(t?.scriptStart||t?.registeredAt||0),a={fcp:{get value(){const e=i(r);return e>1e4?void 0:e},set value(e){s||(r=e)}},lcp:{get value(){if(void 0!==a.fcp.value)return i(n)},set value(e){s||(n=e)}},cls:{value:void 0},inp:{value:void 0},disconnect:()=>{}};if(!(e&&g.RI&&g.gm.MutationObserver&&g.gm.PerformanceObserver))return a;const o=[];setTimeout(()=>{r||a.disconnect()},1e4);const c=()=>{if(!s){if(null==r){const e=(0,_.t)();if(i(e)>1e4)return void a.disconnect();r=e}n??=(0,_.t)(),a.cls.value??=0}},d=pe(e,(e,t)=>{c(),t.disconnect()});o.push(d);let u=0,l=null;const f=new Set;try{l=new g.gm.ResizeObserver(e=>{s||e.forEach(e=>{try{const t=e.contentRect.width*e.contentRect.height;t>u&&(u=t,a.lcp.value=(0,_.t)()),l.unobserve(e.target)}catch(e){}})})}catch(e){}const h=pe(e,e=>{if(c(),l)try{const t=1===e.nodeType?e:e.parentElement;t&&!f.has(t)&&(f.add(t),"IMG"===t.tagName?t.complete?l.observe(t):t.addEventListener("load",()=>{l.observe(t)},{once:!0}):"VIDEO"===t.tagName?t.readyState>=2?l.observe(t):t.addEventListener("loadeddata",()=>{l.observe(t)},{once:!0}):l.observe(t))}catch(e){}});l&&o.push(l),o.push(h),ge(o,{type:"layout-shift",buffered:!0},t=>{s||t.hadRecentInput||(t.sources||[]).some(r=>!!he(r.node,e)&&(c(),a.cls.value+=t.value,!0))}),ge(o,{type:"event",buffered:!0,durationThreshold:40},t=>{!s&&t.interactionId&&he(t.target,e)&&(void 0===a.inp.value||t.duration>a.inp.value)&&(c(),a.inp.value=t.duration)});const p=["pointerdown","keydown"],m=()=>{p.forEach(e=>{g.gm.removeEventListener(e,v,{passive:!0})})};a.disconnect=()=>{s=!0,o.forEach(e=>{try{e?.disconnect()}catch(e){}}),m(),["visibilitychange","pagehide"].forEach(e=>{g.gm.removeEventListener(e,a.disconnect,{once:!0,passive:!0})})};const v=t=>{he(t?.target,e)&&((()=>{try{h?.disconnect(),l?.disconnect()}catch(e){}})(),m())};p.forEach(e=>{g.gm.addEventListener(e,v,{passive:!0})}),["visibilitychange","pagehide"].forEach(e=>{g.gm.addEventListener(e,a.disconnect,{once:!0,passive:!0})});const y=g.gm.document?.querySelectorAll('[data-nr-mfe-id="'.concat(fe(e.id),'"]'));return Array.from(y||[]).some(t=>le(t.dataset,e))&&c(),a}var ve=r(9566);const ye=["name","id","type"],be=new Map([[oe,"addPageAction"],[ae,"log"],[ce,"measure"],[re,"noticeError"],[de,"recordCustomEvent"]]),Ee={experimental:(0,j.J)(()=>(0,l.R)(54,"newrelic.register")),disabled:(0,j.J)(()=>(0,l.R)(55)),invalidTarget:(0,j.J)(e=>(0,l.R)(48,e)),deregistered:(0,j.J)(()=>(0,l.R)(68))};function we(e){(0,N.Y)(O.eY,function(t){return Re(e,t)},e)}function Re(e,r){Ee.experimental(),r||={},r.instance=(0,ve.LA)(8),r.type=ne.fQ.MFE,r.licenseKey||=e.info.licenseKey,r.blocked=!1,("object"!=typeof r.tags||null===r.tags||Array.isArray(r.tags))&&(r.tags={}),r.parent??={get id(){return e.runtime.appMetadata.agents?.[0].entityGuid},type:ne.fQ.BA};const n=(0,ue.Qr)(r),s=me(r,n),i={};Object.prototype.hasOwnProperty.call(r,"attributes")||Object.defineProperty(r,"attributes",{get:()=>({...i,"source.id":r.id,"source.name":r.name,"source.type":r.type,"parent.type":r.parent?.type||ne.fQ.BA,"parent.id":r.parent?.id})}),Object.entries(r.tags).forEach(([e,t])=>{ye.includes(e)||(i["source.".concat(e)]=t)});let a=()=>{};const o=e.runtime.registeredEntities,c=e=>{r.blocked=!0,a=e};function d(e){return"string"==typeof e&&!!e.trim()&&e.trim().length<501}e.init.api.register.enabled||c(Ee.disabled),d(r.id)&&d(r.name)||c(()=>Ee.invalidTarget(r));const u={addPageAction:(t,n={})=>g(oe,[t,{...i,...n},e],r),deregister:()=>{h(),c(Ee.deregistered)},log:(t,n={})=>g(ae,[t,{...n,customAttributes:{...i,...n.customAttributes||{}}},e],r),measure:(t,n={})=>g(ce,[t,{...n,customAttributes:{...i,...n.customAttributes||{}}},e],r),noticeError:(t,n={})=>g(re,[t,{...i,...n},e],r),recordCustomEvent:(t,n={})=>g(de,[t,{...i,...n},e],r),setApplicationVersion:e=>p("application.version",e),setCustomAttribute:(e,t)=>p(e,t),setUserId:e=>p("enduser.id",e),metadata:{get customAttributes(){return i},target:r,timings:n,vitals:s,events:{latestTimestamp:void 0}}},f=()=>(r.blocked&&a(),r.blocked);function h(){if(n.reportedAt)return;n.reportedAt=(0,_.t)(),s.disconnect(),n.fcp=s.fcp,n.lcp=s.lcp,n.cls=s.cls,n.inp=s.inp;const e=n.fetchEnd-n.fetchStart,t=n.scriptEnd-n.scriptStart,r={assetUrl:n.asset,assetType:n.type,timeAlive:n.reportedAt-n.registeredAt,timeToBeRequested:n.fetchStart,timeToExecute:t,timeToFetch:e,timeToLoad:e+t,timeToRegister:n.registeredAt,...s.fcp.value>=0&&{"nr.vitals.fcp.value":s.fcp.value},...s.lcp.value>=0&&{"nr.vitals.lcp.value":s.lcp.value},...s.cls.value>=0&&{"nr.vitals.cls.value":s.cls.value},...s.inp.value>=0&&{"nr.vitals.inp.value":s.inp.value}};u.recordCustomEvent("MicroFrontEndTiming",r)}f()||(o.push(u),(0,F.G)(h));const p=(e,t)=>{f()||(i[e]=t)},g=(r,n,s)=>{if(f()&&r!==Re)return;const i=u.metadata.events.latestTimestamp??(0,_.t)();u.metadata.events.latestTimestamp=void 0;const a=be.get(r)||"unknown";(0,T.p)(z.xV,["API/register/".concat(a,"/called")],void 0,t.K7.metrics,e.ee);try{return r(...n,s,i)}catch(e){(0,l.R)(50,e)}};return u}class Ae extends B{static featureName=q.T;constructor(e){var t;super(e,q.T),t=e,(0,N.Y)(O.o5,(e,r)=>re(e,r,t),t),function(e){(0,N.Y)(O.bt,function(t){e.runtime.onerror=t},e)}(e),function(e){let t=0;(0,N.Y)(O.k6,function(e,r){++t>10||(this.runtime.releaseIds[e.slice(-200)]=(""+r).slice(-200))},e)}(e),we(e);try{this.removeOnAbort=new AbortController}catch(e){}this.ee.on("internal-error",(t,r)=>{this.abortHandler&&(0,T.p)("ierr",[X(t),(0,_.t)(),!0,{},e.runtime.isRecording,r],void 0,this.featureName,this.ee)}),g.gm.addEventListener("unhandledrejection",t=>{this.abortHandler&&(0,T.p)("err",[J(t),(0,_.t)(),!1,{unhandledPromiseRejection:1},e.runtime.isRecording],void 0,this.featureName,this.ee)},(0,Y.jT)(!1,this.removeOnAbort?.signal)),g.gm.addEventListener("error",t=>{this.abortHandler&&(0,T.p)("err",[ee(t),(0,_.t)(),!1,{},e.runtime.isRecording],void 0,this.featureName,this.ee)},(0,Y.jT)(!1,this.removeOnAbort?.signal)),this.abortHandler=this.#n,this.importAggregator(e,()=>r.e(478).then(r.bind(r,9377)))}#n(){this.removeOnAbort?.abort(),this.abortHandler=void 0}}var Te=r(8990);let xe=1;function Se(e){const t=typeof e;return!e||"object"!==t&&"function"!==t?-1:e===g.gm?0:(0,Te.I)(e,"nr@id",function(){return xe++})}function _e(e){if("string"==typeof e)return e.length;if("object"==typeof e){if("undefined"!=typeof ArrayBuffer&&e instanceof ArrayBuffer&&e.byteLength)return e.byteLength;if("undefined"!=typeof Blob&&e instanceof Blob&&e.size)return e.size;if(!("undefined"!=typeof FormData&&e instanceof FormData))try{return(0,Z.A)(e).length}catch(e){return}}}var Oe=r(8139),Ne=r(3434);const Pe={},Le=["open","send","setRequestHeader"];function ke(e,t){var r=e||w.ee;const n=function(e){return(e||w.ee).get("xhr")}(r);if(void 0===g.gm.XMLHttpRequest)return n;if(Pe[n.debugId]++)return n;Pe[n.debugId]=1,(0,Oe.u)(r);var s=(0,Ne.YM)(n),i=g.gm.XMLHttpRequest,a=g.gm.MutationObserver,o=g.gm.Promise,c=g.gm.setInterval,d="readystatechange",u=["onload","onerror","onabort","onloadstart","onloadend","onprogress","ontimeout"],f=[],h=g.gm.XMLHttpRequest=function(e){const r=new i(e),a=n.context(r);a.targets=(0,ne.$5)(t);try{n.emit("new-xhr",[r],a),r.addEventListener(d,(o=a,function(){var e=this;e.readyState>3&&!o.resolved&&(o.resolved=!0,n.emit("xhr-resolved",[],e)),s.inPlace(e,u,"fn-",E)}),(0,Y.jT)(!1))}catch(e){(0,l.R)(15,e);try{n.emit("internal-error",[e])}catch(e){}}var o;return r};function p(e,t){s.inPlace(t,["onreadystatechange"],"fn-",E)}if(function(e,t){for(var r in e)t[r]=e[r]}(i,h),h.prototype=i.prototype,s.inPlace(h.prototype,Le,"-xhr-",E),n.on("send-xhr-start",function(e,t){p(e,t),function(e){f.push(e),a&&(m?m.then(b):c?c(b):(v=-v,y.data=v))}(t)}),n.on("open-xhr-start",p),a){var m=o&&o.resolve();if(!c&&!o){var v=1,y=document.createTextNode(v);new a(b).observe(y,{characterData:!0})}}else r.on("fn-end",function(e){e[0]&&e[0].type===d||b()});function b(){for(var e=0;e<f.length;e++)p(0,f[e]);f.length&&(f=[])}function E(e,t){return t}return n}var Ce="fetch-",Ie=Ce+"body-",je=["arrayBuffer","blob","json","text","formData"],Me=g.gm.Request,De=g.gm.Response,Be="prototype";const He={};function Ke(e,t){const r=function(e){return(e||w.ee).get("fetch")}(e);if(!(Me&&De&&g.gm.fetch))return r;if(He[r.debugId]++)return r;function n(e,n,s){var i=e[n];"function"==typeof i&&(e[n]=function(){var e=[...arguments];const n={},a=(0,ne.$5)(t);var o;r.emit(s+"before-start",[e],n),n[w.P]&&n[w.P].dt&&(o=n[w.P].dt);var c=i.apply(this,e);return r.emit(s+"start",[e,o],c),c.then(function(e){return r.emit(s+"end",[null,e,a],c),e},function(e){throw r.emit(s+"end",[e,void 0,a],c),e})})}return He[r.debugId]=1,je.forEach(e=>{n(Me[Be],e,Ie),n(De[Be],e,Ie)}),n(g.gm,"fetch",Ce),r.on(Ce+"end",function(e,t,n){var s=this;if(s.targets=n||[void 0],t){var i=t.headers.get("content-length");null!==i&&(s.rxSize=i),r.emit(Ce+"done",[null,t],s)}else r.emit(Ce+"done",[e],s)}),r}var We=r(7485);class Fe{constructor(e){this.agentRef=e}generateTracePayload(e){const t=this.agentRef.loader_config;if(!this.shouldGenerateTrace(e)||!t)return null;var r=(t.accountID||"").toString()||null,n=(t.agentID||"").toString()||null,s=(t.trustKey||"").toString()||null;if(!r||!n)return null;var i=(0,ve.ZF)(),a=(0,ve.el)(),o=Date.now(),c={spanId:i,traceId:a,timestamp:o};return(e.sameOrigin||this.isAllowedOrigin(e)&&this.useTraceContextHeadersForCors())&&(c.traceContextParentHeader=this.generateTraceContextParentHeader(i,a),c.traceContextStateHeader=this.generateTraceContextStateHeader(i,o,r,n,s)),(e.sameOrigin&&!this.excludeNewrelicHeader()||!e.sameOrigin&&this.isAllowedOrigin(e)&&this.useNewrelicHeaderForCors())&&(c.newrelicHeader=this.generateTraceHeader(i,a,o,r,n,s)),c}generateTraceContextParentHeader(e,t){return"00-"+t+"-"+e+"-01"}generateTraceContextStateHeader(e,t,r,n,s){return s+"@nr=0-1-"+r+"-"+n+"-"+e+"----"+t}generateTraceHeader(e,t,r,n,s,i){if(!("function"==typeof g.gm?.btoa))return null;var a={v:[0,1],d:{ty:"Browser",ac:n,ap:s,id:e,tr:t,ti:r}};return i&&n!==i&&(a.d.tk=i),btoa((0,Z.A)(a))}shouldGenerateTrace(e){return this.agentRef.init?.distributed_tracing?.enabled&&this.isAllowedOrigin(e)}isAllowedOrigin(e){var t=!1;const r=this.agentRef.init?.distributed_tracing;if(e.sameOrigin)t=!0;else if(r?.allowed_origins instanceof Array)for(var n=0;n<r.allowed_origins.length;n++){var s=(0,We.D)(r.allowed_origins[n]);if(e.hostname===s.hostname&&e.protocol===s.protocol&&e.port===s.port){t=!0;break}}return t}excludeNewrelicHeader(){var e=this.agentRef.init?.distributed_tracing;return!!e&&!!e.exclude_newrelic_header}useNewrelicHeaderForCors(){var e=this.agentRef.init?.distributed_tracing;return!!e&&!1!==e.cors_use_newrelic_header}useTraceContextHeadersForCors(){var e=this.agentRef.init?.distributed_tracing;return!!e&&!!e.cors_use_tracecontext_headers}}var Ue=r(7295);function Ve(e){return"string"==typeof e?e:e instanceof(0,s.dV)().o.REQ?e.url:g.gm?.URL&&e instanceof URL?e.href:void 0}function ze(e,t){var r=(0,We.D)(t),n=e.params||e;n.hostname=r.hostname,n.port=r.port,n.protocol=r.protocol,n.host=r.hostname+":"+r.port,n.pathname=r.pathname,e.parsedOrigin=r,e.sameOrigin=r.sameOrigin}var Ge=["load","error","abort","timeout"],qe=Ge.length,Ye=(0,s.dV)().o.REQ,Ze=(0,s.dV)().o.XHR;const $e="X-NewRelic-App-Data",Qe="internal-error";class Xe extends B{static featureName=c.TZ;constructor(e){super(e,c.TZ),this.dt=new Fe(e),this.handler=(e,t,r,n)=>(0,T.p)(e,t,r,n,this.ee);try{const e={xmlhttprequest:"xhr",fetch:"fetch",beacon:"beacon"};g.gm?.performance?.getEntriesByType("resource").forEach(r=>{if(r.initiatorType in e&&0!==r.responseStatus){const n={status:r.responseStatus},s={rxSize:r.transferSize,duration:Math.floor(r.duration),cbTime:0};ze(n,r.name),this.handler("xhr",[n,s,r.startTime,r.responseEnd,e[r.initiatorType]],void 0,t.K7.ajax)}})}catch(e){}Ke(this.ee,e),ke(this.ee,e),function(e,r,n,s){const i=[c.mo.ALL,c.mo.FAILURES].includes(e.init.ajax?.capture_payloads);function a(e){var t=this;t.totalCbs=0,t.called=0,t.cbTime=0,t.end=x,t.ended=!1,t.xhrGuids={},t.lastSize=null,t.loadCaptureCalled=!1,t.params=this.params||{},t.metrics=this.metrics||{},t.latestLongtaskEnd=0,e.addEventListener("load",function(r){O(t,e)},(0,Y.jT)(!1)),g.lR||e.addEventListener("progress",function(e){t.lastSize=e.loaded},(0,Y.jT)(!1))}function o(e){this.params={method:e[0]},ze(this,e[1]),this.metrics={}}function d(t,r){e.loader_config.xpid&&this.sameOrigin&&r.setRequestHeader("X-NewRelic-ID",e.loader_config.xpid);var n=s.generateTracePayload(this.parsedOrigin);if(n){var i=!1;n.newrelicHeader&&(r.setRequestHeader("newrelic",n.newrelicHeader),i=!0),n.traceContextParentHeader&&(r.setRequestHeader("traceparent",n.traceContextParentHeader),n.traceContextStateHeader&&r.setRequestHeader("tracestate",n.traceContextStateHeader),i=!0),i&&(this.dt=n)}}function u(e,t){i&&e.length>=2&&(this.requestHeaders??={},this.requestHeaders[e[0].toLowerCase()]=e[1])}function l(e,t){var n=this.metrics,s=e[0],i=this;if(n&&s){var a=_e(s);a&&(n.txSize=a)}this.startTime=(0,_.t)(),this.requestBody=s,this.listener=function(e){try{"abort"!==e.type||i.loadCaptureCalled||(i.params.aborted=!0),("load"!==e.type||i.called===i.totalCbs&&(i.onloadCalled||"function"!=typeof t.onload)&&"function"==typeof i.end)&&i.end(t)}catch(e){try{r.emit(Qe,[e])}catch(e){}}};for(var o=0;o<qe;o++)t.addEventListener(Ge[o],this.listener,(0,Y.jT)(!1))}function f(e,t,r){this.cbTime+=e,t?this.onloadCalled=!0:this.called+=1,this.called!==this.totalCbs||!this.onloadCalled&&"function"==typeof r.onload||"function"!=typeof this.end||this.end(r)}function h(e,t){var r=""+Se(e)+!!t;this.xhrGuids&&!this.xhrGuids[r]&&(this.xhrGuids[r]=!0,this.totalCbs+=1)}function p(e,t){var r=""+Se(e)+!!t;this.xhrGuids&&this.xhrGuids[r]&&(delete this.xhrGuids[r],this.totalCbs-=1)}function m(){this.endTime=(0,_.t)()}function v(e,t){t instanceof Ze&&"load"===e[0]&&r.emit("xhr-load-added",[e[1],e[2]],t)}function y(e,t){t instanceof Ze&&"load"===e[0]&&r.emit("xhr-load-removed",[e[1],e[2]],t)}function b(e,t,r){t instanceof Ze&&("onload"===r&&(this.onload=!0),("load"===(e[0]&&e[0].type)||this.onload)&&(this.xhrCbStart=(0,_.t)()))}function E(e,t){this.xhrCbStart&&r.emit("xhr-cb-time",[(0,_.t)()-this.xhrCbStart,this.onload,t],t)}function w(e){var t,r=e[1]||{};if("string"==typeof e[0]?0===(t=e[0]).length&&g.RI&&(t=""+g.gm.location.href):e[0]&&e[0].url?t=e[0].url:g.gm?.URL&&e[0]&&e[0]instanceof URL?t=e[0].href:"function"==typeof e[0].toString&&(t=e[0].toString()),"string"==typeof t&&0!==t.length){t&&(this.parsedOrigin=(0,We.D)(t),this.sameOrigin=this.parsedOrigin.sameOrigin);var n=s.generateTracePayload(this.parsedOrigin);if(n&&(n.newrelicHeader||n.traceContextParentHeader))if(e[0]&&e[0].headers)o(e[0].headers,n)&&(this.dt=n);else{var i={};for(var a in r)i[a]=r[a];i.headers=new Headers(r.headers||{}),o(i.headers,n)&&(this.dt=n),e.length>1?e[1]=i:e.push(i)}}function o(e,t){var r=!1;return t.newrelicHeader&&(e.set("newrelic",t.newrelicHeader),r=!0),t.traceContextParentHeader&&(e.set("traceparent",t.traceContextParentHeader),t.traceContextStateHeader&&e.set("tracestate",t.traceContextStateHeader),r=!0),r}}function R(e,t){this.params={},this.metrics={},this.startTime=(0,_.t)(),this.dt=t;let[r,n={}]=e;ze(this,Ve(r));const s=(""+(r&&r instanceof Ye&&r.method||n.method||"GET")).toUpperCase();this.params.method=s,this.txSize=_e(n.body||r?.body)||0;try{var a=n.headers||r?.headers;if(i&&a)if(this.requestHeaders??={},a instanceof Headers)a.forEach(function(e,t){this.requestHeaders[t.toLowerCase()]=e}.bind(this));else if("object"==typeof a)for(var o in a)this.requestHeaders[o.toLowerCase()]=a[o]}catch(e){}this.requestBody=n.body||r?.body}function A(e,t){if(this.endTime=(0,_.t)(),this.params||(this.params={}),(0,Ue.iW)(this.params))return;this.params.status=t?t.status:0;const n=()=>{const e=+this.rxSize,t=null==this.rxSize||isNaN(e)?void 0:e,r={txSize:this.txSize,rxSize:t,duration:this.endTime-this.startTime},n=[this.params,r,this.startTime,this.endTime,"fetch"];this.targets.forEach(e=>S(n,this,e))};t&&i?t.clone().text().then(e=>{this.responseBody=e,this.rxSize&&"0"!==this.rxSize&&0!==this.rxSize||void 0===e||0===this.params.status||(this.rxSize=_e(e)),t?.headers&&(this.responseHeaders={},t.headers.forEach(function(e,t){this.responseHeaders[t.toLowerCase()]=e}.bind(this)))}).catch(e=>{r.emit(Qe,[e])}).finally(()=>{n()}):n()}function x(e){const t=this.params,n=this.metrics;if(this.ended)return;this.ended=!0;for(let t=0;t<qe;t++)e.removeEventListener(Ge[t],this.listener,!1);if(t.aborted)return;if((0,Ue.iW)(t))return;if(n.duration=this.endTime-this.startTime,this.loadCaptureCalled||4!==e.readyState?null==t.status&&(t.status=0):O(this,e),n.cbTime=this.cbTime,i){try{this.responseBody=e.responseText}catch(t){this.responseBody=e.response}if((!n.rxSize||0===n.rxSize)&&void 0!==this.responseBody&&0!==t.status){const e=_e(this.responseBody);void 0!==e&&(n.rxSize=e)}try{this.responseHeaders=function(e){const t={};return e?(e.split("\r\n").forEach(function(e){const r=e.indexOf(": ");if(r>0){const n=e.substring(0,r),s=e.substring(r+2);t[n.toLowerCase()]=s}}),t):t}(e.getAllResponseHeaders())}catch(e){r.emit(Qe,[e])}}const s=[t,n,this.startTime,this.endTime,"xhr"];this.targets.forEach(e=>S(s,this,e))}function S(e,r,s){n("xhr",[...e,s],r,t.K7.ajax)}function O(e,n){e.params.status=n.status;var s=function(e,t){var r=e.responseType;return"json"===r&&null!==t?t:"arraybuffer"===r||"blob"===r||"json"===r?_e(e.response):"text"===r||""===r||void 0===r?_e(e.responseText):void 0}(n,e.lastSize);if(void 0!==s&&0!==n.status&&(e.metrics.rxSize=s),e.sameOrigin&&n.getAllResponseHeaders().indexOf($e)>=0){var i=n.getResponseHeader($e);i&&((0,T.p)("sm",["Ajax/CrossApplicationTracing/Header/Seen"],void 0,t.K7.metrics,r),e.params.cat=i.split(", ").pop())}e.loadCaptureCalled=!0}r.on("new-xhr",a),r.on("open-xhr-start",o),r.on("open-xhr-end",d),r.on("send-xhr-start",l),r.on("setRequestHeader-xhr-start",u),r.on("xhr-cb-time",f),r.on("xhr-load-added",h),r.on("xhr-load-removed",p),r.on("xhr-resolved",m),r.on("addEventListener-end",v),r.on("removeEventListener-end",y),r.on("fn-end",E),r.on("fetch-before-start",w),r.on("fetch-start",R),r.on("fn-start",b),r.on("fetch-done",A)}(e,this.ee,this.handler,this.dt),this.importAggregator(e,()=>r.e(478).then(r.bind(r,3845)))}}const Je={},et=["pushState","replaceState"];function tt(e){const t=function(e){return(e||w.ee).get("history")}(e);return!g.RI||Je[t.debugId]++||(Je[t.debugId]=1,(0,Ne.YM)(t).inPlace(window.history,et,"-")),t}var rt=r(3738);function nt(e){(0,N.Y)(O.BL,function(r=Date.now()){const n=r-g.WN;n<0&&(0,l.R)(62,r),(0,T.p)(z.XG,[O.BL,{time:n}],void 0,t.K7.metrics,e.ee),e.addToTrace({name:O.BL,start:r,origin:"nr"}),(0,T.p)("api-"+O.hG,[n,O.BL],void 0,t.K7.genericEvents,e.ee)},e)}const{He:st,bD:it,d3:at,Kp:ot,TZ:ct,Lc:dt,uP:ut,Rz:lt}=rt;class ft extends B{static featureName=ct;constructor(e){var n;super(e,ct),n=e,(0,N.Y)(O.U2,function(e){if(!(e&&"object"==typeof e&&e.name&&e.start))return;const r={n:e.name,s:e.start-g.WN,e:(e.end||e.start)-g.WN,o:e.origin||"",t:"api"};r.s<0||r.e<0||r.e<r.s?(0,l.R)(61,{start:r.s,end:r.e}):(0,T.p)("bstApi",[r],void 0,t.K7.sessionTrace,n.ee)},n),nt(e);if(!(0,I.V)(e.init))return void this.deregisterDrain();const s=this.ee;let i;tt(s),this.eventsEE=(0,Oe.u)(s),this.eventsEE.on(ut,function(e,t){this.bstStart=(0,_.t)()}),this.eventsEE.on(dt,function(e,r){(0,T.p)("bst",[e[0],r,this.bstStart,(0,_.t)()],void 0,t.K7.sessionTrace,s)}),s.on(lt+at,function(e){this.time=(0,_.t)(),this.startPath=location.pathname+location.hash}),s.on(lt+ot,function(e){(0,T.p)("bstHist",[location.pathname+location.hash,this.startPath,this.time],void 0,t.K7.sessionTrace,s)});try{i=new PerformanceObserver(e=>{const r=e.getEntries();(0,T.p)(st,[r],void 0,t.K7.sessionTrace,s)}),i.observe({type:it,buffered:!0})}catch(e){}this.importAggregator(e,()=>r.e(478).then(r.bind(r,6974)),{resourceObserver:i})}}var ht=r(733),pt=r(6344);class gt extends B{static featureName=pt.TZ;#s;recorder;constructor(e){var n;let s;super(e,pt.TZ),n=e,(0,N.Y)(O.CH,function(){(0,T.p)(O.CH,[],void 0,t.K7.sessionReplay,n.ee)},n),function(e){(0,N.Y)(O.Tb,function(){(0,T.p)(O.Tb,[],void 0,t.K7.sessionReplay,e.ee)},e)}(e);const i="".concat(u.Wt).concat((0,ht.Y)(e.info.licenseKey,e.info.applicationID));try{s=JSON.parse(localStorage.getItem(i))}catch(e){}(0,C.SR)(e.init)&&this.ee.on(O.CH,()=>this.#i()),this.#a(s)&&this.importRecorder().then(e=>{e.startRecording(pt.Qb.PRELOAD,s?.sessionReplayMode)}),this.importAggregator(this.agentRef,()=>r.e(478).then(r.bind(r,6167)),this),this.ee.on("err",e=>{this.blocked||this.agentRef.runtime.isRecording&&(this.errorNoticed=!0,(0,T.p)(pt.Vh,[e],void 0,this.featureName,this.ee))})}#a(e){return e&&(e.sessionReplayMode===u.g.FULL||e.sessionReplayMode===u.g.ERROR)||(0,C.Aw)(this.agentRef.init)}importRecorder(){return this.recorder?Promise.resolve(this.recorder):(this.#s??=Promise.all([r.e(478),r.e(249)]).then(r.bind(r,4866)).then(({Recorder:e})=>(this.recorder=new e(this),this.recorder)).catch(e=>{throw this.ee.emit("internal-error",[e]),this.blocked=!0,e}),this.#s)}#i(){this.blocked||(this.featAggregate?this.featAggregate.mode!==u.g.FULL&&this.featAggregate.initializeRecording(u.g.FULL,!0,pt.Qb.API):this.importRecorder().then(()=>{this.recorder.startRecording(pt.Qb.API,u.g.FULL)}))}}var mt=r(3962);class vt extends B{static featureName=mt.TZ;constructor(e){if(super(e,mt.TZ),function(e){const r=e.ee.get("tracer");function n(){}(0,N.Y)(O.dT,function(e){return(new n).get("object"==typeof e?e:{})},e);const s=n.prototype={createTracer:function(n,s){var i={},a=this,o="function"==typeof s;return(0,T.p)(z.xV,["API/createTracer/called"],void 0,t.K7.metrics,e.ee),function(){if(r.emit((o?"":"no-")+"fn-start",[(0,_.t)(),a,o],i),o)try{return s.apply(this,arguments)}catch(e){const t="string"==typeof e?new Error(e):e;throw r.emit("fn-err",[arguments,this,t],i),t}finally{r.emit("fn-end",[(0,_.t)()],i)}}}};["actionText","setName","setAttribute","save","ignore","onEnd","getContext","end","get"].forEach(r=>{N.Y.apply(this,[r,function(){return(0,T.p)(O.hw+r,[performance.now(),...arguments],this,t.K7.softNav,e.ee),this},e,s])}),(0,N.Y)(O.PA,function(){(0,T.p)(O.hw+"routeName",[performance.now(),...arguments],void 0,t.K7.softNav,e.ee)},e)}(e),!g.RI||!(0,s.dV)().o.MO)return;const n=tt(this.ee);try{this.removeOnAbort=new AbortController}catch(e){}mt.tC.forEach(e=>{(0,Y.sp)(e,e=>{c(e)},!0,this.removeOnAbort?.signal)});const i=()=>(0,T.p)("newURL",[(0,_.t)(),""+window.location],void 0,this.featureName,this.ee);n.on("pushState-end",i),n.on("replaceState-end",i),(0,Y.sp)(mt.OV,e=>{c(e),(0,T.p)("newURL",[e.timeStamp,""+window.location],void 0,this.featureName,this.ee)},!0,this.removeOnAbort?.signal);let a=!1;const o=new((0,s.dV)().o.MO)((e,t)=>{a||(a=!0,requestAnimationFrame(()=>{(0,T.p)("newDom",[(0,_.t)()],void 0,this.featureName,this.ee),a=!1}))}),c=(0,j.s)(e=>{"loading"!==document.readyState&&((0,T.p)("newUIEvent",[e],void 0,this.featureName,this.ee),o.observe(document.body,{attributes:!0,childList:!0,subtree:!0,characterData:!0}))},100,{leading:!0});this.abortHandler=function(){this.removeOnAbort?.abort(),o.disconnect(),this.abortHandler=void 0},this.importAggregator(e,()=>r.e(478).then(r.bind(r,4393)),{domObserver:o})}}var yt=r(9119),bt=r(981);const Et={},wt=new Set;function Rt(e){return"string"==typeof e?{type:"string",size:(new TextEncoder).encode(e).length}:e instanceof ArrayBuffer?{type:"ArrayBuffer",size:e.byteLength}:e instanceof Blob?{type:"Blob",size:e.size}:e instanceof DataView?{type:"DataView",size:e.byteLength}:ArrayBuffer.isView(e)?{type:"TypedArray",size:e.byteLength}:{type:"unknown",size:0}}class At{constructor(e,t){this.timestamp=(0,_.t)(),this.currentUrl=(0,yt.L)(window.location.href),this.socketId=(0,ve.LA)(8),this.requestedUrl=(0,yt.L)(e),this.requestedProtocols=Array.isArray(t)?t.join(","):t||"",this.openedAt=void 0,this.protocol=void 0,this.extensions=void 0,this.binaryType=void 0,this.messageOrigin=void 0,this.messageCount=0,this.messageBytes=0,this.messageBytesMin=0,this.messageBytesMax=0,this.messageTypes=void 0,this.sendCount=0,this.sendBytes=0,this.sendBytesMin=0,this.sendBytesMax=0,this.sendTypes=void 0,this.closedAt=void 0,this.closeCode=void 0,this.closeReason="unknown",this.closeWasClean=void 0,this.connectedDuration=0,this.hasErrors=void 0}}class Tt extends B{static featureName=o.TZ;constructor(e){super(e,o.TZ);const n=e.init.feature_flags.includes("websockets")||e.init.web_sockets?.enabled,i=!e.init.feature_flags.includes("no_spv"),a=[e.init.page_action.enabled,e.init.performance.capture_marks,e.init.performance.capture_measures,e.init.performance.resources.enabled,e.init.user_actions.enabled,n,i];var c;let d;if(c=e,(0,N.Y)(O.hG,(e,t)=>oe(e,t,c),c),function(e){(0,N.Y)(O.fF,(t,r)=>de(t,r,e),e)}(e),nt(e),we(e),function(e){(0,N.Y)(O.V1,(t,r)=>ce(t,r,e),e)}(e),this.removeOnAbort=new AbortController,this.abortHandler=()=>{this.removeOnAbort.abort(),this.abortHandler=void 0},n){const u=function(e,t){if(!(0,s.dV)().o.WS)return e;const r=e.get("websockets");if(Et[r.debugId]++)return r;Et[r.debugId]=1,(0,F.G)(()=>{const e=(0,_.t)();wt.forEach(t=>{t.nrData.closedAt=e,t.nrData.closeCode=1001,t.nrData.closeReason="Page navigating away",t.nrData.closeWasClean=!1,t.nrData.openedAt&&(t.nrData.connectedDuration=e-t.nrData.openedAt),r.emit("ws",[t.nrData,t.targets],t)})});class n extends WebSocket{static name=bt.R.WS;static toString(){return"function WebSocket() { [native code] }"}toString(){return"[object WebSocket]"}get[Symbol.toStringTag](){return n.name}#o(e){(e.__newrelic??={}).socketId=this.nrData.socketId,this.nrData.hasErrors??=!0}constructor(...e){super(...e),this.nrData=new At(e[0],e[1]),this.targets=(0,ne.$5)(t),this.addEventListener("open",()=>{this.nrData.openedAt=(0,_.t)(),["protocol","extensions","binaryType"].forEach(e=>{this.nrData[e]=this[e]}),wt.add(this)}),this.addEventListener("message",e=>{const{type:t,size:r}=Rt(e.data);this.nrData.messageOrigin??=(0,yt.L)(e.origin),this.nrData.messageCount++,this.nrData.messageBytes+=r,this.nrData.messageBytesMin=Math.min(this.nrData.messageBytesMin||1/0,r),this.nrData.messageBytesMax=Math.max(this.nrData.messageBytesMax,r),(this.nrData.messageTypes??"").includes(t)||(this.nrData.messageTypes=this.nrData.messageTypes?"".concat(this.nrData.messageTypes,",").concat(t):t)}),this.addEventListener("close",e=>{this.nrData.closedAt=(0,_.t)(),this.nrData.closeCode=e.code,e.reason&&(this.nrData.closeReason=e.reason),this.nrData.closeWasClean=e.wasClean,this.nrData.connectedDuration=this.nrData.closedAt-this.nrData.openedAt,wt.delete(this),r.emit("ws",[this.nrData,this.targets],this)})}addEventListener(e,t,...r){const n=this,s="function"==typeof t?function(...e){try{return t.apply(this,e)}catch(e){throw n.#o(e),e}}:t?.handleEvent?{handleEvent:function(...e){try{return t.handleEvent.apply(t,e)}catch(e){throw n.#o(e),e}}}:t;return super.addEventListener(e,s,...r)}send(e){if(this.readyState===WebSocket.OPEN){const{type:t,size:r}=Rt(e);this.nrData.sendCount++,this.nrData.sendBytes+=r,this.nrData.sendBytesMin=Math.min(this.nrData.sendBytesMin||1/0,r),this.nrData.sendBytesMax=Math.max(this.nrData.sendBytesMax,r),(this.nrData.sendTypes??"").includes(t)||(this.nrData.sendTypes=this.nrData.sendTypes?"".concat(this.nrData.sendTypes,",").concat(t):t)}try{return super.send(e)}catch(e){throw this.#o(e),e}}close(...e){try{super.close(...e)}catch(e){throw this.#o(e),e}}}return g.gm.WebSocket=n,r}(this.ee,e);u.on("ws",(e,t)=>{(0,T.p)("ws-complete",[e,t],void 0,this.featureName,this.ee)})}if(i&&g.gm.addEventListener("securitypolicyviolation",e=>{(0,T.p)("spv",[e],void 0,t.K7.genericEvents,this.ee)},(0,Y.jT)(!1,this.removeOnAbort.signal)),g.RI){if(Ke(this.ee,e),ke(this.ee,e),d=tt(this.ee),e.init.user_actions.enabled){function l(t){const r=(0,We.D)(t);return e.beacons.includes(r.hostname+":"+r.port)}function f(){d.emit("navChange")}o.Zp.forEach(e=>(0,Y.sp)(e,e=>(0,T.p)("ua",[e],void 0,this.featureName,this.ee),!0)),o.qN.forEach(e=>{const t=(0,j.s)(e=>{(0,T.p)("ua",[e],void 0,this.featureName,this.ee)},500,{leading:!0});(0,Y.sp)(e,t)}),g.gm.addEventListener("error",()=>{(0,T.p)("uaErr",[],void 0,t.K7.genericEvents,this.ee)},(0,Y.jT)(!1,this.removeOnAbort.signal)),this.ee.on("open-xhr-start",(e,r)=>{l(e[1])||r.addEventListener("readystatechange",()=>{2===r.readyState&&(0,T.p)("uaXhr",[],void 0,t.K7.genericEvents,this.ee)},(0,Y.jT)(void 0,this.removeOnAbort.signal))}),this.ee.on("fetch-start",e=>{e.length>=1&&!l(Ve(e[0]))&&(0,T.p)("uaXhr",[],void 0,t.K7.genericEvents,this.ee)}),d.on("pushState-end",f),d.on("replaceState-end",f),window.addEventListener("hashchange",f,(0,Y.jT)(!0,this.removeOnAbort.signal)),window.addEventListener("popstate",f,(0,Y.jT)(!0,this.removeOnAbort.signal))}if(e.init.performance.resources.enabled&&g.gm.PerformanceObserver?.supportedEntryTypes.includes("resource")){new PerformanceObserver(e=>{e.getEntries().forEach(e=>{(0,T.p)("browserPerformance.resource",[e],void 0,this.featureName,this.ee)})}).observe({type:"resource",buffered:!0})}}a.some(e=>e)?this.importAggregator(e,()=>r.e(478).then(r.bind(r,8019))):this.deregisterDrain()}}var xt=r(2646);const St=new Map;function _t(e,t,r,n,s=!0,i){if("object"!=typeof t||!t||"string"!=typeof r||!r||"function"!=typeof t[r])return(0,l.R)(29);const a=function(e){return(e||w.ee).get("logger")}(e),o=(0,Ne.YM)(a,void 0,i),c=new xt.y(w.P);c.level=n.level,c.customAttributes=n.customAttributes,c.autoCaptured=s;const d=t[r]?.[Ne.Jt]||t[r];return St.set(d,c),o.inPlace(t,[r],"wrap-logger-",()=>St.get(d),void 0,!0),a}var Ot=r(1910);class Nt extends B{static featureName=se.TZ;constructor(e){var t;super(e,se.TZ),t=e,(0,N.Y)("log",(e,r)=>ae(e,r,t),t),function(e){(0,N.Y)(O.Wb,(t,r,{customAttributes:n={},level:s=se.p_.INFO}={})=>{_t(e.ee,t,r,{customAttributes:n,level:s},!1,e)},e)}(e),we(e);const n=this.ee;["log","error","warn","info","debug","trace"].forEach(t=>{(0,Ot.i)(g.gm.console[t]),_t(n,g.gm.console,t,{level:"log"===t?"info":t},void 0,e)}),this.ee.on("wrap-logger-end",function([e],t,r,s=[]){const{level:i,customAttributes:a,autoCaptured:o}=this;s.forEach(t=>{(0,ie.R)(n,e,a,i,o,t)})}),this.importAggregator(e,()=>r.e(478).then(r.bind(r,5288)))}}new W({features:[Xe,K,V,ft,gt,G,Ae,Tt,Nt,vt],loaderType:"spa"})})()})();</script>
9 +<noscript><style>form.antibot > * { display: none !important; } form.antibot noscript { display: block !important; }</style>
10 +</noscript><link rel="canonical" href="https://www.banq.qc.ca/calendrier/a-venir/146831" />
11 +<meta property="og:site_name" content="BAnQ" />
12 +<meta property="og:type" content="website" />
13 +<meta name="twitter:card" content="summary_large_image" />
14 +<meta name="twitter:site" content="@_BANQ" />
15 +<meta name="MobileOptimized" content="width" />
16 +<meta name="HandheldFriendly" content="true" />
17 +<meta name="viewport" content="width=device-width, initial-scale=1.0" />
18 +<script type="application/ld+json">{
19 + "@context": "https://schema.org",
20 + "@graph": [
21 + {
22 + "breadcrumb": {
23 + "@type": "BreadcrumbList",
24 + "itemListElement": [
25 + {
26 + "@type": "ListItem",
27 + "position": 1,
28 + "name": "Accueil",
29 + "item": "https://www.banq.qc.ca/"
30 + },
31 + {
32 + "@type": "ListItem",
33 + "position": 2,
34 + "name": "À venir",
35 + "item": "https://www.banq.qc.ca/calendrier/a-venir"
36 + }
37 + ]
38 + }
39 + }
40 + ]
41 +}</script>
42 +<meta property="description" content="Que se cache-t-il dans le coffre aux trésors de l’Espace Jeunes? Une formidable collection de jouets à découvrir. Jeux libres avec les parents et bouquinage feront de ce moment privilégié une activité inoubliable. 40 places" />
43 +<meta property="og:title" content="La boîte à jouets" />
44 +<meta property="og:description" content="Que se cache-t-il dans le coffre aux trésors de l’Espace Jeunes? Une formidable collection de jouets à découvrir. Jeux libres avec les parents et bouquinage feront de ce moment privilégié une activité inoubliable. 40 places" />
45 +<meta property="og:url" content="https://www.banq.qc.ca/calendrier/a-venir/146831" />
46 +<meta property="og:image" content="https://www2.banq.qc.ca/cal/attachments/24184/146831/BoiteJouets2026_cr_MikaelTheimer_page.jpg" />
47 +<meta property="og:image:alt" content="Un bébé assis est entouré de jouets." />
48 +<meta property="twitter:title" content="La boîte à jouets" />
49 +<meta property="twitter:description" content="Que se cache-t-il dans le coffre aux trésors de l’Espace Jeunes? Une formidable collection de jouets à découvrir. Jeux libres avec les parents et bouquinage feront de ce moment privilégié une activité inoubliable. 40 places" />
50 +<meta property="twitter:image" content="https://www2.banq.qc.ca/cal/attachments/24184/146831/BoiteJouets2026_cr_MikaelTheimer_page.jpg" />
51 +<link rel="icon" href="/themes/custom/banq_lyxn_glider/favicon.ico" type="image/vnd.microsoft.icon" />
52 +
53 + <title>La boîte à jouets | BAnQ</title>
54 + <link rel="stylesheet" media="all" href="/sites/default/files/css/css_JiR-qDezkZH222ORik4Ehh2AeShqoZyziEt3oaYcEm0.css?delta=0&amp;language=fr&amp;theme=banq_lyxn_glider&amp;include=eJxtjmtywzAIhC-kWkdikERcGiRUgTN2T187qfuY5t_utwuzCds7ZBRqBUc8BRRyZAnpSGVbG8zChUacRRMKmG9CFrKg2RYTGp26khnOR6aDYhlLR5nwDdcHaDoqCn9QoNWF2_WsfNnAdIGufenxW4VB1rUZ3wgckxBcWHwf88Nf7jyYZt7XVSqMcLyz-B9N_kp1727mVB_bF4aOvr9sFvVGQ3CDLGoEaXHXNj2DT69EZ51-mz-tztl14qztPuUTdoKWvw" />
55 +<link rel="stylesheet" media="all" href="/sites/default/files/css/css_ZBVJbbm1Jk5KKhmyKPBfLLpllYt2NGa_olOxShA1u68.css?delta=1&amp;language=fr&amp;theme=banq_lyxn_glider&amp;include=eJxtjmtywzAIhC-kWkdikERcGiRUgTN2T187qfuY5t_utwuzCds7ZBRqBUc8BRRyZAnpSGVbG8zChUacRRMKmG9CFrKg2RYTGp26khnOR6aDYhlLR5nwDdcHaDoqCn9QoNWF2_WsfNnAdIGufenxW4VB1rUZ3wgckxBcWHwf88Nf7jyYZt7XVSqMcLyz-B9N_kp1727mVB_bF4aOvr9sFvVGQ3CDLGoEaXHXNj2DT69EZ51-mz-tztl14qztPuUTdoKWvw" />
56 +
57 + <script type="application/json" data-drupal-selector="drupal-settings-json">{"path":{"baseUrl":"\/","pathPrefix":"","currentPath":"calendrier\/a-venir\/146831","currentPathIsAdmin":false,"isFront":false,"currentLanguage":"fr"},"pluralDelimiter":"\u0003","suppressDeprecationErrors":true,"gtag":{"tagId":"","consentMode":false,"otherIds":[],"events":[],"additionalConfigInfo":[]},"ajaxPageState":{"libraries":"eJxtj-FyAiEMhF-IHo_EBIgUGwiSnD369HJW21r9w-x-GzJZD_XkAhDWCN3ehYuokMn4PaWxVZcoR-zWM6toh_YcJWIP5EQHoZhAIDKsB8G7LigCac-4o63cC1D-QoObUq4fNva1AS03axJzInQKyab5_PcLHGF7hMVkPLjGbW32R5njacU-XOTPmjpEtDdAmCAM01EaV8nnfYufuw6ZdNb55W9XboRDnv0Kxgxuv1DsM1r0HcucHaJYvtuv2TXQubKK5TN2guECsaDzqyrX5RV8-Ys48fLXPEy1HJSXHLheT7kAspK7fg","theme":"banq_lyxn_glider","theme_token":null},"ajaxTrustedUrl":[],"gtm":{"tagId":null,"settings":{"data_layer":"dataLayer","include_classes":false,"allowlist_classes":"google\nnonGooglePixels\nnonGoogleScripts\nnonGoogleIframes","blocklist_classes":"customScripts\ncustomPixels","include_environment":false,"environment_id":"","environment_token":""},"tagIds":["GTM-MZT2GP"]},"data":{"extlink":{"extTarget":true,"extTargetAppendNewWindowDisplay":true,"extTargetAppendNewWindowLabel":"(opens in a new window)","extTargetNoOverride":false,"extNofollow":false,"extTitleNoOverride":true,"extNoreferrer":true,"extFollowNoOverride":false,"extClass":"0","extLabel":"(le lien est externe)","extImgClass":false,"extSubdomains":false,"extExclude":"","extInclude":"","extCssExclude":"","extCssInclude":"","extCssExplicit":"","extAlert":false,"extAlertText":"Ce lien vous dirige vers un site externe. Nous ne sommes pas responsables de son contenu.","extHideIcons":false,"mailtoClass":"0","telClass":"0","mailtoLabel":"(link sends email)","telLabel":"(link is a phone number)","extUseFontAwesome":false,"extIconPlacement":"before","extPreventOrphan":false,"extPreventOrphanTextLike":true,"extFaLinkClasses":"fa fa-external-link","extFaMailtoClasses":"fa fa-envelope-o","extAdditionalLinkClasses":"","extAdditionalMailtoClasses":"","extAdditionalTelClasses":"","extFaTelClasses":"fa fa-phone","allowedDomains":[],"extExcludeNoreferrer":""}},"csp":{"nonce":"GH3YVDAfWUwDiEqzVIINXA"},"user":{"uid":0,"permissionsHash":"ea3be2288ab1e45832377ae78437242ea994a2b24631afb0998b547dc6aebe32"}}</script>
58 +<script src="/sites/default/files/js/js_C9YbPmG1N1pTuZpodSeeqTHcR88Da8e__6Xpq5nF9Cw.js?scope=header&amp;delta=0&amp;language=fr&amp;theme=banq_lyxn_glider&amp;include=eJxtiW0OAiEMBS9EliM1ZakNWimWosvt1WQ18ePPy8y8hPUCKwrVjBZfAJkci4T0fGVuFVhKJotJ1bsbtt-LRRMKdJ9CPdDmUuopZhsNZdk1sCoLgSNHfsy3L3jE7TOeQ6EDNG2jxTeF42WQTch6q2yYKe5BiHGdYRRo6E5We9QrmeCEVbQTpOGudfkX7yo5ahg"></script>
59 +<script src="/modules/contrib/google_tag/js/gtag.js?tizv1o"></script>
60 +<script src="/modules/contrib/google_tag/js/gtm.js?tizv1o"></script>
61 +
62 + <script>var __uzdbm_1 = "17bbe614-9f8b-43cd-9e9b-a7ef9d5e90ee";var __uzdbm_2 = "OGVjYTA1ZGUtY2g4bi00MjVkLThjY2YtZDVhMGQzZjdhNWQ4JDIxNi4xNzAuMTIzLjk2";var __uzdbm_3 = "7f900017bbe614-9f8b-43cd-9e9b-a7ef9d5e90ee1-17876923923960-004ed916e48120a652110";var __uzdbm_4 = "false";var __uzdbm_5 = "uzmx";var __uzdbm_6 = "7fc0006618ceaa-854a-4e87-8f8b-bcbfcdcaaf021-17876923923950-00278506918be97226710";var __uzdbm_7 = "banq.qc.ca";</script> <script> (function (w, d, e, u, c, g, a, b) { w["SSJSConnectorObj"] = w["SSJSConnectorObj"] || { ss_cid: c, domain_info: "auto", }; w[g] = function (i, j) { w["SSJSConnectorObj"][i] = j; }; a = d.createElement(e); a.async = true; if ( navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > -1 ) { u = u.replace("/advanced/", "/advanced/ie/"); } a.src = u; b = d.getElementsByTagName(e)[0]; b.parentNode.insertBefore(a, b); })( window, document, "script", "https://www.banq.qc.ca/18f5227b-e27b-445a-a53f-f845fbe69b40/stormcaster.js", "ch8p", "ssConf" ); ssConf("c1", "https://www.banq.qc.ca"); ssConf("c3", "c99a4269-161c-4242-a3f0-28d44fa6ce24"); ssConf("au", "banq.qc.ca"); ssConf("cu", "validate.perfdrive.com, ssc"); </script></head>
63 + <body class="body-sidebars-none alias--calendrier-a-venir-146831 logged-out has-block-breadcrumbs path-calendrier">
64 + <a href="#main-content" class="visually-hidden focusable skip-link">
65 + Aller au contenu principal
66 + </a>
67 + <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-MZT2GP"
68 + height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
69 +
70 + <div class="dialog-off-canvas-main-canvas" data-off-canvas-main-canvas>
71 + <div class="page-standard internal-page" id="pg__c">
72 + <header id="header" class="bq-header sticky-top">
73 + <div class="region region-header-messages">
74 + <div id="block-banq-lyxn-glider-importantmessagesblock" class="block block-banq-common block-banq-common-messages">
75 +
76 +
77 + <div class="block block- bq-important-message-global-block">
78 +
79 +
80 + </div>
81 +
82 + </div>
83 +
84 + </div>
85 +
86 +
87 + <div class="bq-header__wrapper navbar navbar-expand-lg navbar-light">
88 +
89 +
90 + <div class="bq-header__branding region region-header-branding d-lg-flex">
91 + <div id="block-banq-lyxn-glider-branding" class="block block-system block-system-branding-block">
92 +
93 +
94 + <a href="/" rel="home" class="site-logo navbar-brand">
95 + <img src="/themes/custom/banq_lyxn_glider/logo.svg" alt="BAnQ : Accueil" class="img-fluid"/>
96 + </a>
97 + <div class="site-name">
98 + <a href="/" title="BAnQ : Accueil" class="navbar-brand" rel="home">BAnQ</a>
99 + </div>
100 + <div class="site-full-name">
101 + <div class="site-full-name__square">
102 + <img src="/themes/custom/banq_lyxn_glider/images/banq-logo-text.svg"
103 + alt="Bibliothèque et Archives Nationales du Québec">
104 + </div>
105 + <div class="site-full-name__inline">
106 + <img src="/themes/custom/banq_lyxn_glider/images/banq-logo-text-inline.svg"
107 + alt="Bibliothèque et Archives Nationales du Québec">
108 + </div>
109 + </div>
110 + </div>
111 +
112 + </div>
113 +
114 +
115 +
116 + <nav aria-label="Menu principal" class="bq-menu region region-header-menu">
117 +
118 +
119 +
120 +<div id="block-maintaskbar" class="bq-menu__primary block block-menu navigation menu--task-bar">
121 +
122 +
123 +
124 +
125 +
126 + <ul class="menu menu-level-0">
127 +
128 + <li class="menu-item">
129 +
130 +
131 +
132 +
133 + <div class="menu_link_content menu-link-contenttask-bar view-mode-default menu-dropdown menu-dropdown--has-overlay menu-dropdown-0 menu-type-default">
134 + <button
135 + class="bq-menu__button main_nav_overlay"
136 + type="button"
137 + aria-label="Menu"
138 + data-overlay="main_nav_overlay"
139 + title="Menu"
140 + data-toggle="tooltip"
141 + data-placement="left"
142 + data-trigger="hover"
143 + data-custom-class="menu"
144 + >
145 + <span class="sr-only">Menu</span>
146 +
147 +
148 +
149 + <i class="picto__icon picto__icon--1.5x icon icon-action-hamburger_menu"></i>
150 +
151 + </button>
152 + </div>
153 +
154 +
155 +
156 + </li>
157 +
158 + <li class="menu-item">
159 +
160 +
161 +
162 +
163 + <div class="menu_link_content menu-link-contenttask-bar view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
164 + <a
165 + class="bq-menu__link"
166 + href="/recherche/"
167 + aria-label="Recherche"
168 + title="Recherche"
169 + data-toggle="tooltip"
170 + data-placement="left"
171 + data-custom-class="menu"
172 + >
173 + <span class="sr-only">Recherche</span>
174 +
175 +
176 +
177 + <i class="picto__icon picto__icon--1.5x icon icon-action-search"></i>
178 +
179 + </a>
180 + </div>
181 +
182 +
183 +
184 + </li>
185 +
186 + <li class="menu-item">
187 +
188 +
189 +
190 +
191 + <div class="menu_link_content menu-link-contenttask-bar view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
192 + <a
193 + class="bq-menu__link"
194 + href="https://www2.banq.qc.ca/mon_dossier/mon_dossier.html"
195 + aria-label="Mon dossier"
196 + title="Mon dossier"
197 + data-toggle="tooltip"
198 + data-placement="left"
199 + data-custom-class="menu"
200 + >
201 + <span class="sr-only">Mon dossier</span>
202 +
203 +
204 +
205 + <i class="picto__icon picto__icon--1.5x icon icon-action-monespace"></i>
206 +
207 + </a>
208 + </div>
209 +
210 +
211 +
212 + </li>
213 + </ul>
214 +
215 +
216 + </div>
217 +
218 +
219 +
220 +<div id="block-taskbarsecondary" class="bq-menu__secondary block block-menu navigation menu--task-bar-secondary">
221 +
222 +
223 +
224 +
225 +
226 + <ul class="menu menu-level-0">
227 +
228 + <li class="menu-item">
229 +
230 +
231 +
232 +
233 + <div class="menu_link_content menu-link-contenttask-bar-secondary view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
234 + <a
235 + class="bq-menu__link"
236 + href="/aller-sur-place/"
237 + aria-label="Aller sur place"
238 + title="Aller sur place"
239 + data-toggle="tooltip"
240 + data-placement="left"
241 + data-custom-class="menu"
242 + >
243 + <span class="sr-only">Aller sur place</span>
244 +
245 +
246 +
247 + <i class="picto__icon picto__icon--1.5x icon icon-signalisation-pointer"></i>
248 +
249 + </a>
250 + </div>
251 +
252 +
253 +
254 + </li>
255 +
256 + <li class="menu-item">
257 +
258 +
259 +
260 +
261 + <div class="menu_link_content menu-link-contenttask-bar-secondary view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
262 + <a
263 + class="bq-menu__link"
264 + href="/calendrier/a-venir"
265 + aria-label="Activités"
266 + title="Activités"
267 + data-toggle="tooltip"
268 + data-placement="left"
269 + data-custom-class="menu"
270 + >
271 + <span class="sr-only">Activités</span>
272 +
273 +
274 +
275 + <i class="picto__icon picto__icon--1.5x icon icon-action-calendrier"></i>
276 +
277 + </a>
278 + </div>
279 +
280 +
281 +
282 + </li>
283 +
284 + <li class="menu-item">
285 +
286 +
287 +
288 +
289 + <div class="menu_link_content menu-link-contenttask-bar-secondary view-mode-default menu-dropdown menu-dropdown--has-overlay menu-dropdown-0 menu-type-default">
290 + <button
291 + class="bq-menu__button a11y_options_overlay"
292 + type="button"
293 + aria-label="Préférences de présentation visuelle"
294 + data-overlay="a11y_options_overlay"
295 + title="Préférences de présentation visuelle"
296 + data-toggle="tooltip"
297 + data-placement="left"
298 + data-trigger="hover"
299 + data-custom-class="menu"
300 + >
301 + <span class="sr-only">Préférences de présentation visuelle</span>
302 +
303 +
304 +
305 + <i class="picto__icon picto__icon--1.5x icon icon-action-accessibilite"></i>
306 +
307 + </button>
308 + </div>
309 +
310 +
311 +
312 + </li>
313 +
314 + <li class="menu-item">
315 +
316 +
317 +
318 +
319 + <div class="menu_link_content menu-link-contenttask-bar-secondary view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
320 + <a
321 + class="bq-menu__link"
322 + href="/obtenir-de-laide/"
323 + aria-label="Obtenir de l’aide"
324 + title="Obtenir de l’aide"
325 + data-toggle="tooltip"
326 + data-placement="left"
327 + data-custom-class="menu"
328 + >
329 + <span class="sr-only">Obtenir de l’aide</span>
330 +
331 +
332 +
333 + <i class="picto__icon picto__icon--1.5x icon icon-action-question"></i>
334 +
335 + </a>
336 + </div>
337 +
338 +
339 +
340 + </li>
341 + </ul>
342 +
343 +
344 + </div>
345 +
346 + </nav>
347 +
348 + </div>
349 + <div class="region region-header-breadcrumb">
350 + <link rel="stylesheet" media="all" href="/sites/default/files/css/css_Y5_6JKjhx5lOxrNKG_ydBuY_0zWCzqLehyI9ey3Sm9E.css?delta=0&amp;language=fr&amp;theme=banq_lyxn_glider&amp;include=eJwrzYwvSCwpSS3KK9ZPKkpNTEkuKs1N0kMwAeKIDTo&amp;exclude=eJxtUm2WwiAMvBDCkXgpRERTgiTVdk-_WD_Wrv7hZSZkkgzgrJTLycU2VSCLd2gaSuUi-YJeYSD0-0yKzf3xu5U3wiED-RFjBn8rFfdJWT3g2O8uoji6AQRNIBBZNvGIIpBQTOCGrnAbgfIPmgHK2dMyF58oxz7EwKyiDepnKhEPvbfoQl0oMSe6bZBc0nELIf3HFo4wm-N5wrb4yNeSGkR0D4IwQVjMJeNV3HrakePULXinVokp-wra7SriFGYuPGbsJrxCs4eAKk_T78iuCrtV4Ws-HDCcBp531xwT6rYN1xx8bFxvg9stvBv6EMv9CXaURZ-2F4640eILNoLFB2JBP0yqXOw38msVcWL7DkzGva9cp-pe0aayj6psc-Cy_pZftFwD4A" />
351 +<div id="block-breadcrumbs" class="block block-system block-system-breadcrumb-block">
352 +
353 +
354 +
355 +
356 +
357 +
358 + <nav aria-label="Parcours de navigation" class="breadcrumb breadcrumb--default rounded-0 mb-0 items-count-3">
359 + <ol class="d-flex align-items-center">
360 +
361 + <li class="is-home" >
362 + <a href="/" class="is-home" title="BAnQ : Accueil" aria-label="BAnQ : Accueil">
363 + <i class="icon icon-toggleaction-chevron_left"></i>
364 +
365 + <i class="icon icon-action-home"></i>
366 + <span class="home-page-label">Accueil</span>
367 +
368 + <i class="icon icon-toggleaction-chevron_right"></i>
369 + </a>
370 + </li>
371 +
372 + <li class="" >
373 + <a href="/calendrier/a-venir" title="À venir">
374 + <i class="icon icon-toggleaction-chevron_left"></i>
375 +
376 + <span>À venir</span>
377 +
378 + <i class="icon icon-toggleaction-chevron_right"></i>
379 + </a>
380 + </li>
381 +
382 + <li class="" aria-current="page">
383 + <a href="/calendrier/a-venir/146831" class="active" title="La boîte à jouets">
384 + <i class="icon icon-toggleaction-chevron_left"></i>
385 +
386 + <span>La boîte à jouets</span>
387 +
388 + <i class="icon icon-toggleaction-chevron_right"></i>
389 + </a>
390 + </li>
391 + </ol>
392 + </nav>
393 +
394 +
395 + </div>
396 +
397 + </div>
398 +
399 + </header>
400 +
401 + <div class="container menu-overlay" id="menu-overlay">
402 + <section class="bq-menu-overlay bq-menu-overlay--main-nav"
403 + id="main_nav_overlay" data-url-segment="#menu" role="dialog" aria-modal="true" aria-labelledby="bq-menu-overlay-title">
404 + <!-- h2 class="bq-menu-overlay__title">Menu</h2 -->
405 +
406 + <div class="bq-menu-overlay__container">
407 +
408 +
409 +<div class="bq-menu-overlay__logo">
410 + <span class="bq-menu-overlay__logo__link">
411 + <img
412 + src="/themes/custom/banq_lyxn_glider/logo.svg"
413 + alt="BAnQ Logo"
414 + />
415 + </span>
416 + <span class="bq-menu-overlay__logo__link bq-menu-overlay__logo__link--small">
417 + <img
418 + src="/themes/custom/banq_lyxn_glider/logo.svg"
419 + alt="BAnQ Logo"
420 + />
421 + </span>
422 +</div>
423 +
424 + <nav class="bq-menu-overlay__subcontainer" aria-label="Navigation principale">
425 + <div class="region region-main-nav-overlay">
426 + <div id="block-mainnavigation" class="block block-menu navigation menu--main">
427 +
428 +
429 +
430 +
431 +
432 + <ul class="menu navbar-nav mx-0">
433 + <li class="menu-item nav-item">
434 +
435 +
436 + <a href="https://www.banq.qc.ca/collections/" class="nav-link" aria-label="Collections (Ce lien s&#039;ouvre dans un nouvel onglet)">Collections</a>
437 + <i class="icon-toggleaction-chevron_right"></i>
438 +
439 + </li>
440 + <li class="menu-item nav-item">
441 +
442 +
443 + <a href="https://www.banq.qc.ca/services/" class="nav-link" aria-label="Services (Ce lien s&#039;ouvre dans un nouvel onglet)">Services</a>
444 + <i class="icon-toggleaction-chevron_right"></i>
445 +
446 + </li>
447 + <li class="menu-item nav-item">
448 +
449 +
450 + <a href="/collections/outils-de-recherche/" class="nav-link" data-drupal-link-system-path="node/1436">Outils de recherche</a>
451 + <i class="icon-toggleaction-chevron_right"></i>
452 +
453 + </li>
454 + <li class="menu-item nav-item">
455 +
456 +
457 + <a href="/explorer/" class="nav-link" data-drupal-link-system-path="node/1431">Articles et suggestions</a>
458 + <i class="icon-toggleaction-chevron_right"></i>
459 +
460 + </li>
461 + <li class="menu-item nav-item">
462 +
463 +
464 + <a href="/services/apprendre/" class="nav-link" data-drupal-link-system-path="node/4161">Formations et guides</a>
465 + <i class="icon-toggleaction-chevron_right"></i>
466 +
467 + </li>
468 + <li class="menu-item nav-item">
469 +
470 +
471 + <a href="/education/" target="_blank" class="nav-link" data-drupal-link-system-path="node/25091">Éducation</a>
472 + <i class="icon-toggleaction-chevron_right"></i>
473 +
474 + </li>
475 + <li class="menu-item nav-item">
476 +
477 +
478 + <a href="https://www.banq.qc.ca/jeunes/" class="nav-link" aria-label="Espace Jeunes (Ce lien s&#039;ouvre dans un nouvel onglet)">Espace Jeunes</a>
479 + <i class="icon-toggleaction-chevron_right"></i>
480 +
481 + </li>
482 + <li class="menu-item nav-item nav-item--collapsed">
483 +
484 +
485 + <a href="/notre-institution/" class="nav-link" data-drupal-link-system-path="node/2166">Notre institution</a>
486 + <i class="icon-toggleaction-chevron_right"></i>
487 +
488 + </li>
489 + </ul>
490 +
491 +
492 +
493 + </div>
494 +
495 + </div>
496 +
497 + <div class="region region-secondary-nav-overlay">
498 + <div id="block-tasksmenu" class="block block-menu navigation menu--tasks-menu">
499 +
500 +
501 +
502 +
503 +
504 + <ul class="menu menu-level-0">
505 +
506 + <li class="menu-item">
507 +
508 +
509 +
510 +
511 + <div class="menu_link_content menu-link-contenttasks-menu view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
512 + <a
513 + class="bq-tasks-menu__link"
514 + href="https://www2.banq.qc.ca/mon_dossier/mon_dossier.html"
515 + aria-label="Mon dossier"
516 + title="Mon dossier"
517 + data-toggle="tooltip"
518 + data-placement="left"
519 + data-custom-class="menu">
520 +
521 +
522 +
523 + <i class="picto__icon picto__icon--1.5x icon icon-action-monespace"></i>
524 +
525 + <span>Mon dossier</span>
526 + </a>
527 + </div>
528 +
529 +
530 +
531 + </li>
532 +
533 + <li class="menu-item">
534 +
535 +
536 +
537 +
538 + <div class="menu_link_content menu-link-contenttasks-menu view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
539 + <a
540 + class="bq-tasks-menu__link"
541 + href="/aller-sur-place/"
542 + aria-label="Heures et coordonnées"
543 + title="Heures et coordonnées"
544 + data-toggle="tooltip"
545 + data-placement="left"
546 + data-custom-class="menu">
547 +
548 +
549 +
550 + <i class="picto__icon picto__icon--1.5x icon icon-signalisation-pointer"></i>
551 +
552 + <span>Heures et coordonnées</span>
553 + </a>
554 + </div>
555 +
556 +
557 +
558 + </li>
559 +
560 + <li class="menu-item">
561 +
562 +
563 +
564 +
565 + <div class="menu_link_content menu-link-contenttasks-menu view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
566 + <a
567 + class="bq-tasks-menu__link"
568 + href="/calendrier/a-venir"
569 + aria-label="Activités"
570 + title="Activités"
571 + data-toggle="tooltip"
572 + data-placement="left"
573 + data-custom-class="menu">
574 +
575 +
576 +
577 + <i class="picto__icon picto__icon--1.5x icon icon-action-calendrier"></i>
578 +
579 + <span>Activités</span>
580 + </a>
581 + </div>
582 +
583 +
584 +
585 + </li>
586 +
587 + <li class="menu-item">
588 +
589 +
590 +
591 +
592 + <div class="menu_link_content menu-link-contenttasks-menu view-mode-default menu-dropdown menu-dropdown--has-overlay menu-dropdown-0 menu-type-default">
593 + <button
594 + class="bq-tasks-menu__button bq-menu__button a11y_options_overlay"
595 + type="button"
596 + aria-label="Présentation visuelle"
597 + data-overlay="a11y_options_overlay"
598 + title="Présentation visuelle"
599 + data-toggle="tooltip"
600 + data-placement="left"
601 + data-trigger="hover"
602 + data-custom-class="menu">
603 +
604 +
605 +
606 + <i class="picto__icon picto__icon--1.5x icon icon-action-accessibilite"></i>
607 +
608 + <span>Présentation visuelle</span>
609 + </button>
610 + </div>
611 +
612 +
613 +
614 + </li>
615 +
616 + <li class="menu-item">
617 +
618 +
619 +
620 +
621 + <div class="menu_link_content menu-link-contenttasks-menu view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
622 + <a
623 + class="bq-tasks-menu__link"
624 + href="/recherche/"
625 + aria-label="Recherche"
626 + title="Recherche"
627 + data-toggle="tooltip"
628 + data-placement="left"
629 + data-custom-class="menu">
630 +
631 +
632 +
633 + <i class="picto__icon picto__icon--1.5x icon icon-action-search"></i>
634 +
635 + <span>Recherche</span>
636 + </a>
637 + </div>
638 +
639 +
640 +
641 + </li>
642 +
643 + <li class="menu-item">
644 +
645 +
646 +
647 +
648 + <div class="menu_link_content menu-link-contenttasks-menu view-mode-default menu-dropdown menu-dropdown-0 menu-type-default">
649 + <a
650 + class="bq-tasks-menu__link"
651 + href="/obtenir-de-laide/"
652 + aria-label="Obtenir de l’aide"
653 + title="Obtenir de l’aide"
654 + data-toggle="tooltip"
655 + data-placement="left"
656 + data-custom-class="menu">
657 +
658 +
659 +
660 + <i class="picto__icon picto__icon--1.5x icon icon-action-question"></i>
661 +
662 + <span>Obtenir de l’aide</span>
663 + </a>
664 + </div>
665 +
666 +
667 +
668 + </li>
669 + </ul>
670 +
671 +
672 + </div>
673 +<div id="block-secondarymenu" class="block block-menu navigation menu--secondary">
674 +
675 +
676 +
677 +
678 + <ul class="menu navbar-nav mx-0">
679 + <li class="menu-item nav-item">
680 + <a href="/obtenir-de-laide/" class="nav-link" data-drupal-link-system-path="node/4011">Nous joindre</a>
681 + </li>
682 + <li class="menu-item nav-item">
683 + <a href="/politique-daccessibilite/" class="nav-link" data-drupal-link-system-path="node/7121">Accessibilité</a>
684 + </li>
685 + <li class="menu-item nav-item">
686 + <a href="/politique-de-confidentialite/" class="nav-link" data-drupal-link-system-path="node/9086">Confidentialité</a>
687 + </li>
688 + </ul>
689 +
690 +
691 +

Diff truncated — file too large.