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%

docs: documentation standardisée des connecteurs (générateur rejouable)

- scripts/gen_connector_docs.py : générateur rejouable qui croise
  data/sources.json, l introspection des connecteurs (endpoints, pagination,
  backend, plafonds env, en-têtes) et la BD live (volumétrie, complétude
  GPS/prix/heure/artistes/image/salle, exemples réels, sync_log)
- docs/connecteurs/ : INDEX.md + 18 fiches (sections fixes : Description,
  Accès, Champs récupérés → schéma, Fréquence & budgets, Volumétrie &
  complétude live, Erreurs & dépannage, Licence/attribution avec mentions
  requises CC BY Montréal / CC 4.0 SITQ & Laval, Historique incl. vagues
  d enrichissement 2026-08-18 7d9d1b0 et 65d2d33)

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

20 changed files +2,093 −0

added docs/connecteurs/INDEX.md +41 −0
@@ -0,0 +1,41 @@
1 +# Sorti-Ka — Connecteurs de sources d'événements
2 +
3 +> Index généré le 2026-08-18 03:22 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 +
5 +**18 sources au registre** — 11 actives/prêtes, 7 abandonnées · **18292 événements en base** (dont 15820 actifs).
6 +
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 +
9 +## Sources actives / prêtes
10 +
11 +| Source | Type | Statut | Actifs | GPS | Prix | Heure | Artistes | Image | Salle | Dernier sync |
12 +|---|---|---|---|---|---|---|---|---|---|---|
13 +| [SIT Québec — Ministère du Tourisme](sitq.md) | donnees-ouvertes | actif | 1598 | 97 % | 0 % | 0 % | 0 % | 35 % | 66 % | 2026-08-18 02:41 |
14 +| [Ville de Montréal — Événements publics](montreal.md) | donnees-ouvertes | actif | 5679 | 97 % | 100 % | 10 % | 0 % | 58 % | 4 % | 2026-08-18 02:41 |
15 +| [Le point de vente — billetterie québécoise](lepointdevente.md) | billetterie | actif | 1787 | 27 % | 31 % | 94 % | 1 % | 100 % | 99 % | 2026-08-18 02:41 |
16 +| [Ville de Laval — Calendrier des activités](laval.md) | donnees-ouvertes | actif | 277 | 51 % | 0 % | 95 % | 0 % | 98 % | 90 % | 2026-08-18 02:35 |
17 +| [Ville de Sherbrooke — Calendrier des événements](sherbrooke.md) | municipal | actif | 271 | 0 % | 100 % | 88 % | 0 % | 100 % | 0 % | 2026-08-18 02:41 |
18 +| [evenko — grands concerts et festivals](evenko.md) | promoteur | actif | 423 | 77 % | 0 % | 99 % | 99 % | 100 % | 100 % | 2026-08-18 02:35 |
19 +| [Atuvu.ca — calendrier culturel](atuvu.md) | agregateur | actif | 145 | 62 % | 35 % | 0 % | 0 % | 100 % | 100 % | 2026-08-18 02:31 |
20 +| [La Vitrine culturelle](lavitrine.md) | billetterie | actif | 5127 | 52 % | 0 % | 0 % | 0 % | 100 % | 100 % | 2026-08-18 02:35 |
21 +| [Ville de Brossard — Calendrier des événements](brossard.md) | municipal | actif | 262 | 0 % | 0 % | 54 % | 0 % | 55 % | 53 % | 2026-08-18 02:35 |
22 +| [Ticketmaster — Discovery API (Québec)](ticketmaster.md) | billetterie | prêt — clé requise | 0 | — | — | — | — | — | — | 2026-08-18 02:41 |
23 +| [Bandsintown — dates de concerts par artiste](bandsintown.md) | agregateur | actif | 251 | 100 % | 1 % | 100 % | 100 % | 10 % | 100 % | 2026-08-18 02:32 |
24 +
25 +## Sources abandonnées
26 +
27 +| Source | Type | Raison |
28 +|---|---|---|
29 +| [Ville de Longueuil — Événements](longueuil.md) | donnees-ouvertes | Jeu de données périmé — dernière date d'événement : 2017-03 (vérifié 2026-08-17) |
30 +| [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) |
31 +| [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) |
32 +| [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)… |
33 +| [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 … |
34 +| [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é… |
35 +| [Ville de Terrebonne — Calendrier](terrebonne.md) | municipal | Cloudflare bloque tout GET non navigateur (403) et aucun jeu « événements » sur Données Québec (… |
36 +
37 +## Régénérer cette documentation
38 +
39 +```bash
40 +cd ~/apps/sorti-ka && python3 scripts/gen_connector_docs.py
41 +```
added docs/connecteurs/atuvu.md +108 −0
@@ -0,0 +1,108 @@
1 +# Atuvu.ca — calendrier culturel (`atuvu`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `agregateur` · Connecteur : `sortika/connectors/atuvu.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Atuvu.ca — calendrier culturel](https://atuvu.ca/)
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
12 +
13 +## Accès
14 +
15 +- **Type** : agregateur
16 +- **Endpoint** (`SITEMAP_INDEX`) : `https://atuvu.ca/atuvu_sitemap_index.xml`
17 +- **Auth** : Aucune côté source ; `SCRAPFLY_API_KEY` requis pour le backend Scrapfly
18 +- **Pagination** : 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 : Allow / + sitemap public
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/atuvu.py : connecteur Atuvu.ca — calendrier culturel (Grand Montréal +)
26 +> Source : atuvu.ca (théâtre, musique, humour, danse, expositions…)
27 +> Extraction: SCRAPFLY obligatoire (Cloudflare) — sitemaps XML publics
28 +> (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.
33 +> Accès : robots.txt « User-Agent: * / Allow: / » + sitemap public.
34 +> Prix : bloc .price-reg de la fiche (prix régulier) → price_min ;
35 +> « Invitation gratuite » = offre membre, PAS gratuité → ignorée.
36 +
37 +</details>
38 +
39 +## Champs récupérés → schéma
40 +
41 +Complétude mesurée sur les **145 événements actifs** de la source ; exemples tirés d'un événement réel (`atuvu:requiem-de-mozart-2026-11-07`).
42 +
43 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
44 +|---|---|---|---|
45 +| `title` — Titre | titre de l'événement | 100 % | Requiem de Mozart |
46 +| `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/requiem-de-mozart |
48 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
49 +| `venue` — Salle (venue) | nom du lieu | 100 % | Basilique Notre-Dame de Montréal |
50 +| `address` — Adresse | adresse civique | 0 % | ∅ |
51 +| `city` — Ville | municipalité | 97 % | Montréal |
52 +| `region` — Région | région administrative | 93 % | Montréal |
53 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 62 % | 45.5044365, -73.5559901 |
54 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-11-07 |
55 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-11-07 |
56 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 0 % | ∅ |
57 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 29 % | 0 |
58 +| `price_min` — Prix min | tarif minimal réel ($) | 35 % | 54.58 |
59 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 29 % | 54.58 $ (prix régulier atuvu.ca) |
60 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
61 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
62 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://atuvu.ca/uploads/images/2026/08/requiem-mozart.jpg |
63 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
64 +
65 +## Fréquence & budgets
66 +
67 +- **Cadence** : horaire (watcher PM2, incrémental)
68 +- **Budgets / plafonds (variables d'environnement)** :
69 +
70 +| Variable | Défaut | Rôle |
71 +|---|---|---|
72 +| `ATUVU_MAX_FETCH` | `120` | fiches/sync (coût Scrapfly) |
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-18 03:22 EDT dans `data/sortika.db`)*
78 +
79 +- **Événements en base** : 145 (dont **145 actifs**)
80 +- **GPS** : 90/145 actifs (62 %)
81 +- **Prix** : 51/145 actifs (35 %)
82 +- **Heure (start_time)** : 0/145 actifs (0 %)
83 +- **Artistes** : 0/145 actifs (0 %)
84 +- **Image** : 145/145 actifs (100 %)
85 +- **Salle (venue)** : 145/145 actifs (100 %)
86 +- **Dernier sync** : 2026-08-18 02:31 — fetched 156, +0 ajoutés, ~146 mis à jour, -0 retirés
87 +- **Syncs enregistrés** (sync_log) : 16
88 +
89 +## Erreurs & dépannage
90 +
91 +Aucune erreur dans `sync_log` pour cette source. ✅
92 +
93 +- Source derrière anti-bot/JS lourd → backend **Scrapfly** requis : vérifier `SCRAPFLY_API_KEY` dans `.env` et le solde de crédits Scrapfly si le connecteur ne rapporte plus rien.
94 +- Rejouer un sync isolé : `python3 run.py sync --source atuvu` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='atuvu' ORDER BY ts DESC LIMIT 5;"`
95 +
96 +## Licence / attribution
97 +
98 +- robots.txt : Allow / + sitemap public
99 +
100 +## Historique
101 +
102 +- **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.
103 +- **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).
104 +
105 +Commits touchant `sortika/connectors/atuvu.py` :
106 +
107 +- `65d2d33` 2026-08-18 Vague 2 d'enrichissement : heure précise, artistes, 3 nouveaux connecteurs
108 +- `9164456` 2026-08-17 Sorti·Ka v1 — agrégateur de sorties & événements du Québec
added docs/connecteurs/bandsintown.md +113 −0
@@ -0,0 +1,113 @@
1 +# Bandsintown — dates de concerts par artiste (`bandsintown`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `agregateur` · Connecteur : `sortika/connectors/bandsintown.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Bandsintown — dates de concerts par artiste](https://rest.bandsintown.com/)
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
12 +
13 +## Accès
14 +
15 +- **Type** : agregateur
16 +- **Endpoint** (`API`) : `https://rest.bandsintown.com/artists/{artist}/events`
17 +- **Auth** : `app_id` d'identification (`BANDSINTOWN_APP_ID`, format widget accepté)
18 +- **Pagination** : aucune (flux unique)
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.5 s entre requêtes
21 +- **Accès légal** : API publique Bandsintown (app_id d'identification) — usage index + lien vers la fiche originale
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/bandsintown.py : connecteur Bandsintown (API publique par artiste)
26 +> Source : rest.bandsintown.com/artists/{artiste}/events — API publique
27 +> (app_id d'identification ; le format « js_<domaine> » des
28 +> widgets officiels est accepté, testé 2026-08 — un app_id
29 +> arbitraire simple est refusé « not authorized »).
30 +> Approche : l'API ne permet pas de recherche géographique → on interroge
31 +> les artistes déjà connus de la base (colonne `artists` extraite
32 +> d'evenko / lepointdevente / ticketmaster, événements à venir)
33 +> et on ne garde que leurs dates au QUÉBEC (venue.region = QC).
34 +> Volume : plafond BIT_ARTIST_MAX artistes/sync (défaut 100), rotation par
35 +> ancienneté via cache disque (data/bandsintown_cache.json) ; les
36 +> événements des artistes déjà sondés sont ré-émis depuis le
37 +> cache entre deux visites (re-visite après 3 jours).
38 +> Champs : datetime local (date + heure), salle géolocalisée (lat/lng),
39 +> lineup → artistes, `free` → is_free, offres/billets → url,
40 +> image de l'artiste.
41 +
42 +</details>
43 +
44 +## Champs récupérés → schéma
45 +
46 +Complétude mesurée sur les **251 événements actifs** de la source ; exemples tirés d'un événement réel (`bandsintown:108747079`).
47 +
48 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
49 +|---|---|---|---|
50 +| `title` — Titre | titre de l'événement | 100 % | Doug St-Louis & Dj Chuck / Ptit Gros Bison à Grenville en Fête |
51 +| `description` — Description | texte descriptif (source ou fiche détail) | 18 % | Au Parc Normand-Woodbury à Grenville. Spectacle Gratuit Ils seront en duo pour vous faire danser… |
52 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.bandsintown.com/e/108747079?app_id=js_www.sorti-ka.com&came_from=267&utm_medium=api&… |
53 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
54 +| `venue` — Salle (venue) | nom du lieu | 100 % | Doug St-Louis & Dj Chuck / Ptit Gros Bison à Grenville en Fête |
55 +| `address` — Adresse | adresse civique | 98 % | 21 Rue Tri Jean |
56 +| `city` — Ville | municipalité | 100 % | Grenville |
57 +| `region` — Région | région administrative | 98 % | Laurentides |
58 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 100 % | 45.630063, -74.600568 |
59 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
60 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
61 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 100 % | 20:30 |
62 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 1 % | 1 |
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) | 100 % | ["Doug St-Louis", "P'tit Gros Bison"] |
66 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
67 +| `image` — Image | visuel (og:image ou média source) | 10 % | https://photos.bandsintown.com/large/25853181.jpeg |
68 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
69 +
70 +## Fréquence & budgets
71 +
72 +- **Cadence** : horaire (watcher PM2, rotation d'artistes)
73 +- **Budgets / plafonds (variables d'environnement)** :
74 +
75 +| Variable | Défaut | Rôle |
76 +|---|---|---|
77 +| `BANDSINTOWN_APP_ID` | `js_www.sorti-ka.com` | — |
78 +| `BIT_ARTIST_MAX` | `100` | artistes/sync |
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-18 03:22 EDT dans `data/sortika.db`)*
84 +
85 +- **Événements en base** : 251 (dont **251 actifs**)
86 +- **GPS** : 251/251 actifs (100 %)
87 +- **Prix** : 2/251 actifs (1 %)
88 +- **Heure (start_time)** : 250/251 actifs (100 %)
89 +- **Artistes** : 251/251 actifs (100 %)
90 +- **Image** : 24/251 actifs (10 %)
91 +- **Salle (venue)** : 251/251 actifs (100 %)
92 +- **Dernier sync** : 2026-08-18 02:32 — fetched 251, +143 ajoutés, ~0 mis à jour, -0 retirés
93 +- **Syncs enregistrés** (sync_log) : 2
94 +
95 +## Erreurs & dépannage
96 +
97 +Aucune erreur dans `sync_log` pour cette source. ✅
98 +
99 +- L'API n'accepte que certains formats d'`app_id` : le format widget `js_www.sorti-ka.com` est testé accepté (2026-08) ; un app_id arbitraire simple est refusé.
100 +- Rejouer un sync isolé : `python3 run.py sync --source bandsintown` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='bandsintown' ORDER BY ts DESC LIMIT 5;"`
101 +
102 +## Licence / attribution
103 +
104 +- API publique Bandsintown (app_id d'identification) — usage index + lien vers la fiche originale
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 +
111 +Commits touchant `sortika/connectors/bandsintown.py` :
112 +
113 +- `65d2d33` 2026-08-18 Vague 2 d'enrichissement : heure précise, artistes, 3 nouveaux connecteurs
added docs/connecteurs/brossard.md +111 −0
@@ -0,0 +1,111 @@
1 +# Ville de Brossard — Calendrier des événements (`brossard`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/brossard.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Brossard — Calendrier des événements](https://brossard.ca/calendrier-des-evenements/)
10 +- **Couverture** : Événements municipaux et de bibliothèque, Brossard (Montérégie)
11 +- **Extraction** : API REST WordPress publique /wp-json/wp/v2/city-event (JSON paginé, ~400 fiches) + dates/heures par fiche : bannière « Cet événement aura lieu le … » (fiches ville) ou JSON-LD Event (fiches biblio.brossard.ca) — cache incrémental data/brossard_cache.json, 80 fiches/sync, re-visite 7 j ; repli : date entre parenthèses dans le titre
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`API`) : `https://brossard.ca/wp-json/wp/v2/city-event`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : par numéro de page, per_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 authentification) — GET throttlés 0,8 s, identification honnête
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/brossard.py : connecteur Ville de Brossard — calendrier des
26 +> événements municipaux (brossard.ca, Montérégie)
27 +> Source : API REST WordPress publique — type de contenu `city-event`
28 +> (~400 événements : bibliothèque, conférences, ateliers,
29 +> spectacles familiaux). Fraîcheur vérifiée 2026-08 (fiches
30 +> publiées en août 2026 pour des dates jusqu'en décembre 2026).
31 +> Extraction: 1) liste JSON /wp-json/wp/v2/city-event (paginée, champs
32 +> title/link/content/modified) ; 2) dates + heures par fiche :
33 +> bannière « Cet événement aura lieu le 6 décembre 2026, de
34 +> 15 h 00 à 16 h 00 » (fiches ville) ou JSON-LD Event
35 +> (fiches biblio.brossard.ca, redirection suivie) — cache disque
36 +> incrémental data/brossard_cache.json, plafond
37 +> BROSSARD_DETAIL_MAX fiches/sync, re-visite 7 jours.
38 +> Accès : API REST WordPress publique (aucune authentification), GET
39 +> throttlés, identification honnête. robots.txt : Allow.
40 +> Prix : non publié par la source → jamais inventé.
41 +
42 +</details>
43 +
44 +## Champs récupérés → schéma
45 +
46 +Complétude mesurée sur les **262 événements actifs** de la source ; exemples tirés d'un événement réel (`brossard:37979`).
47 +
48 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
49 +|---|---|---|---|
50 +| `title` — Titre | titre de l'événement | 100 % | Atelier libre : crée ton bracelet d’amitié (26 septembre 2026) |
51 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Dans le cadre des Journées de la culture, viens créer ton propre bracelet d’amitié ! Choisis par… |
52 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://brossard.ca/calendrier-evenements/atelier-libre-cree-ton-bracelet-damitie-26-septembre-2… |
53 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["conference"] |
54 +| `venue` — Salle (venue) | nom du lieu | 53 % | Bibliothèque de Brossard Georgette-Lepage |
55 +| `address` — Adresse | adresse civique | 53 % | 7855, av San Francisco |
56 +| `city` — Ville | municipalité | 100 % | Brossard |
57 +| `region` — Région | région administrative | 100 % | Montérégie |
58 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 0 % | 45.4551469, -73.4682277 |
59 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-26 |
60 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-26 |
61 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 54 % | 13:00 |
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 Brossard |
67 +| `image` — Image | visuel (og:image ou média source) | 55 % | https://biblio.brossard.ca/wp-content/uploads/2026/07/bracelets_amitie.jpg |
68 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
69 +
70 +## Fréquence & budgets
71 +
72 +- **Cadence** : horaire (watcher PM2, incrémental)
73 +- **Budgets / plafonds (variables d'environnement)** :
74 +
75 +| Variable | Défaut | Rôle |
76 +|---|---|---|
77 +| `BROSSARD_DETAIL_MAX` | `80` | fiches/sync |
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-18 03:22 EDT dans `data/sortika.db`)*
83 +
84 +- **Événements en base** : 262 (dont **262 actifs**)
85 +- **GPS** : 1/262 actifs (0 %)
86 +- **Prix** : 0/262 actifs (0 %)
87 +- **Heure (start_time)** : 141/262 actifs (54 %)
88 +- **Artistes** : 0/262 actifs (0 %)
89 +- **Image** : 145/262 actifs (55 %)
90 +- **Salle (venue)** : 138/262 actifs (53 %)
91 +- **Dernier sync** : 2026-08-18 02:35 — fetched 262, +11 ajoutés, ~56 mis à jour, -0 retirés
92 +- **Syncs enregistrés** (sync_log) : 2
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 brossard` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='brossard' ORDER BY ts DESC LIMIT 5;"`
99 +
100 +## Licence / attribution
101 +
102 +- API REST WordPress publique (aucune authentification) — GET throttlés 0,8 s, identification honnête
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 +
109 +Commits touchant `sortika/connectors/brossard.py` :
110 +
111 +- `65d2d33` 2026-08-18 Vague 2 d'enrichissement : heure précise, artistes, 3 nouveaux connecteurs
added docs/connecteurs/evenko.md +105 −0
@@ -0,0 +1,105 @@
1 +# evenko — grands concerts et festivals (`evenko`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `promoteur` · Connecteur : `sortika/connectors/evenko.py`
6 +
7 +## Description
8 +
9 +- **Source** : [evenko — grands concerts et festivals](https://evenko.ca/fr/evenements)
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
12 +
13 +## Accès
14 +
15 +- **Type** : promoteur
16 +- **Endpoint** (`API`) : `https://evenko.ca/api/search`
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 +- **Accès légal** : robots.txt : Allow /api/search* (explicite) — lien billets = URL Ticketmaster officielle
21 +
22 +<details><summary>En-tête documentaire du connecteur</summary>
23 +
24 +> connectors/evenko.py : connecteur evenko — grands concerts & festivals
25 +> Source : evenko.ca (promoteur : Centre Bell, Place Bell, MTELUS, Osheaga…)
26 +> Extraction: endpoint /api/search (proxy Algolia interne) — EXPLICITEMENT
27 +> permis par robots.txt (« Allow: /api/search* »). Format percé
28 +> dans leur bundle JS : GET ?body=base64(JSON{params:[query,
29 +> options], lang:"fr-CA"}). ~14 requêtes/sync (100 hits/page).
30 +> Accès : robots.txt Allow /api/search* ; GET throttlés, identification
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.
38 +
39 +</details>
40 +
41 +## Champs récupérés → schéma
42 +
43 +Complétude mesurée sur les **423 événements actifs** de la source ; exemples tirés d'un événement réel (`evenko:e008791`).
44 +
45 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
46 +|---|---|---|---|
47 +| `title` — Titre | titre de l'événement | 100 % | Disney Sur Glace présente La Magie en Famille |
48 +| `description` — Description | texte descriptif (source ou fiche détail) | 18 % | ∅ |
49 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.ticketmaster.ca/event/310064E3C5686712?lang=fr-ca&brand=placebell |
50 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
51 +| `venue` — Salle (venue) | nom du lieu | 100 % | Place Bell |
52 +| `address` — Adresse | adresse civique | 0 % | ∅ |
53 +| `city` — Ville | municipalité | 100 % | Laval |
54 +| `region` — Région | région administrative | 100 % | Laval |
55 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 77 % | 45.5558423, -73.7216633 |
56 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-12-17 |
57 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-12-17 |
58 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 99 % | 19:00 |
59 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
60 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
61 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
62 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 99 % | ["Disney Sur Glace présente La Magie en Famille"] |
63 +| `organizer` — Organisateur | promoteur / organisme | 100 % | evenko |
64 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://images.ctfassets.net/3yxl57nu0yl4/3sI9AuDa7Ju1B2dtmFzVtY/a977ac5c95df131068a94762069db2f… |
65 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
66 +
67 +## Fréquence & budgets
68 +
69 +- **Cadence** : horaire (watcher PM2)
70 +- **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).
71 +
72 +## Volumétrie & complétude live
73 +
74 +*(mesuré le 2026-08-18 03:22 EDT dans `data/sortika.db`)*
75 +
76 +- **Événements en base** : 423 (dont **423 actifs**)
77 +- **GPS** : 327/423 actifs (77 %)
78 +- **Prix** : 0/423 actifs (0 %)
79 +- **Heure (start_time)** : 420/423 actifs (99 %)
80 +- **Artistes** : 420/423 actifs (99 %)
81 +- **Image** : 423/423 actifs (100 %)
82 +- **Salle (venue)** : 423/423 actifs (100 %)
83 +- **Dernier sync** : 2026-08-18 02:35 — fetched 423, +0 ajoutés, ~9 mis à jour, -0 retirés
84 +- **Syncs enregistrés** (sync_log) : 18
85 +
86 +## Erreurs & dépannage
87 +
88 +Aucune erreur dans `sync_log` pour cette source. ✅
89 +
90 +- Rejouer un sync isolé : `python3 run.py sync --source evenko` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='evenko' ORDER BY ts DESC LIMIT 5;"`
91 +
92 +## Licence / attribution
93 +
94 +- robots.txt : Allow /api/search* (explicite) — lien billets = URL Ticketmaster officielle
95 +
96 +## Historique
97 +
98 +- **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.
99 +- **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).
100 +
101 +Commits touchant `sortika/connectors/evenko.py` :
102 +
103 +- `65d2d33` 2026-08-18 Vague 2 d'enrichissement : heure précise, artistes, 3 nouveaux connecteurs
104 +- `7d9d1b0` 2026-08-18 Enrichissement des connecteurs + géocodage des salles
105 +- `9164456` 2026-08-17 Sorti·Ka v1 — agrégateur de sorties & événements du Québec
added docs/connecteurs/gatineau.md +45 −0
@@ -0,0 +1,45 @@
1 +# Ville de Gatineau — Événements (`gatineau`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : abandonnee** · Type : `municipal` · Connecteur : *aucun (source sans connecteur runtime)*
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Gatineau — Événements](https://www.gatineau.ca/)
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)
13 +
14 +## Accès
15 +
16 +- **Type** : municipal
17 +- **Endpoint** : voir URL source (https://www.gatineau.ca/)
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** — 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 +- **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).
added docs/connecteurs/laval.md +99 −0
@@ -0,0 +1,99 @@
1 +# Ville de Laval — Calendrier des activités (`laval`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `donnees-ouvertes` · Connecteur : `sortika/connectors/laval.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Laval — Calendrier des activités](https://www.donneesquebec.ca/recherche/dataset/calendrier-des-activites)
10 +- **Couverture** : Activités municipales, Laval
11 +- **Extraction** : fichier JSON officiel (Données Québec) — EventDate/EndDate publiés en UTC (vérifié 2026-08) → convertis en date + heure locales Québec (minuit local = « toute la journée », pas d'heure)
12 +
13 +## Accès
14 +
15 +- **Type** : donnees-ouvertes
16 +- **Endpoint** (`FEED`) : `https://www.donneesquebec.ca/recherche/dataset/df7add74-a741-4374-b3bd-22da8921c8b3/resource/b51a25de-bd06-4247-87ba-2b1ea8228005/download/calendrier-activites.json`
17 +- **Endpoint** (`SITE`) : `https://www.laval.ca`
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 +- **Accès légal** : Licence CC 4.0 (Données Québec) — Ville de Laval
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/laval.py : connecteur Ville de Laval — calendrier des activités
26 +> Source : donneesquebec.ca, jeu « Calendrier des activités » (laval.ca)
27 +> Extraction: fichier JSON officiel (données ouvertes, licence CC 4.0)
28 +> Couverture: ~300 activités municipales (bibliothèques, culture, famille,
29 +> plein air) avec dates, catégories, clientèles, lieux, images.
30 +> https://www.donneesquebec.ca/recherche/dataset/calendrier-des-activites
31 +
32 +</details>
33 +
34 +## Champs récupérés → schéma
35 +
36 +Complétude mesurée sur les **277 é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 +
38 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
39 +|---|---|---|---|
40 +| `title` — Titre | titre de l'événement | 100 % | Éducazoo |
41 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Cadeaux, parades, chants langoureux et techniques parfois étranges : les animaux déballent un ar… |
42 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.laval.ca/Pages/Fr/Calendrier/biblio-jeunesse-educazoo-saint-valentin.aspx |
43 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["conference", "musique", "famille"] |
44 +| `venue` — Salle (venue) | nom du lieu | 90 % | Bibliothèque Émile-Nelligan |
45 +| `address` — Adresse | adresse civique | 0 % | ∅ |
46 +| `city` — Ville | municipalité | 100 % | Laval |
47 +| `region` — Région | région administrative | 100 % | Laval |
48 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 51 % | 45.5521349, -73.7077536 |
49 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2025-02-15 |
50 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2025-02-15 |
51 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 95 % | 14:45 |
52 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 0 % | ∅ |
53 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
54 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
55 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
56 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Laval |
57 +| `image` — Image | visuel (og:image ou média source) | 98 % | https://www.laval.ca/PublishingImages/Calendrier-des-activites/bibliotheque/jeunesse-educazoo-sa… |
58 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
59 +
60 +## Fréquence & budgets
61 +
62 +- **Cadence** : horaire (watcher PM2)
63 +- **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).
64 +
65 +## Volumétrie & complétude live
66 +
67 +*(mesuré le 2026-08-18 03:22 EDT dans `data/sortika.db`)*
68 +
69 +- **Événements en base** : 277 (dont **277 actifs**)
70 +- **GPS** : 142/277 actifs (51 %)
71 +- **Prix** : 0/277 actifs (0 %)
72 +- **Heure (start_time)** : 264/277 actifs (95 %)
73 +- **Artistes** : 0/277 actifs (0 %)
74 +- **Image** : 271/277 actifs (98 %)
75 +- **Salle (venue)** : 248/277 actifs (90 %)
76 +- **Dernier sync** : 2026-08-18 02:35 — fetched 293, +0 ajoutés, ~32 mis à jour, -0 retirés
77 +- **Syncs enregistrés** (sync_log) : 21
78 +
79 +## Erreurs & dépannage
80 +
81 +Aucune erreur dans `sync_log` pour cette source. ✅
82 +
83 +- Rejouer un sync isolé : `python3 run.py sync --source laval` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='laval' ORDER BY ts DESC LIMIT 5;"`
84 +
85 +## Licence / attribution
86 +
87 +- Licence CC 4.0 (Données Québec) — Ville de Laval
88 +
89 +**Mention requise (CC 4.0 — Données Québec)** : « Données de la **Ville de Laval** (Calendrier des activités), diffusées sous licence Creative Commons 4.0 via Données Québec. » L'attribution à la Ville de Laval est obligatoire.
90 +
91 +## Historique
92 +
93 +- **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.
94 +- **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).
95 +
96 +Commits touchant `sortika/connectors/laval.py` :
97 +
98 +- `65d2d33` 2026-08-18 Vague 2 d'enrichissement : heure précise, artistes, 3 nouveaux connecteurs
99 +- `9164456` 2026-08-17 Sorti·Ka v1 — agrégateur de sorties & événements du Québec
added docs/connecteurs/lavitrine.md +110 −0
@@ -0,0 +1,110 @@
1 +# La Vitrine culturelle (`lavitrine`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/lavitrine.py`
6 +
7 +## Description
8 +
9 +- **Source** : [La Vitrine culturelle](https://www.lavitrine.com/)
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
12 +
13 +## Accès
14 +
15 +- **Type** : billetterie
16 +- **Endpoint** (`SITEMAP`) : `https://www.lavitrine.com/sitemap.xml`
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 : Allow / + Content-Signal search=yes, use=reference — usage index + lien vers la fiche originale
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/lavitrine.py : connecteur La Vitrine culturelle (lavitrine.com)
26 +> Source : billetterie/calendrier culturel — spectacles, expositions,
27 +> concerts (Grand Montréal + Québec + tournées).
28 +> Extraction: sitemap.xml public (~2 000 fiches /fr/evenement/ + 185
29 +> /fr/exposition/, lastmod par URL) puis pages fiche rendues
30 +> serveur (h1, bloc .event_calendar : jour/mois/année, salle,
31 +> ville ; og:image). GET directs — pas d'anti-bot.
32 +> INCRÉMENTAL avec cache disque (data/lavitrine_cache.json) :
33 +> nouvelles fiches d'abord, re-visite roulante après 7 jours,
34 +> plafonné à LV_MAX_FETCH fiches/sync.
35 +> Accès : robots.txt « User-Agent: * / Allow: / » + Content-Signal
36 +> « search=yes, use=reference » → usage index + lien vers la
37 +> fiche originale, conforme aux signaux publiés (CLAUDE.md §15).
38 +> Prix : non exposés sur la fiche → is_free inconnu (jamais inventé).
39 +
40 +</details>
41 +
42 +## Champs récupérés → schéma
43 +
44 +Complétude mesurée sur les **5127 événements actifs** de la source ; exemples tirés d'un événement réel (`lavitrine:23777-2026-11-22`).
45 +
46 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
47 +|---|---|---|---|
48 +| `title` — Titre | titre de l'événement | 100 % | 100 ans de chansons françaises avec Richard Charest |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 0 % | ∅ |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.lavitrine.com/fr/evenement/100-ans-de-chansons-francaises-avec-richard-charest/23777 |
51 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["musique"] |
52 +| `venue` — Salle (venue) | nom du lieu | 100 % | Théâtre Petit Champlain |
53 +| `address` — Adresse | adresse civique | 0 % | ∅ |
54 +| `city` — Ville | municipalité | 100 % | Québec |
55 +| `region` — Région | région administrative | 100 % | Capitale-Nationale |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 52 % | 46.8115511, -71.203632 |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-11-22 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-11-22 |
59 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 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 | 0 % | ∅ |
65 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://lvc-medias.lavitrine.com/acf4025b-3502-5217-b26d-f3bd5a35c6dc.webp |
66 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
67 +
68 +## Fréquence & budgets
69 +
70 +- **Cadence** : horaire (watcher PM2, incrémental)
71 +- **Budgets / plafonds (variables d'environnement)** :
72 +
73 +| Variable | Défaut | Rôle |
74 +|---|---|---|
75 +| `LV_MAX_FETCH` | `400` | fiches/sync (GET directs polis) |
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-18 03:22 EDT dans `data/sortika.db`)*
81 +
82 +- **Événements en base** : 5137 (dont **5127 actifs**)
83 +- **GPS** : 2681/5127 actifs (52 %)
84 +- **Prix** : 0/5127 actifs (0 %)
85 +- **Heure (start_time)** : 0/5127 actifs (0 %)
86 +- **Artistes** : 0/5127 actifs (0 %)
87 +- **Image** : 5127/5127 actifs (100 %)
88 +- **Salle (venue)** : 5125/5127 actifs (100 %)
89 +- **Dernier sync** : 2026-08-18 02:35 — fetched 5127, +0 ajoutés, ~5127 mis à jour, -0 retirés
90 +- **Syncs enregistrés** (sync_log) : 11
91 +
92 +## Erreurs & dépannage
93 +
94 +Aucune erreur dans `sync_log` pour cette source. ✅
95 +
96 +- Parcours par sitemap incrémental (`LV_MAX_FETCH` fiches/sync) : après un reset de cache, plusieurs cycles sont nécessaires pour recouvrir tout le catalogue.
97 +- Rejouer un sync isolé : `python3 run.py sync --source lavitrine` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='lavitrine' ORDER BY ts DESC LIMIT 5;"`
98 +
99 +## Licence / attribution
100 +
101 +- robots.txt : Allow / + Content-Signal search=yes, use=reference — usage index + lien vers la fiche originale
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 +
108 +Commits touchant `sortika/connectors/lavitrine.py` :
109 +
110 +- `9164456` 2026-08-17 Sorti·Ka v1 — agrégateur de sorties & événements du Québec
added docs/connecteurs/lepointdevente.md +115 −0
@@ -0,0 +1,115 @@
1 +# Le point de vente — billetterie québécoise (`lepointdevente`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `billetterie` · Connecteur : `sortika/connectors/lepointdevente.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Le point de vente — billetterie québécoise](https://lepointdevente.com/billets)
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 »)
12 +
13 +## Accès
14 +
15 +- **Type** : billetterie
16 +- **Endpoint** (`BASE`) : `https://lepointdevente.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** : robots.txt : « User-Agent: * / Allow: * » — GET throttlés, identification honnête, lien direct vers chaque billetterie
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/lepointdevente.py : connecteur Le point de vente (lepointdevente.com)
26 +> Source : billetterie québécoise indépendante — concerts, humour, théâtre,
27 +> festivals, partout au Québec (~1 500 événements à venir).
28 +> Extraction: pages de liste HTML rendues serveur (/billets?page=N, paginé),
29 +> parse regex des cartes (titre, date FR, salle + ville, image).
30 +> Aucun rendu JS nécessaire.
31 +> Accès : robots.txt = « User-Agent: * / Allow: * » (scraping permis) ;
32 +> GET throttlés 0,6 s, identification honnête, ~65 requêtes/sync.
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é.
42 +
43 +</details>
44 +
45 +## Champs récupérés → schéma
46 +
47 +Complétude mesurée sur les **1787 événements actifs** de la source ; exemples tirés d'un événement réel (`lepointdevente:98a260908001`).
48 +
49 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
50 +|---|---|---|---|
51 +| `title` — Titre | titre de l'événement | 100 % | GRANDE ALLÉE - Souper tardif festif |
52 +| `description` — Description | texte descriptif (source ou fiche détail) | 31 % | Séance d'information des Missions commerciales de l'Université Laval!Le programme des Missions c… |
53 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://lepointdevente.com/billets/98a260908001 |
54 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
55 +| `venue` — Salle (venue) | nom du lieu | 99 % | Grande-Allée |
56 +| `address` — Adresse | adresse civique | 0 % | ∅ |
57 +| `city` — Ville | municipalité | 100 % | Québec |
58 +| `region` — Région | région administrative | 98 % | Capitale-Nationale |
59 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 27 % | 46.2897843, -72.6724193 |
60 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-09-08 |
61 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-09-08 |
62 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 94 % | 19:00 |
63 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 31 % | 1 |
64 +| `price_min` — Prix min | tarif minimal réel ($) | 29 % | ∅ |
65 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 31 % | Admission générale 0 $ |
66 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 1 % | [] |
67 +| `organizer` — Organisateur | promoteur / organisme | 0 % | ∅ |
68 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://tpos.s3.amazonaws.com/events/98A/26/09/03/001/98a260903001-1152x648-fr-6a677853.png |
69 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
70 +
71 +## Fréquence & budgets
72 +
73 +- **Cadence** : horaire (watcher PM2)
74 +- **Budgets / plafonds (variables d'environnement)** :
75 +
76 +| Variable | Défaut | Rôle |
77 +|---|---|---|
78 +| `LPDV_DETAIL_MAX` | `150` | fiches/sync |
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-18 03:22 EDT dans `data/sortika.db`)*
84 +
85 +- **Événements en base** : 4191 (dont **1787 actifs**)
86 +- **GPS** : 476/1787 actifs (27 %)
87 +- **Prix** : 547/1787 actifs (31 %)
88 +- **Heure (start_time)** : 1673/1787 actifs (94 %)
89 +- **Artistes** : 16/1787 actifs (1 %)
90 +- **Image** : 1787/1787 actifs (100 %)
91 +- **Salle (venue)** : 1777/1787 actifs (99 %)
92 +- **Dernier sync** : 2026-08-18 02:41 — fetched 1787, +0 ajoutés, ~164 mis à jour, -0 retirés
93 +- **Syncs enregistrés** (sync_log) : 22
94 +
95 +## Erreurs & dépannage
96 +
97 +Aucune erreur dans `sync_log` pour cette source. ✅
98 +
99 +- L'enrichissement par fiches détail est incrémental (cache `data/lepointdevente_cache.json`, plafond `LPDV_DETAIL_MAX`/sync, re-visite 7 j) : la complétude description/heure/prix monte au fil des cycles.
100 +- Rejouer un sync isolé : `python3 run.py sync --source lepointdevente` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='lepointdevente' ORDER BY ts DESC LIMIT 5;"`
101 +
102 +## Licence / attribution
103 +
104 +- robots.txt : « User-Agent: * / Allow: * » — GET throttlés, identification honnête, lien direct vers chaque billetterie
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 +
111 +Commits touchant `sortika/connectors/lepointdevente.py` :
112 +
113 +- `65d2d33` 2026-08-18 Vague 2 d'enrichissement : heure précise, artistes, 3 nouveaux connecteurs
114 +- `7d9d1b0` 2026-08-18 Enrichissement des connecteurs + géocodage des salles
115 +- `9164456` 2026-08-17 Sorti·Ka v1 — agrégateur de sorties & événements du Québec
added docs/connecteurs/levis.md +45 −0
@@ -0,0 +1,45 @@
1 +# Ville de Lévis — Calendrier (`levis`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : abandonnee** · Type : `municipal` · Connecteur : *aucun (source sans connecteur runtime)*
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Lévis — Calendrier](https://www.ville.levis.qc.ca/)
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)
13 +
14 +## Accès
15 +
16 +- **Type** : municipal
17 +- **Endpoint** : voir URL source (https://www.ville.levis.qc.ca/)
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** — 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 +- **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).
added docs/connecteurs/longueuil.md +45 −0
@@ -0,0 +1,45 @@
1 +# Ville de Longueuil — Événements (`longueuil`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : abandonnee** · Type : `donnees-ouvertes` · Connecteur : *aucun (source sans connecteur runtime)*
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Longueuil — Événements](https://www.donneesquebec.ca/recherche/dataset/evenements)
10 +- **Couverture** : Longueuil (Montérégie)
11 +- **Extraction** : CSV (Données Québec)
12 +- **Note** : Jeu de données périmé — dernière date d'événement : 2017-03 (vérifié 2026-08-17)
13 +
14 +## Accès
15 +
16 +- **Type** : donnees-ouvertes
17 +- **Endpoint** : voir URL source (https://www.donneesquebec.ca/recherche/dataset/evenements)
18 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 +- **Accès légal** : Licence CC 4.0 (Données Québec)
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 +- Licence CC 4.0 (Données Québec)
40 +
41 +## Historique
42 +
43 +- **Source abandonnée** — Jeu de données périmé — dernière date d'événement : 2017-03 (vérifié 2026-08-17)
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).
added docs/connecteurs/montreal.md +114 −0
@@ -0,0 +1,114 @@
1 +# Ville de Montréal — Événements publics (`montreal`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `donnees-ouvertes` · Connecteur : `sortika/connectors/montreal.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Montréal — Événements publics](https://donnees.montreal.ca/dataset/evenements-publics)
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)
12 +
13 +## Accès
14 +
15 +- **Type** : donnees-ouvertes
16 +- **Endpoint** (`API`) : `https://donnees.montreal.ca/api/3/action/datastore_search`
17 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
18 +- **Pagination** : offset/limit
19 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
20 +- **Throttle** : 0.4 s entre requêtes
21 +- **Accès légal** : Licence CC BY 4.0 (donnees.montreal.ca) — attribution : Ville de Montréal
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/montreal.py : connecteur Ville de Montréal — événements publics
26 +> Source : donnees.montreal.ca, jeu « Événements publics » (montreal.ca)
27 +> Extraction: API CKAN datastore_search (JSON, paginée) — données ouvertes,
28 +> Licence CC BY 4.0. Aucune restriction de scraping.
29 +> Couverture: ~5 600 événements municipaux (culture, conseils, famille,
30 +> sport, marchés) avec dates, coût, arrondissement, lat/lng.
31 +> Images : absentes du jeu de données → enrichissement incrémental par
32 +> og:image des fiches montreal.ca (cache disque, plafonné par
33 +> sync via MTL_IMG_MAX, défaut 300).
34 +> https://donnees.montreal.ca/dataset/evenements-publics
35 +
36 +</details>
37 +
38 +## Champs récupérés → schéma
39 +
40 +Complétude mesurée sur les **5679 événements actifs** de la source ; exemples tirés d'un événement réel (`montreal:les-legendaires-cine-ete-111611`).
41 +
42 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
43 +|---|---|---|---|
44 +| `title` — Titre | titre de l'événement | 100 % | Les Légendaires – Ciné-été |
45 +| `description` — Description | texte descriptif (source ou fiche détail) | 100 % | Découvrez cette adaptation cinématographique des bandes dessinées de Patrick Sobral, réalisée pa… |
46 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://montreal.ca/evenements/les-legendaires-cine-ete-111611 |
47 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["cinema"] |
48 +| `venue` — Salle (venue) | nom du lieu | 4 % | Cinéma Beaubien |
49 +| `address` — Adresse | adresse civique | 4 % | 2396 Rue Beaubien E |
50 +| `city` — Ville | municipalité | 100 % | Montréal |
51 +| `region` — Région | région administrative | 100 % | Montréal |
52 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 97 % | 45.548115, -73.5921 |
53 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
54 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2026-08-22 |
55 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 10 % | 09:30 |
56 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 100 % | 1 |
57 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
58 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 100 % | Gratuit — Avec billet |
59 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
60 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Montréal — Rosemont–La Petite-Patrie |
61 +| `image` — Image | visuel (og:image ou média source) | 58 % | https://res.cloudinary.com/villemontreal/image/upload/w_1200,h_630,c_fill/f_auto,dpr_auto,q_auto… |
62 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
63 +
64 +## Fréquence & budgets
65 +
66 +- **Cadence** : horaire (watcher PM2)
67 +- **Budgets / plafonds (variables d'environnement)** :
68 +
69 +| Variable | Défaut | Rôle |
70 +|---|---|---|
71 +| `MTL_IMG_MAX` | `300` | — |
72 +- **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).
73 +
74 +## Volumétrie & complétude live
75 +
76 +*(mesuré le 2026-08-18 03:22 EDT dans `data/sortika.db`)*
77 +
78 +- **Événements en base** : 5729 (dont **5679 actifs**)
79 +- **GPS** : 5530/5679 actifs (97 %)
80 +- **Prix** : 5679/5679 actifs (100 %)
81 +- **Heure (start_time)** : 588/5679 actifs (10 %)
82 +- **Artistes** : 0/5679 actifs (0 %)
83 +- **Image** : 3296/5679 actifs (58 %)
84 +- **Salle (venue)** : 246/5679 actifs (4 %)
85 +- **Dernier sync** : 2026-08-18 02:41 — fetched 5679, +0 ajoutés, ~300 mis à jour, -0 retirés
86 +- **Syncs enregistrés** (sync_log) : 22
87 +
88 +## Erreurs & dépannage
89 +
90 +Erreurs relevées dans `sync_log` :
91 +
92 +| Erreur | Occurrences | Dernière fois |
93 +|---|---|---|
94 +| 403 Client Error: Forbidden for url: https://donnees.montreal.ca/api/3/action/datastore_search?r… | 1 | 2026-08-17 15:45 |
95 +
96 +- Le WAF de donnees.montreal.ca renvoie **403** à tout User-Agent contenant « bot » — le UA `SortiKa/1.0 (+URL; courriel)` est volontairement sans ce mot. Un 403 ponctuel est en général transitoire (WAF) ; vérifier le UA avant tout.
97 +- Rejouer un sync isolé : `python3 run.py sync --source montreal` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='montreal' ORDER BY ts DESC LIMIT 5;"`
98 +
99 +## Licence / attribution
100 +
101 +- Licence CC BY 4.0 (donnees.montreal.ca) — attribution : Ville de Montréal
102 +
103 +**Mention requise (CC BY 4.0)** : « Contient des données de la **Ville de Montréal**, sous licence CC BY 4.0 (donnees.montreal.ca). » L'attribution doit accompagner toute réutilisation publique des événements de cette source.
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 +
110 +Commits touchant `sortika/connectors/montreal.py` :
111 +
112 +- `65d2d33` 2026-08-18 Vague 2 d'enrichissement : heure précise, artistes, 3 nouveaux connecteurs
113 +- `7d9d1b0` 2026-08-18 Enrichissement des connecteurs + géocodage des salles
114 +- `9164456` 2026-08-17 Sorti·Ka v1 — agrégateur de sorties & événements du Québec
added docs/connecteurs/repentigny.md +45 −0
@@ -0,0 +1,45 @@
1 +# Ville de Repentigny — Événements (`repentigny`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : abandonnee** · Type : `donnees-ouvertes` · Connecteur : *aucun (source sans connecteur runtime)*
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Repentigny — Événements](https://www.donneesquebec.ca/recherche/dataset/evenement-sur-le-territoire-de-la-ville)
10 +- **Couverture** : Repentigny (Lanaudière)
11 +- **Extraction** : CSV (Données Québec)
12 +- **Note** : Jeu de données périmé — dernière date d'événement : 2021-09 (vérifié 2026-08-17)
13 +
14 +## Accès
15 +
16 +- **Type** : donnees-ouvertes
17 +- **Endpoint** : voir URL source (https://www.donneesquebec.ca/recherche/dataset/evenement-sur-le-territoire-de-la-ville)
18 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 +- **Accès légal** : Licence CC 4.0 (Données Québec)
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 +- Licence CC 4.0 (Données Québec)
40 +
41 +## Historique
42 +
43 +- **Source abandonnée** — Jeu de données périmé — dernière date d'événement : 2021-09 (vérifié 2026-08-17)
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).
added docs/connecteurs/saguenay.md +45 −0
@@ -0,0 +1,45 @@
1 +# Ville de Saguenay — Événements culturels (`saguenay`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : abandonnee** · Type : `donnees-ouvertes` · Connecteur : *aucun (source sans connecteur runtime)*
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Saguenay — Événements culturels](https://www.donneesquebec.ca/recherche/dataset/sag_evenements-culturels)
10 +- **Couverture** : Saguenay
11 +- **Extraction** : CSV (Données Québec)
12 +- **Note** : Jeu de données périmé — dernière date d'événement : 2022-06 (vérifié 2026-08-17)
13 +
14 +## Accès
15 +
16 +- **Type** : donnees-ouvertes
17 +- **Endpoint** : voir URL source (https://www.donneesquebec.ca/recherche/dataset/sag_evenements-culturels)
18 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 +- **Accès légal** : Licence CC 4.0 (Données Québec)
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 +- Licence CC 4.0 (Données Québec)
40 +
41 +## Historique
42 +
43 +- **Source abandonnée** — Jeu de données périmé — dernière date d'événement : 2022-06 (vérifié 2026-08-17)
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).
added docs/connecteurs/sherbrooke.md +100 −0
@@ -0,0 +1,100 @@
1 +# Ville de Sherbrooke — Calendrier des événements (`sherbrooke`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `municipal` · Connecteur : `sortika/connectors/sherbrooke.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Sherbrooke — Calendrier des événements](https://www.sherbrooke.ca/fr/culture-sports-et-loisirs/calendrier-des-activites)
10 +- **Couverture** : Événements municipaux et culturels, Sherbrooke (Estrie)
11 +- **Extraction** : la page rend TOUT le calendrier dans window['TRANSFER_CACHE'] (JSON embarqué, CMS Maruche) → 1 seule requête, ~270 événements (nom, périodes en UTC → date + heure locales Québec, gratuité `payant`, catégories, clientèles, images)
12 +
13 +## Accès
14 +
15 +- **Type** : municipal
16 +- **Endpoint** (`PAGE`) : `https://www.sherbrooke.ca/fr/culture-sports-et-loisirs/calendrier-des-activites`
17 +- **Endpoint** (`SITE`) : `https://www.sherbrooke.ca`
18 +- **Auth** : Aucune (accès public, identification honnête par User-Agent)
19 +- **Pagination** : aucune (flux unique)
20 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
21 +- **Accès légal** : page municipale publique (référencée sur Données Québec) — GET direct, identification honnête
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/sherbrooke.py : connecteur Ville de Sherbrooke — calendrier officiel
26 +> Source : sherbrooke.ca, « Calendrier des événements en temps réel »
27 +> (référencé sur Données Québec : calendrier-des-evenements-en-temps-reel)
28 +> Extraction: la page rend TOUT le calendrier dans window['TRANSFER_CACHE']
29 +> (JSON embarqué, CMS Maruche) → 1 seule requête, ~270 événements
30 +> (nom, périodes, gratuité `payant`, catégories, clientèles,
31 +> images /Fichiers/<module>/Entities/<id>/<mainPicture>).
32 +> Accès : page municipale publique, GET direct, identification honnête.
33 +> Prix : booléen `payant` de la source → is_free (montants non exposés).
34 +
35 +</details>
36 +
37 +## Champs récupérés → schéma
38 +
39 +Complétude mesurée sur les **271 événements actifs** de la source ; exemples tirés d'un événement réel (`sherbrooke:499`).
40 +
41 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
42 +|---|---|---|---|
43 +| `title` — Titre | titre de l'événement | 100 % | Séance d’échange et de discussion - relocalisation de la caserne d’incendie Hector-Olivier-Camir… |
44 +| `description` — Description | texte descriptif (source ou fiche détail) | 96 % | La construction d’un nouveau poste incendie vise à bonifier le service et à réduire les délais d… |
45 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | https://www.sherbrooke.ca/fr/culture-sports-et-loisirs/calendrier-des-activites/499/seance-d-ech… |
46 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["autre"] |
47 +| `venue` — Salle (venue) | nom du lieu | 0 % | ∅ |
48 +| `address` — Adresse | adresse civique | 0 % | ∅ |
49 +| `city` — Ville | municipalité | 100 % | Sherbrooke |
50 +| `region` — Région | région administrative | 100 % | Estrie |
51 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 0 % | ∅ |
52 +| `start_date` — Date début | date locale AAAA-MM-JJ | 100 % | 2022-06-13 |
53 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 100 % | 2022-06-13 |
54 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 88 % | 18:30 |
55 +| `is_free` — Gratuité (is_free) | 1 gratuit / 0 payant / NULL inconnu | 100 % | 1 |
56 +| `price_min` — Prix min | tarif minimal réel ($) | 0 % | ∅ |
57 +| `price_label` — Libellé prix | étiquette tarifaire affichable | 0 % | ∅ |
58 +| `artists` — Artistes | têtes d'affiche / lineup (JSON) | 0 % | [] |
59 +| `organizer` — Organisateur | promoteur / organisme | 100 % | Ville de Sherbrooke |
60 +| `image` — Image | visuel (og:image ou média source) | 100 % | https://www.sherbrooke.ca/Fichiers/3337a882-4a53-e611-80ea-00155d09650f/Entities/49d0f046-294c-4… |
61 +| `website` — Site web | site officiel de l'événement | 0 % | ∅ |
62 +
63 +## Fréquence & budgets
64 +
65 +- **Cadence** : horaire (watcher PM2)
66 +- **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).
67 +
68 +## Volumétrie & complétude live
69 +
70 +*(mesuré le 2026-08-18 03:22 EDT dans `data/sortika.db`)*
71 +
72 +- **Événements en base** : 271 (dont **271 actifs**)
73 +- **GPS** : 0/271 actifs (0 %)
74 +- **Prix** : 271/271 actifs (100 %)
75 +- **Heure (start_time)** : 239/271 actifs (88 %)
76 +- **Artistes** : 0/271 actifs (0 %)
77 +- **Image** : 270/271 actifs (100 %)
78 +- **Salle (venue)** : 0/271 actifs (0 %)
79 +- **Dernier sync** : 2026-08-18 02:41 — fetched 271, +0 ajoutés, ~0 mis à jour, -0 retirés
80 +- **Syncs enregistrés** (sync_log) : 14
81 +
82 +## Erreurs & dépannage
83 +
84 +Aucune erreur dans `sync_log` pour cette source. ✅
85 +
86 +- Rejouer un sync isolé : `python3 run.py sync --source sherbrooke` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='sherbrooke' ORDER BY ts DESC LIMIT 5;"`
87 +
88 +## Licence / attribution
89 +
90 +- page municipale publique (référencée sur Données Québec) — GET direct, identification honnête
91 +
92 +## Historique
93 +
94 +- **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.
95 +- **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).
96 +
97 +Commits touchant `sortika/connectors/sherbrooke.py` :
98 +
99 +- `65d2d33` 2026-08-18 Vague 2 d'enrichissement : heure précise, artistes, 3 nouveaux connecteurs
100 +- `9164456` 2026-08-17 Sorti·Ka v1 — agrégateur de sorties & événements du Québec
added docs/connecteurs/sitq.md +112 −0
@@ -0,0 +1,112 @@
1 +# SIT Québec — Ministère du Tourisme (`sitq`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : actif** · Type : `donnees-ouvertes` · Connecteur : `sortika/connectors/sitq.py`
6 +
7 +## Description
8 +
9 +- **Source** : [SIT Québec — Ministère du Tourisme](https://www.donneesquebec.ca/recherche/dataset/sit-quebec-evenements)
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)
12 +
13 +## Accès
14 +
15 +- **Type** : donnees-ouvertes
16 +- **Endpoint** (`FEED`) : `https://api-v3.tourinsoft.com/api/syndications/mto.tourinsoft.com/c6ab1c9e-f594-463e-b036-5a029197f3a9?format=json`
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.3 s entre requêtes
21 +- **Accès légal** : Licence Creative Commons 4.0 (Données Québec) — API officielle publiée par le Ministère du Tourisme
22 +
23 +<details><summary>En-tête documentaire du connecteur</summary>
24 +
25 +> connectors/sitq.py : connecteur SIT Québec — Ministère du Tourisme
26 +> Source : Système d'information touristique Québec (Données Québec)
27 +> Extraction: API JSON officielle Tourinsoft (syndication publique)
28 +> Accès : données ouvertes — Licence CC 4.0 (Données Québec), API publiée
29 +> par le Ministère du Tourisme. Aucune restriction de scraping.
30 +> Couverture: ~1 600 événements touristiques, TOUTES les régions du Québec
31 +> (festivals, salons, manifestations sportives et culturelles),
32 +> avec région touristique, thèmes, dates, géolocalisation, site web.
33 +> Contacts : TelephonePrincipals / Courriels (moyens de communication) →
34 +> ligne « Renseignements : … » en description (le flux syndiqué
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).
38 +> https://www.donneesquebec.ca/recherche/dataset/sit-quebec-evenements
39 +
40 +</details>
41 +
42 +## Champs récupérés → schéma
43 +
44 +Complétude mesurée sur les **1598 événements actifs** de la source ; exemples tirés d'un événement réel (`sitq:EVENMTOV500NIO`).
45 +
46 +| Champ (`events.*`) | Description | Complétude | Exemple réel |
47 +|---|---|---|---|
48 +| `title` — Titre | titre de l'événement | 100 % | JACKALOPE |
49 +| `description` — Description | texte descriptif (source ou fiche détail) | 87 % | ∅ |
50 +| `url` — URL | lien vers la fiche originale / billetterie | 100 % | http://jackalopefest.ca |
51 +| `categories` — Catégories | taxonomie canonique Sorti-Ka (JSON) | 100 % | ["sport", "musique"] |
52 +| `venue` — Salle (venue) | nom du lieu | 66 % | Quai Jacques-Cartier |
53 +| `address` — Adresse | adresse civique | 84 % | Rue de la Commune Est |
54 +| `city` — Ville | municipalité | 96 % | Montréal |
55 +| `region` — Région | région administrative | 99 % | Montréal |
56 +| `lat` — GPS (lat/lng) | coordonnées géographiques | 97 % | 45.507703, -73.552067 |
57 +| `start_date` — Date début | date locale AAAA-MM-JJ | 99 % | 2026-09-11 |
58 +| `end_date` — Date fin | date locale AAAA-MM-JJ | 99 % | 2026-09-13 |
59 +| `start_time` — Heure (start_time) | heure locale HH:MM (Québec) | 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 % | Tourisme Montréal |
65 +| `image` — Image | visuel (og:image ou média source) | 35 % | http://jackalope.co/cdn/shop/files/jackalope_e0d516dd-87d0-4058-ad4d-153c59ef7f99.jpg?v=17736954… |
66 +| `website` — Site web | site officiel de l'événement | 93 % | http://jackalopefest.ca |
67 +
68 +## Fréquence & budgets
69 +
70 +- **Cadence** : horaire (watcher PM2)
71 +- **Budgets / plafonds (variables d'environnement)** :
72 +
73 +| Variable | Défaut | Rôle |
74 +|---|---|---|
75 +| `SITQ_IMG_MAX` | `200` | — |
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-18 03:22 EDT dans `data/sortika.db`)*
81 +
82 +- **Événements en base** : 1606 (dont **1598 actifs**)
83 +- **GPS** : 1548/1598 actifs (97 %)
84 +- **Prix** : 0/1598 actifs (0 %)
85 +- **Heure (start_time)** : 0/1598 actifs (0 %)
86 +- **Artistes** : 0/1598 actifs (0 %)
87 +- **Image** : 558/1598 actifs (35 %)
88 +- **Salle (venue)** : 1050/1598 actifs (66 %)
89 +- **Dernier sync** : 2026-08-18 02:41 — fetched 1598, +0 ajoutés, ~1598 mis à jour, -0 retirés
90 +- **Syncs enregistrés** (sync_log) : 21
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 sitq` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='sitq' ORDER BY ts DESC LIMIT 5;"`
97 +
98 +## Licence / attribution
99 +
100 +- Licence Creative Commons 4.0 (Données Québec) — API officielle publiée par le Ministère du Tourisme
101 +
102 +**Mention requise (CC 4.0 — Données Québec)** : « Données du **SIT Québec — Ministère du Tourisme**, diffusées sous licence Creative Commons 4.0 via Données Québec. » L'attribution au Ministère du Tourisme est obligatoire.
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 +
109 +Commits touchant `sortika/connectors/sitq.py` :
110 +
111 +- `7d9d1b0` 2026-08-18 Enrichissement des connecteurs + géocodage des salles
112 +- `9164456` 2026-08-17 Sorti·Ka v1 — agrégateur de sorties & événements du Québec
added docs/connecteurs/terrebonne.md +45 −0
@@ -0,0 +1,45 @@
1 +# Ville de Terrebonne — Calendrier (`terrebonne`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : abandonnee** · Type : `municipal` · Connecteur : *aucun (source sans connecteur runtime)*
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Terrebonne — Calendrier](https://terrebonne.ca/calendrier/)
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)
13 +
14 +## Accès
15 +
16 +- **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
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** — Cloudflare bloque tout GET non navigateur (403) et aucun jeu « événements » sur Données Québec (vérifié 2026-08-18)
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).
added docs/connecteurs/ticketmaster.md +89 −0
@@ -0,0 +1,89 @@
1 +# Ticketmaster — Discovery API (Québec) (`ticketmaster`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : prêt — clé requise** · Type : `billetterie` · Connecteur : `sortika/connectors/ticketmaster.py`
6 +
7 +## Description
8 +
9 +- **Source** : [Ticketmaster — Discovery API (Québec)](https://developer.ticketmaster.com/products-and-docs/apis/discovery-api/v2/)
10 +- **Couverture** : Grands spectacles, sports et concerts — partout au Québec
11 +- **Extraction** : API officielle Discovery v2 — GET JSON paginé (countryCode=CA & stateCode=QC, size=200, tri par date) : nom, dates + heure locale, salle géolocalisée (venues lat/lng), fourchette de prix (priceRanges min/max), images 16:9, classifications → catégories, attractions → artistes, URL de billetterie officielle ; throttle 0,25 s (quota 5 req/s)
12 +- **Note** : Nécessite TICKETMASTER_API_KEY dans .env (clé gratuite). Sans clé, le connecteur s'annonce « clé manquante » et ne retourne rien.
13 +
14 +## Accès
15 +
16 +- **Type** : billetterie
17 +- **Endpoint** (`API`) : `https://app.ticketmaster.com/discovery/v2/events.json`
18 +- **Auth** : Clé API gratuite `TICKETMASTER_API_KEY` (developer.ticketmaster.com)
19 +- **Pagination** : par numéro de page
20 +- **Backend** : GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)
21 +- **Throttle** : 0.25 s entre requêtes
22 +- **Accès légal** : API officielle publique avec clé gratuite (developer.ticketmaster.com) — conditions Ticketmaster Developer respectées
23 +
24 +<details><summary>En-tête documentaire du connecteur</summary>
25 +
26 +> connectors/ticketmaster.py : connecteur Ticketmaster Discovery API (Québec)
27 +> Source : app.ticketmaster.com/discovery/v2/events — API officielle
28 +> publique (clé gratuite sur developer.ticketmaster.com).
29 +> Extraction: GET JSON paginé (countryCode=CA & stateCode=QC, size=200) —
30 +> nom, dates + heure locale, salle géolocalisée (venues lat/lng),
31 +> fourchette de prix (priceRanges min/max), images 16:9,
32 +> classifications (segment/genre) → catégories, attractions →
33 +> artistes, URL de billetterie officielle.
34 +> Accès : API officielle avec clé — quota 5 req/s & 5 000 req/jour
35 +> (throttle 0,25 s ; ~10 requêtes/sync, pagination plafonnée par
36 +> l'API à 1 000 éléments par requête profonde).
37 +> Clé : TICKETMASTER_API_KEY dans .env (voir .env.example). Sans clé,
38 +> le connecteur s'annonce « clé manquante » et ne retourne rien —
39 +> statut « prêt — clé requise » dans data/sources.json.
40 +
41 +</details>
42 +
43 +## Champs récupérés → schéma
44 +
45 +*Aucun événement actif en base pour cette source — pas de schéma live à montrer.*
46 +
47 +## Fréquence & budgets
48 +
49 +- **Cadence** : horaire (watcher PM2) dès que la clé est fournie
50 +- **Budgets / plafonds (variables d'environnement)** :
51 +
52 +| Variable | Défaut | Rôle |
53 +|---|---|---|
54 +| `TICKETMASTER_API_KEY` | `` | — |
55 +- **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).
56 +
57 +## Volumétrie & complétude live
58 +
59 +*(mesuré le 2026-08-18 03:22 EDT dans `data/sortika.db`)*
60 +
61 +- **Événements en base** : 0 (dont **0 actifs**)
62 +- **GPS** : 0/0 actifs (—)
63 +- **Prix** : 0/0 actifs (—)
64 +- **Heure (start_time)** : 0/0 actifs (—)
65 +- **Artistes** : 0/0 actifs (—)
66 +- **Image** : 0/0 actifs (—)
67 +- **Salle (venue)** : 0/0 actifs (—)
68 +- **Dernier sync** : 2026-08-18 02:41 — fetched 0, +0 ajoutés, ~0 mis à jour, -0 retirés
69 +- **Syncs enregistrés** (sync_log) : 2
70 +
71 +## Erreurs & dépannage
72 +
73 +Aucune erreur dans `sync_log` pour cette source. ✅
74 +
75 +- Sans `TICKETMASTER_API_KEY` dans `.env`, le connecteur s'annonce « clé manquante » et ne retourne rien (statut « prêt — clé requise »). Quota API : 5 req/s (throttle 0,25 s côté connecteur).
76 +- Rejouer un sync isolé : `python3 run.py sync --source ticketmaster` (ou via le watcher PM2) ; inspecter ensuite `sync_log` : `sqlite3 data/sortika.db "SELECT * FROM sync_log WHERE source='ticketmaster' ORDER BY ts DESC LIMIT 5;"`
77 +
78 +## Licence / attribution
79 +
80 +- API officielle publique avec clé gratuite (developer.ticketmaster.com) — conditions Ticketmaster Developer respectées
81 +
82 +## Historique
83 +
84 +- **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.
85 +- **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).
86 +
87 +Commits touchant `sortika/connectors/ticketmaster.py` :
88 +
89 +- `65d2d33` 2026-08-18 Vague 2 d'enrichissement : heure précise, artistes, 3 nouveaux connecteurs
added docs/connecteurs/trois-rivieres.md +45 −0
@@ -0,0 +1,45 @@
1 +# Ville de Trois-Rivières — Événements (`trois-rivieres`)
2 +
3 +> Fiche générée le 2026-08-18 03:22 EDT par `scripts/gen_connector_docs.py` — ne pas éditer à la main, relancer le script.
4 +
5 +**Statut : abandonnee** · Type : `municipal` · Connecteur : *aucun (source sans connecteur runtime)*
6 +
7 +## Description
8 +
9 +- **Source** : [Ville de Trois-Rivières — Événements](https://www.v3r.net/)
10 +- **Couverture** : Trois-Rivières (Mauricie)
11 +- **Extraction** : n/d
12 +- **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)
13 +
14 +## Accès
15 +
16 +- **Type** : municipal
17 +- **Endpoint** : voir URL source (https://www.v3r.net/)
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** — 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)
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).
added scripts/gen_connector_docs.py +561 −0
@@ -0,0 +1,561 @@
1 +#!/usr/bin/env python3
2 +# -----------------------------------------------------------------------------
3 +# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)
4 +# Auteur : Simon-Pierre Boucher — contact@spboucher.ai
5 +# scripts/gen_connector_docs.py : générateur REJOUABLE de la documentation
6 +# standardisée des connecteurs → docs/connecteurs/INDEX.md + une fiche par
7 +# source. Croise trois vérités :
8 +# 1. le registre data/sources.json (description, licence, cadence, statut)
9 +# 2. l'introspection statique des connecteurs sortika/connectors/*.py
10 +# (endpoints, pagination, backend, plafonds env, en-tête documentaire)
11 +# 3. la base LIVE data/sortika.db (volumétrie, complétude par champ,
12 +# exemples réels, dernier sync, erreurs sync_log)
13 +# + l'historique git de chaque connecteur.
14 +#
15 +# Usage : python3 scripts/gen_connector_docs.py
16 +# (aucune dépendance hors stdlib ; la BD est ouverte en lecture seule)
17 +# -----------------------------------------------------------------------------
18 +from __future__ import annotations
19 +
20 +import ast
21 +import json
22 +import re
23 +import sqlite3
24 +import subprocess
25 +import sys
26 +from datetime import datetime, timezone
27 +from pathlib import Path
28 +
29 +ROOT = Path(__file__).resolve().parents[1]
30 +SOURCES_JSON = ROOT / "data" / "sources.json"
31 +DB_PATH = ROOT / "data" / "sortika.db"
32 +CONNECTORS_DIR = ROOT / "sortika" / "connectors"
33 +OUT_DIR = ROOT / "docs" / "connecteurs"
34 +
35 +NOW = datetime.now().astimezone()
36 +STAMP = NOW.strftime("%Y-%m-%d %H:%M %Z")
37 +
38 +# Champs documentés dans le tableau « Champs récupérés → schéma » de chaque
39 +# fiche : (colonne SQL, libellé, description courte).
40 +SCHEMA_FIELDS = [
41 + ("title", "Titre", "titre de l'événement"),
42 + ("description", "Description", "texte descriptif (source ou fiche détail)"),
43 + ("url", "URL", "lien vers la fiche originale / billetterie"),
44 + ("categories", "Catégories", "taxonomie canonique Sorti-Ka (JSON)"),
45 + ("venue", "Salle (venue)","nom du lieu"),
46 + ("address", "Adresse", "adresse civique"),
47 + ("city", "Ville", "municipalité"),
48 + ("region", "Région", "région administrative"),
49 + ("lat", "GPS (lat/lng)","coordonnées géographiques"),
50 + ("start_date", "Date début", "date locale AAAA-MM-JJ"),
51 + ("end_date", "Date fin", "date locale AAAA-MM-JJ"),
52 + ("start_time", "Heure (start_time)", "heure locale HH:MM (Québec)"),
53 + ("is_free", "Gratuité (is_free)", "1 gratuit / 0 payant / NULL inconnu"),
54 + ("price_min", "Prix min", "tarif minimal réel ($)"),
55 + ("price_label", "Libellé prix", "étiquette tarifaire affichable"),
56 + ("artists", "Artistes", "têtes d'affiche / lineup (JSON)"),
57 + ("organizer", "Organisateur", "promoteur / organisme"),
58 + ("image", "Image", "visuel (og:image ou média source)"),
59 + ("website", "Site web", "site officiel de l'événement"),
60 +]
61 +
62 +# Complétude « clé » suivie dans l'INDEX et la section Volumétrie (SQL, libellé).
63 +KEY_METRICS = [
64 + ("lat IS NOT NULL AND lng IS NOT NULL", "GPS"),
65 + ("(is_free IS NOT NULL OR price_min IS NOT NULL OR (price_label IS NOT NULL AND price_label != ''))", "Prix"),
66 + ("start_time IS NOT NULL AND start_time != ''", "Heure (start_time)"),
67 + ("artists IS NOT NULL AND artists NOT IN ('', '[]')", "Artistes"),
68 + ("image IS NOT NULL AND image != ''", "Image"),
69 + ("venue IS NOT NULL AND venue != ''", "Salle (venue)"),
70 +]
71 +
72 +# Attributions REQUISES par licence (rappelées mot à mot dans les fiches).
73 +ATTRIBUTIONS = {
74 + "montreal": ("**Mention requise (CC BY 4.0)** : « Contient des données de "
75 + "la **Ville de Montréal**, sous licence CC BY 4.0 "
76 + "(donnees.montreal.ca). » L'attribution doit accompagner "
77 + "toute réutilisation publique des événements de cette source."),
78 + "sitq": ("**Mention requise (CC 4.0 — Données Québec)** : « Données du "
79 + "**SIT Québec — Ministère du Tourisme**, diffusées sous "
80 + "licence Creative Commons 4.0 via Données Québec. » "
81 + "L'attribution au Ministère du Tourisme est obligatoire."),
82 + "laval": ("**Mention requise (CC 4.0 — Données Québec)** : « Données de "
83 + "la **Ville de Laval** (Calendrier des activités), diffusées "
84 + "sous licence Creative Commons 4.0 via Données Québec. » "
85 + "L'attribution à la Ville de Laval est obligatoire."),
86 +}
87 +
88 +# Notes de dépannage spécifiques (complètent les erreurs sync_log live).
89 +DEPANNAGE = {
90 + "montreal": ["Le WAF de donnees.montreal.ca renvoie **403** à tout "
91 + "User-Agent contenant « bot » — le UA `SortiKa/1.0 (+URL; "
92 + "courriel)` est volontairement sans ce mot. Un 403 ponctuel "
93 + "est en général transitoire (WAF) ; vérifier le UA avant tout."],
94 + "atuvu": ["Source derrière anti-bot/JS lourd → backend **Scrapfly** "
95 + "requis : vérifier `SCRAPFLY_API_KEY` dans `.env` et le solde "
96 + "de crédits Scrapfly si le connecteur ne rapporte plus rien."],
97 + "ticketmaster": ["Sans `TICKETMASTER_API_KEY` dans `.env`, le connecteur "
98 + "s'annonce « clé manquante » et ne retourne rien (statut "
99 + "« prêt — clé requise »). Quota API : 5 req/s (throttle "
100 + "0,25 s côté connecteur)."],
101 + "bandsintown": ["L'API n'accepte que certains formats d'`app_id` : le "
102 + "format widget `js_www.sorti-ka.com` est testé accepté "
103 + "(2026-08) ; un app_id arbitraire simple est refusé."],
104 + "lepointdevente": ["L'enrichissement par fiches détail est incrémental "
105 + "(cache `data/lepointdevente_cache.json`, plafond "
106 + "`LPDV_DETAIL_MAX`/sync, re-visite 7 j) : la complétude "
107 + "description/heure/prix monte au fil des cycles."],
108 + "lavitrine": ["Parcours par sitemap incrémental (`LV_MAX_FETCH` fiches/"
109 + "sync) : après un reset de cache, plusieurs cycles sont "
110 + "nécessaires pour recouvrir tout le catalogue."],
111 +}
112 +
113 +WAVE_HISTORY = (
114 + "- **2026-08-18 — Vague 1 d'enrichissement** (commit `7d9d1b0`) : "
115 + "géocodage Nominatim des salles (`sortika/geocode.py`, cache disque, "
116 + "plafond `SORTIKA_GEOCODE_MAX`), fiches détail lepointdevente "
117 + "(description, heure, tarifs réels `/plugins/rates/<id>`), contacts "
118 + "Tourinsoft (SITQ), heure locale Montréal via JSON-LD.\n"
119 + "- **2026-08-18 — Vague 2 d'enrichissement** (commit `65d2d33`) : "
120 + "colonnes additives `events.start_time` (HH:MM locale QC) et "
121 + "`events.artists` (JSON) + migration auto ; heure captée partout où la "
122 + "source la publie (lepointdevente, evenko, Laval, Sherbrooke, Montréal, "
123 + "atuvu) ; artistes (headliners evenko, lineup Bandsintown, attractions "
124 + "Ticketmaster, patrons sûrs sur les titres) ; 3 nouveaux connecteurs "
125 + "(brossard, ticketmaster, bandsintown)."
126 +)
127 +
128 +
129 +# ---------------------------------------------------------------------------
130 +# Introspection statique d'un connecteur
131 +# ---------------------------------------------------------------------------
132 +
133 +def introspect(path: Path) -> dict:
134 + text = path.read_text(encoding="utf-8")
135 + info: dict = {"file": path.name, "endpoints": [], "env": [], "header": [],
136 + "pagination": "", "backend": "", "request_delay": None}
137 +
138 + # En-tête documentaire (bloc de commentaires en tête de fichier).
139 + for line in text.splitlines():
140 + s = line.rstrip()
141 + if s.startswith("#"):
142 + body = s.lstrip("#").strip()
143 + if set(body) <= {"-"}: # séparateurs -----
144 + continue
145 + info["header"].append(body)
146 + elif s.startswith(("from ", "import ")) or not s:
147 + if info["header"]:
148 + break
149 + else:
150 + break
151 + # Retire le tri-boilerplate commun (nom du projet, auteur, nom du fichier).
152 + info["header"] = [h for h in info["header"]
153 + if not h.startswith(("Sorti-Ka —", "Auteur :"))]
154 +
155 + # Constantes URL au niveau module (ast gère la concaténation implicite).
156 + try:
157 + tree = ast.parse(text)
158 + for node in tree.body:
159 + if isinstance(node, ast.Assign) and len(node.targets) == 1 \
160 + and isinstance(node.targets[0], ast.Name):
161 + name = node.targets[0].id
162 + val = node.value
163 + if isinstance(val, ast.Constant) and isinstance(val.value, str) \
164 + and val.value.startswith("http"):
165 + info["endpoints"].append((name, val.value))
166 + except SyntaxError:
167 + pass
168 +
169 + # Plafonds / clés env : VAR = int(os.environ.get("X", "def")) # commentaire
170 + env_re = re.compile(
171 + r'(?:os\.environ|__import__\("os"\)\.environ)\.get\(\s*"([A-Z_]+)"\s*'
172 + r'(?:,\s*"([^"]*)")?\s*\)[^\n#]*(?:#\s*(.*))?')
173 + for m in env_re.finditer(text):
174 + var, default, comment = m.group(1), m.group(2), (m.group(3) or "").strip()
175 + info["env"].append((var, default if default is not None else "—", comment))
176 +
177 + # Backend réseau.
178 + low = text.lower()
179 + if "scrapfly" in low:
180 + info["backend"] = "Scrapfly (anti-bot / rendu JS) via `base.get_scrapfly()`"
181 + else:
182 + info["backend"] = "GET direct `requests` (throttlé, UA `SortiKa/1.0` identifié)"
183 +
184 + # Pagination (heuristique sur le texte + l'en-tête).
185 + hints = []
186 + if "offset" in low:
187 + hints.append("offset/limit")
188 + if re.search(r'page["\']?\s*[:=]|page=\{page\}|\?page=', text):
189 + hints.append("par numéro de page")
190 + if "sitemap" in low:
191 + hints.append("parcours de sitemap XML")
192 + if "per_page" in low:
193 + hints.append("per_page")
194 + info["pagination"] = ", ".join(dict.fromkeys(hints)) or "aucune (flux unique)"
195 +
196 + m = re.search(r"request_delay\s*[:=]\s*(?:float\s*=\s*)?([\d.]+)", text)
197 + if m:
198 + info["request_delay"] = m.group(1)
199 +
200 + return info
201 +
202 +
203 +# ---------------------------------------------------------------------------
204 +# Base live
205 +# ---------------------------------------------------------------------------
206 +
207 +def db_stats(cur: sqlite3.Cursor, sid: str) -> dict:
208 + row = cur.execute(
209 + "SELECT COUNT(*), COALESCE(SUM(active),0) FROM events WHERE source=?",
210 + (sid,)).fetchone()
211 + total, active = row
212 + metrics = []
213 + for cond, label in KEY_METRICS:
214 + n = cur.execute(
215 + f"SELECT COUNT(*) FROM events WHERE source=? AND active=1 AND ({cond})",
216 + (sid,)).fetchone()[0]
217 + metrics.append((label, n))
218 + # Complétude par champ du schéma (sur les événements actifs).
219 + per_field = {}
220 + for col, _label, _desc in SCHEMA_FIELDS:
221 + if col == "lat":
222 + cond = "lat IS NOT NULL AND lng IS NOT NULL"
223 + elif col in ("categories", "artists"):
224 + cond = f"{col} IS NOT NULL AND {col} NOT IN ('', '[]')"
225 + elif col == "is_free":
226 + cond = "is_free IS NOT NULL"
227 + elif col == "price_min":
228 + cond = "price_min IS NOT NULL"
229 + else:
230 + cond = f"{col} IS NOT NULL AND CAST({col} AS TEXT) != ''"
231 + per_field[col] = cur.execute(
232 + f"SELECT COUNT(*) FROM events WHERE source=? AND active=1 AND ({cond})",
233 + (sid,)).fetchone()[0]
234 +
235 + # Exemple réel : l'événement actif le plus « complet », le plus récent.
236 + score = " + ".join(f"(CASE WHEN {c} THEN 1 ELSE 0 END)" for c, _ in KEY_METRICS)
237 + ex = cur.execute(
238 + f"SELECT * FROM events WHERE source=? AND active=1 "
239 + f"ORDER BY ({score}) DESC, updated_at DESC LIMIT 1", (sid,)).fetchone()
240 + example = dict(ex) if ex else {}
241 +
242 + # sync_log : dernier sync, compteurs, erreurs.
243 + last = cur.execute(
244 + "SELECT ts, fetched, added, updated, removed, error FROM sync_log "
245 + "WHERE source=? ORDER BY ts DESC LIMIT 1", (sid,)).fetchone()
246 + nsync = cur.execute("SELECT COUNT(*) FROM sync_log WHERE source=?",
247 + (sid,)).fetchone()[0]
248 + errors = cur.execute(
249 + "SELECT error, COUNT(*), MAX(ts) FROM sync_log WHERE source=? AND "
250 + "error IS NOT NULL GROUP BY error ORDER BY MAX(ts) DESC LIMIT 5",
251 + (sid,)).fetchall()
252 + return {"total": total, "active": active, "metrics": metrics,
253 + "per_field": per_field, "example": example,
254 + "last_sync": dict(last) if last else None, "nsync": nsync,
255 + "errors": [dict(e) for e in errors] if errors else
256 + []}
257 +
258 +
259 +def fmt_ts(ts: float | None) -> str:
260 + if not ts:
261 + return "n/d"
262 + return datetime.fromtimestamp(ts, tz=timezone.utc).astimezone() \
263 + .strftime("%Y-%m-%d %H:%M")
264 +
265 +
266 +def pct(n: int, d: int) -> str:
267 + return f"{100.0 * n / d:.0f} %" if d else "—"
268 +
269 +
270 +def md_escape(v) -> str:
271 + if v is None:
272 + return "∅"
273 + s = str(v).replace("\n", " ").replace("|", "\\|").strip()
274 + return (s[:96] + "…") if len(s) > 96 else (s or "∅")
275 +
276 +
277 +def git_history(path: Path) -> list[str]:
278 + try:
279 + out = subprocess.run(
280 + ["git", "log", "--follow", "--date=short",
281 + "--format=%h %ad %s", "--", str(path.relative_to(ROOT))],
282 + cwd=ROOT, capture_output=True, text=True, timeout=30).stdout
283 + return out.strip().splitlines()[:12]
284 + except Exception:
285 + return []
286 +
287 +
288 +# ---------------------------------------------------------------------------
289 +# Rendu des fiches
290 +# ---------------------------------------------------------------------------
291 +
292 +def render_fiche(src: dict, intro: dict | None, stats: dict | None) -> str:
293 + sid = src["id"]
294 + L: list[str] = []
295 + L.append(f"# {src['name']} (`{sid}`)")
296 + L.append("")
297 + L.append(f"> Fiche générée le {STAMP} par `scripts/gen_connector_docs.py` "
298 + f"— ne pas éditer à la main, relancer le script.")
299 + L.append("")
300 + L.append(f"**Statut : {src.get('statut', 'n/d')}** · Type : "
301 + f"`{src.get('type', 'n/d')}`" +
302 + (f" · Connecteur : `sortika/connectors/{intro['file']}`" if intro
303 + else " · Connecteur : *aucun (source sans connecteur runtime)*"))
304 + L.append("")
305 +
306 + # 1. Description --------------------------------------------------------
307 + L.append("## Description")
308 + L.append("")
309 + L.append(f"- **Source** : [{src['name']}]({src['url']})")
310 + L.append(f"- **Couverture** : {src.get('couverture', 'n/d')}")
311 + L.append(f"- **Extraction** : {src.get('extraction', 'n/d')}")
312 + if src.get("note"):
313 + L.append(f"- **Note** : {src['note']}")
314 + L.append("")
315 +
316 + # 2. Accès ---------------------------------------------------------------
317 + L.append("## Accès")
318 + L.append("")
319 + L.append(f"- **Type** : {src.get('type', 'n/d')}")
320 + if intro and intro["endpoints"]:
321 + for name, url in intro["endpoints"]:
322 + L.append(f"- **Endpoint** (`{name}`) : `{url}`")
323 + else:
324 + L.append(f"- **Endpoint** : voir URL source ({src['url']})")
325 + auth = "Aucune (accès public, identification honnête par User-Agent)"
326 + if sid == "ticketmaster":
327 + auth = "Clé API gratuite `TICKETMASTER_API_KEY` (developer.ticketmaster.com)"
328 + elif sid == "bandsintown":
329 + auth = "`app_id` d'identification (`BANDSINTOWN_APP_ID`, format widget accepté)"
330 + elif intro and "Scrapfly" in intro["backend"]:
331 + auth = "Aucune côté source ; `SCRAPFLY_API_KEY` requis pour le backend Scrapfly"
332 + L.append(f"- **Auth** : {auth}")
333 + if intro:
334 + L.append(f"- **Pagination** : {intro['pagination']}")
335 + L.append(f"- **Backend** : {intro['backend']}")
336 + if intro["request_delay"]:
337 + L.append(f"- **Throttle** : {intro['request_delay']} s entre requêtes")
338 + L.append(f"- **Accès légal** : {src.get('acces_legal', 'n/d')}")
339 + if intro["header"]:
340 + L.append("")
341 + L.append("<details><summary>En-tête documentaire du connecteur</summary>")
342 + L.append("")
343 + for h in intro["header"]:
344 + L.append(f"> {h}")
345 + L.append("")
346 + L.append("</details>")
347 + else:
348 + L.append(f"- **Accès légal** : {src.get('acces_legal', 'n/d')}")
349 + L.append("")
350 +
351 + # 3. Champs récupérés → schéma -------------------------------------------
352 + L.append("## Champs récupérés → schéma")
353 + L.append("")
354 + if stats and stats["active"]:
355 + ex = stats["example"]
356 + L.append(f"Complétude mesurée sur les **{stats['active']} événements "
357 + f"actifs** de la source ; exemples tirés d'un événement réel "
358 + f"(`{md_escape(ex.get('uid'))}`).")
359 + L.append("")
360 + L.append("| Champ (`events.*`) | Description | Complétude | Exemple réel |")
361 + L.append("|---|---|---|---|")
362 + for col, label, desc in SCHEMA_FIELDS:
363 + n = stats["per_field"][col]
364 + if col == "lat":
365 + val = (f"{ex.get('lat')}, {ex.get('lng')}"
366 + if ex.get("lat") is not None else None)
367 + else:
368 + val = ex.get(col)
369 + L.append(f"| `{col}` — {label} | {desc} | "
370 + f"{pct(n, stats['active'])} | {md_escape(val)} |")
371 + else:
372 + L.append("*Aucun événement actif en base pour cette source — pas de "
373 + "schéma live à montrer.*")
374 + L.append("")
375 +
376 + # 4. Fréquence & budgets ---------------------------------------------------
377 + L.append("## Fréquence & budgets")
378 + L.append("")
379 + L.append(f"- **Cadence** : {src.get('cadence', 'n/d')}")
380 + if intro and intro["env"]:
381 + L.append("- **Budgets / plafonds (variables d'environnement)** :")
382 + L.append("")
383 + L.append("| Variable | Défaut | Rôle |")
384 + L.append("|---|---|---|")
385 + for var, default, comment in intro["env"]:
386 + L.append(f"| `{var}` | `{default}` | {comment or '—'} |")
387 + L.append("- **Budget global** : géocodage Nominatim plafonné à "
388 + "`SORTIKA_GEOCODE_MAX` (défaut 120) requêtes réseau/cycle, "
389 + "toutes sources confondues (`sortika/geocode.py`, cache disque).")
390 + L.append("")
391 +
392 + # 5. Volumétrie & complétude live ------------------------------------------
393 + L.append("## Volumétrie & complétude live")
394 + L.append("")
395 + if stats:
396 + L.append(f"*(mesuré le {STAMP} dans `data/sortika.db`)*")
397 + L.append("")
398 + L.append(f"- **Événements en base** : {stats['total']} "
399 + f"(dont **{stats['active']} actifs**)")
400 + for label, n in stats["metrics"]:
401 + L.append(f"- **{label}** : {n}/{stats['active']} actifs "
402 + f"({pct(n, stats['active'])})")
403 + ls = stats["last_sync"]
404 + if ls:
405 + L.append(f"- **Dernier sync** : {fmt_ts(ls['ts'])} — "
406 + f"fetched {ls['fetched']}, +{ls['added']} ajoutés, "
407 + f"~{ls['updated']} mis à jour, -{ls['removed']} retirés"
408 + + (f", **erreur : {md_escape(ls['error'])}**"
409 + if ls["error"] else ""))
410 + L.append(f"- **Syncs enregistrés** (sync_log) : {stats['nsync']}")
411 + else:
412 + L.append("*Source sans connecteur runtime : aucune donnée en base.*")
413 + L.append("")
414 +
415 + # 6. Erreurs & dépannage -----------------------------------------------------
416 + L.append("## Erreurs & dépannage")
417 + L.append("")
418 + if stats and stats["errors"]:
419 + L.append("Erreurs relevées dans `sync_log` :")
420 + L.append("")
421 + L.append("| Erreur | Occurrences | Dernière fois |")
422 + L.append("|---|---|---|")
423 + for e in stats["errors"]:
424 + err, cnt, ts = list(e.values())
425 + L.append(f"| {md_escape(err)} | {cnt} | {fmt_ts(ts)} |")
426 + L.append("")
427 + elif stats:
428 + L.append("Aucune erreur dans `sync_log` pour cette source. ✅")
429 + L.append("")
430 + for tip in DEPANNAGE.get(sid, []):
431 + L.append(f"- {tip}")
432 + if intro:
433 + L.append("- Rejouer un sync isolé : `python3 run.py sync --source "
434 + f"{sid}` (ou via le watcher PM2) ; inspecter ensuite "
435 + "`sync_log` : `sqlite3 data/sortika.db \"SELECT * FROM "
436 + f"sync_log WHERE source='{sid}' ORDER BY ts DESC LIMIT 5;\"`")
437 + L.append("")
438 +
439 + # 7. Licence / attribution ------------------------------------------------------
440 + L.append("## Licence / attribution")
441 + L.append("")
442 + L.append(f"- {src.get('acces_legal', 'n/d')}")
443 + if sid in ATTRIBUTIONS:
444 + L.append("")
445 + L.append(ATTRIBUTIONS[sid])
446 + L.append("")
447 +
448 + # 8. Historique -------------------------------------------------------------------
449 + L.append("## Historique")
450 + L.append("")
451 + if src.get("statut") == "abandonnee":
452 + L.append(f"- **Source abandonnée** — {src.get('note', 'sans note')}")
453 + L.append(WAVE_HISTORY)
454 + if intro:
455 + hist = git_history(CONNECTORS_DIR / intro["file"])
456 + if hist:
457 + L.append("")
458 + L.append(f"Commits touchant `sortika/connectors/{intro['file']}` :")
459 + L.append("")
460 + for h in hist:
461 + L.append(f"- `{h.split(' ', 1)[0]}` {h.split(' ', 1)[1]}")
462 + L.append("")
463 + return "\n".join(L)
464 +
465 +
466 +# ---------------------------------------------------------------------------
467 +# INDEX
468 +# ---------------------------------------------------------------------------
469 +
470 +def render_index(rows: list[dict]) -> str:
471 + L: list[str] = []
472 + L.append("# Sorti-Ka — Connecteurs de sources d'événements")
473 + L.append("")
474 + L.append(f"> Index généré le {STAMP} par `scripts/gen_connector_docs.py` "
475 + f"(rejouable : `python3 scripts/gen_connector_docs.py`). "
476 + f"Croise `data/sources.json`, l'introspection de "
477 + f"`sortika/connectors/*.py` et la base live `data/sortika.db`.")
478 + L.append("")
479 + actives = [r for r in rows if r["src"].get("statut") != "abandonnee"]
480 + abandon = [r for r in rows if r["src"].get("statut") == "abandonnee"]
481 + tot = sum(r["stats"]["total"] for r in rows if r["stats"])
482 + act = sum(r["stats"]["active"] for r in rows if r["stats"])
483 + L.append(f"**{len(rows)} sources au registre** — {len(actives)} "
484 + f"actives/prêtes, {len(abandon)} abandonnées · "
485 + f"**{tot} événements en base** (dont {act} actifs).")
486 + L.append("")
487 + L.append("⚠️ **Attributions requises** : Ville de Montréal (CC BY 4.0) ; "
488 + "SIT Québec / Ministère du Tourisme et Ville de Laval "
489 + "(CC 4.0 — Données Québec). Détails dans chaque fiche, section "
490 + "« Licence / attribution ».")
491 + L.append("")
492 + L.append("## Sources actives / prêtes")
493 + L.append("")
494 + L.append("| Source | Type | Statut | Actifs | GPS | Prix | Heure | "
495 + "Artistes | Image | Salle | Dernier sync |")
496 + L.append("|---|---|---|---|---|---|---|---|---|---|---|")
497 + for r in actives:
498 + s, st = r["src"], r["stats"]
499 + if st and st["active"]:
500 + cells = [pct(n, st["active"]) for _, n in st["metrics"]]
501 + last = fmt_ts(st["last_sync"]["ts"]) if st["last_sync"] else "n/d"
502 + L.append(f"| [{s['name']}]({s['id']}.md) | {s['type']} | "
503 + f"{s['statut']} | {st['active']} | " + " | ".join(cells) +
504 + f" | {last} |")
505 + else:
506 + last = (fmt_ts(st["last_sync"]["ts"])
507 + if st and st["last_sync"] else "n/d")
508 + L.append(f"| [{s['name']}]({s['id']}.md) | {s['type']} | "
509 + f"{s['statut']} | 0 | — | — | — | — | — | — | {last} |")
510 + L.append("")
511 + L.append("## Sources abandonnées")
512 + L.append("")
513 + L.append("| Source | Type | Raison |")
514 + L.append("|---|---|---|")
515 + for r in abandon:
516 + s = r["src"]
517 + L.append(f"| [{s['name']}]({s['id']}.md) | {s['type']} | "
518 + f"{md_escape(s.get('note', 'n/d'))} |")
519 + L.append("")
520 + L.append("## Régénérer cette documentation")
521 + L.append("")
522 + L.append("```bash")
523 + L.append("cd ~/apps/sorti-ka && python3 scripts/gen_connector_docs.py")
524 + L.append("```")
525 + L.append("")
526 + return "\n".join(L)
527 +
528 +
529 +# ---------------------------------------------------------------------------
530 +
531 +def main() -> int:
532 + sources = json.loads(SOURCES_JSON.read_text(encoding="utf-8"))["sources"]
533 + con = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True)
534 + con.row_factory = sqlite3.Row
535 + cur = con.cursor()
536 + OUT_DIR.mkdir(parents=True, exist_ok=True)
537 +
538 + rows = []
539 + for src in sources:
540 + sid = src["id"]
541 + cpath = CONNECTORS_DIR / f"{sid.replace('-', '_')}.py"
542 + intro = introspect(cpath) if cpath.exists() else None
543 + has_data = cur.execute(
544 + "SELECT COUNT(*) FROM events WHERE source=?", (sid,)).fetchone()[0]
545 + has_log = cur.execute(
546 + "SELECT COUNT(*) FROM sync_log WHERE source=?", (sid,)).fetchone()[0]
547 + stats = db_stats(cur, sid) if (intro or has_data or has_log) else None
548 + fiche = render_fiche(src, intro, stats)
549 + (OUT_DIR / f"{sid}.md").write_text(fiche, encoding="utf-8")
550 + rows.append({"src": src, "intro": intro, "stats": stats})
551 + print(f" fiche : docs/connecteurs/{sid}.md"
552 + + (f" ({stats['active']} actifs)" if stats else " (sans données)"))
553 +
554 + (OUT_DIR / "INDEX.md").write_text(render_index(rows), encoding="utf-8")
555 + print(f" index : docs/connecteurs/INDEX.md ({len(rows)} sources)")
556 + con.close()
557 + return 0
558 +
559 +
560 +if __name__ == "__main__":
561 + sys.exit(main())
562