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>
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 | ||