SPB Git

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.

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 (Aug 18, 2026) parent 65d2d33 Browse files

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
Menu

Theme